Windows Process Listing using ToolHelp32 API

Get a detailed walk-through on the code of process listing using ToolHelp32 API from scratch. You will also learn to enumerate the threads and modules for each process and will know about its advantages and challenges

Windows Process Listing using ToolHelp32 API

As I have discussed earlier that there are different techniques that have different challenges and provisions for the amount of information you can gather, ToolHelp32 so far is one of the handiest and verbose interfaces for listing not only the process details but also threads and process modules. Personally, I prefer to use this API above all others

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

About ToolHelp32 Api

Tool help library is a set of functions dealing with snapshots of the process, threads, modules and memory structures like a heap. It provides the utility functions to iterate over these entries without focussing on the number of entries. The snapshot taken at a time is a read-only handle and any entry created or removed will not be reflected in it. So to get a real-time update, you need to call the function in a while(TRUE) { ... } loop.

Today you will learn how to list all the processes and print the basic details of each of them and also integrate them over modules (DLLs loaded in the process's virtual address space) and the threads associated with the process.

Code Walkthrough

We will first start with adding the debug privileges or spawning the elevated process if that failed somehow. Since this is already explained in the separate post, I would recommend you to first read Windows Process Listing Using WTS API – Part 2 and then come back here.

To get the snapshot, we can use the CreateToolhelp32Snapshot() function from the tlhelp32.h header file. If the function will fail it will return INVALID_HANDLE_VALUE, otherwise a valid snapshot handle

HANDLE CreateToolhelp32Snapshot(
  [in] DWORD dwFlags,
  [in] DWORD th32ProcessID
);
Signature of CreateToolhelp32Snapshot function from the tlhelp32.h header file
  1. Since we are looking for the processs' snapshot, this value should be set to TH32CS_SNAPPROCESS
  2. This is required when you want to get the snapshot for a specific process. In this case, it will be ignored. So it's better to have the NULL value.

Now instantiate an object of the PROCESSENTRY32 struct. Assign the size of the PROCESSENTRY32 to its dwSize property. If this is not initialized the traversings function will fail.

To get the first process entry you can use Process32First() function and for the next process entry you can use Process32Next(). If the entry exists and is copied to the buffer, it will return TRUE, otherwise FALSE

BOOL Process32First(
  [in]      HANDLE           hSnapshot,
  [in, out] LPPROCESSENTRY32 lppe
);

BOOL Process32Next(
  [in]  HANDLE           hSnapshot,
  [out] LPPROCESSENTRY32 lppe
);
Signature of the Process32First and Process32Next functions from the tlhelp32.h header file
  1. Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function
  2. Pass the reference to the instance of the PROCESSENTRY32 struct. This will contain the details of the process and will be updated each time when passed into the Process32Next function.

Before moving on to the next process entry, let's call other functions defined in pch.h – ListProcessThreads and ListProcessModules

Listing Information of the Threads in the Process

Create a snapshot handle with TH32CS_SNAPTHREAD flag and 0x0 as the process id. Like processes, this will take a snapshot of all the threads for all the processes at once. You need to now create an instance of THREADENTRY32 struct and initialize the dwSize property with the size of the THREADENTRY32 struct.

For the first entry, you can use Thread32First() and then traverse other entries using Thread32Next(). If the entry exists and is copied to the buffer passed in the second parameter, it will return TRUE, otherwise FALSE. The first argument of the functions is for providing the handle containing a snapshot of all the threads.

BOOL Thread32First(
  [in]      HANDLE          hSnapshot,
  [in, out] LPTHREADENTRY32 lpte
);

BOOL Thread32Next(
  [in]  HANDLE          hSnapshot,
  [out] LPTHREADENTRY32 lpte
);
Signature of the Thread32First and Thread32Next functions from the tlhelp32.h header file
  1. Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function
  2. Pass the reference of the instance of THREADENTRY32 struct which will contain the information of the thread and will be updated each time when passed into the Thread32Next function.

Since we have to filter the threads of a particular process and print them, we can compare te.th32OwnerProcessID with the process ID passed to the function.

VOID ListProcessThreads(DWORD dwPID) {
	// -- SNIP --
	do {
    	if (te.th32OwnerProcessID == dwPID) {
        	/**
				Print the threads details for dwPID process here
             */
        }
	} while (Thread32Next(hThread, &te));
    // -- SNIP --
}
Filtering and printing the threads for the current process only

We will have to create the snapshot and close the handle every time while iterating over each process. One time snapshot will not work in this case because the number of threads can change and it is recommended to get a fresh snapshot while fetching the details.

Now while processing the details of threads, they might get killed or completed. Luckily you can have this information as well by calling GetExitCodeThread() function from processthreadsapi.h header file.

BOOL GetExitCodeThread(
  [in]  HANDLE  hThread,
  [out] LPDWORD lpExitCode
);
Signature of the GetExitCodeThread function from the processthreadsapi.h header file
  1. Handle of the thread opened by OpenThread() function from processthreadsapi.h header with THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION access right
  2. Reference to a DWORD variable that will contain the exit code of the thread. If the value is STILL_ACTIVE, then that thread is still running

Each thread in the system and a process have priority based on which the execution is scheduled. You can get this priority from GetThreadPriority() function from the processthreadsapi.h header file. The function takes in a HANDLE to the thread with THREAD_QUERY_INFORMATION or THREAD_QUERY_LIMITED_INFORMATION access right and returns an integer value corresponding to a priority enum

int GetThreadPriority(
  [in] HANDLE hThread
);
Signature of the GetThreadPriority() function from the processthreadsapi.h header file

Listing Information of the DLL Modules Loaded into Process's Virtual Address Space

Each process requires a set of functions defined in the respective DLL libraries. In case you want to learn about how functions from DLLs are exported and used in a different program, I have written a post – Loading DLLs using C++ in Windows

The application we are building will also have the DLLs to be loaded in order to make it work. You can have a list and other information like a number of references for these modules for a process. Since the modules can be of x64 or x32 architecture, we will be printing for both of them. For more information, read this

Create a snapshot of modules with TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32 as the flags and dwPID (from the function argument) as the process id. After this create an instance of MODULEENTRY32 struct and assign the size of this structure to the dwSize property. This is required, other the functions to traverse the snapshot will fail for sure.

For the first entry, you can use Module32First() and then traverse other entries using Module32Next(). If the entry exists and is copied to the buffer passed in the second parameter, it will return TRUE, otherwise FALSE. The first argument of the functions is for providing the handle containing a snapshot of all the threads.

BOOL Module32First(
  [in]      HANDLE          hSnapshot,
  [in, out] LPMODULEENTRY32 lpme
);


BOOL Module32Next(
  [in]  HANDLE          hSnapshot,
  [out] LPMODULEENTRY32 lpme
);
Signatures of the Module32First and Module32Next functions from the tlhelp32.h header file
  1. Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function for the particular process id passed to dwPID parameter
  2. Pass the reference of the instance of MODULEENTRY32 struct which will contain the information of the thread and will be updated each time when passed into the Module32Next function.

When you will complete the code, build it for any architecture. On running you will get an overwhelming output and now it will make sense to you why I like this method so much

Resources