| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * $Id: process_info.c 1142 2011-10-05 18:45:49Z g.rodola $ | |
| 3 * | |
| 4 * Copyright (c) 2009, Jay Loden, Giampaolo Rodola'. All rights reserved. | |
| 5 * Use of this source code is governed by a BSD-style license that can be | |
| 6 * found in the LICENSE file. | |
| 7 * | |
| 8 * Helper functions related to fetching process information. Used by | |
| 9 * _psutil_mswindows module methods. | |
| 10 */ | |
| 11 | |
| 12 #include <Python.h> | |
| 13 #include <windows.h> | |
| 14 #include <Psapi.h> | |
| 15 #include <tlhelp32.h> | |
| 16 | |
| 17 #include "security.h" | |
| 18 #include "process_info.h" | |
| 19 #include "ntextapi.h" | |
| 20 | |
| 21 /* | |
| 22 * NtQueryInformationProcess code taken from | |
| 23 * http://wj32.wordpress.com/2009/01/24/howto-get-the-command-line-of-processes/ | |
| 24 * typedefs needed to compile against ntdll functions not exposted in the API | |
| 25 */ | |
| 26 typedef LONG NTSTATUS; | |
| 27 | |
| 28 typedef NTSTATUS (NTAPI *_NtQueryInformationProcess)( | |
| 29 HANDLE ProcessHandle, | |
| 30 DWORD ProcessInformationClass, | |
| 31 PVOID ProcessInformation, | |
| 32 DWORD ProcessInformationLength, | |
| 33 PDWORD ReturnLength | |
| 34 ); | |
| 35 | |
| 36 typedef struct _PROCESS_BASIC_INFORMATION | |
| 37 { | |
| 38 PVOID Reserved1; | |
| 39 PVOID PebBaseAddress; | |
| 40 PVOID Reserved2[2]; | |
| 41 ULONG_PTR UniqueProcessId; | |
| 42 PVOID Reserved3; | |
| 43 } PROCESS_BASIC_INFORMATION, *PPROCESS_BASIC_INFORMATION; | |
| 44 | |
| 45 | |
| 46 /* | |
| 47 * A wrapper around OpenProcess setting NSP exception if process | |
| 48 * no longer exists. | |
| 49 * "pid" is the process pid, "dwDesiredAccess" is the first argument | |
| 50 * exptected by OpenProcess. | |
| 51 * Return a process handle or NULL. | |
| 52 */ | |
| 53 HANDLE | |
| 54 handle_from_pid_waccess(DWORD pid, DWORD dwDesiredAccess) | |
| 55 { | |
| 56 HANDLE hProcess; | |
| 57 DWORD processExitCode = 0; | |
| 58 | |
| 59 hProcess = OpenProcess(dwDesiredAccess, FALSE, pid); | |
| 60 if (hProcess == NULL) { | |
| 61 if (GetLastError() == ERROR_INVALID_PARAMETER) { | |
| 62 NoSuchProcess(); | |
| 63 } | |
| 64 else { | |
| 65 PyErr_SetFromWindowsErr(0); | |
| 66 } | |
| 67 return NULL; | |
| 68 } | |
| 69 | |
| 70 /* make sure the process is running */ | |
| 71 GetExitCodeProcess(hProcess, &processExitCode); | |
| 72 if (processExitCode == 0) { | |
| 73 NoSuchProcess(); | |
| 74 CloseHandle(hProcess); | |
| 75 return NULL; | |
| 76 } | |
| 77 return hProcess; | |
| 78 } | |
| 79 | |
| 80 | |
| 81 /* | |
| 82 * Same as handle_from_pid_waccess but implicitly uses | |
| 83 * PROCESS_QUERY_INFORMATION | PROCESS_VM_READ as dwDesiredAccess | |
| 84 * parameter for OpenProcess. | |
| 85 */ | |
| 86 HANDLE | |
| 87 handle_from_pid(DWORD pid) { | |
| 88 DWORD dwDesiredAccess = PROCESS_QUERY_INFORMATION | PROCESS_VM_READ; | |
| 89 return handle_from_pid_waccess(pid, dwDesiredAccess); | |
| 90 } | |
| 91 | |
| 92 | |
| 93 // fetch the PEB base address from NtQueryInformationProcess() | |
| 94 PVOID | |
| 95 GetPebAddress(HANDLE ProcessHandle) | |
| 96 { | |
| 97 _NtQueryInformationProcess NtQueryInformationProcess = | |
| 98 (_NtQueryInformationProcess)GetProcAddress( | |
| 99 GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); | |
| 100 PROCESS_BASIC_INFORMATION pbi; | |
| 101 | |
| 102 NtQueryInformationProcess(ProcessHandle, 0, &pbi, sizeof(pbi), NULL); | |
| 103 return pbi.PebBaseAddress; | |
| 104 } | |
| 105 | |
| 106 | |
| 107 DWORD* | |
| 108 get_pids(DWORD *numberOfReturnedPIDs) { | |
| 109 int procArraySz = 1024; | |
| 110 | |
| 111 /* Win32 SDK says the only way to know if our process array | |
| 112 * wasn't large enough is to check the returned size and make | |
| 113 * sure that it doesn't match the size of the array. | |
| 114 * If it does we allocate a larger array and try again*/ | |
| 115 | |
| 116 /* Stores the actual array */ | |
| 117 DWORD *procArray = NULL; | |
| 118 DWORD procArrayByteSz; | |
| 119 | |
| 120 /* Stores the byte size of the returned array from enumprocesses */ | |
| 121 DWORD enumReturnSz = 0; | |
| 122 | |
| 123 do { | |
| 124 free(procArray); | |
| 125 procArrayByteSz = procArraySz * sizeof(DWORD); | |
| 126 procArray = malloc(procArrayByteSz); | |
| 127 | |
| 128 if (! EnumProcesses(procArray, procArrayByteSz, &enumReturnSz)) { | |
| 129 free(procArray); | |
| 130 PyErr_SetFromWindowsErr(0); | |
| 131 return NULL; | |
| 132 } | |
| 133 else if (enumReturnSz == procArrayByteSz) { | |
| 134 /* Process list was too large. Allocate more space*/ | |
| 135 procArraySz += 1024; | |
| 136 } | |
| 137 | |
| 138 /* else we have a good list */ | |
| 139 | |
| 140 } while(enumReturnSz == procArraySz * sizeof(DWORD)); | |
| 141 | |
| 142 /* The number of elements is the returned size / size of each element */ | |
| 143 *numberOfReturnedPIDs = enumReturnSz / sizeof(DWORD); | |
| 144 | |
| 145 return procArray; | |
| 146 } | |
| 147 | |
| 148 | |
| 149 int | |
| 150 pid_is_running(DWORD pid) | |
| 151 { | |
| 152 HANDLE hProcess; | |
| 153 DWORD exitCode; | |
| 154 | |
| 155 // Special case for PID 0 System Idle Process | |
| 156 if (pid == 0) { | |
| 157 return 1; | |
| 158 } | |
| 159 | |
| 160 if (pid < 0) { | |
| 161 return 0; | |
| 162 } | |
| 163 | |
| 164 hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, | |
| 165 FALSE, pid); | |
| 166 if (NULL == hProcess) { | |
| 167 // invalid parameter is no such process | |
| 168 if (GetLastError() == ERROR_INVALID_PARAMETER) { | |
| 169 CloseHandle(hProcess); | |
| 170 return 0; | |
| 171 } | |
| 172 | |
| 173 // access denied obviously means there's a process to deny access to... | |
| 174 if (GetLastError() == ERROR_ACCESS_DENIED) { | |
| 175 CloseHandle(hProcess); | |
| 176 return 1; | |
| 177 } | |
| 178 | |
| 179 CloseHandle(hProcess); | |
| 180 PyErr_SetFromWindowsErr(0); | |
| 181 return -1; | |
| 182 } | |
| 183 | |
| 184 if (GetExitCodeProcess(hProcess, &exitCode)) { | |
| 185 CloseHandle(hProcess); | |
| 186 return (exitCode == STILL_ACTIVE); | |
| 187 } | |
| 188 | |
| 189 // access denied means there's a process there so we'll assume it's running | |
| 190 if (GetLastError() == ERROR_ACCESS_DENIED) { | |
| 191 CloseHandle(hProcess); | |
| 192 return 1; | |
| 193 } | |
| 194 | |
| 195 PyErr_SetFromWindowsErr(0); | |
| 196 CloseHandle(hProcess); | |
| 197 return -1; | |
| 198 } | |
| 199 | |
| 200 | |
| 201 int | |
| 202 pid_in_proclist(DWORD pid) | |
| 203 { | |
| 204 DWORD *proclist = NULL; | |
| 205 DWORD numberOfReturnedPIDs; | |
| 206 DWORD i; | |
| 207 | |
| 208 proclist = get_pids(&numberOfReturnedPIDs); | |
| 209 if (NULL == proclist) { | |
| 210 return -1; | |
| 211 } | |
| 212 | |
| 213 for (i = 0; i < numberOfReturnedPIDs; i++) { | |
| 214 if (pid == proclist[i]) { | |
| 215 free(proclist); | |
| 216 return 1; | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 free(proclist); | |
| 221 return 0; | |
| 222 } | |
| 223 | |
| 224 | |
| 225 // Check exit code from a process handle. Return FALSE on an error also | |
| 226 BOOL is_running(HANDLE hProcess) | |
| 227 { | |
| 228 DWORD dwCode; | |
| 229 | |
| 230 if (NULL == hProcess) { | |
| 231 return FALSE; | |
| 232 } | |
| 233 | |
| 234 if (GetExitCodeProcess(hProcess, &dwCode)) { | |
| 235 return (dwCode == STILL_ACTIVE); | |
| 236 } | |
| 237 return FALSE; | |
| 238 } | |
| 239 | |
| 240 | |
| 241 // Return None to represent NoSuchProcess, else return NULL for | |
| 242 // other exception or the name as a Python string | |
| 243 PyObject* | |
| 244 get_name(long pid) | |
| 245 { | |
| 246 HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | |
| 247 PROCESSENTRY32 pe = { 0 }; | |
| 248 pe.dwSize = sizeof(PROCESSENTRY32); | |
| 249 | |
| 250 if( Process32First(h, &pe)) { | |
| 251 do { | |
| 252 if (pe.th32ProcessID == pid) { | |
| 253 CloseHandle(h); | |
| 254 return Py_BuildValue("s", pe.szExeFile); | |
| 255 } | |
| 256 } while(Process32Next(h, &pe)); | |
| 257 | |
| 258 // the process was never found, set NoSuchProcess exception | |
| 259 NoSuchProcess(); | |
| 260 CloseHandle(h); | |
| 261 return NULL; | |
| 262 } | |
| 263 | |
| 264 CloseHandle(h); | |
| 265 return PyErr_SetFromWindowsErr(0); | |
| 266 } | |
| 267 | |
| 268 | |
| 269 /* returns parent pid (as a Python int) for given pid or None on failure */ | |
| 270 PyObject* | |
| 271 get_ppid(long pid) | |
| 272 { | |
| 273 HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); | |
| 274 PROCESSENTRY32 pe = { 0 }; | |
| 275 pe.dwSize = sizeof(PROCESSENTRY32); | |
| 276 | |
| 277 if( Process32First(h, &pe)) { | |
| 278 do { | |
| 279 if (pe.th32ProcessID == pid) { | |
| 280 ////printf("PID: %i; PPID: %i\n", pid, pe.th32ParentProcessID); | |
| 281 CloseHandle(h); | |
| 282 return Py_BuildValue("I", pe.th32ParentProcessID); | |
| 283 } | |
| 284 } while(Process32Next(h, &pe)); | |
| 285 | |
| 286 // the process was never found, set NoSuchProcess exception | |
| 287 NoSuchProcess(); | |
| 288 CloseHandle(h); | |
| 289 return NULL; | |
| 290 } | |
| 291 | |
| 292 CloseHandle(h); | |
| 293 return PyErr_SetFromWindowsErr(0); | |
| 294 } | |
| 295 | |
| 296 | |
| 297 | |
| 298 /* | |
| 299 * returns a Python list representing the arguments for the process | |
| 300 * with given pid or NULL on error. | |
| 301 */ | |
| 302 PyObject* | |
| 303 get_arg_list(long pid) | |
| 304 { | |
| 305 int nArgs, i; | |
| 306 LPWSTR *szArglist; | |
| 307 HANDLE hProcess; | |
| 308 PVOID pebAddress; | |
| 309 PVOID rtlUserProcParamsAddress; | |
| 310 UNICODE_STRING commandLine; | |
| 311 WCHAR *commandLineContents; | |
| 312 PyObject *arg = NULL; | |
| 313 PyObject *arg_from_wchar = NULL; | |
| 314 PyObject *argList = NULL; | |
| 315 | |
| 316 hProcess = handle_from_pid(pid); | |
| 317 if(hProcess == NULL) { | |
| 318 return NULL; | |
| 319 } | |
| 320 | |
| 321 pebAddress = GetPebAddress(hProcess); | |
| 322 | |
| 323 /* get the address of ProcessParameters */ | |
| 324 #ifdef _WIN64 | |
| 325 if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 32, | |
| 326 &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) | |
| 327 #else | |
| 328 if (!ReadProcessMemory(hProcess, (PCHAR)pebAddress + 0x10, | |
| 329 &rtlUserProcParamsAddress, sizeof(PVOID), NULL)) | |
| 330 #endif | |
| 331 { | |
| 332 ////printf("Could not read the address of ProcessParameters!\n"); | |
| 333 PyErr_SetFromWindowsErr(0); | |
| 334 CloseHandle(hProcess); | |
| 335 return NULL; | |
| 336 } | |
| 337 | |
| 338 /* read the CommandLine UNICODE_STRING structure */ | |
| 339 #ifdef _WIN64 | |
| 340 if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 112, | |
| 341 &commandLine, sizeof(commandLine), NULL)) | |
| 342 #else | |
| 343 if (!ReadProcessMemory(hProcess, (PCHAR)rtlUserProcParamsAddress + 0x40, | |
| 344 &commandLine, sizeof(commandLine), NULL)) | |
| 345 #endif | |
| 346 { | |
| 347 ////printf("Could not read CommandLine!\n"); | |
| 348 CloseHandle(hProcess); | |
| 349 PyErr_SetFromWindowsErr(0); | |
| 350 return NULL; | |
| 351 } | |
| 352 | |
| 353 | |
| 354 /* allocate memory to hold the command line */ | |
| 355 commandLineContents = (WCHAR *)malloc(commandLine.Length+1); | |
| 356 | |
| 357 /* read the command line */ | |
| 358 if (!ReadProcessMemory(hProcess, commandLine.Buffer, | |
| 359 commandLineContents, commandLine.Length, NULL)) | |
| 360 { | |
| 361 ////printf("Could not read the command line string!\n"); | |
| 362 CloseHandle(hProcess); | |
| 363 PyErr_SetFromWindowsErr(0); | |
| 364 free(commandLineContents); | |
| 365 return NULL; | |
| 366 } | |
| 367 | |
| 368 /* print the commandline */ | |
| 369 ////printf("%.*S\n", commandLine.Length / 2, commandLineContents); | |
| 370 | |
| 371 // null-terminate the string to prevent wcslen from returning incorrect leng
th | |
| 372 // the length specifier is in characters, but commandLine.Length is in bytes | |
| 373 commandLineContents[(commandLine.Length/sizeof(WCHAR))] = '\0'; | |
| 374 | |
| 375 // attemempt tp parse the command line using Win32 API, fall back on string | |
| 376 // cmdline version otherwise | |
| 377 szArglist = CommandLineToArgvW(commandLineContents, &nArgs); | |
| 378 if (NULL == szArglist) { | |
| 379 // failed to parse arglist | |
| 380 // encode as a UTF8 Python string object from WCHAR string | |
| 381 arg_from_wchar = PyUnicode_FromWideChar(commandLineContents, | |
| 382 commandLine.Length / 2); | |
| 383 #if PY_MAJOR_VERSION >= 3 | |
| 384 argList = Py_BuildValue("N", PyUnicode_AsUTF8String(arg_from_wchar))
; | |
| 385 #else | |
| 386 argList = Py_BuildValue("N", PyUnicode_FromObject(arg_from_wchar)); | |
| 387 #endif | |
| 388 } | |
| 389 else { | |
| 390 // arglist parsed as array of UNICODE_STRING, so convert each to Python | |
| 391 // string object and add to arg list | |
| 392 argList = Py_BuildValue("[]"); | |
| 393 for(i=0; i<nArgs; i++) { | |
| 394 ////printf("%d: %.*S (%d characters)\n", i, wcslen(szArglist[i]), | |
| 395 // szArglist[i], wcslen(szArglist[i])); | |
| 396 arg_from_wchar = PyUnicode_FromWideChar(szArglist[i], | |
| 397 wcslen(szArglist[i]) | |
| 398 ); | |
| 399 #if PY_MAJOR_VERSION >= 3 | |
| 400 arg = PyUnicode_FromObject(arg_from_wchar); | |
| 401 #else | |
| 402 arg = PyUnicode_AsUTF8String(arg_from_wchar); | |
| 403 #endif | |
| 404 Py_XDECREF(arg_from_wchar); | |
| 405 PyList_Append(argList, arg); | |
| 406 Py_XDECREF(arg); | |
| 407 } | |
| 408 } | |
| 409 | |
| 410 LocalFree(szArglist); | |
| 411 free(commandLineContents); | |
| 412 CloseHandle(hProcess); | |
| 413 return argList; | |
| 414 } | |
| 415 | |
| 416 | |
| 417 #define PH_FIRST_PROCESS(Processes) ((PSYSTEM_PROCESS_INFORMATION)(Processes)) | |
| 418 | |
| 419 #define PH_NEXT_PROCESS(Process) ( \ | |
| 420 ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset ? \ | |
| 421 (PSYSTEM_PROCESS_INFORMATION)((PCHAR)(Process) + \ | |
| 422 ((PSYSTEM_PROCESS_INFORMATION)(Process))->NextEntryOffset) : \ | |
| 423 NULL \ | |
| 424 ) | |
| 425 | |
| 426 const STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; | |
| 427 const STATUS_BUFFER_TOO_SMALL = 0xC0000023L; | |
| 428 | |
| 429 /* | |
| 430 * Given a process PID and a PSYSTEM_PROCESS_INFORMATION structure | |
| 431 * fills the structure with process information. | |
| 432 * On success return 1, else 0 with Python exception already set. | |
| 433 */ | |
| 434 int | |
| 435 get_process_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, PVOID *retB
uffer) | |
| 436 { | |
| 437 static ULONG initialBufferSize = 0x4000; | |
| 438 NTSTATUS status; | |
| 439 PVOID buffer; | |
| 440 ULONG bufferSize; | |
| 441 PSYSTEM_PROCESS_INFORMATION process; | |
| 442 | |
| 443 // get NtQuerySystemInformation | |
| 444 typedef DWORD (_stdcall *NTQSI_PROC) (int, PVOID, ULONG, PULONG); | |
| 445 NTQSI_PROC NtQuerySystemInformation; | |
| 446 HINSTANCE hNtDll; | |
| 447 hNtDll = LoadLibrary(TEXT("ntdll.dll")); | |
| 448 NtQuerySystemInformation = (NTQSI_PROC)GetProcAddress( | |
| 449 hNtDll, "NtQuerySystemInformation"); | |
| 450 | |
| 451 bufferSize = initialBufferSize; | |
| 452 buffer = malloc(bufferSize); | |
| 453 | |
| 454 while (TRUE) { | |
| 455 status = NtQuerySystemInformation(SystemProcessInformation, buffer, | |
| 456 bufferSize, &bufferSize); | |
| 457 | |
| 458 if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_INFO_LENGTH_MI
SMATCH) | |
| 459 { | |
| 460 free(buffer); | |
| 461 buffer = malloc(bufferSize); | |
| 462 } | |
| 463 else { | |
| 464 break; | |
| 465 } | |
| 466 } | |
| 467 | |
| 468 if (status != 0) { | |
| 469 PyErr_Format(PyExc_RuntimeError, "NtQuerySystemInformation() failed"); | |
| 470 return 0; | |
| 471 } | |
| 472 | |
| 473 if (bufferSize <= 0x20000) { | |
| 474 initialBufferSize = bufferSize; | |
| 475 } | |
| 476 | |
| 477 process = PH_FIRST_PROCESS(buffer); | |
| 478 do { | |
| 479 if (process->UniqueProcessId == (HANDLE)pid) { | |
| 480 *retProcess = process; | |
| 481 *retBuffer = buffer; | |
| 482 return 1; | |
| 483 } | |
| 484 } while ( (process = PH_NEXT_PROCESS(process)) ); | |
| 485 | |
| 486 NoSuchProcess(); | |
| 487 return 0; | |
| 488 } | |
| 489 | |
| 490 | |
| OLD | NEW |