3 - Building the agent#
This section guides you through the process of implementing a kAFL agent, which includes both the harness and the specifics tailored for our Linux target.
Agent protocol#
The implementation of a kAFL agent can be broadly categorized into two main components:
Initialization
Harness
Initialization#
The initialization phase of the agent is responsible for:
Configuring the agent settings to optimize fuzzing behavior.
Mapping the payload buffer, which is shared among the Fuzzer, QEMU, and the VM.
Setting Intel PT filters to enhance coverage precision and execution speed.
Registering crash handlers to notify the fuzzer of target crashes and defining crash criteria.
Specifying IP ranges to inform the fuzzer about Intel PT ranges, crucial for obtaining accurate coverage.
Note
This protocol serves as a reference and is not strictly mandated.
However, Certain hypercalls must be executed in sequence.
Example: GET_HOST_CONFIG before SET_AGENT_CONFIG
The corresponding hypercalls to use:
Handshake:
ACQUIREandRELEASEQuery host config:
GET_HOST_CONFIGSet agent config:
SET_AGENT_CONFIGMap payload buffer:
GET_PAYLOADSubmit crash handlers:
SUBMIT_PANICandSUBMIT_KASANSubmit Intel PT filters:
RANGE_SUBMITSubmit CR3 (optional):
SUBMIT_CR3
Note
For step 4 (Allocate payload buffer), there is no hypercall involved.
But please note that the payload buffer should be page-aligned.
Harness#
After the agent initialization is complete, the harness logic comes into play.
The harness performs the following sequence of operations:
Write the next payload in the buffer:
NEXT_PAYLOADStart Intel PT coverage:
ACQUIRECall target function
End Intel PT coverage, restore the guest snapshot:
RELEASE
// 🔁 restore the snapshot and write next payload into the buffer
// (take a snapshot on first call)
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
// 🟢 start coverage feedback collection
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
// ⚡ call fuzz target with the buffer
target_entry(payload_buffer->data, payload_buffer->size);
// ⚪ stop coverage feedback collection
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
DVKM target#
Kernel crash#
To handle kernel crashes with the kAFL agent for DVKM targets, we need to locate the Linux kernel crash routine and insert a PANIC hypercall at the relevant point.
Checkout the agent_tutorial branch of the kafl.linux repository.
It consists of 4 commits:
Adds the
nyx_api.hhypercall header to the Linux sources.Replaces the kernel’s printk implementation by our own own implementation based on HPRINTF hypercall.
Inserts a PANIC hypercall in the
oops_exitfunction of the kernel, invoked during the crash handling sequence.Inserts a KASAN hypercall in the
kasan_reportfunction of the kernel. Further discussion on enabling KASAN will appear in the tutorial’s improvements section.
oops_exit handler# 1/*
2 * Called when the architecture exits its oops handler, after printing
3 * everything.
4 */
5void oops_exit(void)
6{
7 do_oops_enter_exit();
8 print_oops_end_marker();
9 kmsg_dump(KMSG_DUMP_OOPS);
10
11 kAFL_hypercall(HYPERCALL_KAFL_PANIC, 0);
12}
Initialization#
The kAFL agent’s remaining code resides in userland, within test_dvkm.c
Keys points:
The payload buffer is page-aligned using aligned_alloc.
kAFL_payload* payload_buffer = aligned_alloc((size_t)sysconf(_SC_PAGESIZE), host_config.payload_buffer_size);
We ensure that the payload is in resident memory with mlock()
mlock(payload_buffer, host_config.payload_buffer_size);
The IP ranges are identified by parsing /proc/modules, for the dvkm module
detectranges("/proc/modules", "dvkm");
...
static int detectranges(char *mapfile, char *pattern) {
// dvkm 24576 0 - Live 0xffffffffc0201000 (O)
ret = sscanf(line, "%s %lu %d - %s %lx", module_name, &module_size, &instances_loaded, load_state, &kernel_offset);
}
Harness#
The harness is constructed around the ioctl() function call:
kAFL_hypercall(HYPERCALL_KAFL_NEXT_PAYLOAD, 0);
// prepare ioctl code and io_buffer struct range[0-0xC]
ioctl_code = payload_buffer->data[0] % 0xD;
ioctl_num = IOCTL(ioctl_code);
// write width, height and datasize
size_t write_size = sizeof(struct dvkm_obj) - sizeof(io_buffer.data);
memcpy((void*)&io_buffer, &payload_buffer[1], write_size);
// assign rest of payload_buffer to io_buffer.data
io_buffer.data = (char*)&payload_buffer->data[write_size+1];
// struct is now ready
kAFL_hypercall(HYPERCALL_KAFL_ACQUIRE, 0);
ioctl(fd, ioctl_num, &io_buffer);
kAFL_hypercall(HYPERCALL_KAFL_RELEASE, 0);
the
ioctl_codeis generated from the first payload byte, and modulo0xDensures a valid IOCTL.write_sizeis calculated to only write the relevant fields (width,heightanddatasize) of the payload buffer.the remaining payload buffer fills the data pointer field.
For the dvkm.c module, we’ve limited the INFO() printk format strings to prevent output congestion during fuzzing:
int Use_after_free_IOCTL_Handler(struct dvkm_obj *io)
{
INFO("[+] data: %.50s\n", kernel_data_buffer);
}
Congratulations! You now have a comprehensive understanding of the kAFL agent tailored for the DVKM target.
Proceed to the next section to commence your fuzzing campaign.