0x101 - GetProcessID
January 11, 2020 ยท View on GitHub
Let's start simple and get a process ID (PID). You can get the current PID by calling GetCurrentProcessId(). To get the PID of another process by its name is much more complicated.
Getting the PID of Another Process
Traditionally you would get a process ID by looking for the window name, but this can cause issues if the window title changes. Instead, we will use a better technique, although it isn't as simple.
Before I show you how we will do this, think about how we could. The first thing that comes to my mind is somehow getting a list or structure which contains a list of processes. We could then loop over this list looking for the process name we are after.
Getting All Processes
As it turns out there is a way for us to loop over every running process using something called a snapshot. Inside of TlHelp32.h there is a function called CreateToolhelp32Snapshot(). This function "Takes a snapshot of the specified processes, as well as the heaps, modules, and threads used by these processes" (MSDN).
Documentation: https://docs.microsoft.com/en-us/windows/win32/api/tlhelp32/nf-tlhelp32-createtoolhelp32snapshot
Here is the definition of CreateToolhelp32Snapshot():
HANDLE CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);
dwFlagsis what you want the snapshot to contain. There are many flags and a list of them can be found in the documentation. We will be usingTH32CS_SNAPPROCESSwhich will get us a snapshot of all running processes.th32ProcessIDis a PID if one is required. For example, the flagTH32CS_SNAPMODULEgets a snapshot of all modules in a process. To specify what process to get the modules from you would provide a PID through theth32ProcessIDparameter.
CreateToolhelp32Snapshot() returns a HANDLE.
Here is how we can call this function:
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
hSnapshot now contains a snapshot of all processes. Now it's time to find a process given its name.
Looping Over Snapshot
To find the process we are looking for by name we will loop over every process in hSnapshot and check their names. In the documentation for CreateToolhelp32Snapshot() there is a note with TH32CS_SNAPPROCESS that says "To enumerate the processes, see Process32First." Process32First() returns true if the first entry of the process list has been copied to the buffer. This function will validate that the snapshot we give it contains at least one valid process.
Here is the definition of Process32First():
BOOL Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);
hSnapshotis a handle to a snapshot which we obtained viaCreateToolhelp32Snapshot().lppeis a pointer to aPROCESSENTRY32structure. APROCESSENTRY32contains all sorts of useful information such as the process name, ID, parent ID, and more. ThePROCESSENTRY32structure passed to this function must have thedwSizemember initialized.
Here is the definition for PROCESSENTRY32:
typedef struct tagPROCESSENTRY32W
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // this process
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID; // associated exe
DWORD cntThreads;
DWORD th32ParentProcessID; // this process's parent process
LONG pcPriClassBase; // Base priority of process's threads
DWORD dwFlags;
WCHAR szExeFile[MAX_PATH]; // Path
} PROCESSENTRY32W;
typedef PROCESSENTRY32W * PPROCESSENTRY32W;
typedef PROCESSENTRY32W * LPPROCESSENTRY32W;
Now we can get the first process, but that's not very helpful because we want to look at all processes. To keep moving through the snapshot we will use Process32Next which is alost exactly the same as Process32First(). The difference between the two is that Process32Next() will get the next process, not the first.
I know it's a tad confusing, but that's what we get for using functions written by Microsoft.
Here is everything implemented, and don't worry I'll explain the code.
DWORD procID;
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry); // Init dwSize for Process32First()
// "Snapshot" of all running processes.
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(hSnapshot, &procEntry)) {
// Loop over the Snapshot looking for a process with a name matching the procName parameter:
do {
if (!wcscmp(procEntry.szExeFile, L"PROCESS.exe")) {
CloseHandle(hSnapshot);
procID = procEntry.th32ProcessID;
}
} while (Process32Next(hSnapshot, &procEntry));
CloseHandle(hSnapshot);
}
procIDis going to contain the PID of the process we are looking for. In this case, that process is"PROCESS.exe".- First, we create and initialize a
PROCESSENTRY32calledprocEntry. - Then we create our snapshot.
- Then we use
Process32First()to validate the snapshot before we loop over it. - We use a do-while loop to iterate over the snapshot. The condition of the loop is
Process32Next(hSnapshot, &procEntry)which will make the loop iterate over the entire snapshot until it reaches the end. When it reaches the end there is no other process in the snapshot, soProcess32Next()returns false, thus the loop ends. - In the loop there is the if-statement
if (!wcscmp(procEntry.szExeFile, L"PROCESS.exe")). This compares the name of the process the loop is currently on (procEntry.szExeFile) to the name we are looking for ("PROCESS.exe").procEntry.szExeFileis aWCHARwhich is why I'm usingwcscmp. It's also why there is anLin front of the process name. TheLspecifies the string as being aWCHAR. Finally, the!in front ofwcscmpis there becausewcscmpreturns zero if the two strings match, and a number less than or greater than zero if they don't. The!is shorter than writing== 0. - If the if-statement runs, meaning the process we were looking for was found, then we close the snapshot handle and set
procIDto the PID of the current process in the loop (procEntry.th32ProcessID). - If no match is found the handle is closed and nothing happens.
Final Product:
Here is a function that gets the PID of a process given its name:
// Get PID from an executable given its name:
DWORD GetProcID(const wchar_t* procName) {
PROCESSENTRY32 procEntry;
procEntry.dwSize = sizeof(procEntry); // Init dwSize for Process32First()
// "Snapshot" of all running processes.
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
if (Process32First(hSnapshot, &procEntry)) {
// Loop over the Snapshot looking for a process with a name matching the procName parameter:
do {
if (!wcscmp(procEntry.szExeFile, procName)) {
CloseHandle(hSnapshot);
return procEntry.th32ProcessID;
}
} while (Process32Next(hSnapshot, &procEntry));
CloseHandle(hSnapshot);
}
return 0;
}