2 * Rufus: The Reliable USB Formatting Utility
3 * Process search functionality
5 * Modified from Process Hacker:
6 * https://github.com/processhacker2/processhacker2/
7 * Copyright © 2017-2019 Pete Batard <pete@akeo.ie>
8 * Copyright © 2017 dmex
9 * Copyright © 2009-2016 wj32
10 * Copyright (c) 2020, longpanda <admin@ventoy.net>
12 * This program is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation, either version 3 of the License, or
15 * (at your option) any later version.
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <http://www.gnu.org/licenses/>.
35 #include "Ventoy2Disk.h"
36 #include "fat_filelib.h"
42 OPENED_LIBRARIES_VARS
;
44 STATIC WCHAR
*_wHandleName
= NULL
;
45 static PVOID PhHeapHandle
= NULL
;
49 * Convert an NT Status to an error message
51 * \param Status An operattonal status.
53 * \return An error message string.
56 char* NtStatusError(NTSTATUS Status
) {
57 static char unknown
[32];
61 return "Operation Successful";
62 case STATUS_UNSUCCESSFUL
:
63 return "Operation Failed";
64 case STATUS_BUFFER_OVERFLOW
:
65 return "Buffer Overflow";
66 case STATUS_NOT_IMPLEMENTED
:
67 return "Not Implemented";
68 case STATUS_INFO_LENGTH_MISMATCH
:
69 return "Info Length Mismatch";
70 case STATUS_INVALID_HANDLE
:
71 return "Invalid Handle.";
72 case STATUS_INVALID_PARAMETER
:
73 return "Invalid Parameter";
74 case STATUS_NO_MEMORY
:
75 return "Not Enough Quota";
76 case STATUS_ACCESS_DENIED
:
77 return "Access Denied";
78 case STATUS_BUFFER_TOO_SMALL
:
79 return "Buffer Too Small";
80 case STATUS_OBJECT_TYPE_MISMATCH
:
82 case STATUS_OBJECT_NAME_INVALID
:
83 return "Object Name Invalid";
84 case STATUS_OBJECT_NAME_NOT_FOUND
:
85 return "Object Name not found";
86 case STATUS_OBJECT_PATH_INVALID
:
87 return "Object Path Invalid";
88 case STATUS_SHARING_VIOLATION
:
89 return "Sharing Violation";
90 case STATUS_INSUFFICIENT_RESOURCES
:
91 return "Insufficient resources";
92 case STATUS_NOT_SUPPORTED
:
93 return "Operation is not supported";
95 safe_sprintf(unknown
, "Unknown error 0x%08lx", Status
);
101 static NTSTATUS
PhCreateHeap(VOID
)
103 NTSTATUS status
= STATUS_SUCCESS
;
105 if (PhHeapHandle
!= NULL
)
106 return STATUS_ALREADY_COMPLETE
;
108 PF_INIT_OR_SET_STATUS(RtlCreateHeap
, Ntdll
);
110 if (NT_SUCCESS(status
)) {
111 PhHeapHandle
= pfRtlCreateHeap(HEAP_NO_SERIALIZE
| HEAP_GROWABLE
, NULL
, 2 * MB
, 1 * MB
, NULL
, NULL
);
112 if (PhHeapHandle
== NULL
)
113 status
= STATUS_UNSUCCESSFUL
;
119 static NTSTATUS
PhDestroyHeap(VOID
)
121 NTSTATUS status
= STATUS_SUCCESS
;
123 if (PhHeapHandle
== NULL
)
124 return STATUS_ALREADY_COMPLETE
;
126 PF_INIT_OR_SET_STATUS(RtlDestroyHeap
, Ntdll
);
128 if (NT_SUCCESS(status
)) {
129 if (pfRtlDestroyHeap(PhHeapHandle
) == NULL
) {
133 status
= STATUS_UNSUCCESSFUL
;
141 * Allocates a block of memory.
143 * \param Size The number of bytes to allocate.
145 * \return A pointer to the allocated block of memory.
148 static PVOID
PhAllocate(SIZE_T Size
)
150 PF_INIT(RtlAllocateHeap
, Ntdll
);
151 if (pfRtlAllocateHeap
== NULL
)
154 return pfRtlAllocateHeap(PhHeapHandle
, 0, Size
);
158 * Frees a block of memory allocated with PhAllocate().
160 * \param Memory A pointer to a block of memory.
163 static VOID
PhFree(PVOID Memory
)
165 PF_INIT(RtlFreeHeap
, Ntdll
);
167 if (pfRtlFreeHeap
!= NULL
)
168 pfRtlFreeHeap(PhHeapHandle
, 0, Memory
);
172 * Enumerates all open handles.
174 * \param Handles A variable which receives a pointer to a structure containing information about
175 * all opened handles. You must free the structure using PhFree() when you no longer need it.
177 * \return An NTStatus indicating success or the error code.
179 NTSTATUS
PhEnumHandlesEx(PSYSTEM_HANDLE_INFORMATION_EX
*Handles
)
181 static ULONG initialBufferSize
= 0x10000;
182 NTSTATUS status
= STATUS_SUCCESS
;
186 PF_INIT_OR_SET_STATUS(NtQuerySystemInformation
, Ntdll
);
187 if (!NT_SUCCESS(status
))
190 bufferSize
= initialBufferSize
;
191 buffer
= PhAllocate(bufferSize
);
193 return STATUS_NO_MEMORY
;
195 while ((status
= pfNtQuerySystemInformation(SystemExtendedHandleInformation
,
196 buffer
, bufferSize
, NULL
)) == STATUS_INFO_LENGTH_MISMATCH
) {
200 // Fail if we're resizing the buffer to something very large.
201 if (bufferSize
> PH_LARGE_BUFFER_SIZE
)
202 return STATUS_INSUFFICIENT_RESOURCES
;
204 buffer
= PhAllocate(bufferSize
);
206 return STATUS_NO_MEMORY
;
209 if (!NT_SUCCESS(status
)) {
214 if (bufferSize
<= 0x200000)
215 initialBufferSize
= bufferSize
;
216 *Handles
= (PSYSTEM_HANDLE_INFORMATION_EX
)buffer
;
224 * \param ProcessHandle A variable which receives a handle to the process.
225 * \param DesiredAccess The desired access to the process.
226 * \param ProcessId The ID of the process.
228 * \return An NTStatus indicating success or the error code.
230 NTSTATUS
PhOpenProcess(PHANDLE ProcessHandle
, ACCESS_MASK DesiredAccess
, HANDLE ProcessId
)
232 NTSTATUS status
= STATUS_SUCCESS
;
233 OBJECT_ATTRIBUTES objectAttributes
;
236 if ((LONG_PTR
)ProcessId
== (LONG_PTR
)GetCurrentProcessId()) {
237 *ProcessHandle
= NtCurrentProcess();
241 PF_INIT_OR_SET_STATUS(NtOpenProcess
, Ntdll
);
242 if (!NT_SUCCESS(status
))
245 clientId
.UniqueProcess
= ProcessId
;
246 clientId
.UniqueThread
= NULL
;
248 InitializeObjectAttributes(&objectAttributes
, NULL
, 0, NULL
, NULL
);
249 status
= pfNtOpenProcess(ProcessHandle
, DesiredAccess
, &objectAttributes
, &clientId
);
255 * Query processes with open handles to a file, volume or disk.
257 * \param VolumeOrFileHandle The handle to the target.
258 * \param Information The returned list of processes.
260 * \return An NTStatus indicating success or the error code.
262 NTSTATUS
PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle
,
263 PFILE_PROCESS_IDS_USING_FILE_INFORMATION
*Information
)
265 static ULONG initialBufferSize
= 16 * KB
;
266 NTSTATUS status
= STATUS_SUCCESS
;
271 PF_INIT_OR_SET_STATUS(NtQueryInformationFile
, NtDll
);
272 if (!NT_SUCCESS(status
))
275 bufferSize
= initialBufferSize
;
276 buffer
= PhAllocate(bufferSize
);
278 return STATUS_INSUFFICIENT_RESOURCES
;
280 while ((status
= pfNtQueryInformationFile(VolumeOrFileHandle
, &isb
, buffer
, bufferSize
,
281 FileProcessIdsUsingFileInformation
)) == STATUS_INFO_LENGTH_MISMATCH
) {
284 // Fail if we're resizing the buffer to something very large.
285 if (bufferSize
> 64 * MB
)
286 return STATUS_INSUFFICIENT_RESOURCES
;
287 buffer
= PhAllocate(bufferSize
);
290 if (!NT_SUCCESS(status
)) {
295 if (bufferSize
<= 64 * MB
)
296 initialBufferSize
= bufferSize
;
297 *Information
= (PFILE_PROCESS_IDS_USING_FILE_INFORMATION
)buffer
;
303 * Query the full commandline that was used to create a process.
304 * This can be helpful to differentiate between service instances (svchost.exe).
305 * Taken from: https://stackoverflow.com/a/14012919/1069307
307 * \param hProcess A handle to a process.
309 * \return A Unicode commandline string, or NULL on error.
310 * The returned string must be freed by the caller.
312 static PWSTR
GetProcessCommandLine(HANDLE hProcess
)
314 PWSTR wcmdline
= NULL
;
316 DWORD pp_offset
, cmd_offset
;
317 NTSTATUS status
= STATUS_SUCCESS
;
319 PBYTE peb
= NULL
, pp
= NULL
;
321 // Determine if 64 or 32-bit processor
322 GetNativeSystemInfo(&si
);
323 if ((si
.wProcessorArchitecture
== PROCESSOR_ARCHITECTURE_AMD64
) || (si
.wProcessorArchitecture
== PROCESSOR_ARCHITECTURE_ARM64
)) {
332 // PEB and Process Parameters (we only need the beginning of these structs)
333 peb
= (PBYTE
)calloc(pp_offset
+ 8, 1);
336 pp
= (PBYTE
)calloc(cmd_offset
+ 16, 1);
340 IsWow64Process(GetCurrentProcess(), &wow
);
342 // 32-bit process running on a 64-bit OS
343 PROCESS_BASIC_INFORMATION_WOW64 pbi
= { 0 };
345 UNICODE_STRING_WOW64
* ucmdline
;
347 PF_INIT_OR_OUT(NtWow64QueryInformationProcess64
, NtDll
);
348 PF_INIT_OR_OUT(NtWow64ReadVirtualMemory64
, NtDll
);
350 status
= pfNtWow64QueryInformationProcess64(hProcess
, 0, &pbi
, sizeof(pbi
), NULL
);
351 if (!NT_SUCCESS(status
))
354 status
= pfNtWow64ReadVirtualMemory64(hProcess
, pbi
.PebBaseAddress
, peb
, pp_offset
+ 8, NULL
);
355 if (!NT_SUCCESS(status
))
358 // Read Process Parameters from the 64-bit address space
359 params
= (ULONGLONG
)*((ULONGLONG
*)(peb
+ pp_offset
));
360 status
= pfNtWow64ReadVirtualMemory64(hProcess
, params
, pp
, cmd_offset
+ 16, NULL
);
361 if (!NT_SUCCESS(status
))
364 ucmdline
= (UNICODE_STRING_WOW64
*)(pp
+ cmd_offset
);
365 wcmdline
= (PWSTR
)calloc(ucmdline
->Length
+ 1, sizeof(WCHAR
));
366 if (wcmdline
== NULL
)
368 status
= pfNtWow64ReadVirtualMemory64(hProcess
, ucmdline
->Buffer
, wcmdline
, ucmdline
->Length
, NULL
);
369 if (!NT_SUCCESS(status
)) {
375 // 32-bit process on a 32-bit OS, or 64-bit process on a 64-bit OS
376 PROCESS_BASIC_INFORMATION pbi
= { 0 };
378 UNICODE_STRING
* ucmdline
;
380 PF_INIT_OR_OUT(NtQueryInformationProcess
, NtDll
);
382 status
= pfNtQueryInformationProcess(hProcess
, 0, &pbi
, sizeof(pbi
), NULL
);
383 if (!NT_SUCCESS(status
))
387 if (!ReadProcessMemory(hProcess
, pbi
.PebBaseAddress
, peb
, pp_offset
+ 8, NULL
))
390 // Read Process Parameters
391 params
= (PBYTE
*)*(LPVOID
*)(peb
+ pp_offset
);
392 if (!ReadProcessMemory(hProcess
, params
, pp
, cmd_offset
+ 16, NULL
))
395 ucmdline
= (UNICODE_STRING
*)(pp
+ cmd_offset
);
396 // In the absolute, someone could craft a process with dodgy attributes to try to cause an overflow
397 ucmdline
->Length
= min(ucmdline
->Length
, 512);
398 wcmdline
= (PWSTR
)calloc(ucmdline
->Length
+ 1, sizeof(WCHAR
));
399 if (!ReadProcessMemory(hProcess
, ucmdline
->Buffer
, wcmdline
, ucmdline
->Length
, NULL
)) {
412 static int GetDevicePathName(PHY_DRIVE_INFO
*pPhyDrive
, WCHAR
*wDevPath
)
416 CHAR DevPath
[MAX_PATH
] = { 0 };
418 safe_sprintf(PhyDrive
, "\\\\.\\PhysicalDrive%d", pPhyDrive
->PhyDrive
);
420 if (0 == QueryDosDeviceA(PhyDrive
+ 4, DevPath
, sizeof(DevPath
)))
422 Log("QueryDosDeviceA failed error:%u", GetLastError());
423 strcpy_s(DevPath
, sizeof(DevPath
), "???");
427 Log("QueryDosDeviceA success %s", DevPath
);
430 for (i
= 0; DevPath
[i
] && i
< MAX_PATH
; i
++)
432 wDevPath
[i
] = DevPath
[i
];
439 static __inline DWORD
GetModuleFileNameExU(HANDLE hProcess
, HMODULE hModule
, char* lpFilename
, DWORD nSize
)
441 DWORD ret
= 0, err
= ERROR_INVALID_DATA
;
442 // coverity[returned_null]
443 walloc(lpFilename
, nSize
);
444 ret
= GetModuleFileNameExW(hProcess
, hModule
, wlpFilename
, nSize
);
445 err
= GetLastError();
447 && ((ret
= wchar_to_utf8_no_alloc(wlpFilename
, lpFilename
, nSize
)) == 0)) {
448 err
= GetLastError();
455 int FindProcessOccupyDisk(HANDLE hDrive
, PHY_DRIVE_INFO
*pPhyDrive
)
457 WCHAR wDevPath
[MAX_PATH
] = { 0 };
458 const char *access_rights_str
[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" };
459 NTSTATUS status
= STATUS_SUCCESS
;
460 PSYSTEM_HANDLE_INFORMATION_EX handles
= NULL
;
461 POBJECT_NAME_INFORMATION buffer
= NULL
;
464 ULONG_PTR last_access_denied_pid
= 0;
466 USHORT wHandleNameLen
;
467 HANDLE dupHandle
= NULL
;
468 HANDLE processHandle
= NULL
;
469 BOOLEAN bFound
= FALSE
, bGotCmdLine
, verbose
= TRUE
;
470 ULONG access_rights
= 0;
472 char cmdline
[MAX_PATH
] = { 0 };
473 wchar_t wexe_path
[MAX_PATH
], *wcmdline
;
475 time_t starttime
, curtime
;
478 Log("FindProcessOccupyDisk for PhyDrive %d", pPhyDrive
->PhyDrive
);
480 GetDevicePathName(pPhyDrive
, wDevPath
);
481 _wHandleName
= wDevPath
;
484 PF_INIT_OR_SET_STATUS(NtQueryObject
, Ntdll
);
485 PF_INIT_OR_SET_STATUS(NtDuplicateObject
, NtDll
);
486 PF_INIT_OR_SET_STATUS(NtClose
, NtDll
);
488 if (NT_SUCCESS(status
))
489 status
= PhCreateHeap();
491 if (NT_SUCCESS(status
))
492 status
= PhEnumHandlesEx(&handles
);
494 if (!NT_SUCCESS(status
)) {
495 Log("Warning: Could not enumerate process handles: %s", NtStatusError(status
));
499 pid
[0] = (ULONG_PTR
)0;
502 wHandleNameLen
= (USHORT
)wcslen(_wHandleName
);
505 buffer
= PhAllocate(bufferSize
);
509 Log("handles->NumberOfHandles = %lu", (ULONG
)handles
->NumberOfHandles
);
511 if (handles
->NumberOfHandles
> 10000)
516 starttime
= time(NULL
);
518 for (i
= 0; i
< handles
->NumberOfHandles
; i
++) {
520 PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo
=
521 (i
< handles
->NumberOfHandles
) ? &handles
->Handles
[i
] : NULL
;
523 //limit the search time
526 curtime
= time(NULL
);
527 if (curtime
- starttime
> 10)
533 if ((dupHandle
!= NULL
) && (processHandle
!= NtCurrentProcess())) {
534 pfNtClose(dupHandle
);
538 // Update the current handle's process PID and compare against last
539 // Note: Be careful about not trying to overflow our list!
540 pid
[cur_pid
] = (handleInfo
!= NULL
) ? handleInfo
->UniqueProcessId
: -1;
542 if (pid
[0] != pid
[1]) {
543 cur_pid
= (cur_pid
+ 1) % 2;
545 // If we're switching process and found a match, print it
547 Log("* [%06u] %s (%s)", (UINT32
)pid
[cur_pid
], cmdline
, access_rights_str
[access_rights
& 0x7]);
552 // Close the previous handle
553 if (processHandle
!= NULL
) {
554 if (processHandle
!= NtCurrentProcess())
555 pfNtClose(processHandle
);
556 processHandle
= NULL
;
560 // Exit loop condition
561 if (i
>= handles
->NumberOfHandles
)
564 // Don't bother with processes we can't access
565 if (handleInfo
->UniqueProcessId
== last_access_denied_pid
)
568 // Filter out handles that aren't opened with Read (bit 0), Write (bit 1) or Execute (bit 5) access
569 if ((handleInfo
->GrantedAccess
& 0x23) == 0)
572 // Open the process to which the handle we are after belongs, if not already opened
573 if (pid
[0] != pid
[1]) {
574 status
= PhOpenProcess(&processHandle
, PROCESS_DUP_HANDLE
| PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ
,
575 (HANDLE
)handleInfo
->UniqueProcessId
);
576 // There exists some processes we can't access
577 if (!NT_SUCCESS(status
)) {
578 //Log("SearchProcess: Could not open process %ld: %s",
579 // handleInfo->UniqueProcessId, NtStatusError(status));
580 processHandle
= NULL
;
581 if (status
== STATUS_ACCESS_DENIED
) {
582 last_access_denied_pid
= handleInfo
->UniqueProcessId
;
588 // Now duplicate this handle onto our own process, so that we can access its properties
589 if (processHandle
== NtCurrentProcess()) {
593 status
= pfNtDuplicateObject(processHandle
, (HANDLE
)handleInfo
->HandleValue
,
594 NtCurrentProcess(), &dupHandle
, 0, 0, 0);
595 if (!NT_SUCCESS(status
))
599 // Filter non-storage handles. We're not interested in them and they make NtQueryObject() freeze
600 if (GetFileType(dupHandle
) != FILE_TYPE_DISK
)
603 // A loop is needed because the I/O subsystem likes to give us the wrong return lengths...
606 // TODO: We might potentially still need a timeout on ObjectName queries, as PH does...
607 status
= pfNtQueryObject(dupHandle
, ObjectNameInformation
, buffer
, bufferSize
, &returnSize
);
608 if (status
== STATUS_BUFFER_OVERFLOW
|| status
== STATUS_INFO_LENGTH_MISMATCH
||
609 status
== STATUS_BUFFER_TOO_SMALL
) {
610 Log("SearchProcess: Realloc from %d to %d", bufferSize
, returnSize
);
611 bufferSize
= returnSize
;
613 buffer
= PhAllocate(bufferSize
);
618 } while (--attempts
);
619 if (!NT_SUCCESS(status
)) {
620 Log("SearchProcess: NtQueryObject failed for handle %X of process %ld: %s",
621 handleInfo
->HandleValue
, handleInfo
->UniqueProcessId
, NtStatusError(status
));
625 // we are looking for a partial match and the current length is smaller
626 if (wHandleNameLen
> buffer
->Name
.Length
)
629 // Match against our target string
630 if (wcsncmp(_wHandleName
, buffer
->Name
.Buffer
, wHandleNameLen
) != 0)
633 // If we are here, we have a process accessing our target!
636 // Keep a mask of all the access rights being used
637 access_rights
|= handleInfo
->GrantedAccess
;
638 // The Executable bit is in a place we don't like => reposition it
639 if (access_rights
& 0x20)
640 access_rights
= (access_rights
& 0x03) | 0x04;
642 // If this is the very first process we find, print a header
644 Log("WARNING: The following process(es) or service(s) are accessing %S:", _wHandleName
);
646 // Where possible, try to get the full command line
649 wcmdline
= GetProcessCommandLine(processHandle
);
650 if (wcmdline
!= NULL
) {
652 wchar_to_utf8_no_alloc(wcmdline
, cmdline
, sizeof(cmdline
));
656 // If we couldn't get the full commandline, try to get the executable path
658 bGotCmdLine
= (GetModuleFileNameExU(processHandle
, 0, cmdline
, MAX_PATH
- 1) != 0);
660 // The above may not work on Windows 7, so try QueryFullProcessImageName (Vista or later)
662 bGotCmdLine
= QueryFullProcessImageNameW(processHandle
, 0, wexe_path
, &size
);
664 wchar_to_utf8_no_alloc(wexe_path
, cmdline
, sizeof(cmdline
));
667 // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses
668 // '\Device\Harddisk#\Partition#\' instead drive letters
670 bGotCmdLine
= (GetProcessImageFileNameW(processHandle
, wexe_path
, MAX_PATH
) != 0);
672 wchar_to_utf8_no_alloc(wexe_path
, cmdline
, sizeof(cmdline
));
675 // Complete failure => Just craft a default process name that includes the PID
677 safe_sprintf(cmdline
, "Unknown_Process_0x%llx", (ULONGLONG
)handleInfo
->UniqueProcessId
);
683 Log("You should close these applications before attempting to reformat the drive.");
685 Log("NOTE: Could not identify the process(es) or service(s) accessing %S", _wHandleName
);