Introduction

"kAFL: Hardware-Assisted Feedback Fuzzing for OS Kernels" noted that non-determinism due to kernel-space interrupts, kernel threads, statefulness, and similar mechanisms makes kernel fuzzing more difficult. The kernel region has a memory structure different from that of the user land, and the execution flow can be changed by various unexpected requests such as interrupts. So it is not easy to perform a fuzzing test focusing only on a specific target region.

In addition, instrumentation is required to receive feedback on coverage increase or decrease by executing the fuzzing routine. In the case of open source user land applications, it is possible to easily measure coverage by using a code compilation technique such as AFL, but since the Windows kernel is closed source, it is impossible to use the instrumentation technique to modify the inside of the code.

Accordingly, IRPT borrowed the idea of using intel-PT technology in the fuzzer from kAFL to measure the increase or decrease of coverage in the kernel. In addition, we modified the KVM-PT, QEMU-PT and hypercall communication technology developed by kAFL to implement communication between the VM loaded with the target driver and the fuzzer performing the mutation.

kAFL is a nice tool in that it enables hardware-assisted kernel fuzzing that is not dependent on the OS, but it is far from the ideal fuzzer that our pursues. The reason is that kAFL targets only a single IOCTL code. This means that the ordering dependency that exists between IOCTL routines cannot be considered.

Therefore, we tried to develop a fuzzer that solves the problems that kAFL cannot solve. Based on driver interface information that can be easily obtained using IREC.

What is IRPT?

IRPT is a fuzzer specialized in a windows driver. It measures the coverage of windows kernel using Intel PT technology and resolves global data problem and IOCTL dependency. Let me explain the challenges we encountered while implementing IRPT and the solutions we came up with.

History of IRPT

Brute-Force OneCode Fuzzer

The first fuzzer developed by the Kronl team is the Brute-Force OneCode Fuzzer. Brute-Force OneCode Fuzzer sends one IOCTL code per fuzzing routine to target driver considering only constraints such as InputBuffer and inputBufferLength obtained from IREC.

However, since only one IOCTL CODE is sended in one fuzzing routine, it is difficult to expect the dependency between driver routines to be satisfied only with the payload. Furthermore, kAFL reflects only the feedback result from the most recent fuzzing routine, even though the existing routines have contributed largely to coverage extension. Although the previous fuzzing routine satisfies the ordering dependency and thus the coverage of the last executed fuzzing routine could be increased, all contributions to the previous routine are ignored when coverage feedback is reflected.

As such, Brute-Force OneCode Fuzzer completely relies on luck to satisfy the ordering dependency between IOCTL routines during fuzzing. Even if the fuzzer finds a crash fortunately, there is a fatal drawback that all payloads sent to target driver during fuzzing must be delivered to the driver in order to reproduce.

Brute-Force MultiCode Fuzzer

The second fuzzer of team Kronl developed to solve the fatal problem of Brute-Force OneCode Fuzzer is Brute-Force MultiCode Fuzzer.

Brute-Force MultiCode Fuzzer decodes the payload generated by the mutation, devides it into several IOCTL codes, and sends them to target driver. As a result, it is possible to satisfy the ordering dependency between IOCTL routines by delivering only one payload to the driver. Also, in order to prevent the state of the driver from being affected by the previous fuzzing routine, we implemented reload-logic that initializes the state of the driver by reloading the driver for each fuzzing routine.

However, the Brute-Force MultiCode Fuzzer also had drawbacks. Brute-Force MultiCode Fuzzer uses the method of decoding the payload into IOCTL codes and InputBeffer just before sending to target driver.

Decoding the payload just before it is sent to the driver means that the fuzzer does not know at all the structure of the payload. (The payload just before sending to the driver is the payload that has already been mutated in the fuzzer.)

The fatal drawback of Brute-Force MultiCode Fuzzer is that the fuzzer performs the mutation without knowing the structure of the payload in the mutation stage. When generating a payload, the fuzzer mutates the inputs without considering the driver interface at all. This method obviously requires luck to satisfy the ordering dependency of IOCTL routines. That's why the word Brute-Force comes into the name.

The Kronl team determined that Brute-Force MultiCode Fuzzer was also not suitable for the ideal fuzzer to achieve our ultimate goals. We needed a more driver-friendly fuzzer to build an efficient driver fuzzing framework.

IRPT

Beyond sending a single payload generated by the fuzzer into multiple IOCTL codes, we wanted a fuzzer that considers the driver interface in the overall fuzzing logic. IRPT is a fuzzer for ideal driver fuzzing and solving all the problems found in the previous fuzzers.

IRPT bundles IRP packets in a unit called IRP Program and then performs mutation for this single unit. It sends a single IRP program to the driver as a payload.

In addition to mutating the IOCTL code and InputBuffer values for each IRP in the program, the sequence of the IRP is also mutated. The biggest advantage of IRPT that differentiates from the two Brute-force Fuzzers is mutation 'sequence' of the IRP.

Due to this advantage, if the dependency is satisfied with the preceding IOCTL routine and the coverage is increased in the subsequent fuzzing routine, Fuzzer understands the relationship between the routines that caused the increase in coverage and can reflect it in the next mutation. With the development of IRPT, we were able to successfully solve problems that could not be solved in existing fuzzers and realize efficient driver fuzzing.