Unicorefuzz
Fuzzing the Kernel using UnicornAFL and AFL++. For details, skim through the WOOT paper or watch this talk at CCCamp19.
Is it any good?
yes.
Unicorefuzz Setup
- Install python2 & python3 (ucf uses python3, however qemu/unicorn needs python2 to build)
- Run
./setup.sh
, preferrably inside a Virtualenv (else python deps will be installed using--user
). During install, afl++ and uDdbg as well as python deps will be pulled and installed. - Enjoy
ucf
Upgrading
When upgrading from an early version of ucf:
- Unicorefuzz will notify you of config changes and new options automatically.
- Alternatively, run
ucf spec
to output a commentedconfig.py
spec-like element. probe_wrapper.py
is nowucf attach
.harness.py
is now nameducf emu
.- The song remains the same.
Debug Kernel Setup (Skip this if you know how this works)
- Create a qemu-img and install your preferred OS on there through qemu
- An easy way to get a working userspace up and running in QEMU is to follow the steps described by syzkaller, namely create-image.sh
- For kernel customization you might want to clone your preferred kernel version and compile it on the host. This way you can also compile your own kernel modules (e.g. example_module).
- In order to find out the address of a loaded module in the guest OS you can use
cat /proc/modules
to find out the base address of the module location. Use this as the offset for the function where you want to break. If you specifyMODULE
andBREAK_OFFSET
in theconfig.py
, it should use./get_mod_addr.sh
to start it automated. - You can compile the kernel with debug info. When you have compiled the linux kernel you can start gdb from the kernel folder with
gdb vmlinux
. After having loaded other modules you can use thelx-symbols
command in gdb to load the symbols for the other modules (make sure the .ko files of the modules are in your kernel folder). This way you can just use something likebreak function_to_break
to set breakpoints for the required functions. - In order to compile a custom kernel for Arch, download the current Arch kernel and set the .config to the Arch default. Then set
DEBUG_KERNEL=y
,DEBUG_INFO=y
,GDB_SCRIPTS=y
(for convenience),KASAN=y
,KASAN_EXTRA=y
. For convenience, we added a working example_config that can be place to the linux dir. - To only get necessary kernel modules boot the current system and execute
lsmod > mylsmod
and copy the mylsmod file to your host system into the linux kernel folder that you downloaded. Then you can usemake LSMOD=mylsmod localmodconfig
to only make the kernel modules that are actually needed by the guest system. Then you can compile the kernel like normal withmake
. Then mount the guest file system to/mnt
and usemake modules_install INSTALL_MOD_PATH=/mnt
. At last you have to create a new initramfs, which apparently has to be done on the guest system. Here usemkinitcpio -k <folder in /lib/modules/...> -g <where to put initramfs>
. Then you just need to copy that back to the host and let qemu know where your kernel and the initramfs are located. - Setting breakpoints anywhere else is possible. For this, set
BREAKADDR
in theconfig.py
instead. - For fancy debugging, ucf uses uDdbg
- Before fuzzing, run
sudo ./setaflops.sh
to initialize your system for fuzzing.
Run
- ensure a target gdbserver is reachable, for example via
./startvm.sh
- adapt
config.py
:- provide the target’s gdbserver network address in the config to the probe wrapper
- provide the target’s target function to the probe wrapper and harness
- make the harness put AFL’s input to the desired memory location by adopting the
place_input
funcconfig.py
- add all EXITs
- start
ucf attach
, it will (try to) connect to gdb. - make the target execute the target function (by using it inside the vm)
- after the breakpoint was hit, run
ucf fuzz
. Make sure afl++ is in the PATH. (Use./resumeafl.sh
to resume using the same input folder)
Putting afl’s input to the correct location must be coded invididually for most targets. However with modern binary analysis frameworks like IDA or Ghidra it’s possible to find the desired location’s address.
The following place_input
method places at the data section of sk_buff
in key_extract
:
<div class="highlight highlight-source-python position-relative overflow-auto" data-snippet-clipboard-copy-content=" # read input into param xyz here:
rdx = uc.reg_read(UC_X86_REG_RDX)
utils.map_page(uc, rdx) # ensure sk_buf is mapped
bufferPtr = struct.unpack("
# read input into param xyz here: rdx = uc.reg_read(UC_X86_REG_RDX) utils.map_page(uc, rdx) # ensure sk_buf is mapped bufferPtr = struct.unpack("<Q",uc.mem_read(rdx + 0xd8, 8))[0] utils.map_page(uc, bufferPtr) # ensure the buffer is mapped uc.mem_write(rdx, input) # insert afl input uc.mem_write(rdx + 0xc4, b"\xdc\x05") # fix tail