afl-unicorn: Fuzzing Arbitrary Binary Code

13,218 reads

American Fuzzy Lop (AFL) is awesome. It’s easily the best thing out there for quickly doing cutting-edge fuzzing analysis on command line applications. But what about the situations where accessing the stuff you want to fuzz via command line isn’t so simple? Lots of times you can write a test harness (or maybe use libFuzzer instead), but what if you could just emulate the parts of the code that you want to fuzz and still get all the coverage-based advantages of AFL? For example, maybe you want to fuzz a parsing function from an embedded system that receives input via RF and isn’t easily debugged. Maybe the code you’re interested in is buried deep within a complex, slow program that you can’t easily fuzz through any traditional tools.

I’ve created a new ‘Unicorn Mode’ for AFL to let you do just that. If you can emulate the code you’re interested in using the Unicorn Engine, you can fuzz it with afl-unicorn. All of the source code (and a bunch of additional documentation) is available at the afl-unicorn GitHub page.

How To Get It

Clone or download the afl-unicorn git repo from GitHub to a Linux system (I’ve only tested it on Ubuntu 16.04 LTS). After that, build and install AFL as usual, then go into the ‘unicorn_mode’ folder and run the ‘build_unicorn_support.sh’ script as root.

cd /path/to/afl-unicorn

make

sudo make install

cd unicorn_mode

sudo ./build_unicorn_support.sh

How It Works

Unicorn Mode works by implementing the block-edge instrumentation that AFL’s QEMU Mode normally does into Unicorn Engine. Basically, AFL will use block coverage information from any emulated code snippet to drive its input generation. The whole idea revolves around proper construction of a Unicorn-based test harness, as shown in the figure below:

The only addition to normal AFL use is the Unicorn-based test harness

The Unicorn-based test harness loads the target code, sets up the initial state, and loads in data mutated by AFL from disk. The test harness then emulates the target binary code, and if it detects that a crash or error occurred it throws a signal. AFL will do all its normal stuff, but it’s actually fuzzing the emulated target binary code!

Unicorn Mode should work as expected with Unicorn scripts or applications written in any of the standard Unicorn bindings (C/Python/Go/Whatever), as long as the at the end of the day the test harness uses libunicorn.so compiled from the patched Unicorn Engine source code that is created by afl-unicorn. So far I’ve only tested this with Python, so please provide feedback and/or patches to the repo if you test this out.

Be aware that building afl-unicorn will compile and install a patched version of Unicorn Engine v1.0.1 on your local system. You’ll have to uninstall any existing Unicorn binaries prior to building afl-unicorn. As with off-the-shelf AFL, afl-unicorn only supports Linux. I’ve only tested it on Ubuntu 16.04 LTS, but it should work smoothly with any OS capable of running both AFL and Unicorn.

Note: At least 1 instruction must be emulated before loading the fuzzed input data. This is an artifact of how AFL’s fork server is started in QEMU mode. It could probably be fixed with some more work, but for now just emulate at least 1 instruction as shown in the example and don’t worry about it. The example below demonstrates how to easily workaround this limitation.

Example Use

Note: This is the same as the ‘simple example’ included in the repo. Please play with it on your own system to see it in action. The repo contains a pre-built MIPS binary of main(), which is demonstrated here.

First, let’s look at the code that we will fuzz. This is just a contrived toy example that will crash really easily in a few different ways, but I’ve extended this to real-world use cases and it works exactly as expected.

Contrived crashing example code used to test basic afl-unicorn functionality

Notice that this code by itself it totally bogus. It assumes that data for ‘data_buf’ will magically be located at the address 0x00300000. While this seems strange, this is analogous to lots of parsing functions which assume that they will find data at buffer at a fixed address.

In real-world situations you will need to reverse engineer your target binary to find and identify the exact functionality that you want to emulate and fuzz. In upcoming blog posts I will present some tools to make extraction and loading of process states simple, but for now you will need to do the leg work of getting all the required components up and running in Unicorn.

Your test harness must take the input to mutate via a file specified on the command line. This is the glue that allows AFL to mutate input via its normal interface. The test harness must also forcibly crash itself if it detects a crashing condition during emulation, such as if emu_start() throws an exception. Below is an example test harness which does both of these:

Create a few test inputs and run your test harness on its own to verify that it emulates the code (and crashes) as expected.

Now that the test harness is up and running, create a few sample inputs and run it under afl-fuzz as shown below. Make sure you add the ‘-U’ parameter to specify Unicorn Mode, and I recommend setting the memory limit parameter (‘-m’) to ‘none’ since running a Unicorn script can take quite a bit of RAM. Follow the normal AFL convention of replacing the parameter containing the path of the file to fuzz with ‘@@’ (see AFL’s README for more info)

afl-fuzz -U -m none -i /path/to/sample/inputs -o /path/to/results

-- python simple_test_harness.py @@

If all goes according to plan AFL will start up and find some crashes pretty quickly.

AFL found crashes in the sample in just few seconds!

You can then run the crashing inputs (found in the results/crashes/ directory) through the test harness manually to learn more about why they crashed. I recommend keeping a second copy of your Unicorn test harness and modifying as necessary to debug the crash in emulation. For example, you can turn on instruction tracing, disassemble along the way using Capstone, dump registers at critical points, etc.

Once you think that you have a valid crash you’ll need to figure out a way to deliver it to the actual program outside of emulation and verify that the crash works on the physical system.

It’s worth noting that the overall fuzzing speed and performance is going to be largely determined by how fast your test harness is. A large, intricate Python-based test harness is going to run much slower than a tight, optimized C-based harness. Make sure to consider this if you plan on running extensive, long-running fuzzers. As a rough point of reference, I found a C-based harness to get 5–10x more executions per second than an analogous Python harness.

Where to Go From Here

While I originally created this to find vulnerabilities in embedded systems (like those found in the Broadcom WiFi chipset by Project Zero and Comsecuris), in my follow-up blog post I’ll release tools and describe a methodology for using afl-unicorn to fuzz emulated functionality in Windows, Linux, and Android processes.

Afl-unicorn can also be used to not only find crashes, but to do basic path finding. In your test harness you can force a crash if a specific instruction is executed (or any other condition you choose). AFL will catch these ‘crashes’ and store the inputs which lead to that condition being met. This can be used as a poor-man’s replacement for symbolic analysis to discover inputs that drill down into deep parsing logic trees.

The makers of Unicorn and Capstone have recently been tweeting images that hint that AFL support may be coming soon…it will be interesting to see what capabilities they have created, and if there is any chance for collaboration to optimize our tools.

Credits

I developed afl-unicorn as an internal research project while working as a cyber security researcher at Battelle in Columbus, OH. Battelle is an awesome place to work, and afl-unicorn is just one of many examples of novel cyber security research being done there. For more Battelle-sponsored projects, check out Chris Domas and John Toterhi’s previous work. For information about careers at Battelle, check out their careers page.

Of course none of this would be possible without AFL and Unicorn Engine. Lots of additional inspiration came from Alex Hude‘s awesome uEmu plugin for IDA, and many of the general concepts were borrowed from the NCC Group’s AFLTriforce project.

To continue on, check out afl-unicorn: Part 2 - Fuzzing the ‘Unfuzzable’

Tags