RTCore64.sys - CVE-2019-16098

This blog details the methodology and the approach used against developing exploit code for CVE-2019-16098

This blog documents the approach towards reversing the driver and writing a working exploit for 64-bit Windows 10 OS.

Link to download the vulnerable RTCore64.sys driver: https://drive.google.com/file/d/1zpIg87Ts5drcV7oG3Jvz7Ugf-d-iOVR5/view?usp=sharing


The initial approach to developing the exploit is to understand the driver. We hence reverse the driver and find the vulnerable code snippet which could later be used for developing the exploit. Since any driver code runs in ring0 of the OS, the approach to developing the exploit will be different from the one used during user mode exploit development.

We fire up IDA, load the RTCore64.sys driver into it and locate the DriverEntry method.

The DriverEntry has a IoCreateSymbolicLink which creates a symbolic link to the driver that would be accessible from user-space. The SymbolicLinkName here is \\DosDevice\\RTCore64 which implies that the driver is accessible using \\\\.\\RTCore64 . There is also no access control on incoming IRP requests which implies any low privilege user should be able to interact with the driver.

Moving on to DriverIoControl function, we notice 3 important opcodes. The first opcode i.e. IOCTL_READ_MSR (0x80002030) allows reading from an MSR register. This is directly taken from SystemBuffer which implies an arbitrary read of MSR inside the kernel.

Moving on to the next IOCTL code, the opcode IOCTL_READ_DATA (0x80002048) allows arbitrary reading by using the SystemBuffer offset. This implies a carefully planned input would allow an arbitrary read of 4 bytes inside the kernel.

Finally the last IOCTL code IOCTL_WRITE_DATA (0x8000204C) allows arbitrary write using the SystemBuffer offsets. This implies a carefully planned input would allow an arbitrary write of 4 bytes inside the kernel.

Windows Internals

Given that we now have a write and read inside the kernel, we would now want to perform privilege escalation using this vulnerablity. Any process created in a user mode has a corresponding TOKEN structure in the kernel which contains information about the privileges of that process. This TOKEN structure is found at an offset from _EPROCESS structure. This structure is used to represent a process which includes details about the process like PIDs, pointer to _KPROCESS, Image Name, etc.

Let's talk a bit about the _EPROCESS structure.

kd> dt !_eprocess

This _EPROCESS structure has multiple members and the ones we are interested in are UniqueProcessId, ActiveProcessLinks, and Token (offset 0x4b8 which may differ for different Windows versions)

  • UniqueProcessId: This tells us the PID of the process whose _EPROCESS structure we are viewing right now. This would help us narrow down the processes we are interested in as part of the exploit.

  • ActiveProcessLinks: This is a double-linked list that contains a Flink and Blink entry to the ActiveProcessLinks of the next process and previous process. This in short will help us traverse all the processes running in the OS from the kernel directly. To get the next process, we simply would need to get the value of ActiveProcessLinks member and subtract the offset 0x448 (offset of ActiveProcessLinks from _EPROCESS) from it to make it point to the start of _EPROCESS structure of the next process.

  • Token: This member contains the pointer to the _EX_FAST_REF . To get the exact token address from this, we would and the least significant byte of the address with 0. (This blog explains in detail why we do so)

Now that we know a bit about the kernel, let's talk about PsInitialSystemProcess variables. Windows has multiple global variables which are at a specific offset from the kernel's base address. This means leaking the base address of the kernel will allow us to get the address of any global variables in the kernel. One such variable is PsInitialSystemProcess which is a pointer to the _EPROCESS structure of the kernel. This will help us to get a starting point for traversing all the processes.

A full list of global variables of the kernel can be found here.


We would create a cmd.exe application with low privileges. The end goal would be to swap _TOKEN this process with the _TOKEN system permissions.

The approach would be first to read the 64-bit address present at PsInitialSystemProcess and add offsets to get value of UniqueProcessId and ActiveProcessLinks. We would then compare the UniqueProcessId with the one of the cmd.exe. If its not the same, we will move to the next process using ActiveProcessLinks linked-list and keep on checking pid and in case it matches, we will return with the address of the _eprocess structure of the cmd.exe process.From here we can find the address of _TOKEN structure for cmd.exe.

We would similarly fetch the address of _TOKEN structure for kernel and simply copy the kernel's token address into cmd.exe's token address. This would now mean that cmd.exe has system permissions.

Exact exploit code: https://github.com/0xDivyanshu-new/CVE-2019-16098/

This driver was also being used by BlackByte during their ransomware operations where using this driver they would nullify/remove the EDR's callback functions from the global Callbacks like ProcessCallbacks, HandleCallbacks, etc. This allowed them to create process without EDR hooking them and inspecting the process.

Last updated