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 |