Windows Process Listing using NTQuerySystemInformation
Get acquainted with the undocumented low-level yet powerful APIs from winternls and how to use the NtQuerySystemInformation function to get a list of all the processes running in the system
Hello friends! Today you will learn one of the most underrated yet powerful APIs from the undocumented set of APIs. In this post, I will guide you on how to get the crucial details like the memory usage of the process along with basic details like PID, Process Name and Session ID using NTQuerySystemInformation function from ntdll.dll library
Overview
NTQuerySystemInformation is defined in the low-level library ntdll.dll, which contains the NT kernel functions. These functions are then later used by other high-level APIs like WTS32Api, ToolHelp32 or PSApi with another set of functions to provide required details.
For a long time, this API wasn't documented on MSDN because it's being used internally by the Windows OS and its developers. But later on, some reverse engineers dug deep into the applications and found that it exists and tried creating malware that circumvented the anti-malware software. So, Microsoft decided to document it in order to have the signatures for such malware. Well, this is purely what I think, not from any official resource.
This is widely used in the Sysinternal tools like Process Explorer and the Windows Task Manager. In this post, I will only guide you through its usage, not in conjunction with other functions to enumerate furthermore details of the process. You can use your knowledge from the previous posts and create your own stealthy process explorer tool. I would love to see that, once you do, let me know at [email protected]
Code Walkthrough
C++ is a strictly typed compiled language that validates the parameters and the return type of a program during compile time. The winternl.h
header file does not contain the prototype of this function. So let's have it in our pch.h
header file
This signature is shamelessly copied from the MSDN documentation here. This also contains other rarely used APIs from the ntdll.dll library.
You will see IN
and OUT
placeholders before the types of the parameters. They are defined as nothing, so during compile time, nothing will be replaced at that place. This is only used to help developers by giving additional information about how to handle the argument.
Getting an appropriate buffer with a list of all the processes
The NtQuerySystemInformation function requires a pre-allocated buffer to copy the information and its size. Since we don't have the required size, we can use the same function to return the amount of size required and loop until all the process gets copied into the buffer.
Function parameters description for NtQuerySystemInformation are as follows:–
- Pass the value
SystemProcessInformation
from theSYSTEM_INFORMATION_CLASS
enumeration class to tell the function that we want to process specific information only. - Convert the allocated buffer of
SYSTEM_PROCESS_INFORMATION
struct toLPVOID
and pass its reference here. Once the function will succeed, this will contain all the required information about processes - Pass the size used while allocating the above buffer.
- Create a
DWORD
to contain a number of bytes returned and pass its reference to this parameter. If the function fails with the error codeSTATUS_INFO_LENGTH_MISMATCH
, this parameter will contain the actual size required to allocate the container buffer.
Traversing the entries from the buffer
The very first thing to print is the unique process identifier. In this, we will get UniqueProcessId
in the form of a HANDLE
object. Luckily there is a macro used to convert the handles to ULONG
– HandleToUlong
.
Now it's time for the image name (aka process's name). It is a UNICODE_STRING
structure containing an optional Buffer
field. To keep things simple and easy, you can use the following ternary syntax directly in the std::wcout
stream.
After printing the number of handles open and threads running, their other set fields left contain the data related to occupied size in bytes. Now printing such a number is pretty boring. We can use the StrFormatByteSizeW
from the shlwapi.h
header file. You can see the usage of this function here, in the code. If the function fails, it will return NULL
- Size in bytes to be converted to human-readable format.
- Create a buffer with size
MAX_PATH
and pass its reference here which will contain the contents of the formatted size - Pass the size of the buffer pointed to by pszBuf, in characters.
Check the video below to know how this function works and what information has been returned by it.
Resources
- https://www.computerhope.com/issues/ch000960.htm
- https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation
- http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented Functions%2FSystem Information%2FNtQuerySystemInformation.html
- http://undocumented.ntinternals.net/UserMode/Undocumented Functions/System Information/SYSTEM_INFORMATION_CLASS.html
- http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented Functions%2FSystem Information%2FStructures%2FSYSTEM_PROCESS_INFORMATION.html
- https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/class.htm
- https://docs.microsoft.com/en-us/windows/win32/api/subauth/ns-subauth-unicode_string