Windows Kernel Exploitation via Vulnerable Drivers

By Brandon Adler

In the Microsoft Windows operating system, kernel exploitations techniques have been significantly mitigated due to protections Microsoft has put in place such as Kernel Patch Protection (PatchGuard) and driver signature enforcement.

PatchGuard is Microsoft’s attempt at preventing unauthorized modification of system service tables, the interrupt descriptor table, the global descriptor table, and data owned by the kernel such as libraries by device drivers in the x64 Windows operating systems.  Device drivers have the same privileges as the kernel itself since the drivers must be loaded into kernel memory space to sufficiently interact with the hardware it is communicating with.  Ultimately, PatchGuard prevents the patching of kernel-owned memory from device drivers, not one device driver patching another.  If PatchGuard detects kernel memory has been modified, it causes a “bug check” and the operating system will blue screen.

Driver signature enforcement, which was first implemented in Windows Vista, only allows valid signed drivers to be loaded into the kernel.  Microsoft implemented this to prevent arbitrary code from being loaded into the kernel and given kernel privileges.  As of Windows 10 update 1607, Microsoft must sign any drivers that are being loaded into the kernel which also must be signed with a valid Extended Validation Code Signing Certificate to even be processed by Microsoft.  These strict requirements were created to only allow trusted code and heavily tested code to be loaded into kernel space.  There are specific rules Microsoft has put in place to allow backwards compatibility with drivers that do not yet meet these requirements which will be shown later.


Bypassing Prevention Techniques

 The technique to we will be using here to bypass the restrictions outlined above is by exploiting a valid device driver to load our own driver into kernel space.  Specifically, we will be using an exploit in the VirtualBox kernel-mode driver that has been used multiple times in the wild for attackers to load rootkits and gain kernel privileges from an unprivileged user.  This exploit is an example of the dangers that come in developing kernel level code and the severe security risks that can come from any piece of software that requires access to kernel resources.

The vulnerability in the VirtualBox driver (VBoxDrv.sys), defined in CVE-2008-3431, allows users to open the device driver and send it an IO command using an arbitrary kernel address due to the driver not validating user input and using the METHOD_NEITHER communication method for IOCTLS.  We will go into more depth on the technical details of this vulnerability and the stepping stones as to what creates this vulnerability in the next section.


VBoxDrv.sys Dissection

When the VirtualBox package is installed on a host the ‘VBoxDrv.sys’ driver is loaded on the machine. This driver allows any unprivileged user to open the device ‘\\.\VBoxDrv’ and issue IOCTLs with a buffering mode of METHOD_NEITHER without any kind of validation. This allows untrusted user mode code to pass arbitrary kernel addresses as arguments to the driver. With specially constructed input, a malicious user can use functionality within the driver to patch kernel addresses and execute arbitrary code in kernel mode. When handling IOCTLs a communication method must be pre-defined between the user-mode application and the driver module. The selected method will determine how the I/O Manager manipulates memory buffers used in the communication.


  NTSTATUS _stdcall VBoxDrvNtDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp)


    PSUPDRVDEVEXT pDevExt = (PSUPDRVDEVEXT)pDevObj->DeviceExtension;

    PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);

    PSUPDRVSESSION pSession = (PSUPDRVSESSION)pStack->FileObject-                       >FsContext;


    * Deal with the two high-speed IOCtl that takes it's  *arguments from

    * the session and iCmd, and only returns a VBox status code.


ULONG ulCmd = pStack->Parameters.DeviceIoControl.IoControlCode;

(1)   if ( ulCmd == SUP_IOCTL_FAST_DO_RAW_RUN

      ||  ulCmd == SUP_IOCTL_FAST_DO_HWACC_RUN

      ||  ulCmd == SUP_IOCTL_FAST_DO_NOP)


      KIRQL oldIrql;

      int   rc;

/* Raise the IRQL to DISPATCH_LEVEL to prevent Windows from

rescheduling us to another CPU/core. */

      Assert(KeGetCurrentIrql() <= DISPATCH_LEVEL);

      KeRaiseIrql(DISPATCH_LEVEL, &oldIrql);

(2)                   rc = supdrvIOCtlFast(ulCmd, pDevExt, pSession);


      /* Complete the I/O request. */

      NTSTATUS rcNt = pIrp->IoStatus.Status = STATUS_SUCCESS;

      pIrp->IoStatus.Information = sizeof(rc);



(3)                  *(int *)pIrp->UserBuffer = rc;




    rcNt = pIrp->IoStatus.Status = GetExceptionCode();

    dprintf(("VBoxSupDrvDeviceContorl: Exception Code %#x\n", rcNt));


      IoCompleteRequest(pIrp, IO_NO_INCREMENT);

      return rcNt;


    return VBoxDrvNtDeviceControlSlow(pDevExt, pSession, pIrp, pStack);




(1) Checks the IOCTL code from user input against constants defined as functions.  The definitions below are found in “SUPDrvIOC.h”.

#define SUP_IOCTL_FAST_DO_RAW_RUN               SUP_CTL_CODE_FAST(64)

/** Fast path IOCtl: VMMR0_DO_HWACC_RUN */


/** Just a NOP call for profiling the latency of a fast ioctl call to

VMMR0. */

#define SUP_IOCTL_FAST_DO_NOP                   SUP_CTL_CODE_FAST(66)






In SUP_CTL_CODE_FAST(), METHOD_NEITHER is used which means that the pointer passed to “DeviceIOControl” will be sent directly to the driver without being buffered in the IO Manager.  This means the driver is expected to properly check and validate the addresses given to it from user input.  The “VBoxDrv.sys” driver then does not properly validate the buffer being sent in the IRP object, which is what allows an attacker to write to any memory address in kernel space.

(2) The value returned by “supdrvIOCtlFast()” is saved to “rc”.

(3) The value in “rc” it written directly to the buffer.  Since this memory address is given to the driver from user input, this can be an arbitrary address in kernel space.


Writing to Kernel Space

Using the vulnerability in the loaded “VBoxDrv.sys” driver, we can write to memory that we do not necessarily have the privilege to even view.  The course of action we are going to take is temporarily flip the “nt!g_CiEnabled” byte from one to zero.  Since “nt!g_CiEnabled” is not an exported symbol, it is a bit more difficult to find the actual address of the flag we need in memory.  Although, it is known that this variable exists in the memory space of the Client/Server Run-Time Subsystem, or csrss.exe. The “nt!g_CiEnabled” variable disables Driver Signature Enforcement which allows us to load an unsigned driver into kernel space.  This functionality can be used legitimately to aid the driver development process so developers aren’t forced to sign drivers legitimately every time they test the functionality of the driver.  Disabling Driver Signature Enforcement puts the operating system into “Test Mode” which required Administrator privileges or access to the boot options in the F8 menu before the operating system boots.  Each of these conditions are unfavorable due to requiring Administrator privileges and being obvious that Driver Signature Enforcement is disabled due to a watermark on the desktop.  In order to do this successfully in the most covert way, we must flip the “nt!g_CiEnabled” byte from one to zero, load the arbitrary driver, and then flip the “nt!g_CiEnabled” back to one in order to prevent PatchGuard from catching the modification, if it is done quickly enough.


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s