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/>.
34 #include "Ventoy2Disk.h"
35 #include "fat_filelib.h"
41 OPENED_LIBRARIES_VARS
;
43 STATIC WCHAR
*_wHandleName
= NULL
;
44 static PVOID PhHeapHandle
= NULL
;
48 * Convert an NT Status to an error message
50 * \param Status An operattonal status.
52 * \return An error message string.
55 char* NtStatusError(NTSTATUS Status
) {
56 static char unknown
[32];
60 return "Operation Successful";
61 case STATUS_UNSUCCESSFUL
:
62 return "Operation Failed";
63 case STATUS_BUFFER_OVERFLOW
:
64 return "Buffer Overflow";
65 case STATUS_NOT_IMPLEMENTED
:
66 return "Not Implemented";
67 case STATUS_INFO_LENGTH_MISMATCH
:
68 return "Info Length Mismatch";
69 case STATUS_INVALID_HANDLE
:
70 return "Invalid Handle.";
71 case STATUS_INVALID_PARAMETER
:
72 return "Invalid Parameter";
73 case STATUS_NO_MEMORY
:
74 return "Not Enough Quota";
75 case STATUS_ACCESS_DENIED
:
76 return "Access Denied";
77 case STATUS_BUFFER_TOO_SMALL
:
78 return "Buffer Too Small";
79 case STATUS_OBJECT_TYPE_MISMATCH
:
81 case STATUS_OBJECT_NAME_INVALID
:
82 return "Object Name Invalid";
83 case STATUS_OBJECT_NAME_NOT_FOUND
:
84 return "Object Name not found";
85 case STATUS_OBJECT_PATH_INVALID
:
86 return "Object Path Invalid";
87 case STATUS_SHARING_VIOLATION
:
88 return "Sharing Violation";
89 case STATUS_INSUFFICIENT_RESOURCES
:
90 return "Insufficient resources";
91 case STATUS_NOT_SUPPORTED
:
92 return "Operation is not supported";
94 safe_sprintf(unknown
, "Unknown error 0x%08lx", Status
);
100 static NTSTATUS
PhCreateHeap(VOID
)
102 NTSTATUS status
= STATUS_SUCCESS
;
104 if (PhHeapHandle
!= NULL
)
105 return STATUS_ALREADY_COMPLETE
;
107 PF_INIT_OR_SET_STATUS(RtlCreateHeap
, Ntdll
);
109 if (NT_SUCCESS(status
)) {
110 PhHeapHandle
= pfRtlCreateHeap(HEAP_NO_SERIALIZE
| HEAP_GROWABLE
, NULL
, 2 * MB
, 1 * MB
, NULL
, NULL
);
111 if (PhHeapHandle
== NULL
)
112 status
= STATUS_UNSUCCESSFUL
;
118 static NTSTATUS
PhDestroyHeap(VOID
)
120 NTSTATUS status
= STATUS_SUCCESS
;
122 if (PhHeapHandle
== NULL
)
123 return STATUS_ALREADY_COMPLETE
;
125 PF_INIT_OR_SET_STATUS(RtlDestroyHeap
, Ntdll
);
127 if (NT_SUCCESS(status
)) {
128 if (pfRtlDestroyHeap(PhHeapHandle
) == NULL
) {
132 status
= STATUS_UNSUCCESSFUL
;
140 * Allocates a block of memory.
142 * \param Size The number of bytes to allocate.
144 * \return A pointer to the allocated block of memory.
147 static PVOID
PhAllocate(SIZE_T Size
)
149 PF_INIT(RtlAllocateHeap
, Ntdll
);
150 if (pfRtlAllocateHeap
== NULL
)
153 return pfRtlAllocateHeap(PhHeapHandle
, 0, Size
);
157 * Frees a block of memory allocated with PhAllocate().
159 * \param Memory A pointer to a block of memory.
162 static VOID
PhFree(PVOID Memory
)
164 PF_INIT(RtlFreeHeap
, Ntdll
);
166 if (pfRtlFreeHeap
!= NULL
)
167 pfRtlFreeHeap(PhHeapHandle
, 0, Memory
);
171 * Enumerates all open handles.
173 * \param Handles A variable which receives a pointer to a structure containing information about
174 * all opened handles. You must free the structure using PhFree() when you no longer need it.
176 * \return An NTStatus indicating success or the error code.
178 NTSTATUS
PhEnumHandlesEx(PSYSTEM_HANDLE_INFORMATION_EX
*Handles
)
180 static ULONG initialBufferSize
= 0x10000;
181 NTSTATUS status
= STATUS_SUCCESS
;
185 PF_INIT_OR_SET_STATUS(NtQuerySystemInformation
, Ntdll
);
186 if (!NT_SUCCESS(status
))
189 bufferSize
= initialBufferSize
;
190 buffer
= PhAllocate(bufferSize
);
192 return STATUS_NO_MEMORY
;
194 while ((status
= pfNtQuerySystemInformation(SystemExtendedHandleInformation
,
195 buffer
, bufferSize
, NULL
)) == STATUS_INFO_LENGTH_MISMATCH
) {
199 // Fail if we're resizing the buffer to something very large.
200 if (bufferSize
> PH_LARGE_BUFFER_SIZE
)
201 return STATUS_INSUFFICIENT_RESOURCES
;
203 buffer
= PhAllocate(bufferSize
);
205 return STATUS_NO_MEMORY
;
208 if (!NT_SUCCESS(status
)) {
213 if (bufferSize
<= 0x200000)
214 initialBufferSize
= bufferSize
;
215 *Handles
= (PSYSTEM_HANDLE_INFORMATION_EX
)buffer
;
223 * \param ProcessHandle A variable which receives a handle to the process.
224 * \param DesiredAccess The desired access to the process.
225 * \param ProcessId The ID of the process.
227 * \return An NTStatus indicating success or the error code.
229 NTSTATUS
PhOpenProcess(PHANDLE ProcessHandle
, ACCESS_MASK DesiredAccess
, HANDLE ProcessId
)
231 NTSTATUS status
= STATUS_SUCCESS
;
232 OBJECT_ATTRIBUTES objectAttributes
;
235 if ((LONG_PTR
)ProcessId
== (LONG_PTR
)GetCurrentProcessId()) {
236 *ProcessHandle
= NtCurrentProcess();
240 PF_INIT_OR_SET_STATUS(NtOpenProcess
, Ntdll
);
241 if (!NT_SUCCESS(status
))
244 clientId
.UniqueProcess
= ProcessId
;
245 clientId
.UniqueThread
= NULL
;
247 InitializeObjectAttributes(&objectAttributes
, NULL
, 0, NULL
, NULL
);
248 status
= pfNtOpenProcess(ProcessHandle
, DesiredAccess
, &objectAttributes
, &clientId
);
254 * Query processes with open handles to a file, volume or disk.
256 * \param VolumeOrFileHandle The handle to the target.
257 * \param Information The returned list of processes.
259 * \return An NTStatus indicating success or the error code.
261 NTSTATUS
PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle
,
262 PFILE_PROCESS_IDS_USING_FILE_INFORMATION
*Information
)
264 static ULONG initialBufferSize
= 16 * KB
;
265 NTSTATUS status
= STATUS_SUCCESS
;
270 PF_INIT_OR_SET_STATUS(NtQueryInformationFile
, NtDll
);
271 if (!NT_SUCCESS(status
))
274 bufferSize
= initialBufferSize
;
275 buffer
= PhAllocate(bufferSize
);
277 return STATUS_INSUFFICIENT_RESOURCES
;
279 while ((status
= pfNtQueryInformationFile(VolumeOrFileHandle
, &isb
, buffer
, bufferSize
,
280 FileProcessIdsUsingFileInformation
)) == STATUS_INFO_LENGTH_MISMATCH
) {
283 // Fail if we're resizing the buffer to something very large.
284 if (bufferSize
> 64 * MB
)
285 return STATUS_INSUFFICIENT_RESOURCES
;
286 buffer
= PhAllocate(bufferSize
);
289 if (!NT_SUCCESS(status
)) {
294 if (bufferSize
<= 64 * MB
)
295 initialBufferSize
= bufferSize
;
296 *Information
= (PFILE_PROCESS_IDS_USING_FILE_INFORMATION
)buffer
;
302 * Query the full commandline that was used to create a process.
303 * This can be helpful to differentiate between service instances (svchost.exe).
304 * Taken from: https://stackoverflow.com/a/14012919/1069307
306 * \param hProcess A handle to a process.
308 * \return A Unicode commandline string, or NULL on error.
309 * The returned string must be freed by the caller.
311 static PWSTR
GetProcessCommandLine(HANDLE hProcess
)
313 PWSTR wcmdline
= NULL
;
315 DWORD pp_offset
, cmd_offset
;
316 NTSTATUS status
= STATUS_SUCCESS
;
318 PBYTE peb
= NULL
, pp
= NULL
;
320 // Determine if 64 or 32-bit processor
321 GetNativeSystemInfo(&si
);
322 if ((si
.wProcessorArchitecture
== PROCESSOR_ARCHITECTURE_AMD64
) || (si
.wProcessorArchitecture
== PROCESSOR_ARCHITECTURE_ARM64
)) {
331 // PEB and Process Parameters (we only need the beginning of these structs)
332 peb
= (PBYTE
)calloc(pp_offset
+ 8, 1);
335 pp
= (PBYTE
)calloc(cmd_offset
+ 16, 1);
339 IsWow64Process(GetCurrentProcess(), &wow
);
341 // 32-bit process running on a 64-bit OS
342 PROCESS_BASIC_INFORMATION_WOW64 pbi
= { 0 };
344 UNICODE_STRING_WOW64
* ucmdline
;
346 PF_INIT_OR_OUT(NtWow64QueryInformationProcess64
, NtDll
);
347 PF_INIT_OR_OUT(NtWow64ReadVirtualMemory64
, NtDll
);
349 status
= pfNtWow64QueryInformationProcess64(hProcess
, 0, &pbi
, sizeof(pbi
), NULL
);
350 if (!NT_SUCCESS(status
))
353 status
= pfNtWow64ReadVirtualMemory64(hProcess
, pbi
.PebBaseAddress
, peb
, pp_offset
+ 8, NULL
);
354 if (!NT_SUCCESS(status
))
357 // Read Process Parameters from the 64-bit address space
358 params
= (ULONGLONG
)*((ULONGLONG
*)(peb
+ pp_offset
));
359 status
= pfNtWow64ReadVirtualMemory64(hProcess
, params
, pp
, cmd_offset
+ 16, NULL
);
360 if (!NT_SUCCESS(status
))
363 ucmdline
= (UNICODE_STRING_WOW64
*)(pp
+ cmd_offset
);
364 wcmdline
= (PWSTR
)calloc(ucmdline
->Length
+ 1, sizeof(WCHAR
));
365 if (wcmdline
== NULL
)
367 status
= pfNtWow64ReadVirtualMemory64(hProcess
, ucmdline
->Buffer
, wcmdline
, ucmdline
->Length
, NULL
);
368 if (!NT_SUCCESS(status
)) {
374 // 32-bit process on a 32-bit OS, or 64-bit process on a 64-bit OS
375 PROCESS_BASIC_INFORMATION pbi
= { 0 };
377 UNICODE_STRING
* ucmdline
;
379 PF_INIT_OR_OUT(NtQueryInformationProcess
, NtDll
);
381 status
= pfNtQueryInformationProcess(hProcess
, 0, &pbi
, sizeof(pbi
), NULL
);
382 if (!NT_SUCCESS(status
))
386 if (!ReadProcessMemory(hProcess
, pbi
.PebBaseAddress
, peb
, pp_offset
+ 8, NULL
))
389 // Read Process Parameters
390 params
= (PBYTE
*)*(LPVOID
*)(peb
+ pp_offset
);
391 if (!ReadProcessMemory(hProcess
, params
, pp
, cmd_offset
+ 16, NULL
))
394 ucmdline
= (UNICODE_STRING
*)(pp
+ cmd_offset
);
395 // In the absolute, someone could craft a process with dodgy attributes to try to cause an overflow
396 ucmdline
->Length
= min(ucmdline
->Length
, 512);
397 wcmdline
= (PWSTR
)calloc(ucmdline
->Length
+ 1, sizeof(WCHAR
));
398 if (!ReadProcessMemory(hProcess
, ucmdline
->Buffer
, wcmdline
, ucmdline
->Length
, NULL
)) {
411 static int GetDevicePathName(PHY_DRIVE_INFO
*pPhyDrive
, WCHAR
*wDevPath
)
415 CHAR DevPath
[MAX_PATH
] = { 0 };
417 safe_sprintf(PhyDrive
, "\\\\.\\PhysicalDrive%d", pPhyDrive
->PhyDrive
);
419 if (0 == QueryDosDeviceA(PhyDrive
+ 4, DevPath
, sizeof(DevPath
)))
421 Log("QueryDosDeviceA failed error:%u", GetLastError());
422 strcpy_s(DevPath
, sizeof(DevPath
), "???");
426 Log("QueryDosDeviceA success %s", DevPath
);
429 for (i
= 0; DevPath
[i
] && i
< MAX_PATH
; i
++)
431 wDevPath
[i
] = DevPath
[i
];
438 static __inline DWORD
GetModuleFileNameExU(HANDLE hProcess
, HMODULE hModule
, char* lpFilename
, DWORD nSize
)
440 DWORD ret
= 0, err
= ERROR_INVALID_DATA
;
441 // coverity[returned_null]
442 walloc(lpFilename
, nSize
);
443 ret
= GetModuleFileNameExW(hProcess
, hModule
, wlpFilename
, nSize
);
444 err
= GetLastError();
446 && ((ret
= wchar_to_utf8_no_alloc(wlpFilename
, lpFilename
, nSize
)) == 0)) {
447 err
= GetLastError();
454 int FindProcessOccupyDisk(HANDLE hDrive
, PHY_DRIVE_INFO
*pPhyDrive
)
456 WCHAR wDevPath
[MAX_PATH
] = { 0 };
457 const char *access_rights_str
[8] = { "n", "r", "w", "rw", "x", "rx", "wx", "rwx" };
458 NTSTATUS status
= STATUS_SUCCESS
;
459 PSYSTEM_HANDLE_INFORMATION_EX handles
= NULL
;
460 POBJECT_NAME_INFORMATION buffer
= NULL
;
463 ULONG_PTR last_access_denied_pid
= 0;
465 USHORT wHandleNameLen
;
466 HANDLE dupHandle
= NULL
;
467 HANDLE processHandle
= NULL
;
468 BOOLEAN bFound
= FALSE
, bGotCmdLine
, verbose
= TRUE
;
469 ULONG access_rights
= 0;
471 char cmdline
[MAX_PATH
] = { 0 };
472 wchar_t wexe_path
[MAX_PATH
], *wcmdline
;
476 Log("FindProcessOccupyDisk for PhyDrive %d", pPhyDrive
->PhyDrive
);
478 GetDevicePathName(pPhyDrive
, wDevPath
);
479 _wHandleName
= wDevPath
;
482 PF_INIT_OR_SET_STATUS(NtQueryObject
, Ntdll
);
483 PF_INIT_OR_SET_STATUS(NtDuplicateObject
, NtDll
);
484 PF_INIT_OR_SET_STATUS(NtClose
, NtDll
);
486 if (NT_SUCCESS(status
))
487 status
= PhCreateHeap();
489 if (NT_SUCCESS(status
))
490 status
= PhEnumHandlesEx(&handles
);
492 if (!NT_SUCCESS(status
)) {
493 Log("Warning: Could not enumerate process handles: %s", NtStatusError(status
));
497 pid
[0] = (ULONG_PTR
)0;
500 wHandleNameLen
= (USHORT
)wcslen(_wHandleName
);
503 buffer
= PhAllocate(bufferSize
);
509 PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo
=
510 (i
< handles
->NumberOfHandles
) ? &handles
->Handles
[i
] : NULL
;
512 if ((dupHandle
!= NULL
) && (processHandle
!= NtCurrentProcess())) {
513 pfNtClose(dupHandle
);
517 // Update the current handle's process PID and compare against last
518 // Note: Be careful about not trying to overflow our list!
519 pid
[cur_pid
] = (handleInfo
!= NULL
) ? handleInfo
->UniqueProcessId
: -1;
521 if (pid
[0] != pid
[1]) {
522 cur_pid
= (cur_pid
+ 1) % 2;
524 // If we're switching process and found a match, print it
526 Log("* [%06u] %s (%s)", (UINT32
)pid
[cur_pid
], cmdline
, access_rights_str
[access_rights
& 0x7]);
531 // Close the previous handle
532 if (processHandle
!= NULL
) {
533 if (processHandle
!= NtCurrentProcess())
534 pfNtClose(processHandle
);
535 processHandle
= NULL
;
539 // Exit loop condition
540 if (i
>= handles
->NumberOfHandles
)
543 // Don't bother with processes we can't access
544 if (handleInfo
->UniqueProcessId
== last_access_denied_pid
)
547 // Filter out handles that aren't opened with Read (bit 0), Write (bit 1) or Execute (bit 5) access
548 if ((handleInfo
->GrantedAccess
& 0x23) == 0)
551 // Open the process to which the handle we are after belongs, if not already opened
552 if (pid
[0] != pid
[1]) {
553 status
= PhOpenProcess(&processHandle
, PROCESS_DUP_HANDLE
| PROCESS_QUERY_INFORMATION
| PROCESS_VM_READ
,
554 (HANDLE
)handleInfo
->UniqueProcessId
);
555 // There exists some processes we can't access
556 if (!NT_SUCCESS(status
)) {
557 //Log("SearchProcess: Could not open process %ld: %s",
558 // handleInfo->UniqueProcessId, NtStatusError(status));
559 processHandle
= NULL
;
560 if (status
== STATUS_ACCESS_DENIED
) {
561 last_access_denied_pid
= handleInfo
->UniqueProcessId
;
567 // Now duplicate this handle onto our own process, so that we can access its properties
568 if (processHandle
== NtCurrentProcess()) {
572 status
= pfNtDuplicateObject(processHandle
, (HANDLE
)handleInfo
->HandleValue
,
573 NtCurrentProcess(), &dupHandle
, 0, 0, 0);
574 if (!NT_SUCCESS(status
))
578 // Filter non-storage handles. We're not interested in them and they make NtQueryObject() freeze
579 if (GetFileType(dupHandle
) != FILE_TYPE_DISK
)
582 // A loop is needed because the I/O subsystem likes to give us the wrong return lengths...
585 // TODO: We might potentially still need a timeout on ObjectName queries, as PH does...
586 status
= pfNtQueryObject(dupHandle
, ObjectNameInformation
, buffer
, bufferSize
, &returnSize
);
587 if (status
== STATUS_BUFFER_OVERFLOW
|| status
== STATUS_INFO_LENGTH_MISMATCH
||
588 status
== STATUS_BUFFER_TOO_SMALL
) {
589 Log("SearchProcess: Realloc from %d to %d", bufferSize
, returnSize
);
590 bufferSize
= returnSize
;
592 buffer
= PhAllocate(bufferSize
);
597 } while (--attempts
);
598 if (!NT_SUCCESS(status
)) {
599 Log("SearchProcess: NtQueryObject failed for handle %X of process %ld: %s",
600 handleInfo
->HandleValue
, handleInfo
->UniqueProcessId
, NtStatusError(status
));
604 // we are looking for a partial match and the current length is smaller
605 if (wHandleNameLen
> buffer
->Name
.Length
)
608 // Match against our target string
609 if (wcsncmp(_wHandleName
, buffer
->Name
.Buffer
, wHandleNameLen
) != 0)
612 // If we are here, we have a process accessing our target!
615 // Keep a mask of all the access rights being used
616 access_rights
|= handleInfo
->GrantedAccess
;
617 // The Executable bit is in a place we don't like => reposition it
618 if (access_rights
& 0x20)
619 access_rights
= (access_rights
& 0x03) | 0x04;
621 // If this is the very first process we find, print a header
623 Log("WARNING: The following process(es) or service(s) are accessing %S:", _wHandleName
);
625 // Where possible, try to get the full command line
628 wcmdline
= GetProcessCommandLine(processHandle
);
629 if (wcmdline
!= NULL
) {
631 wchar_to_utf8_no_alloc(wcmdline
, cmdline
, sizeof(cmdline
));
635 // If we couldn't get the full commandline, try to get the executable path
637 bGotCmdLine
= (GetModuleFileNameExU(processHandle
, 0, cmdline
, MAX_PATH
- 1) != 0);
639 // The above may not work on Windows 7, so try QueryFullProcessImageName (Vista or later)
641 bGotCmdLine
= QueryFullProcessImageNameW(processHandle
, 0, wexe_path
, &size
);
643 wchar_to_utf8_no_alloc(wexe_path
, cmdline
, sizeof(cmdline
));
646 // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses
647 // '\Device\Harddisk#\Partition#\' instead drive letters
649 bGotCmdLine
= (GetProcessImageFileNameW(processHandle
, wexe_path
, MAX_PATH
) != 0);
651 wchar_to_utf8_no_alloc(wexe_path
, cmdline
, sizeof(cmdline
));
654 // Complete failure => Just craft a default process name that includes the PID
656 safe_sprintf(cmdline
, "Unknown_Process_0x%llx", (ULONGLONG
)handleInfo
->UniqueProcessId
);
662 Log("You should close these applications before attempting to reformat the drive.");
664 Log("NOTE: Could not identify the process(es) or service(s) accessing %S", _wHandleName
);