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(): 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: 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... 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.