Windows Process Listing Using PSApi
Get a detailed walkthrough on process listing via PSApi. You will also learn about its pros-n-cons and the new set of functions from the PS Api suite related to module enumeration.
Hey there! Today you will learn a very basic API to get the list of processes and minimum details like a list of its modules. You will get a deeper dive into enumerating the processes and set of functions that are used to get information like modules, image file name and base name of the process (aka process name)
Overview
PS Api is also known as Process Status API. It contains various methods to enumerate the running process on the system, device drivers and other information regarding the process like modules loaded into the process's virtual address space.
This set of functions will fail in the least privileged session. At most, you will be able to get the process basename and file name. But since we are using the AddSeDebugPrivilege and SpawnElevatedProcess functions to forcefully enable the debug privileges on the currently running process, we can the details of the modules from the processs' memory. For this
In case you haven't read my post on enabling the SeDebugPrivilege on the current process's token and spawning the elevated process when failed to enable it, I would recommend you to first read that post to debug the processed owned by different users.
Code Walkthrough
While enumerating the process, the function requires a block of memory to copy the process ids. It doesn't scale automatically as we saw in Tool Help32 API. You can get a list of process ids from the EnumProcesses()
function from Psapi.h
header file. Since the function. If the function succeeds, it will return TRUE
, otherwise FALSE
BOOL EnumProcesses(
[out] DWORD *lpidProcess,
[in] DWORD cb,
[out] LPDWORD lpcbNeeded
);
EnumProcess
function from Psapi.h
header fileTo get the number of processes you can utilize the value from lpcbNeeded and divide it by the sizeof(DWORD)
.
DWORD dwCount = lpcbNeeded / sizeof(DWORD);
If the value of dwCount is equal to dwLimit then we can say that there might be more process ids that failed from being copied into the buffer because of less memory. In this case, it is recommended to release the memory of the current buffer, reallocate the memory with double size than of the current. Check this until all the processes are copied into the buffer.
Now you have all the processes to find and print the details. Before doing that, if the PID is 0x0
, which is for "System Idle Process", simply put continue
statement and prevent further execution of the loop
DWORD dwPID = *(lpProcesses + i);
if (dwPID == 0x0) continue;
To get the handle of the process by its id, you can use the OpenProcess()
function from processthreadsapi.h
header file. If the function succeeds, it will return a valid handle to the process, otherwise NULL
HANDLE OpenProcess(
[in] DWORD dwDesiredAccess,
[in] BOOL bInheritHandle,
[in] DWORD dwProcessId
);
OpenProcess
function from processthreadsapi.h
header filer- Since we are supposed to read the list of the module mapped into the process virtual memory. For this, it is required to read the specific memory contents. It is required to have
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
access rights for the handle - We are not inheriting any process from the handle, so provide
FALSE
here - Pass the process's id whose handle you want to get. In this case, it would be dwPID
Are you wondering why we are skipping the System Idle Process? From the documentation of OpenProcess function – "If the specified process is the System Idle Process (0x00000000), the function fails and the last error code is ERROR_INVALID_PARAMETER
"
To get the process's name, you can use the GetModuleBaseNameA()
function from Psapi.h
. If the function succeeds, it will return the total number of characters copied in the buffer, otherwise 0x0
.
DWORD GetModuleBaseNameA(
[in] HANDLE hProcess,
[in, optional] HMODULE hModule,
[out] LPSTR lpBaseName,
[in] DWORD nSize
);
GetModuleBaseNameA
from Psapi.h
header file- Handle of the process for which you want to get the module base name.
- The very first module in the list of the mapped module is the image base or the current image file. if this parameter is
NULL
, it will return the name of the file used for creating the process. - A buffer of
CHAR *
orLPSTR
to hold the contents of the base name. - The size of the lpBaseName buffer.
The reason why we are using PROCESS_VM_READ
access while opening the process is found in the documentation of the GetModuleBaseName
function – "The GetModuleBaseName function is primarily designed for use by debuggers and similar applications that must extract module information from another process."
To get the full path of the image file used for starting the process, we can use the GetProcessImageFileNameA()
function from the Psapi.h
header file. If the function succeeds, it will return the number of bytes copied in the buffer, otherwise 0x0
.
DWORD GetProcessImageFileNameA(
[in] HANDLE hProcess,
[out] LPSTR lpImageFileName,
[in] DWORD nSize
);
GetProcessImageFileNameA
function from Psapi.h
header file- Handle of the process with either
PROCESS_QUERY_INFORMATION
orPROCESS_QUERY_LIMITED_INFORMATION
access rights - A buffer to hold the contents of the image file name of the process handle.s
- The size of the lpImageFileName buffer
Enumerating Modules
Now it's time to enumerate modules and print the details like base name, file name, base address and size of the image file. You are supposed to use the EnumProcessModulesEx()
function from the Psapi.h
header file. If the function succeeds it will return TRUE
, otherwise FALSE
BOOL EnumProcessModulesEx(
[in] HANDLE hProcess,
[out] HMODULE *lphModule,
[in] DWORD cb,
[out] LPDWORD lpcbNeeded,
[in] DWORD dwFilterFlag
);
EnumProcessModulesEx
from the Psapi.h
header file- Provide the handle of the process with
PROCESS_VM_READ
access rights. - Initialise a buffer to hold a reference to
HMODULE
s. Like aHANDLE
is for process and files,HMODULE
is a handle to process's modules - The size of the lphModule array, in bytes.
- Pass the reference of
DWORD
variable which will contain the bytes ofHMODULES
returned. This is then divided by thesizeof(DWORD)
to get the number of modules enumerated. - You can explicitly pass the filter flag to enumerate architecture dependant modules. For this demo, we can use the
LIST_MODULES_ALL
value.
Now we will use these handles to further print the details about the modules. Let's start by getting the base name using GetModuleBaseNameA()
from the Psapi.h
header file. This time it is required to pass the HMODULE
instance to 2nd argument.
Like the process's image path, you can also print the details for each module because these DLLs files exist on the filesystem. You can use the GetModuleFileNameExA()
function from the Psapi.h
header file. If the function succeeds, it will return the bytes of data copied into the buffer, otherwise 0x0
DWORD GetModuleFileNameExA(
[in] HANDLE hProcess,
[in, optional] HMODULE hModule,
[out] LPSTR lpFilename,
[in] DWORD nSize
);
GetModuleFileNameExA
function from Psapi.h
header file- Handle of the process with
PROCESS_QUERY_INFORMATION
orPROCESS_QUERY_LIMITED_INFORMATION
access rights. - Handle of the module retrieved from the EnumProcessModulesEx function.
- Reference to the buffer containing the file name of the module. The starting of a few parts of the path will look similar to the process's image file name.
- The size of the lpFilename buffer, in bytes.
Note: The GetModuleFileNameEx function does not retrieve the path for modules that were loaded using the LOAD_LIBRARY_AS_DATAFILE
flag
Other details like the address of the module and the size of its image, when loaded into virtual memory, can be retrieved from the GetModuleInformation()
function from the Psapi.h
header file. If the function succeeds it will return TRUE
, otherwise FALSE
BOOL GetModuleInformation(
[in] HANDLE hProcess,
[in] HMODULE hModule,
[out] LPMODULEINFO lpmodinfo,
[in] DWORD cb
);
GetModuleInformation
from Psapi.h
header file- A handle to the process with
PROCESS_QUERY_INFORMATION
andPROCESS_VM_READ
access rights. - Handle of the module retrieved from the EnumProcessModulesEx function.
- Pass the reference of the
MODULEINFO
structure that will receive the relevant information. Please make sure the buffer is already initialized, otherwise, the function will return error - Provide the size of the structure in bytes. You can use
sizeof(MODULEINFO)
for this
It will provide you with the 3 fields described below
- lpBaseOfDll – Load address of the module in the process's virtual memory
- EntryPoint – Entry point of the module from the PE header file. It is the address that is called when the module is attached to the thread or process, then DllMain is called if it exists
- SizeOfImage – The size of the linear space that the module occupies in the process's virtual address space, in bytes
The addresses look good when printed in hex form. You can use the std::hex
formatter to change the cout stream to hexadecimal and later use the std::dec
formatter to switch back to the default mode. Also, sizes are well represented in human-readable format, instead of plain bytes. Well, we are not going to write the algorithm for that from scratch. Windows provide a set of api functions to perform such an operation. In this case, we will use the StrFormatByteSizeA()
function from the shlwapi.h
header file.
PSTR StrFormatByteSizeA(
[in] DWORD dw,
[out] PSTR pszBuf,
[in] UINT cchBuf
);
StrFormatByteSizeA
function from shlwapi.h
header file- Pass the number of the bytes to be converted to human-readable format
- The buffer to contain the human-readable format of the bytes passed in the first argument
- The size of the buffer pointed to by pszBuf, in bytes.
Once you have completed all these steps, it will look like the following as shown in the video.
References
- https://tbhaxor.com/windows-process-listing-using-wtsapi32-2/
- https://tbhaxor.com/windows-process-listing-using-toolhelp32/
- https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-processes
- https://docs.microsoft.com/en-us/windows/win32/psapi/enumerating-all-modules-for-a-process
- https://docs.microsoft.com/en-us/windows/win32/api/Psapi/nf-psapi-enumprocesses
- https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openprocess
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamea
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulebasenamea
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-enumprocessmodulesex
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmodulefilenameexa
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getmoduleinformation
- https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-moduleinfo
- https://en.cppreference.com/w/cpp/io/manip/hex
- https://docs.microsoft.com/en-us/windows/win32/api/shlwapi/nf-shlwapi-strformatbytesizea