NtQuerySystemInformation
Exploring Process Enumeration with NtQuerySystemInformation
Understanding how to enumerate processes in a system is crucial. One method to achieve this is through the use of NtQuerySystemInformation
, a syscall that provides extensive information about the system. This function, exported from the ntdll.dll
module, requires the use of GetModuleHandle
and GetProcAddress
to retrieve its address.
Understanding NtQuerySystemInformation
According to Microsoft’s documentation, NtQuerySystemInformation
can return a wealth of system information. For process enumeration, we focus on using the SystemProcessInformation
flag, which returns an array of SYSTEM_PROCESS_INFORMATION
structures, each representing a running process.
Retrieving NtQuerySystemInformation
To use NtQuerySystemInformation
, you first need to retrieve its address from ntdll.dll
. This involves defining a function pointer and using GetProcAddress
and GetModuleHandle
to locate the function.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Function pointer
typedef NTSTATUS (NTAPI* fnNtQuerySystemInformation)(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
fnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
// Getting NtQuerySystemInformation's address
pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtQuerySystemInformation");
if (pNtQuerySystemInformation == NULL) {
printf("[!] GetProcAddress Failed With Error : %d\n", GetLastError());
return FALSE;
}
Parameters of NtQuerySystemInformation
The function takes several parameters:
SystemInformationClass
: Specifies the type of system information to return.SystemInformation
: A pointer to a buffer that receives the requested information.SystemInformationLength
: The size of the buffer.ReturnLength
: A pointer to a variable that receives the actual size of the information.
For process enumeration, the SystemProcessInformation
flag is used, which returns an array of SYSTEM_PROCESS_INFORMATION
structures.
The SYSTEM_PROCESS_INFORMATION Structure
The SYSTEM_PROCESS_INFORMATION
structure contains several fields, but for process enumeration, the focus is on UNICODE_STRING ImageName
and UniqueProcessId
. These fields provide the process name and ID, respectively. The NextEntryOffset
field is used to navigate through the array of processes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
KPRIORITY BasePriority;
HANDLE UniqueProcessId;
PVOID Reserved2;
ULONG HandleCount;
ULONG SessionId;
PVOID Reserved3;
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG Reserved4;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
PVOID Reserved5;
SIZE_T QuotaPagedPoolUsage;
PVOID Reserved6;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
SIZE_T PrivatePageCount;
LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;
Calling NtQuerySystemInformation
To enumerate processes, NtQuerySystemInformation
must be called twice. The first call determines the size of the buffer needed, and the second call retrieves the process information. The first call is expected to fail with a STATUS_INFO_LENGTH_MISMATCH
error, which indicates the buffer size required.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ULONG uReturnLen1 = NULL,
uReturnLen2 = NULL;
PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL;
NTSTATUS STATUS = NULL;
// First NtQuerySystemInformation call
// This will fail with STATUS_INFO_LENGTH_MISMATCH
// But it will provide information about how much memory to allocate (uReturnLen1)
pNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &uReturnLen1);
// Allocating enough buffer for the returned array of `SYSTEM_PROCESS_INFORMATION` struct
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
printf("[!] HeapAlloc Failed With Error : %d\n", GetLastError());
return FALSE;
}
// Second NtQuerySystemInformation call
// Calling NtQuerySystemInformation with the correct arguments, the output will be saved to 'SystemProcInfo'
STATUS = pNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
printf("[!] NtQuerySystemInformation Failed With Error : 0x%0.8X \n", STATUS);
return FALSE;
}
Iterating Through Processes
Once the process information is retrieved, iterate through the array to access each process’s name and ID. Use NextEntryOffset
to move to the next process in the array.
1
2
// 'SystemProcInfo' will now represent a new element in the array
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
Freeing Allocated Memory
After processing the information, ensure that the allocated memory is freed to prevent memory leaks. Save the initial address of the allocated buffer to free it after use.
1
2
// Since we will modify 'SystemProcInfo', we will save its initial value before the while loop to free it later
pValueToFree = SystemProcInfo;
Complete Code for Process Enumeration
The complete code for enumerating processes using NtQuerySystemInformation
is provided below. This code demonstrates how to retrieve and iterate through process information efficiently.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
BOOL GetRemoteProcessHandle(LPCWSTR szProcName, DWORD* pdwPid, HANDLE* phProcess) {
fnNtQuerySystemInformation pNtQuerySystemInformation = NULL;
ULONG uReturnLen1 = NULL,
uReturnLen2 = NULL;
PSYSTEM_PROCESS_INFORMATION SystemProcInfo = NULL;
NTSTATUS STATUS = NULL;
PVOID pValueToFree = NULL;
pNtQuerySystemInformation = (fnNtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"NTDLL.DLL"), "NtQuerySystemInformation");
if (pNtQuerySystemInformation == NULL) {
printf("[!] GetProcAddress Failed With Error : %d\n", GetLastError());
return FALSE;
}
pNtQuerySystemInformation(SystemProcessInformation, NULL, NULL, &uReturnLen1);
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (SIZE_T)uReturnLen1);
if (SystemProcInfo == NULL) {
printf("[!] HeapAlloc Failed With Error : %d\n", GetLastError());
return FALSE;
}
// Since we will modify 'SystemProcInfo', we will save its initial value before the while loop to free it later
pValueToFree = SystemProcInfo;
STATUS = pNtQuerySystemInformation(SystemProcessInformation, SystemProcInfo, uReturnLen1, &uReturnLen2);
if (STATUS != 0x0) {
printf("[!] NtQuerySystemInformation Failed With Error : 0x%0.8X \n", STATUS);
return FALSE;
}
while (TRUE) {
// Check the process's name size
// Comparing the enumerated process name to the intended target process
if (SystemProcInfo->ImageName.Length && wcscmp(SystemProcInfo->ImageName.Buffer, szProcName) == 0) {
// Opening a handle to the target process, saving it, and then breaking
*pdwPid = (DWORD)SystemProcInfo->UniqueProcessId;
*phProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, (DWORD)SystemProcInfo->UniqueProcessId);
break;
}
// If NextEntryOffset is 0, we reached the end of the array
if (!SystemProcInfo->NextEntryOffset)
break;
// Move to the next element in the array
SystemProcInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)SystemProcInfo + SystemProcInfo->NextEntryOffset);
}
// Free using the initial address
HeapFree(GetProcessHeap(), 0, pValueToFree);
// Check if we successfully got the target process handle
if (*pdwPid == NULL || *phProcess == NULL)
return FALSE;
else
return TRUE;
}
The Undocumented Aspects of NtQuerySystemInformation
Despite its utility, NtQuerySystemInformation
remains largely undocumented. The SYSTEM_PROCESS_INFORMATION
structure, for instance, contains several Reserved
fields whose purposes are not fully known. Alternative documentation, such as ReactOS and System Informer, provides more detailed insights into these structures.
Conclusion
This method provides a comprehensive view of running processes, aiding in malware analysis and system monitoring. By mastering these techniques, cybersecurity professionals can enhance their ability to detect and analyze malicious activities on a system.
Final project
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#define TARGET_PROCESS L"Notepad.exe"
int EnumProcess_using_NtQuerySystemInformation_example() {
DWORD Pid = NULL;
HANDLE hProcess = NULL;
if (!GetRemoteProcessHandleUsingNtQuerySystem(TARGET_PROCESS, &Pid, &hProcess)) {
wprintf(L"[!] Cound Not Get %s's Process Id \n", TARGET_PROCESS);
return -1;
}
wprintf(L"[+] FOUND \"%s\" - Of Pid : %d \n", TARGET_PROCESS, Pid);
CloseHandle(hProcess);
printf("[#] Press <Enter> To Quit ... ");
getchar();
return 0;
}