Why attack
kernel drivers? They are the ONLY way to touch
Ring 0, or the running kernel. Because of this, vulnerabilities in kernel drivers allow you to elevate to system with no issues. This post is about Windows Kernel Driver internals and how to go about attacking them. For Windows Kernel debugging, use the following post as a reference
Getting Kernel Mode Driver Signed For Windows 10.
What does a driver look like? At a high level, a driver is a loadable kernel module that additionally has a user-mode application counterpart to communicate with it. Below are some more technical details on this process.
Device Objects is created by the driver itself for a communication channel. This can be done via the Windows CreateFile API with a symbolic link to a DEVICE_OBJECT.
In order to create the object, the main is called (DriverEntry). It accepts two parameters: DRIVER_OBJECT and RegistryPath. The DRIVER_OBJECT starts as an empty struct (from the kernel) and is initialized via the driver main function. The RegistryPath is a path where parameter keys are stored at for the driver.
The DRIVER_OBJECT structure has a lot of fields. The most important though, are the functions that the driver supports are added to this struct; they are in the MajorFunction member of this struct. In order to interface with the driver, these interfaces are made into DLLs that can be used in userland.
Under the hood, the flow looks like the following:
- User requests a handle in order to interface with the driver.
- The user uses the handle in order to send IRP (I/O Request Packer) to the driver. The important part of this request is the IOCTL code, which is just the ID for the kernel service routine to call.
- The driver executes the routine.
- The result is sent back to the user.
Easy starting points for finding bugs in Windows Kernel Drivers:
- Driver allows low-privileged user to interact with it.
MmMapIoSpace or ZwMapViewOfSection are in the IAT table.
- Customized
memmove or known unsafe function is used.
For reversing, the first thing to do is fine the main function dispatcher & the register path. The bulk of the reversing process relies on finding the IOCTLs.
The author used a REALLY simple fuzzer on all of the IOCTLs. This led to a buffer overflow that overwrote a function pointer! :) However, it is not as simple as calling system in Ring 0; there is still more work to be done!
On Windows 7, the exploitation involved writing some shellcode. However, the author could not find a way to restore execution back to its normal state (crashes in the kernel are VERY bad). Instead of trying to gain kernel code execution, the author decided to copy the SYSTEM token to an arbitrary process. Thenm to keep execution going, used this
trick (
JMP 0x0) in order to get execution of the kernel thread from crashing.
On Windows 10, there are quite a few kernel mitigations that make this significantly harder, including KMCS, SMEP, KASLR, KPP, CFG and VBS.
Supervisor Mode Execution Prevention (SMEP) can be bypassed by using ROP gadgets in order to change the value in the control register where this lives at. Now, we can trivially jump to our shellcode.
The Kernel Patch Protection (KPP) will bluescreen the computer if it detects the alteration of critical kernel structures being tampered with (including the CR4 register to bypass SMEP). However, this is done on a timed basis. So, a fast enough and well-timed payload can bypass this protection.
KASLR (
Kernel address space layout randomization) can be bypassed quite trivially. KASLR is only meant to protect low-integrity processes, like browsers. However, med privileged users can use the
EnumDeviceDrivers API to easily get the Kernel Address.
As an extra treat, the author dove into running this exploit on the most recent version of Windows. Because of a software patch for the infamous Meltdown vulnerability, Software based SMEP was added. So, all we have to do is add an additional step, which is to disable Software SMEP.