A Deep Dive into the GetProcessHandleFromHwnd API

The GetProcessHandleFromHwnd API, a Windows function that allows an application to obtain a handle to the process owning a specific window handle (HWND), has undergone significant changes since its introduction, with its original documentation containing several inaccuracies. Initially believed to be a convenience function relying on window hooks, its implementation and security properties have evolved, particularly with its move into the Windows kernel.
The API's documentation states that callers with UIAccess can use window hooks to inject code into a target process and retrieve a handle. It also claims the function only succeeds when caller and target processes share the same user, and that it's a convenience function for obtaining a process handle from an HWND. However, research indicates these statements are not entirely accurate. Firstly, using window hooks requires the caller to have an integrity level equal to or greater than the target process, not just UIAccess. Secondly, the current implementation in Windows 11 is a kernel function that opens the process directly, bypassing the described hook mechanism. Lastly, bypasses utilizing this API have been demonstrated even when Administrator Protection is active, suggesting processes can be running under different user contexts.

The earliest version of GetProcessHandleFromHwnd was found in Vista, implemented within the oleacc.dll library. While documentation suggested XP support, the API was not present in XP SP3 builds. This initial version attempted to open the target process directly, but if that failed, it resorted to using a window hook. This hook mechanism involved loading oleacc.dll into the target process via SetWindowsHookEx and waiting for a custom window message, WM_OLEACC_HOOK. Upon receiving this message, the hook function would create a named shared memory section. It would then open a handle to the caller's process with limited rights, duplicate the current process's handle to the caller, and finally write the duplicated handle value into the shared memory. This allowed the caller to retrieve the handle. This implementation could fail if processes were running under different users, as the target process might not be able to open the caller for `PROCESS_DUP_HANDLE` access, and the shared memory's DACL would also prevent access by a different user. However, if the target process was running as an administrator, it would likely have the necessary access.
A minor modification occurred in Windows 7, where the hook function was moved from oleacc.dll to a separate binary, oleacchooks.dll. This DLL, exposed as ordinal 1, still exists on Windows 11, even though the API itself has since been integrated into the kernel.
The API's second major version emerged in Windows 10, version 1803, when it was moved into the Win32k kernel as NtUserGetProcessHandle, residing in win32kfull.sys. This kernel version differs significantly. It accepts an `ACCESS_MASK` parameter, allowing the caller to specify the desired access rights for the process handle, unlike the fixed access in the older version. The function validates the window handle, retrieves the associated thread's THREADINFO structure, and verifies that both the caller and the target window are on the same desktop.
Crucially, the kernel version includes checks for UIPI (User Interface Privilege Isolation) enforcement. If UIPI is enabled, it calls a `CheckAccess` method to determine if the caller has permission to access the target process. This check considers the integrity levels and AppContainer status of both processes. If the caller's integrity level is higher than the target's, access is granted. If it's lower, access is denied. If integrity levels are the same, it checks if both processes are within the same AppContainer sandbox. If UIPI is not enforced, it compares authentication IDs, requiring the caller to be in the same logon session. Finally, access is denied if the target thread belongs to a system process or CSRSS. If all checks pass, the function looks up the target process's KPROCESS pointer and uses ObOpenObjectByPointer to obtain a handle with the requested access rights.





