Chapter 10. FUZZING WINDOWS DRIVERS

Attacking Windows drivers is becoming commonplace for bug hunters and exploit developers alike. Although there have been some remote attacks on drivers in the past few years, it is far more common to use a local attack against a driver to obtain escalated privileges on the compromised machine. In the previous chapter, we used Sulley to find a stack overflow in WarFTPD. What we didn't know was that the WarFTPD daemon was running as a limited user, essentially the user that had started the executable. If we were to attack it remotely, we would end up with only limited privileges on the machine, which in some cases severely hinders what kind of information we can steal from that host as well as what services we can access. If we had known there was a driver installed on the local machine that was vulnerable to an overflow[45] or impersonation[46] attack, we could have used that driver as a means to obtain System privileges and have unfettered access to the machine and all its juicy information.

In order for us to interact with a driver, we need to transition between user mode and kernel mode. We do this by passing information to the driver using input/output controls (IOCTLs), which are special gateways that allow user-mode services or applications to access kernel devices or components. As with any means of passing information from one application to another, we can exploit insecure implementations of IOCTL handlers to gain escalated privileges or completely crash a target system.

We will first cover how to connect to a local device that implements IOCTLs as well as how to issue IOCTLs to the devices in question. From there we will explore using Immunity Debugger to mutate IOCTLs before they are sent to a driver. Next we'll use the debugger's built-in static analysis library, driverlib, to provide us with some detailed information about a target driver. We'll also look under the hood of driverlib and learn how to decode important control flows, device names, and IOCTL codes from a compiled driver file. And finally we'll take our results from driverlib to build test cases for a standalone driver fuzzer, loosely based on a fuzzer I released called ioctlizer. Let's get started.

Almost every driver on a Windows system registers with the operating system with a specific device name and a symbolic link that enables user mode to obtain a handle to the driver so that it can communicate with it. We use the CreateFileW[47] call exported from kernel32.dll to obtain this handle. The function prototype looks like the following:

HANDLE WINAPI CreateFileW(
    LPCTSTR lpFileName,
    DWORD   dwDesiredAccess,
    DWORD   dwShareMode,
    LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    DWORD   dwCreationDisposition,
    DWORD   dwFlagsAndAttributes,
    HANDLE  hTemplateFile
);

The first parameter is the name of the file or device that we wish to obtain a handle to; this will be the symbolic link value that our target driver exports. The dwDesiredAccess flag determines whether we would like to read or write (or both or neither) to this device; for our purposes we would like GENERIC_READ (0x80000000) and GENERIC_WRITE (0x40000000) access. We will set the dwShareMode parameter to zero, which means that the device cannot be accessed until we close the handle returned from CreateFileW. We set the lpSecurityAttributes parameter to NULL, which means that a default security descriptor is applied to the handle and can't be inherited by any child processes we may create, which is fine for us. We will set the dwCreationDisposition parameter to OPEN_EXISTING (0x3), which means that we will open the device only if it actually exists; the CreateFileW call will fail otherwise. The last two parameters we set to zero and NULL, respectively.

Once we have obtained a valid handle from our CreateFileW call, we can use that handle to pass an IOCTL to this device. We use the DeviceIoControl[48] API call to send down the IOCTL,which is exported from kernel32.dll as well. It has the following function prototype:

BOOL WINAPI DeviceIoControl(
    HANDLE hDevice,
    DWORD  dwIoControlCode,
    LPVOID lpInBuffer,
    DWORD  nInBufferSize,
    LPVOID lpOutBuffer,
    DWORD  nOutBufferSize,
    LPDWORD lpBytesReturned,
    LPOVERLAPPED lpOverlapped
);

The first parameter is the handle returned from our CreateFileW call. The dwIoControlCode parameter is the IOCTL code that we will be passing to the device driver. This code will determine what type of action the driver will take once it has processed our IOCTL request. The next parameter, lpInBuffer, is a pointer to a buffer that contains the information we are passing to the device driver. This buffer is the one of interest to us, since we will be fuzzing whatever it contains before passing it to the driver. The nInBufferSize parameter is simply an integer that tells the driver the size of the buffer we are passing in. The lpOutBuffer and lpOutBufferSize parameters are identical to the two previous parameters but are used for information that's passed back from the driver rather than passed in. The lpBytesReturned parameter is an optional value that tells us how much data was returned from our call. We are simply going to set the final parameter, lpOverlapped, to NULL.

We now have the basic building blocks of how to communicate with a driver, so let's use Immunity Debugger to hook calls to DeviceIoControl and mutate the input buffer before it is passed to our target driver.



[45] See Kostya Kortchinsky, "Exploiting Kernel Pool Overflows" (2008), http://immunityinc.com/downloads/KernelPool.odp.

[46] See Justin Seitz, "I2OMGMT Driver Impersonation Attack" (2008), http://immunityinc.com/downloads/DriverImpersonationAttack_i2omgmt.pdf.

[47] See the MSDN CreateFile Function (http://msdn.microsoft.com/en-us/library/aa363858.aspx).