Process Enumeration
- Author
- Moise Medici
- Updated
- 23 Nov 2025 · Completed
Method 2
The second method uses the EnumProcesses, OpenProcess and EnumProcessModules functions.
The idea here is to gather all the process IDs that are currently running in the system, store them in PID, and open each process to get the information for that ID.
This is a more effective approach since, by accessing the process, it is already clear that there is access to it. This assumption is not true for the previous method, since CreateToolhelp32Snapshot gathers all admin and non-admin processes without any distinction.
#include <windows.h>#include <psapi.h>#include <stdio.h>
#define RET_SUCCESS 0#define RET_ERROR -1
int main() { DWORD PID[1024], cbNeeded, cbNeeded2, processCount; if (EnumProcesses(PID, sizeof(PID), &cbNeeded) == 0) { printf("cannot execute EnumProcess, error: %lu\n", GetLastError()); return RET_ERROR; }
processCount = cbNeeded / sizeof(DWORD);
HANDLE hProcess = NULL; for (int i = 0; i < processCount; i++) { hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, 0, PID[i]); if (hProcess == NULL) { continue; } HMODULE hModule = NULL; EnumProcessModules(hProcess, &hModule, sizeof(HMODULE), &cbNeeded2); if (hModule == NULL) { CloseHandle(hProcess); continue; }
char baseName[MAX_PATH] = {0}; if (GetModuleBaseNameA(hProcess, hModule, baseName, sizeof(baseName) / sizeof(CHAR)) == 0) { CloseHandle(hProcess); continue; }
printf("Process ID: %lu, Executable: %s\n", PID[i], baseName); if (strcmp(baseName, "notepad.exe") == 0) { printf("Notepad detected. Exiting\n\n"); break; } CloseHandle(hProcess); }
CloseHandle(hProcess); return RET_SUCCESS;}Looking at the function definition:
BOOL EnumProcesses( [out] DWORD *lpidProcess, [in] DWORD cb, [out] LPDWORD lpcbNeeded);There are two output variables: lpidProcess, which is a pointer to DWORD that will contain the list of process IDs currently running, and lpcbNeeded, which is the number of bytes in the lpidProcess array. The oddity about lpidProcess is that it needs to be “big enough” for all the PIDs, without knowing in advance how many there will be. So the Microsoft documentation says: “It is a good idea to use a large array, because it is hard to predict how many processes there will be at the time you call EnumProcesses.”
lpcbNeeded is useful to calculate the number of processes, as the documentation suggests: “To determine how many processes were enumerated, divide the lpcbNeeded value by sizeof(DWORD).” This is done at line 15.
Once the number of processes has been determined, the code iterates over each of them to gather more information, specifically the name.
Using OpenProcess, the code sets the desired access to both PROCESS_QUERY_INFORMATION and PROCESS_VM_READ. Why these permissions? They are defined in a Microsoft article linked in the appendix 9.
The second parameter checks if the handle needs to be inherited by the process. Since there is no need to use the handle in any of the child processes of the current process, it can be set to 0. Lastly, the process ID to open is provided.
HANDLE OpenProcess( [in] DWORD dwDesiredAccess, [in] BOOL bInheritHandle, [in] DWORD dwProcessId);The last step is to get the name of the process. To do so, the code first gets all the modules (a module is an executable or DLL loaded in the process 10) and then extracts the name. With EnumProcessModules it gets the handle needed by GetModuleBaseName.
BOOL EnumProcessModules( [in] HANDLE hProcess, [out] HMODULE *lphModule, [in] DWORD cb, [out] LPDWORD lpcbNeeded);In this function, the cb is again the size of what is being requested, in this case the HMODULE type, since the goal is to retrieve an HMODULE object, whose pointer is passed as the second argument. In this case lpcbNeeded is not used, however it must still be passed to the function.
Once there is a handle to the process and a handle to the module for that process, the code can then get the name.
DWORD GetModuleBaseNameA( [in] HANDLE hProcess, [in, optional] HMODULE hModule, [out] LPSTR lpBaseName, [in] DWORD nSize);The name of the module will be stored in lpBaseName, which is a string. Since the size of the name cannot be known in advance, it is necessary to set a high enough value. Windows provides the MAX_PATH macro, which defines the string as a maximum of 260 characters 11.
Once all the information has been collected, the code searches for the process of interest, in this case, notepad.exe. Similarly to the previous example, the output shows the relationship between PID and process name, and the executable stops if Notepad is running.