Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Getting Process ID from Process Handle

4.40/5 (9 votes)
9 Jan 2008CPOL5 min read 1   1.1K  
Some ways of getting a process identifier if only the process handle is available

Introduction

A process ID is a value uniquely identifying a running process. This means that unlike handles, which can be duplicated, a process ID remains the same during the process life cycle and no other process can have the same ID value during this time. It is common to obtain a process handle by processing an ID using the OpenProcess() call. Here we shall talk about the opposite problem, i.e. having only a handle to a process and how to find the ID of the process represented by that handle. This may arise, in particular, when you inject your code into other applications and you do not know the origination of a handle. If you work under Windows XP SP1 or higher, you can just call the following with a handle you have:

C++
DWORD GetProcessId(HANDLE Process);

For lower versions, there is no simple and uniform solution. Below I shall consider only Windows 2000 and up, as (obsolete) Windows 9X provides no proper tools. Also, driver approaches are not considered here.

Three techniques will be discussed:

  1. Getting the ID by creating remote thread.
  2. Enumerating processes and comparing their attributes.
  3. Using "undocumented" ZwQueryInformationProcess().

1. Getting the ID by Creating a Remote Thread

If we want to find our own process ID, we call GetCurrentProcessId(). Intuitively it would be nice to make a target process to call it in its own context and to return us the result. To implement this, a remote thread creation technique can be used. Here is a prototype of CreateRemoteThread():

C++
HANDLE CreateRemoteThread(
    HANDLE hProcess,        // handle to process to create thread in

    LPSECURITY_ATTRIBUTES lpThreadAttributes,  // pointer to security attributes
    DWORD dwStackSize,      // initial thread stack size, in bytes
    LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function

    LPVOID lpParameter,     // argument for new thread
    DWORD dwCreationFlags,  // creation flags

    LPDWORD lpThreadId      // pointer to returned thread identifier

);

By setting lpStartAddress to the address of GetCurrentProcessId(), we cause this function to be executed as a thread routine in the context of a target process. Then it will remain, using the returned thread's handle, to get the thread exit code. One delicate thing is that GetCurrentProcessId() has no input parameters, whereas LPTHREAD_START_ROUTINE assumes one. Indeed, CreateRemoteThread() just passes execution to the code pointed by lpStartAddress not doing all necessary validations. In the majority of cases, it is rather a problem. Here we use it as an advantage: as GetCurrentProcessId() has been executed not using anything from the stack, the thread exits and then its stack is destroyed, causing no problem. Here is a full code of a function doing necessary steps:

C++
DWORD WINAPI GetProcessIDbyProcessHandle(HANDLE hProcess)
{
    // [in]  process handle
    // [out] process ID, or 0xffffffff in case of failure
    
    if (hProcess == NULL)    return 0xffffffff;
    PTHREAD_START_ROUTINE lpStartAddress = (PTHREAD_START_ROUTINE)
        GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "GetCurrentProcessId");
    if (lpStartAddress == NULL) return 0xffffffff;
    // We do not know, whether process handle already has required access rights;

    // thus we have to duplicate it
    HANDLE hProcessAccAdj;
    BOOL bRes = DuplicateHandle(GetCurrentProcess(), 
                                hProcess, GetCurrentProcess(), &hProcessAccAdj, 
                                PROCESS_QUERY_INFORMATION|PROCESS_CREATE_THREAD|
                                PROCESS_VM_OPERATION|PROCESS_VM_WRITE, 
                                FALSE, 0);
    if (!bRes || hProcessAccAdj == NULL)
    {
        UINT unError = GetLastError();
        return 0xffffffff;
    }
    // Create a remote thread; as its starting address 

    // we specify GetCurrentProcessId() address,
    // which is the same for all processes. Note that GetCurrentProcessId() has no input
    // parameters, and we don't care about our thread stack cleanup,
    // as it will be destroyed right after this call

    DWORD dwThreadID;
    HANDLE hRemoteThread = CreateRemoteThread(hProcessAccAdj, NULL, 
        0, lpStartAddress, 0, 0, &dwThreadID);
    CloseHandle(hProcessAccAdj);
    if (hRemoteThread == NULL) return 0xffffffff;
    // Wait until process ID is obtained

    // (replace INFINITE value below to a smaller value to avoid deadlocks);
    // then get the thread exit code, which is a value returned by GetCurrentProcessId()
    // in the context of the remote process
    WaitForSingleObject(hRemoteThread, INFINITE);
    DWORD dwExitCode;
    if (GetExitCodeThread(hRemoteThread, &dwExitCode) == 0)    dwExitCode = 0xffffffff;
    CloseHandle(hRemoteThread);
    return dwExitCode;
}

