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 use LibFuzzer to fuzz the driver via its IOCTL interface. The handler for the interface is defined here.
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>
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, a handle for the device once we open it, and a
global flag to check whether the device is initialized/opened. We could have also used
the Startup initialization
interface to LibFuzzer, but since we don't really need access to argv
, we follow the
guidance to use a statically initialized global object.
const char g_devname[] = "\\\\.\\HackSysExtremeVulnerableDriver";
HANDLE g_device = INVALID_HANDLE_VALUE;
BOOL g_device_initialized = FALSE;
Now we can declare 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 LLVMFuzzerTestOneInput(const BYTE *data, size_t size) {
This function will be called in a loop with new inputs each time by the fuzz driver.
The first thing we need to do is check if the device handle is initialized, and initialize it if not.
if (!g_device_initialized) {
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");
g_device_initialized = TRUE;
}
Returning -1
from LLVMFuzzerTestOneInput
indicates a bad input, but we will also
use it here to indicate an initialization error.
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 *)data,
(DWORD)size,
NULL, //outBuffer -> None
0, //outBuffer size -> 0
&size_returned,
NULL
);
if (!is_ok) {
printf("Error in DeviceIoControl\n");
return -1;
}
return 0;
}