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.
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
After compiling with
make, we can see
afl-gcc in the repository (among other 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,
We can easily induce a buffer overflow, but how do we find this with AFL?
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.
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.
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.
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.
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!
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.