Unfortunately, this will fail if the security context of a target process does not allow us to open (duplicate) a handle to that process with required access rights. In particular, this will happen with low ID processes such as csrss.exe, winlogon.exe and a few others. Getting debug privilege may or may not solve a problem, as we, by assumption, cannot control the way of getting an initial (available) handle. For instance, in the following sequence of calls...

C++
hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, dwProcessId);
HANDLE hProcessAccessAdjusted;
DuplicateHandle(GetCurrentProcess(), hProcess, GetCurrentProcess(),
                &hProcessAccessAdjusted,
                PROCESS_QUERY_INFORMATION|PROCESS_CREATE_THREAD|
                PROCESS_VM_OPERATION|PROCESS_VM_WRITE,
                FALSE, 0);

...the last call will fail with ERROR_ACCESS_DENIED if dwProcessId represents one of the above processes, whether or not the debug privilege is granted.

2. Is Process Enumeration Helpful?

Using functionality provided by psapi.dll, one can enumerate (identifiers of) processes currently running in the operating system and then get their handles and attempt to work with them. Obviously, it's useless to compare process handles with a given one (think about handle duplication). Instead, we can compare process properties, which can be obtained using process handles. Although none of them uniquely determines a process, GetProcessTimes() can do a pretty good filtering, especially on a single processor machine. The obtained result is valid if no other process has the same creation time. To improve the result, if more than one candidate has the same creation time, we can continue comparison by process name. Below you can see a sketch of a function that does such enumeration. If, by available data, the process can be uniquely identified, its ID is returned. The function fails, returning 0xffffffff, if more than one candidate exists.

C++
#include <Psapi.h>

DWORD WINAPI TryGetProcessIDbyProcessHandle(HANDLE hProcess)
{
    // [in]  process handle
    // [out] process ID, or 0xffffffff in case of failure
    
    if (hProcess == NULL)    return 0xffffffff;
    FILETIME ftSourceCreationTime, ftOtherCreationTime;
    FILETIME ftExitTime;
    FILETIME ftKernelTime;
    FILETIME ftUserTime;
    // Get requested process creation time

    GetProcessTimes(hProcess, &ftSourceCreationTime, 
        &ftExitTime, &ftKernelTime, &ftUserTime);
 
    // Enumerate existing processes and compare creation times
    DWORD cbNeeded;
    DWORD dwProcessesIDs[1024];
    DWORD dwGoodProcessesIDs[1024];
    DWORD dwGoodProcessCount = 0;
    if ( EnumProcesses( dwProcessesIDs, sizeof(dwProcessesIDs), &cbNeeded ) )
    {
        cbNeeded /= sizeof(DWORD);    // bytes to ID cound

        for (UINT i=0; i < cbNeeded; i++)
        {
            HANDLE hOtherProcess = OpenProcess(PROCESS_QUERY_INFORMATION, 
                FALSE, dwProcessesIDs[i]);
            GetProcessTimes(hOtherProcess, &ftOtherCreationTime, &ftExitTime, 
                &ftKernelTime, &ftUserTime);
            CloseHandle(hOtherProcess);
            if (*(__int64*)(&ftSourceCreationTime) == *(__int64*)(&ftOtherCreationTime))
            {
                dwGoodProcessesIDs[dwGoodProcessCount] = dwProcessesIDs[i];
                dwGoodProcessCount ++;
            }
        }
    }
    if (dwGoodProcessCount == 0)    return 0xffffffff;
    if (dwGoodProcessCount == 1)    return dwGoodProcessesIDs[0];

    // Now we can try to compare process names

    DWORD dwRetProcessID = -1;
    USHORT szSourceProcessName[MAX_PATH];
    szSourceProcessName[0] = 0;
    HMODULE hMod[1024];
    if ( EnumProcessModules( hProcess, hMod, sizeof(hMod), &cbNeeded) )
        GetModuleBaseNameW( hProcess, hMod[0], szSourceProcessName, 
        sizeof(szSourceProcessName) );
    if (szSourceProcessName[0] == 0)    return 0xffffffff;        
        // failed to get process name

    UINT i = 0;
    do 
    { 
        szSourceProcessName[i] = towlower(szSourceProcessName[i]);
    } 
    while (szSourceProcessName[++i]);

    USHORT szOtherProcessName[MAX_PATH];
    // work with selected processes only
    for (i=0; i < dwGoodProcessCount; i++)
    {
        HANDLE hOtherProcess = OpenProcess(PROCESS_QUERY_INFORMATION, 
            FALSE, dwGoodProcessesIDs[i]);
        if ( EnumProcessModules( hProcess, hMod, sizeof(hMod), &cbNeeded) )
            GetModuleBaseNameW( hProcess, hMod[0], szOtherProcessName, 
            sizeof(szOtherProcessName) );
        CloseHandle(hOtherProcess);
        if (szOtherProcessName[0] == 0)    return 0xffffffff;  
            // failed to get process name

        UINT j = 0;
        do 
        { 
            szOtherProcessName[j] = towlower(szOtherProcessName[j]);
        } 
        while (szOtherProcessName[++j]);
        if (wcscmp(szSourceProcessName, szOtherProcessName) == 0)
        {
            if (dwRetProcessID == -1)   dwRetProcessID = dwGoodProcessesIDs[i];
            else                        return 0xffffffff;  
            // more than one coincidence

        }
    }
    return dwRetProcessID;
}

