Welcome back, my aspiring cyberwarriors!
Finding vulnerabilities in applications and services is the first step toward developing your own zero-day exploit. Vulnerability scanners are great for finding KNOWN vulnerabilities, but to find unknown or zero-day vulnerabilities a fuzzer is an excellent tool that you should be familiar with. As you know, fuzzing is the process of sending random or pseudo-random input into an application to find out if they break the application. If the application breaks, you can examine and analyze why it broke and then determine whether this vulnerability is exploitable. If nothing else, if you find an input that breaks an application, it can lead to a denial of service (dos) attack.
Secure coding is a very difficult practice. Usually, when programmers develop software, their goal is to make the software work rather than break. In this process, vulnerabilities may develop in cases where a legacy function was used instead of a newer, more secure one. Unfortunately, this is often the case to speed up development. Consequently, legacy software is especially vulnerable. This means that if the programmer is well aware of secure coding practices, their software will likely be secure. On the other hand, for the less sophisticated programmer, legacy functions often make up a significant portion of their code.
Examining the entire source code of a program to identify vulnerabilities such as buffer overflows can be an effective but time-consuming process. This approach, while thorough, is not always the most practical way to identify critical yet straightforward vulnerabilities. Addressing such issues quickly is essential, and one of the most efficient methods for uncovering them is a technique known as Fuzzing.
In this article I want to share a step-by-step guide on how to run American Fuzzy Lop ++ (AFL++) to fuzz an open source target.
Why AFL++?
AFL++ (American Fuzzy Lop Plus Plus) builds upon the foundation of AFL, a highly regarded fuzzing tool for C, C++, and Objective-C programs. AFL++ enhances AFL’s renowned speed and intelligent test case selection with additional features and optimizations that make it even more effective at uncovering vulnerabilities. Like its predecessor, AFL++ remains user-friendly, making it accessible for beginners while offering advanced capabilities for experienced users.
The original American Fuzzy Lop was developed by Michal Zalewski (of poF fame and several No Starch Press books) and first released in November 2013. It was immediately recognized as probably the best fuzzer available and became the de-facto standard for security research. AFL++ is a fork of the AFL source code. It offers better performance and more advanced features and that is why we will use it here.
What Does AFL++ Do?
AFL++ is a coverage-guided fuzzer that methodically explores program execution paths to identify vulnerabilities. It retains the core principles of AFL while introducing several key improvements:
Code Instrumentation: AFL++ enhances the original AFL's instrumentation, supporting various modes (e.g., classic, persistent, and QEMU modes) for broader compatibility and better performance.
Coverage Tracking: It monitors execution paths for each input and prioritizes inputs that lead to new or unique coverage.
Enhanced Mutation Strategies: AFL++ incorporates advanced mutation strategies, including deterministic and stochastic techniques, along with cutting-edge mutators for better test case generation.
Genetic Algorithm: Like AFL, AFL++ employs a genetic algorithm to mutate inputs that trigger new paths, evolving them to maximize coverage.
Input Filtering: Redundant or low-value inputs are efficiently filtered out, optimizing the fuzzing process.
Power Schedules: AFL++ offers advanced power schedules to balance input prioritization and mutation efforts effectively.
Dictionaries: It supports custom dictionaries for testing "magic values" (e.g., specific strings or patterns), boosting its ability to find specific types of vulnerabilities.
Extensibility: AFL++ is designed with extensibility in mind, enabling users to easily integrate custom components or experiment with novel fuzzing techniques.
Download And Build Your Target
For this article, we’ll focus on fuzzing the Xpdf PDF viewer to identify a crash or proof of concept (PoC) for CVE-2019-13288. The target version is Xpdf 3.02, which contains the relevant vulnerability. Thanks to Antonio Morales for publishing the original materials on GitHub.
To begin fuzzing, we need to organize our workspace and prepare the target program:
kali> mkdir fuzzing_xpdf && cd fuzzing_xpdf/
Download Xpdf 3.02:
kali> tar -xvzf xpdf-3.02.tar.gz
Next, build Xpdf:
kali> cd xpdf-3.02
kali> sudo apt update && sudo apt install build-essential gcc -y
kali> ./configure –prefix="$HOME/fuzzing_xpdf/install/"
kali> make
kali> make install
Download sample PDFs: Obtain a collection of PDF files to use as input for testing our build. You can find freely available examples online or use the following command to download:
kali> mkdir pdf_examples && cd pdf_examples
Now, we can test the pdfinfo binary with:
kali> /home/kali/fuzzing_xpdf/install/bin/pdfinfo -box -meta /home/kali/fuzzing_xpdf/pdf_examples/helloworld.pdf
pdfinfo: A binary file to extract information from PDF files
-box: Displays information about page dimensions and media boxes
-meta: Reveals embedded metadata about the document
You should see something like this:
This indicates that we have successfully completed all the steps, and our installation is functioning correctly. We can now proceed with the installation of AFL++.
Install AFL++
kali> git clone https://github.com/AFLplusplus/AFLplusplus
kali> cd AFLplusplus
kali> make distrib
kali> sudo make install
If everything went well, you should now be able to run AFL++.
Simply type:
kali> afl-fuzz
Getting Started With Fuzzing
First of all, we're going to clean all previously compiled object files and executables:
kali> rm -r fuzzing_xpdf/install
kali> cd fuzzing_xpdf/xpdf-3.02
kali> make clean
And now we're going to build xpdf using the afl-clang-fast compiler:
kali> export LLVM_CONFIG="llvm-config-16"
kali> CC=/home/kali/AFLplusplus/afl-clang-fast CXX=/home/kali/AFLplusplus/afl-clang-fast++ ./configure –prefix="/home/kali/fuzzing_xpdf/install/"
kali> make
kali> make install
Now, you can run the fuzzer with the following command:
kali> afl-fuzz -i /home/kali/fuzzing_xpdf/pdf_examples/ -o /home/kali/fuzzing_xpdf/out/ -s 123 -- /home/kali/fuzzing_xpdf/install/bin/pdftotext @@ /home/kali/fuzzing_xpdf/output
Explanation of each option:
-i: Specifies the directory for input cases (example files).
-o: Specifies the directory where AFL++ will store mutated files.
-s: Sets a static random seed for reproducibility.
@@: A placeholder in the target’s command line that AFL replaces with each input file name.
Essentially, the fuzzer will execute:/home/kali/fuzzing_xpdf/install/bin/pdftotext <input-file-name> /home/kali/fuzzing_xpdf/outputfor each input file.
After a few seconds, you should see an output similar to this:
Depending on the power of your virtual machine, you will see the first hangs and crashes over time. You'll see the 'uniq. crashes' value in red, indicating the number of unique crashes found. These crash files are stored in the /home/kali/fuzzing_xpdf/out/ directory. You can stop the fuzzer after finding the first crash, which we'll focus on. This may take one to two hours, depending on your machine's performance.
How to Reproduce the Crash
To reproduce the crash, locate the file corresponding to the crash in the /home/kali/fuzzing_xpdf/out/default/crashes directory.
In my case, the filename is:id:000000,sig:11,src:000972,time:4417804,execs:2703585,op:havoc,rep:2.
Use GDB to determine why the program crashes with this input.
First, rebuild Xpdf with debugging information enabled to obtain a symbolic stack trace.
kali> rm -r /home/kali/fuzzing_xpdf/install
kali> cd /home/kali/fuzzing_xpdf/xpdf-3.02/
kali> make clean
kali> CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure –prefix="$HOME/fuzzing_xpdf/install/"
-g: Includes debug information (symbols and source-level mappings) in the binaries.
-O0: Disables compiler optimizations, making the generated code more predictable and easier to debug.
kali> make
kali> make install
Now, you can run GDB:
kali> gdb --args /home/kali/fuzzing_xpdf/install/bin/pdftotext /home/kali/fuzzing_xpdf/out/default/crashes/id:000000,sig:11,src:000972,time:4417804,execs:2703585,op:havoc,rep:2 /home/kali/fuzzing_xpdf/output
And then, type inside GDB:
(gdb) run
It looks like we are overwriting the return address with a getchar command, causing a segmentation fault. This results in a crash. At a minimum, this behavior can lead to a denial-of-service condition if the circumstances are repeated in a loop.
Summary
Fuzzing is often a technique for identifying unknown vulnerabilities (zero-day) in software. It is often the first step to develop your zero-day exploit.
AFL++ is an incredibly powerful tool that remains remarkably easy to use. It streamlines the fuzzing process into just a few simple steps, managing all the complex tasks in the background.
To learn about Exploit Development, become a Subscriber Pro and join our upcoming Exploit Development training.