-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Starting from windows 8.1, get commandline content using NtQueryInformationProcess (see #1384) #1398
Starting from windows 8.1, get commandline content using NtQueryInformationProcess (see #1384) #1398
Changes from 7 commits
463f9b8
c09a63c
b53fe97
6aba45c
7065a98
af24702
875ab8e
d81b610
05e74a8
b817434
1c2a06e
a827a98
db068de
1042b15
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -162,6 +162,72 @@ const int STATUS_INFO_LENGTH_MISMATCH = 0xC0000004; | |
const int STATUS_BUFFER_TOO_SMALL = 0xC0000023L; | ||
|
||
|
||
|
||
#define WINDOWS_UNITIALIZED 0 | ||
#define WINDOWS_XP 51 | ||
#define WINDOWS_VISTA 60 | ||
#define WINDOWS_7 61 | ||
#define WINDOWS_8 62 | ||
#define WINDOWS_81 63 | ||
#define WINDOWS_10 100 | ||
|
||
|
||
int get_windows_version() { | ||
OSVERSIONINFO ver_info; | ||
BOOL result; | ||
DWORD dwMajorVersion; | ||
DWORD dwMinorVersion; | ||
DWORD dwBuildNumber; | ||
static int windows_version = WINDOWS_UNITIALIZED; | ||
// didn't get version yet | ||
if (windows_version == WINDOWS_UNITIALIZED) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like this is unnecessary as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gotcha. I'd add a comment then ('cause I will forget for sure =)). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done :) |
||
memset(&ver_info, 0, sizeof(ver_info)); | ||
ver_info.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); | ||
result = GetVersionEx(&ver_info); | ||
if (result != FALSE) { | ||
dwMajorVersion = ver_info.dwMajorVersion; | ||
dwMinorVersion = ver_info.dwMinorVersion; | ||
dwBuildNumber = ver_info.dwBuildNumber; | ||
// Windows XP, Windows server 2003 | ||
if (dwMajorVersion == 5 && dwMinorVersion == 1) { | ||
windows_version = WINDOWS_XP; | ||
} | ||
// Windows Vista | ||
else if (dwMajorVersion == 6 && dwMinorVersion == 0) { | ||
windows_version = WINDOWS_VISTA; | ||
} | ||
// Windows 7, Windows Server 2008 R2 | ||
else if (dwMajorVersion == 6 && dwMinorVersion == 1) { | ||
windows_version = WINDOWS_7; | ||
} | ||
// Windows 8, Windows Server 2012 | ||
else if (dwMajorVersion == 6 && dwMinorVersion == 2) { | ||
windows_version = WINDOWS_8; | ||
} | ||
// Windows 8.1, Windows Server 2012 R2 | ||
else if (dwMajorVersion == 6 && dwMinorVersion == 3) | ||
{ | ||
windows_version = WINDOWS_81; | ||
} | ||
// Windows 10, Windows Server 2016 | ||
else if (dwMajorVersion == 10) { | ||
windows_version = WINDOWS_10; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: how about using a |
||
} | ||
} | ||
return windows_version; | ||
} | ||
|
||
_NtQueryInformationProcess get_NtQueryInformationProcess() { | ||
static _NtQueryInformationProcess NtQueryInformationProcess = NULL; | ||
if (NtQueryInformationProcess == NULL) { | ||
NtQueryInformationProcess = (_NtQueryInformationProcess)GetProcAddress( | ||
GetModuleHandleA("ntdll.dll"), "NtQueryInformationProcess"); | ||
} | ||
return NtQueryInformationProcess; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I like putting this in a utility function because it can be reused. Would you mind using this utility function also in other parts of the code? Also, for consistency I think it's better to rename this function to |
||
|
||
|
||
// ==================================================================== | ||
// Process and PIDs utiilties. | ||
// ==================================================================== | ||
|
@@ -804,10 +870,72 @@ psutil_get_cmdline(long pid) { | |
PyObject *py_unicode = NULL; | ||
LPWSTR *szArglist = NULL; | ||
int nArgs, i; | ||
HANDLE hProcess; | ||
int windows_version; | ||
ULONG ret_length = 4096; | ||
NTSTATUS status; | ||
char * cmdline_buffer = NULL; | ||
WCHAR * cmdline_buffer_wchar = NULL; | ||
PUNICODE_STRING tmp = NULL; | ||
DWORD string_size; | ||
_NtQueryInformationProcess NtQueryInformationProcess = NULL; | ||
|
||
windows_version = get_windows_version(); | ||
if (windows_version >= WINDOWS_81) { | ||
// by defaut, still use PEB (if command line params have been patched in PEB, we will get the actual ones) | ||
if (psutil_get_process_data(pid, KIND_CMDLINE, &data, &size) != 0) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmmm... I'm a bit confused about this. It appears regardless of the Windows version you first attempt to use the PEB method first, and fallback on using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reading the PEB to get the command line parameters still seem to be the best method if somebody has tampered with the parameters after creating the process. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I see. In this case I would recommend the following:
Also, please add a full explanation of why one method is preferred over the other one (you can include the information you wrote in the comment above). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, I think using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm.. true. Another possibility is to use the PEB method and check for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As it is, the code works. What I could do is add an explanation as why the PEB method is used first for windows >= 8.1 as you propose, but go with this code anyway, no? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes but as-is it tries the other method for any error. I would like to limit the check to ERROR_ACCESS_DENIED. If it fails for any other reason I think it's saner to just raise exception. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So something like this (not tested): int ret;
ret = psutil_get_process_data(pid, KIND_CMDLINE, &data, &size);
if ((ret != 0) &&
(GetLastError() == ERROR_ACCESS_DENIED) &&
(windows_version >= WINDOWS_81))
{
// try NtQueryInformationProcess()
PyErr_Clear();
...
}
else {
return NULL;
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ...also, I suggest to put the whole There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hint: function name could be |
||
PyErr_Clear(); // reset that we had an error, and retry with NtQueryInformationProcess | ||
// if it fails, fallback to NtQueryInformationProcess (for protected processes) | ||
NtQueryInformationProcess = get_NtQueryInformationProcess(); | ||
if (NtQueryInformationProcess == NULL) { | ||
PyErr_SetFromWindowsErr(0); | ||
goto out; | ||
} | ||
|
||
if (psutil_get_process_data(pid, KIND_CMDLINE, &data, &size) != 0) | ||
goto out; | ||
cmdline_buffer = calloc(4096, 1); | ||
if (cmdline_buffer == NULL) { | ||
PyErr_NoMemory(); | ||
goto out; | ||
} | ||
|
||
hProcess = psutil_handle_from_pid(pid, PROCESS_QUERY_LIMITED_INFORMATION); | ||
if (hProcess == NULL) { | ||
PyErr_SetFromWindowsErr(0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
goto out; | ||
} | ||
status = NtQueryInformationProcess( | ||
hProcess, | ||
60, // ProcessCommandLineInformation | ||
cmdline_buffer, | ||
ret_length, | ||
&ret_length | ||
); | ||
CloseHandle(hProcess); | ||
if (!NT_SUCCESS(status)) { | ||
PyErr_SetFromWindowsErr(0); | ||
goto out; | ||
} | ||
giampaolo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
tmp = (PUNICODE_STRING)cmdline_buffer; | ||
string_size = wcslen(tmp->Buffer) + 1; | ||
cmdline_buffer_wchar = (WCHAR *)calloc(string_size, sizeof(WCHAR)); | ||
|
||
if (cmdline_buffer_wchar == NULL) { | ||
free(cmdline_buffer); | ||
PyErr_NoMemory(); | ||
goto out; | ||
} | ||
|
||
wcscpy_s(cmdline_buffer_wchar, string_size, tmp->Buffer); | ||
data = cmdline_buffer_wchar; | ||
size = string_size * sizeof(WCHAR); | ||
free(cmdline_buffer); | ||
} | ||
} | ||
// legacy version that reads data from PEB | ||
else { | ||
if (psutil_get_process_data(pid, KIND_CMDLINE, &data, &size) != 0) | ||
goto out; | ||
} | ||
// attempt to parse the command line using Win32 API | ||
szArglist = CommandLineToArgvW(data, &nArgs); | ||
if (szArglist == NULL) { | ||
|
@@ -822,7 +950,7 @@ psutil_get_cmdline(long pid) { | |
goto out; | ||
for (i = 0; i < nArgs; i++) { | ||
py_unicode = PyUnicode_FromWideChar(szArglist[i], | ||
wcslen(szArglist[i])); | ||
wcslen(szArglist[i])); | ||
if (py_unicode == NULL) | ||
goto out; | ||
PyList_SET_ITEM(py_retlist, i, py_unicode); | ||
|
@@ -833,7 +961,8 @@ psutil_get_cmdline(long pid) { | |
py_retlist = NULL; | ||
|
||
out: | ||
LocalFree(szArglist); | ||
if (szArglist != NULL) | ||
LocalFree(szArglist); | ||
if (data != NULL) | ||
free(data); | ||
Py_XDECREF(py_unicode); | ||
|
@@ -960,3 +1089,4 @@ psutil_get_proc_info(DWORD pid, PSYSTEM_PROCESS_INFORMATION *retProcess, | |
free(buffer); | ||
return 0; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,7 +34,7 @@ psutil_token_from_handle(HANDLE hProcess) { | |
* constant, we pass through the TOKEN_PRIVILEGES constant. This value returns | ||
* an array of privileges that the account has in the environment. Iterating | ||
* through the array, we call the function LookupPrivilegeName looking for the | ||
* string �SeTcbPrivilege. If the function returns this string, then this | ||
* string “SeTcbPrivilege. If the function returns this string, then this | ||
* account has Local System privileges | ||
*/ | ||
int | ||
|
@@ -131,7 +131,6 @@ psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { | |
); | ||
|
||
if (GetLastError() != ERROR_SUCCESS) return FALSE; | ||
|
||
// second pass. set privilege based on previous setting | ||
tpPrevious.PrivilegeCount = 1; | ||
tpPrevious.Privileges[0].Luid = luid; | ||
|
@@ -160,19 +159,17 @@ psutil_set_privilege(HANDLE hToken, LPCTSTR Privilege, BOOL bEnablePrivilege) { | |
int | ||
psutil_set_se_debug() { | ||
HANDLE hToken; | ||
if (! OpenThreadToken(GetCurrentThread(), | ||
if (!OpenProcessToken(GetCurrentProcess(), | ||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, | ||
FALSE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mmm this looks wrong; aren't you now passing 3 args instead of 4? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm using GetProcessToken instead of GetThreadToken and it needs 1 less argument There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see (didn't notice you changed syscall). I checked ProcessHacker source code and it also uses |
||
&hToken) | ||
) { | ||
if (GetLastError() == ERROR_NO_TOKEN) { | ||
if (!ImpersonateSelf(SecurityImpersonation)) { | ||
CloseHandle(hToken); | ||
return 0; | ||
} | ||
if (!OpenThreadToken(GetCurrentThread(), | ||
if (!OpenProcessToken(GetCurrentProcess(), | ||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, | ||
FALSE, | ||
&hToken) | ||
) { | ||
RevertToSelf(); | ||
|
@@ -198,17 +195,15 @@ psutil_set_se_debug() { | |
int | ||
psutil_unset_se_debug() { | ||
HANDLE hToken; | ||
if (! OpenThreadToken(GetCurrentThread(), | ||
if (!OpenProcessToken(GetCurrentProcess(), | ||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, | ||
FALSE, | ||
&hToken) | ||
) { | ||
if (GetLastError() == ERROR_NO_TOKEN) { | ||
if (! ImpersonateSelf(SecurityImpersonation)) | ||
return 0; | ||
if (!OpenThreadToken(GetCurrentThread(), | ||
if (!OpenProcessToken(GetCurrentProcess(), | ||
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, | ||
FALSE, | ||
&hToken)) | ||
{ | ||
return 0; | ||
|
@@ -223,3 +218,4 @@ psutil_unset_se_debug() { | |
CloseHandle(hToken); | ||
return 1; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo?
WINDOWS_UNITIALIZED
->WINDOWS_UNINITIALIZED