Once again, in general, this way cannot guarantee process ID determination in all cases. Use it at your own risk where function failure is not critical.

3. Using "undocumented" ZwQueryInformationProcess()

If you are not afraid of native API calls, one more way to solve this problem is to use ZwQueryInformationProcess(). Indeed, Microsoft provides some information about ZwQueryInformationProcess Function. The word "undocumented" currently means only that Microsoft does not guarantee that the function will be present in future OS releases. As starting from Window XP SP1 there is a documented straightforward solution, and previously released versions obviously cannot be changed, it is not a big problem.

ZwQueryInformationProcess() is exported by Ntdll.dll, but it has no associated import library. Thus, to use it, run-time dynamic linking is required.

Here is this function prototype:

C++
NTSTATUS WINAPI ZwQueryInformationProcess(
  __in       HANDLE ProcessHandle,
  __in       PROCESSINFOCLASS ProcessInformationClass,
  __out      PVOID ProcessInformation,
  __in       ULONG ProcessInformationLength,
  __out_opt  PULONG ReturnLength
);

Here PROCESSINFOCLASS is an enumeration defined in ntddk.h; for our purposes we will set ProcessInformationClass to ProcessBasicInformation value, which is 0. The result will be returned through ProcessInformation parameter, which must point to the following structure:

C++
typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;

PebBaseAddress is a pointer to process environment block structure. As we don't need it, we may treat it as PVOID.

Following the same style as above, we can wrap all steps in the following function:

C++
#if !defined NTSTATUS
typedef LONG NTSTATUS;
#endif

#if !defined PROCESSINFOCLASS
typedef LONG PROCESSINFOCLASS;
#endif

#if !defined PPEB
typedef struct _PEB *PPEB;
#endif

#if !defined PROCESS_BASIC_INFORMATION
typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
#endif;

typedef NTSTATUS (WINAPI * PFN_ZWQUERYINFORMATIONPROCESS)(HANDLE, PROCESSINFOCLASS,
    PVOID, ULONG, PULONG);


DWORD GetProcessIDbyProcessHandleZw(HANDLE hProcess)
{
    HINSTANCE hNtDll = LoadLibraryW(L"ntdll.dll");
    if (hNtDll == NULL)    return 0xffffffff;
    PFN_ZWQUERYINFORMATIONPROCESS fnProcInfo = PFN_ZWQUERYINFORMATIONPROCESS(
        ::GetProcAddress(hNtDll, "ZwQueryInformationProcess"));
    if (fnProcInfo == NULL)
    {
        CloseHandle(hNtDll);
        return 0xffffffff;
    }
    PROCESS_BASIC_INFORMATION pbi;
    ZeroMemory(&pbi, sizeof(PROCESS_BASIC_INFORMATION));
    if (fnProcInfo(hProcess, 0, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL) == 0)
    {
        CloseHandle(hNtDll);
        return pbi.UniqueProcessId;
    }
    else
    {
        CloseHandle(hNtDll);
        return 0xffffffff;
    }
}

Sample Project

Real projects where you may need this stuff are too complex for a sample project, but as you're finishing reading this article, then you have probably already seen them. The sample project I've attached is pretty artificial. You specify a process ID (well, how to make it easier to communicate with this program?) and a handle to that process is obtained. Then the program attempts to do the reverse procedure, informing you about the results and functions used. The two above functions are located in the ProcessIdFromHandleConversion.cpp file.

History

  • 7 December, 2007 -- Original version posted
  • 9 January, 2008 -- A solution using ZwQueryInformationProcess() added

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)