Fuzzing 101 with AFL

Fuzzing is awesome, I can’t tell you how often even fuzzing URLs helped during pentests. But today we don’t want to fuzz URLs, we want to fuzz binaries. Why? So we can put to good use some of our Exploit Development knowledge.

What is Fuzzing?

Fuzzing or fuzz testing is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks.

Why do we Fuzz?

Fuzzing is great for automating coverage of the code base, when set up correctly fuzzers are really effective at finding issues. AFL has to be my all time favourite, although there are some others for specific use cases, like JVM/Virtual/High-Level Language/Non-Compiled apps that have better fuzzers for the job. You can also edit the program to be fuzzable, when researchers fuzzed NGINX they had to prevent the code from continuing execution after crash, as NGINX handles that most gracefully which means 1 run of the fuzzer could last for as long as the process is alive, which could be years! So I think now you understand that we want the program to execute quick as possible so we can cover and fuzz so much more.

Set Up

I suggest firing up a droplet on Digital ocean as it’s the most straight forward way to deploy fuzzing machines and are easy to create/destroy.

I usually go for Ubuntu 16.04.5 x64 unless I have reasons not to like 32bit target, certain OS bins etc

Prepare Host

apt update
apt install make gcc dpkg-dev

Install AFL

mkdir AFL
cd ~/AFL
wget https://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
tar xzf afl-latest.tgz
cd afl-*
sudo make install

Picking a Target

Any good researcher will tell you this is an art in itself, you may be lucky and have a binary in mind but remember you may have to figure out all sorts of caveats to get started like dependencies, compilers, configure scripts etc

In 2015 evilsocket did a post on AFL which fuzzed binutils and that’s where I first came across it. I still think that binutils is good for getting started with AFL as it’s easy to set plus by the look of CVE entries in 2018, still a target worth fuzzing.

Get the Source

This is a great one to know anyway, a lot of people don’t realise apt-get source is available and miss out on all that static code analysis fun xD

apt-get source binutils
cd binutils-2.24/

We now have enough to do some security research without fuzzing, we can grep for things you think might be an issues like known code problems & raw sockets etc and also run static code analysis tools on src.

Quick grep for /bin/bash

Instrumenting the Program

When source code is available, instrumentation can be injected by a companion
tool that works as a drop-in replacement for gcc or clang in any standard build
process for third-party code.

CC=afl-gcc ./configure

To avoid having crashes misinterpreted as hangs, run this command…

echo core > /proc/sys/kernel/core_pattern

Running AFL

We need to create initial input so AFL can figure out other mutations for fuzzing, as we are fuzzing binutils we need a binary file as input, in this case we copy /bin/ps to our input directory. Output directory is to store crash information etc.

mkdir afl_in afl_out
cp /bin/ps afl_in/

Here are some guidelines for choosing/creating a seed file:

The seed file should be a valid input file for the target application. While invalid input can sometimes yield interesting bugs and crashes, valid input will find more paths, sooner.

As it usually take less time for an program to process smaller input files, the seed file should be small — under 1 KB. This will result in more executions per second.

Existing unit test suites often include input files — these can be useful seed files.

Using multiple seed files can be inefficient if there is a lot of code coverage or functionality overlap. AFL includes corpus minimization tools called afl-cmin and afl-tmin which will reduce the size of a multiple file seed corpus.

Use the AFL included seed files under the testcases\ directory for common file and data formats.

Now we start AFL…

afl-fuzz -i afl_in -o afl_out ./binutils/readelf -a @@

Break it down:

  • afl-fuzz starts the fuzzer
  • -i afl_in specifies location of input data
  • -i afl_out specifies location of output data
  • Next is the program we want to run along with the arguments and input it expects ./binutils/readelf -a @@

@@ is a placeholder used by AFL to insert the mutated payloads into.

All Commands

apt-get source binutils
cd binutils-2.24/
CC=afl-gcc ./configure
echo core > /proc/sys/kernel/core_pattern
mkdir afl_in afl_out
cp /bin/ps afl_in/
afl-fuzz -i afl_in -o afl_out ./binutils/readelf -a @@

Here is a screenshot fuzzing ls with readelf on 16GB Ubuntu 16.04.5 x64 after 5 mins.

Of course we can tweak a lot, we can also get creative and switch things up but we will save that for another post, for now just get familiar with fuzzing binaries with AFL, see what issues you encounter instrumenting programs and see how you can improve coverage and exec speed. In later posts we will also cover understanding crash reports and developing exploits from them. We will use well known vulnerable binaries to ‘discover’ the vulnerabilities ourselves and weaponise the bug to create our first pseudo 0-day!