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.

Windows Process Listing Using PSApi

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)

WinAPI-RedBlue/Process Listing/PS Api at main · tbhaxor/WinAPI-RedBlue
Source codes of Windows API Exploitation for Red and Blue teams from Pentester Academy - WinAPI-RedBlue/Process Listing/PS Api at main · tbhaxor/WinAPI-RedBlue

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
);
Signature of EnumProcess function from Psapi.h header file

To get the number of processes you can utilize the value from lpcbNeeded and divide it by the sizeof(DWORD).

DWORD dwCount = lpcbNeeded / sizeof(DWORD);
Getting the total count of processes copied into the buffer

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;
Skipping the "System Idle Process" entry from the buffer

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
);
Signature of OpenProcess function from processthreadsapi.h header filer
  1. 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
  2. We are not inheriting any process from the handle, so provide FALSE here
  3. 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
);
Signature of GetModuleBaseNameA from Psapi.h header file
  1. Handle of the process for which you want to get the module base name.
  2. 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.
  3. A buffer of CHAR * or LPSTR to hold the contents of the base name.
  4. 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
);
Signature of the GetProcessImageFileNameA function from Psapi.h header file
  1. Handle of the process with either PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights
  2. A buffer to hold the contents of the image file name of the process handle.s
  3. 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
);
Signature of EnumProcessModulesEx from the Psapi.h header file
  1. Provide the handle of the process with PROCESS_VM_READ access rights.
  2. Initialise a buffer to hold a reference to HMODULEs. Like a HANDLE is for process and files, HMODULE is a handle to process's modules
  3. The size of the lphModule array, in bytes.
  4. Pass the reference of DWORD variable which will contain the bytes of HMODULES returned. This is then divided by the sizeof(DWORD) to get the number of modules enumerated.
  5. 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
);
Signature of GetModuleFileNameExA function from Psapi.h header file
  1. Handle of the process with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
  2. Handle of the module retrieved from the EnumProcessModulesEx function.
  3. 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.
  4. 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
);
Signature of GetModuleInformation from Psapi.h header file
  1. A handle to the process with PROCESS_QUERY_INFORMATION and PROCESS_VM_READ access rights.
  2. Handle of the module retrieved from the EnumProcessModulesEx function.
  3. 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
  4. 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
  • EntryPointEntry 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
);
Signature of StrFormatByteSizeA function from shlwapi.h header file
  1. Pass the number of the bytes to be converted to human-readable format
  2. The buffer to contain the human-readable format of the bytes passed in the first argument
  3. 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.

Process listing using PS Api in action. Also compared the output with WTS Api

References