[process] report the full commandline for blocking processes

* This can be useful to find which Service instance an 'svchost.exe'
  is attached to.
This commit is contained in:
Pete Batard 2019-01-02 16:36:34 +00:00
parent 7b2edbfd6f
commit 5309dc88e2
No known key found for this signature in database
GPG key ID: 38E0CF5E69EDD671
3 changed files with 173 additions and 36 deletions

View file

@ -41,6 +41,9 @@ PF_TYPE_DECL(NTAPI, BOOLEAN, RtlFreeHeap, (PVOID, ULONG, PVOID));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtQuerySystemInformation, (SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG)); PF_TYPE_DECL(NTAPI, NTSTATUS, NtQuerySystemInformation, (SYSTEM_INFORMATION_CLASS, PVOID, ULONG, PULONG));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryInformationFile, (HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS)); PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryInformationFile, (HANDLE, PIO_STATUS_BLOCK, PVOID, ULONG, FILE_INFORMATION_CLASS));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryInformationProcess, (HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtWow64QueryInformationProcess64, (HANDLE, ULONG, PVOID, ULONG, PULONG));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtWow64ReadVirtualMemory64, (HANDLE, PVOID64, PVOID, ULONG64, PULONG64));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryObject, (HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG)); PF_TYPE_DECL(NTAPI, NTSTATUS, NtQueryObject, (HANDLE, OBJECT_INFORMATION_CLASS, PVOID, ULONG, PULONG));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtDuplicateObject, (HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG)); PF_TYPE_DECL(NTAPI, NTSTATUS, NtDuplicateObject, (HANDLE, HANDLE, HANDLE, PHANDLE, ACCESS_MASK, ULONG, ULONG));
PF_TYPE_DECL(NTAPI, NTSTATUS, NtOpenProcess, (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, CLIENT_ID*)); PF_TYPE_DECL(NTAPI, NTSTATUS, NtOpenProcess, (PHANDLE, ACCESS_MASK, POBJECT_ATTRIBUTES, CLIENT_ID*));
@ -263,13 +266,13 @@ NTSTATUS PhOpenProcess(PHANDLE ProcessHandle, ACCESS_MASK DesiredAccess, HANDLE
} }
/** /**
* Query processes with open handles to a file, volume or disk. * Query processes with open handles to a file, volume or disk.
* *
* \param VolumeOrFileHandle The handle to the target. * \param VolumeOrFileHandle The handle to the target.
* \param Information The returned list of processes. * \param Information The returned list of processes.
* *
* \return An NTStatus indicating success or the error code. * \return An NTStatus indicating success or the error code.
*/ */
NTSTATUS PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle, NTSTATUS PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle,
PFILE_PROCESS_IDS_USING_FILE_INFORMATION *Information) PFILE_PROCESS_IDS_USING_FILE_INFORMATION *Information)
{ {
@ -310,6 +313,110 @@ NTSTATUS PhQueryProcessesUsingVolumeOrFile(HANDLE VolumeOrFileHandle,
return status; return status;
} }
/**
* Query the full commandline that was used to create a process.
* This can be helpful to differentiate between service instances (svchost.exe).
* Taken from: https://stackoverflow.com/a/14012919/1069307
*
* \param hProcess A handle to a process.
*
* \return A Unicode commandline string, or NULL on error.
* The returned string must be freed by the caller.
*/
static PWSTR GetProcessCommandLine(HANDLE hProcess)
{
PWSTR wcmdline = NULL;
BOOL wow;
DWORD pp_offset, cmd_offset;
NTSTATUS status = STATUS_SUCCESS;
SYSTEM_INFO si;
PBYTE peb = NULL, pp = NULL;
// Determine if 64 or 32-bit processor
GetNativeSystemInfo(&si);
if ((si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) || (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM64)) {
pp_offset = 0x20;
cmd_offset = 0x70;
} else {
pp_offset = 0x10;
cmd_offset = 0x40;
}
// PEB and Process Parameters (we only need the beginning of these structs)
peb = (PBYTE)calloc(pp_offset + 8, 1);
if (peb == NULL)
goto out;
pp = (PBYTE)calloc(cmd_offset + 16, 1);
if (pp == NULL)
goto out;
IsWow64Process(GetCurrentProcess(), &wow);
if (wow) {
// 32-bit process running on a 64-bit OS
PROCESS_BASIC_INFORMATION_WOW64 pbi = { 0 };
PVOID64 params;
UNICODE_STRING_WOW64* ucmdline;
PF_INIT_OR_OUT(NtWow64QueryInformationProcess64, NtDll);
PF_INIT_OR_OUT(NtWow64ReadVirtualMemory64, NtDll);
status = pfNtWow64QueryInformationProcess64(hProcess, 0, &pbi, sizeof(pbi), NULL);
if (!NT_SUCCESS (status))
goto out;
status = pfNtWow64ReadVirtualMemory64(hProcess, pbi.PebBaseAddress, peb, pp_offset + 8, NULL);
if (!NT_SUCCESS (status))
goto out;
// Read Process Parameters from the 64-bit address space
params = (PVOID64) *((PVOID64*)(peb + pp_offset));
status = pfNtWow64ReadVirtualMemory64(hProcess, params, pp, cmd_offset + 16, NULL);
if (!NT_SUCCESS (status))
goto out;
ucmdline = (UNICODE_STRING_WOW64*)(pp + cmd_offset);
wcmdline = (PWSTR)calloc(ucmdline->Length + 1, sizeof(WCHAR));
if (wcmdline == NULL)
goto out;
status = pfNtWow64ReadVirtualMemory64(hProcess, ucmdline->Buffer, wcmdline, ucmdline->Length, NULL);
if (!NT_SUCCESS (status)) {
safe_free(wcmdline);
goto out;
}
} else {
// 32-bit process on a 32-bit OS, or 64-bit process on a 64-bit OS
PROCESS_BASIC_INFORMATION pbi = { 0 };
PBYTE* params;
UNICODE_STRING* ucmdline;
PF_INIT_OR_OUT(NtQueryInformationProcess, NtDll);
status = pfNtQueryInformationProcess(hProcess, 0, &pbi, sizeof(pbi), NULL);
if (!NT_SUCCESS (status))
goto out;
// Read PEB
if (!ReadProcessMemory(hProcess, pbi.PebBaseAddress, peb, pp_offset + 8, NULL))
goto out;
// Read Process Parameters
params = (PBYTE*)*(LPVOID*)(peb + pp_offset);
if (!ReadProcessMemory(hProcess, params, pp, cmd_offset + 16, NULL))
goto out;
ucmdline = (UNICODE_STRING*)(pp + cmd_offset);
wcmdline = (PWSTR)calloc(ucmdline->Length + 1, sizeof(WCHAR));
if (!ReadProcessMemory(hProcess, ucmdline->Buffer, wcmdline, ucmdline->Length, NULL)) {
safe_free(wcmdline);
goto out;
}
}
out:
free(peb);
free(pp);
return wcmdline;
}
static DWORD WINAPI SearchProcessThread(LPVOID param) static DWORD WINAPI SearchProcessThread(LPVOID param)
{ {
@ -326,11 +433,11 @@ static DWORD WINAPI SearchProcessThread(LPVOID param)
WCHAR *wHandleName = NULL; WCHAR *wHandleName = NULL;
HANDLE dupHandle = NULL; HANDLE dupHandle = NULL;
HANDLE processHandle = NULL; HANDLE processHandle = NULL;
BOOLEAN bFound = FALSE, bGotExePath, verbose = !_bQuiet; BOOLEAN bFound = FALSE, bGotCmdLine, verbose = !_bQuiet;
ULONG access_rights = 0; ULONG access_rights = 0;
DWORD size; DWORD size;
char exe_path[MAX_PATH] = { 0 }; char cmdline[MAX_PATH] = { 0 };
wchar_t wexe_path[MAX_PATH]; wchar_t wexe_path[MAX_PATH], *wcmdline;
int cur_pid; int cur_pid;
PF_INIT_OR_SET_STATUS(NtQueryObject, Ntdll); PF_INIT_OR_SET_STATUS(NtQueryObject, Ntdll);
@ -380,7 +487,7 @@ static DWORD WINAPI SearchProcessThread(LPVOID param)
// If we're switching process and found a match, print it // If we're switching process and found a match, print it
if (bFound) { if (bFound) {
static_sprintf (tmp, "● [%06u] %s (%s)", (uint32_t)pid[cur_pid], exe_path, access_rights_str[access_rights & 0x7]); static_sprintf (tmp, "● [%06u] %s (%s)", (uint32_t)pid[cur_pid], cmdline, access_rights_str[access_rights & 0x7]);
vuprintf(tmp); vuprintf(tmp);
StrArrayAdd(&BlockingProcess, tmp, TRUE); StrArrayAdd(&BlockingProcess, tmp, TRUE);
bFound = FALSE; bFound = FALSE;
@ -411,7 +518,7 @@ static DWORD WINAPI SearchProcessThread(LPVOID param)
// Open the process to which the handle we are after belongs, if not already opened // Open the process to which the handle we are after belongs, if not already opened
if (pid[0] != pid[1]) { if (pid[0] != pid[1]) {
status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, status = PhOpenProcess(&processHandle, PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
(HANDLE)handleInfo->UniqueProcessId); (HANDLE)handleInfo->UniqueProcessId);
// There exists some processes we can't access // There exists some processes we can't access
if (!NT_SUCCESS(status)) { if (!NT_SUCCESS(status)) {
@ -485,38 +592,47 @@ static DWORD WINAPI SearchProcessThread(LPVOID param)
access_mask |= (BYTE) (access_rights & 0x7) + 0x80; // Bit 7 is always set if a process was found access_mask |= (BYTE) (access_rights & 0x7) + 0x80; // Bit 7 is always set if a process was found
// If this is the very first process we find, print a header // If this is the very first process we find, print a header
if (exe_path[0] == 0) if (cmdline[0] == 0)
vuprintf("WARNING: The following process(es) or service(s) are accessing %s:", _HandleName); vuprintf("WARNING: The following process(es) or service(s) are accessing %s:", _HandleName);
// First, we try to get the executable path using GetModuleFileNameEx // Where possible, try to get the full command line
bGotExePath = (GetModuleFileNameExU(processHandle, 0, exe_path, MAX_PATH - 1) != 0); wcmdline = GetProcessCommandLine(processHandle);
if (wcmdline != NULL) {
bGotCmdLine = TRUE;
wchar_to_utf8_no_alloc(wcmdline, cmdline, sizeof(cmdline));
free(wcmdline);
}
// If we couldn't get the full commandline, try to get the executable path
if (!bGotCmdLine)
bGotCmdLine = (GetModuleFileNameExU(processHandle, 0, cmdline, MAX_PATH - 1) != 0);
// The above may not work on Windows 7, so try QueryFullProcessImageName (Vista or later) // The above may not work on Windows 7, so try QueryFullProcessImageName (Vista or later)
if (!bGotExePath) { if (!bGotCmdLine) {
size = MAX_PATH; size = MAX_PATH;
PF_INIT(QueryFullProcessImageNameW, kernel32); PF_INIT(QueryFullProcessImageNameW, kernel32);
if ( (pfQueryFullProcessImageNameW != NULL) && if ( (pfQueryFullProcessImageNameW != NULL) &&
(bGotExePath = pfQueryFullProcessImageNameW(processHandle, 0, wexe_path, &size)) ) (bGotCmdLine = pfQueryFullProcessImageNameW(processHandle, 0, wexe_path, &size)) )
wchar_to_utf8_no_alloc(wexe_path, exe_path, sizeof(exe_path)); wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline));
} }
// Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses // Still nothing? Try GetProcessImageFileName. Note that GetProcessImageFileName uses
// '\Device\Harddisk#\Partition#\' instead drive letters // '\Device\Harddisk#\Partition#\' instead drive letters
if (!bGotExePath) { if (!bGotCmdLine) {
bGotExePath = (GetProcessImageFileNameW(processHandle, wexe_path, MAX_PATH) != 0); bGotCmdLine = (GetProcessImageFileNameW(processHandle, wexe_path, MAX_PATH) != 0);
if (bGotExePath) if (bGotCmdLine)
wchar_to_utf8_no_alloc(wexe_path, exe_path, sizeof(exe_path)); wchar_to_utf8_no_alloc(wexe_path, cmdline, sizeof(cmdline));
} }
// Complete failure => Just craft a default process name that includes the PID // Complete failure => Just craft a default process name that includes the PID
if (!bGotExePath) { if (!bGotCmdLine) {
static_sprintf(exe_path, "Unknown_Process_%" PRIu64, static_sprintf(cmdline, "Unknown_Process_%" PRIu64,
(ULONGLONG)handleInfo->UniqueProcessId); (ULONGLONG)handleInfo->UniqueProcessId);
} }
} }
out: out:
if (exe_path[0] != 0) if (cmdline[0] != 0)
vuprintf("You should close these applications before attempting to reformat the drive."); vuprintf("You should close these applications before attempting to reformat the drive.");
else else
vuprintf("NOTE: Could not identify the process(es) or service(s) accessing %s", _HandleName); vuprintf("NOTE: Could not identify the process(es) or service(s) accessing %s", _HandleName);

View file

@ -4,9 +4,9 @@
* *
* Modified from Process Hacker: * Modified from Process Hacker:
* https://github.com/processhacker2/processhacker2/ * https://github.com/processhacker2/processhacker2/
* Copyright © 2009-2016 wj32 * Copyright © 2017-2019 Pete Batard <pete@akeo.ie>
* Copyright © 2017 dmex * Copyright © 2017 dmex
* Copyright © 2017 Pete Batard <pete@akeo.ie> * Copyright © 2009-2016 wj32
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -50,6 +50,11 @@
#define SystemExtendedHandleInformation 64 #define SystemExtendedHandleInformation 64
#define FileProcessIdsUsingFileInformation 47 #define FileProcessIdsUsingFileInformation 47
// MinGW doesn't know this one yet
#if !defined(PROCESSOR_ARCHITECTURE_ARM64)
#define PROCESSOR_ARCHITECTURE_ARM64 12
#endif
#define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1) #define NtCurrentProcess() ((HANDLE)(LONG_PTR)-1)
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
@ -113,8 +118,24 @@ typedef struct _OBJECT_TYPES_INFORMATION
ULONG NumberOfTypes; ULONG NumberOfTypes;
} OBJECT_TYPES_INFORMATION, *POBJECT_TYPES_INFORMATION; } OBJECT_TYPES_INFORMATION, *POBJECT_TYPES_INFORMATION;
typedef struct _PROCESS_BASIC_INFORMATION_WOW64
{
PVOID Reserved1[2];
PVOID64 PebBaseAddress;
PVOID Reserved2[4];
ULONG_PTR UniqueProcessId[2];
PVOID Reserved3[2];
} PROCESS_BASIC_INFORMATION_WOW64;
typedef struct _FILE_PROCESS_IDS_USING_FILE_INFORMATION { typedef struct _UNICODE_STRING_WOW64
{
USHORT Length;
USHORT MaximumLength;
PVOID64 Buffer;
} UNICODE_STRING_WOW64;
typedef struct _FILE_PROCESS_IDS_USING_FILE_INFORMATION
{
ULONG NumberOfProcessIdsInList; ULONG NumberOfProcessIdsInList;
ULONG_PTR ProcessIdList[1]; ULONG_PTR ProcessIdList[1];
} FILE_PROCESS_IDS_USING_FILE_INFORMATION, *PFILE_PROCESS_IDS_USING_FILE_INFORMATION; } FILE_PROCESS_IDS_USING_FILE_INFORMATION, *PFILE_PROCESS_IDS_USING_FILE_INFORMATION;

View file

@ -33,7 +33,7 @@ LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL
IDD_DIALOG DIALOGEX 12, 12, 232, 326 IDD_DIALOG DIALOGEX 12, 12, 232, 326
STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_MINIMIZEBOX | WS_POPUP | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_ACCEPTFILES EXSTYLE WS_EX_ACCEPTFILES
CAPTION "Rufus 3.5.1434" CAPTION "Rufus 3.5.1435"
FONT 9, "Segoe UI Symbol", 400, 0, 0x0 FONT 9, "Segoe UI Symbol", 400, 0, 0x0
BEGIN BEGIN
LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP LTEXT "Drive Properties",IDS_DRIVE_PROPERTIES_TXT,8,6,53,12,NOT WS_GROUP
@ -394,8 +394,8 @@ END
// //
VS_VERSION_INFO VERSIONINFO VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,5,1434,0 FILEVERSION 3,5,1435,0
PRODUCTVERSION 3,5,1434,0 PRODUCTVERSION 3,5,1435,0
FILEFLAGSMASK 0x3fL FILEFLAGSMASK 0x3fL
#ifdef _DEBUG #ifdef _DEBUG
FILEFLAGS 0x1L FILEFLAGS 0x1L
@ -413,13 +413,13 @@ BEGIN
VALUE "Comments", "https://akeo.ie" VALUE "Comments", "https://akeo.ie"
VALUE "CompanyName", "Akeo Consulting" VALUE "CompanyName", "Akeo Consulting"
VALUE "FileDescription", "Rufus" VALUE "FileDescription", "Rufus"
VALUE "FileVersion", "3.5.1434" VALUE "FileVersion", "3.5.1435"
VALUE "InternalName", "Rufus" VALUE "InternalName", "Rufus"
VALUE "LegalCopyright", "© 2011-2018 Pete Batard (GPL v3)" VALUE "LegalCopyright", "© 2011-2018 Pete Batard (GPL v3)"
VALUE "LegalTrademarks", "https://www.gnu.org/copyleft/gpl.html" VALUE "LegalTrademarks", "https://www.gnu.org/copyleft/gpl.html"
VALUE "OriginalFilename", "rufus-3.5.exe" VALUE "OriginalFilename", "rufus-3.5.exe"
VALUE "ProductName", "Rufus" VALUE "ProductName", "Rufus"
VALUE "ProductVersion", "3.5.1434" VALUE "ProductVersion", "3.5.1435"
END END
END END
BLOCK "VarFileInfo" BLOCK "VarFileInfo"