Process Enumeration
- Author
- Moise Medici
- Updated
- 23 Nov 2025 · Completed
Method 1
The following code illustrates how the combination of CreateToolhelp32Snapshot, Process32First and Process32Next can be used to enumerate the active processes running in the system.
This method is quick and easy to implement; however, it is not very stealthy and is easy to analyze.
#include <windows.h>#include <tlhelp32.h>
#include <stdio.h>#include <string.h>
#define RET_SUCCESS 0#define RET_ERROR -1
int main() { HANDLE hSnapshot = NULL;
hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if (hSnapshot == INVALID_HANDLE_VALUE) { printf("cannot get handle from CreateToolhelp32Snapshot, error: %lu\n", GetLastError()); return RET_ERROR; }
PROCESSENTRY32 process_s = {.dwSize = sizeof(PROCESSENTRY32)}; if (Process32First(hSnapshot, &process_s) == FALSE) { printf("cannot get process from Process32First, error: %lu", GetLastError()); CloseHandle(hSnapshot); return RET_ERROR; }
do {
printf("Process ID: %lu, Executable: %s\n", process_s.th32ProcessID, process_s.szExeFile); if (strcmp(process_s.szExeFile, "notepad.exe") == 0) { printf("Notepad detected. Exiting\n\n"); break; }
} while (Process32Next(hSnapshot, &process_s));
CloseHandle(hSnapshot); return RET_SUCCESS;}The first function, windows.CreateToolhelp32Snapshot, is called with the arguments 2 and 0. Based on the Microsoft documentation, this is the signature of the function:
HANDLE CreateToolhelp32Snapshot( [in] DWORD dwFlags, [in] DWORD th32ProcessID);The first value, dwFlags, indicates which portions of the system to include in the snapshot. Here 2 is equal to TH32CS_SNAPPROCESS, which means, quoting the documentation: “Includes all processes in the system in the snapshot. To enumerate the processes, see Process32First.”
The second value, th32ProcessID, indicates which process to take a snapshot of. However, since with dwFlags the snapshot is requested for all the processes, the th32ProcessID argument is ignored, and 0 is passed.
The return value is a handle to the snapshot if successful.
After the execution of this call, the result is a snapshot of the system’s running processes at the moment of the snapshot. To start iterating over the processes, the code calls Process32First, which starts the “cursor” over the list of processes, and continues the iteration by calling Process32Next.
Both Process32First and Process32Next have the same signature:
BOOL Process32First( [in] HANDLE hSnapshot, [in, out] LPPROCESSENTRY32 lppe);The first parameter is the snapshot and the second is a structure containing information about a single process. In the first iteration, the structure is empty and gets filled in when Process32First and Process32Next are called.
The code then prints the names of the processes and checks them. As an example, it checks if notepad.exe is open. If it is, a message is printed: Notepad detected. Exiting, otherwise no such message appears.
After executing the binary derived from the compilation of the above code, the output will be similar to the following:
Process ID: 5768, Executable: svchost.exeProcess ID: 6992, Executable: dllhost.exeProcess ID: 7276, Executable: svchost.exeProcess ID: 7432, Executable: cmd.exeProcess ID: 7440, Executable: conhost.exeProcess ID: 7680, Executable: myapp.exeIf Notepad is open, the executable recognizes it and stops as soon as it is found:
Process ID: 7276, Executable: svchost.exeProcess ID: 7432, Executable: cmd.exeProcess ID: 7440, Executable: conhost.exeProcess ID: 7844, Executable: notepad.exeNotepad detected. Exiting