Stealing Tokens In Kernel Mode With A Malicious Driver
Introduction
I’ve recently been working on expanding my knowledge of Windows kernel concepts and kernel mode programming. In the process, I wrote a malicious driver that could steal the token of one process and assign it to another. This article by the prolific and ever-informative spotless forms the basis of this post. In that article he walks through the structure of the _EPROCESS
and _TOKEN
kernel mode structures, and how to manipulate them to change the access token of a given process, all via WinDbg. It’s a great post and I highly recommend reading it before continuing on here.
The difference in this post is that I use C++ to write a Windows kernel mode driver from scratch and a user mode program that communicates with that driver. This program passes in two process IDs, one to steal the token from, and another to assign the stolen token to. All the code for this post is available here.
About Access Tokens
A common method of escalating privileges via buggy drivers or kernel mode exploits is to the steal the access token of a SYSTEM process and assign it to a process of your choosing. However this is commonly done with shellcode that is executed by the exploit. Some examples of this can be found in the wonderful HackSys Extreme Vulnerable Driver project. My goal was to learn more about drivers and kernel programming rather than just pure exploitation, so I chose to implement the same concept in C++ via a malicious driver.
Every process has a primary access token, which is a kernel data structure that describes the rights and privileges that a process has. Tokens have been covered in detail by Microsoft and from an offensive perspective, so I won’t spend a lot of time on them here. However it is important to know how the access token structure is associated with each process.
Processes And The _EPROCESS
Structure
Each process is represented in the kernel by a doubly linked list of _EPROCESS
structures. This structure is not fully documented by Microsoft, but the ReactOS project as usual has a good definition of it. One of the members of this structure is called, unsurprisingly, Token
. Technically this member is of type _EX_FAST_REF
, but for our purposes, this is just an implementation detail. This Token
member contains a pointer to the address of the token object belonging to that particular process. An image of this member within the _EPROCESS
structure in WinDbg can be seen below:
As you can see, the Token
member is located at a fixed offset from the beginning of the _EPROCESS
structure. This seems to change between versions of Windows, and on my test machine running Windows 10 20H2, the offset is 0x4b8
.
The Method
Given the above information, the method for stealing a token and assigning it is simple. Find the _EPROCESS
structure of the process we want to steal from, go to the Token
member offset, save the address that it is pointing to, and copy it to the corresponding Token
member of the process we want to elevate privileges with. This is the same process that Spotless performed in WinDbg.
The Driver
In lieu of exploiting a kernel mode exploit, I write a simple test driver. The driver exposes an IOCTL that can be called from user mode. It takes struct that contains two members: an unsigned long for the PID of the process to steal a token from, and an unsigned long for the PID of the process to elevate.
The driver will find the _EPROCESS
structure for each PID, find the Token
members, and copies the target process token to the destination process.
The User Mode Program
The user mode program is a simple C++ CLI application that takes two PIDs as arguments, and copies the token of the first PID to the second PID, via the exposed driver IOCTL. This is done by first opening a handle to the driver by name with CreateFileW
and then calling DeviceIoControl
with the correct IOCTL.
The Driver Code
The code for the token copying is pretty straight forward. In the main function for handling IOCTLs, HandleDeviceIoControl
, we switch on the received IOCTL. When we receive IOCTL_STEAL_TOKEN
, we save the user mode buffer, extract the two PIDs, and attempt to resolve the PID of the target process to the address of its _EPROCESS
structure:
Once we have the _EPROCESS
address, we can use the offset of 0x4b8
to find the Token
member address:
We repeat the process once more for the PID of the process to steal a token from, and now we have all the information we need. The last step is to copy the source token to the target process, like so:
The Whole Process
Here is a visual breakdown of the entire flow. First we create a command prompt and verify who we are:
Next we use the user mode program to pass the two PIDs to the driver. The first PID, 4, is the PID of the System
process, and is usually always 4. We see that the driver was accessed and the PIDs passed to it successfully:
In the debug output view, we can see that HandleDeviceIoControl
is called with the IOCTL_STEAL_TOKEN
IOCTL, the PIDs are processed, and the target token overwritten. Highlighted are the identical addresses of the two tokens after the copy, indicating that we have successfully assigned the token:
Finally we run whoami
again, and see that we are now SYSTEM!
We can even do the same thing with another user’s token:
Conclusion
Kernel mode is fun! If you’re on the offensive side of the house, it’s well worth digging into. After all, every user mode road leads to kernel space; knowing your way around can only make you a better operator, and it expands the attack surface available to you. Blue can benefit just as much, since knowing what you’re defending at a deep level will make you able to defend it more effectively. To dig deeper I highly recommend Pavel Yosifovich’s Windows Kernel Programming, the HackSys Extreme Vulnerable Driver, and of course the Windows Internals books.