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
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
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
- Since we are looking for the processs' snapshot, this value should be set to
TH32CS_SNAPPROCESS
- 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
- Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function
- 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.
- Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function
- 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.
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.
- Handle of the thread opened by
OpenThread()
function fromprocessthreadsapi.h
header withTHREAD_QUERY_INFORMATION
orTHREAD_QUERY_LIMITED_INFORMATION
access right - Reference to a
DWORD
variable that will contain the exit code of the thread. If the value isSTILL_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
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.
- Pass the handle of the snapshot created by the CreateToolhelp32Snapshot function for the particular process id passed to
dwPID
parameter - 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
- https://docs.microsoft.com/en-us/windows/win32/toolhelp/snapshots-of-the-system
- https://docs.microsoft.com/en-us/windows/win32/toolhelp/module-walking
- https://docs.microsoft.com/en-us/windows/win32/toolhelp/process-walking
- https://docs.microsoft.com/en-us/windows/win32/toolhelp/thread-walking
- https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
- https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getexitcodethread
- https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-threadentry32
- https://docs.microsoft.com/en-us/windows/desktop/api/tlhelp32/nf-tlhelp32-thread32first
- https://docs.microsoft.com/en-us/windows/desktop/api/tlhelp32/nf-tlhelp32-thread32next
- https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/ns-tlhelp32-processentry32
- https://docs.microsoft.com/en-us/windows/desktop/api/tlhelp32/nf-tlhelp32-process32first
- https://docs.microsoft.com/en-us/windows/desktop/api/tlhelp32/nf-tlhelp32-process32next
- https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-openthread
- https://docs.microsoft.com/en-us/windows/win32/procthread/scheduling-priorities
- https://docs.microsoft.com/en-us/windows/win32/psapi/module-information