Create a Fuzz Harness

We'll create a directory ~/fuzzer where we'll create and run our fuzz harness:

mkdir ~/fuzzer
cd ~/fuzzer

We're going to fuzz the driver via its IOCTL interface. The handler for the interface is defined here. It is possible as well to harness the kernel driver directly, but it is typically much easier to use a user-space driver to fuzz the kernel driver. This has the added benefit that most test cases for drivers are implemented as user-space programs, so converting a test case to a fuzz driver becomes very simple.

Essentially, if we pass more than 512 * 4 = 2048 bytes of data, we will begin to overflow the stack buffer. Create fuzzer.c by running vim fuzzer.c.

We'll start by including windows.h for the Windows API and stdio.h so we can print.

#include <windows.h>
#include <stdio.h>

We will also include our TSFFS header:

#include "tsffs.h"

Next, we need to define the control code for the driver interface. The device servicing IOCTL we are triggering is not a pre-specified file type, so we access it with an unknown type. We grab the control code for the handler we want from the driver source code. Note that in the handler, we can see this is a type 3 IOCTL handler (AKA METHOD_NEITHER) and that we want RW access to the driver file.

#define HACKSYS_EVD_IOCTL_STACK_OVERFLOW CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_NEITHER, FILE_ANY_ACCESS)

Next, we'll define our device name and a handle for the device once we open it.

const char g_devname[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE g_device = INVALID_HANDLE_VALUE;

Now we can implement our fuzz driver. Since we're compiling as C code, note we do not declare the function as extern "C", but if we were compiling as C++ we would need to do this.

int main() {

The first thing we need to do is check if the device handle is initialized, and initialize it if not.

    printf("Initializing device\n");

    if ((g_device = CreateFileA(g_devname,
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL
    )) == INVALID_HANDLE_VALUE) {
        printf("Failed to initialize device\n");
        return -1;
    }
    printf("Initialized device\n");

Next, we'll declare a buffer and a size of the buffer. We'll make it 1 page in size. Note that the size variable must be a pointer-width integer to be compatible with the TSFFS fuzz harnesses. We will downcast it to the DWORD size parameter for DeviceIoControl later.

   BYTE buffer[4096];
   size_t size = 4096;

Now we can add our start harness. When this harness function executes, the fuzzer will take a snapshot. Each fuzzing iteration, buffer will be filled with up to 4096 bytes of fuzzer data and size will be set to the actual number of bytes of the fuzzing testcase.

    HARNESS_START(buffer, &size);

We'll also add a print for ourselves to let us know if the buffer should be overflowed by an input.

    if (size > 2048) {
        printf("Overflowing buffer!\n");
    }

Finally, we'll call DeviceIoControl to interact with the driver by passing our input data to the IOCTL interface.

    DWORD size_returned = 0;

    BOOL is_ok = DeviceIoControl(g_device,
        HACKSYS_EVD_IOCTL_STACK_OVERFLOW,
        (BYTE *)buffer,
        (DWORD)size,
        NULL, //outBuffer -> None
        0, //outBuffer size -> 0
        &size_returned,
        NULL
    );

After executing the IOCTL, we check the return value and in either case, we will add a HARNESS_STOP call, which signals the fuzzer that this fuzzing iteration is over. The fuzzer will reset to the initial snapshot and run again with a new input. It is important to insert a stop harness before any exit from the fuzzing harness code path so the fuzzer knows when to stop. Otherwise, execution will proceed until a timeout occurs, which in this case would be a false positive.

    if (!is_ok) {
        printf("Error in DeviceIoControl\n");
        HARNESS_STOP();
        return -1;
    }

    HARNESS_STOP();

    return 0;
}

Save the file with

Add Header & ASM File

To build the fuzz harness with the TSFFS harness functions, we need both the header (tsffs.h) and the MSVC ASM file (tsffs-msvc-x86_64.h).

Copy tsffs.h and tsffs-msvc-x86_64.h into the fuzzer directory by running the following on your host machine:

scp -P 2222 harness/tsffs.h "user@localhost:C:\\Users\\user\\fuzzer\\"
scp -P 2222 harness/tsffs-msvc-x86_64.asm "user@localhost:C:\\Users\\user\\fuzzer\\"