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
);
  • dwFlags is 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 using TH32CS_SNAPPROCESS which will get us a snapshot of all running processes.
  • th32ProcessID is a PID if one is required. For example, the flag TH32CS_SNAPMODULE gets a snapshot of all modules in a process. To specify what process to get the modules from you would provide a PID through the th32ProcessID parameter.

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
);
  • hSnapshot is a handle to a snapshot which we obtained via CreateToolhelp32Snapshot().
  • lppe is a pointer to a PROCESSENTRY32 structure. A PROCESSENTRY32 contains all sorts of useful information such as the process name, ID, parent ID, and more. The PROCESSENTRY32 structure passed to this function must have the dwSize member 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);
}
  1. procID is going to contain the PID of the process we are looking for. In this case, that process is "PROCESS.exe".
  2. First, we create and initialize a PROCESSENTRY32 called procEntry.
  3. Then we create our snapshot.
  4. Then we use Process32First() to validate the snapshot before we loop over it.
  5. 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, so Process32Next() returns false, thus the loop ends.
  6. 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.szExeFile is a WCHAR which is why I'm using wcscmp. It's also why there is an L in front of the process name. The L specifies the string as being a WCHAR. Finally, the ! in front of wcscmp is there because wcscmp returns 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.
  7. If the if-statement runs, meaning the process we were looking for was found, then we close the snapshot handle and set procID to the PID of the current process in the loop (procEntry.th32ProcessID).
  8. 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;   
}