Target analysis#
Objectives#
The objective of this tutorial is to fuzz a Windows driver, built for educational purposes.
It doesn’t interact with any hardware device, nor makes any relevant side effect in the kernel.
It simply receives and processes IOCTLs, by calling a handler function which contains vulnerabilities that we want to trigger.
Source code#
The source code for this driver is located at the kafl.targets/windows_x86_64/src
.
It is composed of:
driver.c
: the vulnerable drivervuln_test.c
: a userland application to trigger the driver
This is a representation of what the code does at the core, excluding kAFL hypercalls:
Vulnerability#
Two security flaws have been inserted into the driver code
More specifically they are NULL pointer dereference vulnerabilities, triggered by the crashMe()
function when a certain buffer is received.
NTSTATUS crashMe(IN PIO_STACK_LOCATION IrpStack){
SIZE_T size = 0;
PCHAR userBuffer = NULL;
userBuffer = IrpStack->Parameters.DeviceIoControl.Type3InputBuffer;
size = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
if (size < 0xe){
return STATUS_SUCCESS;
}
if (userBuffer[0] == 'P'){
DbgPrint("[+] KAFL vuln drv -- P");
if (userBuffer[1] == 'w'){
DbgPrint("[+] KAFL vuln drv -- Pw");
if (userBuffer[2] == 'n'){
DbgPrint("[+] KAFL vuln drv -- Pwn");
if (userBuffer[3] == 'T'){
DbgPrint("[+] KAFL vuln drv -- PwnT");
if (userBuffer[4] == 'o'){
DbgPrint("[+] KAFL vuln drv -- PwnTo");
if (userBuffer[5] == 'w'){
DbgPrint("[+] KAFL vuln drv -- PwnTow");
if (userBuffer[6] == 'n'){
DbgPrint("[+] KAFL vuln drv -- PwnTown: CRASH");
((VOID(*)())0x0)();
}
}
}
}
}
}
}
if (userBuffer[0] == 'w'){
DbgPrint("[+] KAFL vuln drv -- w");
if (userBuffer[1] == '0'){
DbgPrint("[+] KAFL vuln drv -- w0");
if (userBuffer[2] == '0'){
DbgPrint("[+] KAFL vuln drv -- w00");
if (userBuffer[3] == 't'){
DbgPrint("[+] KAFL vuln drv -- w00t: CRASH");
size = *((PSIZE_T)(0x0));
}
}
}
}
return STATUS_SUCCESS;
}
We can recognize 2 paths leading to a crash:
Note
This is a good example to showcase kAFL’s RedQueen builtin capabilities.
For the fuzzer to progress quickly through these conditionals, it uses a combination of virtual machine introspection and instruction comparison, which is the core of RedQueen’s implementation.
kAFL agent implementation#
Let’s now have a deeper look at the kAFL agent implementation, to better understand what specific changes this target required.
First , the kAFL agent is only implemented in the userland component vuln_test.c
, for 2 reasons:
the driver implementation remains untouched: there is no requirement to change the driver logic in order to add our harness
at the time of this writing, kAFL hypercall implementation based on
vmcall
instruction doesn’t get along very well with Microsoft’sMSVC
compiler
Agent initialization#
We can find in init_agent_handshake()
the common agent initialization sequence:
Fuzzing harness#
The fuzzing harness is implemented around DeviceIoControl()
Harness sequence diagram between kAFL fuzzer, userspace and driver:
// Snapshot here
// request new payload (*blocking*)
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
// Enable coverage tracing
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
// kernel fuzzing
DeviceIoControl(kafl_vuln_handle,
IOCTL_KAFL_INPUT,
(LPVOID)(payload_buffer->data),
(DWORD)payload_buffer->size,
NULL,
0,
NULL,
NULL
);
// inform fuzzer about finished fuzzing iteration
// Will reset back to start of snapshot here
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
Target specific#
Panic handlers#
If we want kAFL to be aware of a crash on Windows, we need to intercept and hook the crash handlers.
The Windows functions we need are:
Therefore we have to:
locate the address of these functions in the kernel
submit them to kAFL so that QEMU can rewrite these functions with kAFL hypercalls
The first step is performed by resolve_KeBugCheck()
EnumDeviceDrivers()
to retrieve the load address of each driver in the systemGetDeviceDriverFileName()
to retrieve path available for the specified driverif the entry is
ntoskrnl.exe
, useLoadLibrary()
andGetProcAddress()
to retrieve the address ofKeBugCheck
Once this is done, the address is simply sent to kAFL via HYPERCALL_KAFL_SUBMIT_PANIC
.
Set IP ranges#
IP ranges are necessary to run the fuzzer in guided feedback mode.
To retrieve the start and end IP range of our kAFLvulnerabledriver.sys
loaded driver, we need to do the following:
EnumDeviceDrivers()
to retrieve the load address of each driver in the systemNtQuerySystemInformation
withSystemObjectInformation
to retrieve module information about each driveridentify our driver
HYPERCALL_KAFL_RANGE_SUBMIT
to submit the IP ranges to kAFL
Non reload mode#
During the agent configuration, we configured the agent_non_reload_mode
.
We prevent the host from reloading the guest snapshot on each new payload execution.
Why are we doing this ? Snapshot fuzzers are well suited for complex targets with a lot of side effects.
We are lucky because this isn’t our situation: the driver simply doesn’t have any side effect (it doesn’t allocate memory on each payload execution, no hardware communication, etc …)
So we can improve the fuzzing speed by ditching the snapshot reload and keeping the guest running in a persistent state.