Software Exploit Development – Fuzzing with AFL

It’s quite impressive to look back in the past to the early days of software vulnerabilities and observe the ongoing dance between new  mitigation and new exploitation techniques. Powerful fuzzing tools are now common place and operated on a daily basis by IT corporations and security labs; either to find crashes in their software or others’ program, seeking workable exploit out of it. New research is continuously presented to mitigate new procedures while multiple organizations develop new counter-mitigation tricks. In this post, we’ll overview the entire software exploitation process: from fuzzing with American Fuzzy Lop (AFL) to exploit development with gdb-peda and pwntools. For this purpose, we will develop a quick 64-bit program exhibiting a glaring buffer overflow vulnerability. We will then fuzz it to find that vulnerability, analyze the results and development an exploit for it. A video is also available.

The Vuln1 Vulnerable Program

While we could use a known vulnerable program online, we decide to craft our own quick C program so we can understand all its facets. The program below uses two characters buffers of 32 characters; one to hold the username and the other one to hold a password. To manage user input, we used the well-known insecure gets() function, which fails to check buffer boundaries and leads to buffer overflows.

Once executed, the program first asks for a username and a password. The inputs are stored in the login and passwd variables. Their value are then compared with the expected value using strcmp(). If the credentials entered are “root” and “1qazxsw2”, then a “Access Granted.” message is printed out to the console, otherwise “Access Denied.” is shown to the user and the program exits.

To simplify the exploitation process of this exercise, we will compile this program with absolutely no memory protection, i.e. NX will be disabled and no stack canary. NX is usually enabled to prevent code to be executed directly from the stack, which there is no need for other than exploitation purposes. As for stack canaries, they can detect stack overflows by adding an extra value to the software stack. If this value is not found when the function returns, an exception is thrown to prevent further execution. Nowadays, these protection schemes are enabled in a vast majority of cases, but we adopt simplicity rather than realism for this post. Disabling these protection mechanism can be achieved using the following GCC command:

The  -fno-stack-protector will disable the stack canaries while the  -z execstack makes both the heap and stack executable. To verify that these options have not been included, we can use a nifty tool call checksec which is included with pwntools, which will will present later in this post. By executing  checksec vuln1 we confirm that both the stack canaries and NX bit are disabled:

Checksec reports on 2 additional security mechanisms other than the stack canaries and No-eXecute bit. While these concepts are out of scope for this post, we will simply present them

With our target created, we are now ready to start fuzzing it to uncover the buffer overflow it contains.

Fuzzing with AFL

AFL is a popular open-source and free fuzzer that has been leveraged to discover vulnerabilities in a large set of applications and libraries. Before starting AFL, we need to instrumentalize our target using the afl-gcc compiler. The AFL compiler will add code around the source in order to maximize coverage. To compile the source code with AFL, use the same command used above to compile Vuln1 using afl-gcc rather than gcc or use the associated Makefile

The resulting binary is the one that will be used with AFL, but when analyzing the crash later one, we will do it with the gcc compiled binary. Until then, let’s learn how to use AFL to assess the Vuln1 program.

A critical aspect of fuzzing is to craft meaningful test cases, e.g. inputs that will maximize code coverage by exploring all potential paths of the targeted program. The vuln1 program is simple and only has 3 paths:

  1. Username is invalid;
  2. Username is valid, but password in invalid;
  3. Username and password are valid.

In order to reach these 3 paths, we will design our test cases appropriately by creating 3 files. The first file will have 2 lines, none of them containing the appropriate credentials, the second file will have the right username, but an invalid password and the third file will have both correct credentials. AFL will read the contents of each file and feed each line to the stdin of Vuln1. Create a directory called testcases and in it, create 3 files representing these cases. The name of the files does not matter.

test1.txt test2.txt test3.txt
a
a
root
a
root
1qazxsw2

After creating these 3 files, create another directory called results, which will contains the results of the fuzzing run. At this point you’re ready to start AFL using afl-fuzz, the actual fuzzing program. You can do so with the following command:

Where -t ./testcases specifies the directory containing the testcases, -o ./results specifies the output directory and ./vuln1 is that target program. If you run AFL for the first time, you’ll likely be greeted with the following warning:

Core_pattern file warning when running AFL
AFL Warns that the core_pattern file must be changed.

Just follow the instruction given and you’ll get rid of this message. Simply a shell as root using  sudo bash and type the suggested command, i.e.

Retry to start AFL using the same command and you should have no issue this time. A screen will appear and present you with quite a few statistics. This AFL Readme file explains all of these fields very well, and should definitively be read and well understood. For now, let’s focus on the “Overall Results” section.

Fuzzing Vuln1 with AFL - Results
Results of Fuzzing the Vuln1 Program

Two rows of this section are particularly interesting in this example:

  • Total paths; and
  • Unique crashes.

Notice that after a few seconds, the total paths field is 3, which is what we expected based on the code of vuln1. As such, once we reached 3 paths, we can stop AFL by pressing Ctrl-C, as it will not find anything new. In the real world, we have no idea how many paths may be possible. As such AFL provides color codes to help you assess if it’s time to stop. Another field that can help is the last path found. When no new paths have been found after a while, AFL may have cover most of the code it can find and is unlikely to find anything new. Finally, the most interesting field is the unique crashes, which indicates that some of the inputs, stored in the results directory, have successfully crashed the program and should be investigated. We have 2 files in the results/crashes directory:

Each file contains the input that crashed the program so you can reproduce the event and investigate to see if the crash is exploitable.

We can confirm the crash and observe an segmentation fault by piping the contents of the crash to our vuln1 program:

The next step is to analyze the crash data and determine if it can be converted into an exploitable vulnerability. Spoiler alert: it can.

Conclusion

This short post is a simple introduction to AFL, a powerful fuzzer that can be leveraged on source code and binaries to find potential vulnerabilities. This step is usually the first step in exploit development. In the next post, we’ll use PEDA to analyze the results found here and determine it exploitability.

References

See Also

  • Related YouTube Video: Software Exploitation Research: Fuzzing with AFL

Further Reading

Author: Jonathan Racicot

INTJ, goa trance, RE, python, malware, wine, books, french bulldogs, genetics, biohacking, CtF, night owl, transhumanist, AI, machines, cyber ops.

Leave a Reply

Your email address will not be published. Required fields are marked *