AFL Basics - 1

Published:

What is fuzzing?

Simply put, fuzzing is a technique for testing programs with randomly generated input. This randomization helps us find bugs by providing potentially invalid and corner-case inputs for the target program.

In ths post, we will be using American Fuzzy Lop (AFL) to find a simple buffer overflow bug.

Installing AFL

Luckily, installing AFL is pretty simple. It is currently maintained and available on Google’s Github page.

The repository has a great Readme file for getting started. There is even a QuickStartGuide.txt file if the Readme is too long. If even that is too long, you can build AFL with make.

After compiling with make, we can see afl-fuzz and afl-gcc in the repository (among other binaries).

afl-binaries

Fuzzing Hello World

Just so that we start to get comfortable with AFL, let’s try our hand at fuzzing a simple Hello World program.

Consider the following program, hello.c:

We can easily induce a buffer overflow, but how do we find this with AFL?

1) Instrumentation

For this example, we will be adding instrumentation by compiling the source code with afl-gcc. AFL can add instrumentation to compiled binaries (no source code available), but we will explore that in a later post.

Because afl-gcc is a wrapper for gcc, we can use it as we would gcc. For example, compiling and adding instrumentation to hello.c, we would use the command ./afl-gcc -o hello hello.c.

If you are using a Makefile, afl-gcc must be defined directly as below.

2) Run afl-fuzz

We can use afl-fuzz with

afl-fuzz -i /path/to/input/dir -o /path/to/output/dir /path/to/binary/hello

The input directory must contain atleast one starting file that contains an example input to our target binary. Luckily, we can use a sample included in the AFL repository (located in testcases/others/text). The output directory is just a location to which AFL will write results. For this example, we will assume that there is a directory called output/ in our current working directory.

Assuming we are currently in the same directory as hello, we would run the command

afl-fuzz -i /path/to/afl/testcases/others/text -o output/ ./hello

After running this command, we will see the AFL UI. From here we can observe (or go do something else). Luckily, our program is very simple, and a crash will be found immediately.

afl-ui

When you see that a crash has been found (uniq crashes), you can quit AFL using CTRL-C. The input that resulted in the crash will be saved in output/crashes/, which can be used to recreate the crash outside of AFL. In my case, the output/crashes/ has a file named id:000000,sig:06,src:000000,op:havoc,rep:16. This name can tell us things about the fuzzing and the crash (for example, the crash was sig:06).

If we want more information about the crash, we can use an experimental crash triage script named triage_crashes.sh (located in /path/to/afl/experimental/crash_triage/). This script will print out the crash data for each file in output/crashes/. We can run this script using

/path/to/afl/experimental/crash_triage/triage_crashes.sh output/ ./hello

As can be seen, the script needs the output directory and the target binary. After running the script, we’ll be met with the crash data.

crash-traige

The crash data tells us that our bug is at hello.c:6 in function main() (which is our horrible fgets() line). This, in combination with SIGNAL 06 that is also shown, tells us that we have a buffer overflow bug and where we can find it!

Conclusion

Using our simple example, we can see that fuzzing is a powerful tool for finding bugs. In later posts, I’ll discuss more AFL options, other fuzzing tools, and symbolic/concolic execution.