Fuzzing with AFL++. Acquaintance

It is very important for reversers, extractors and binary researchers to be able to fuzz, so these articles are dedicated to beginners who are just starting to fuzz and getting acquainted with the beast – AFL++.

Fuzzing fuzzing is a software testing technique, often automatic or semi-automatic, that involves providing incorrect, unexpected, or random data as input to an application. The subject of interest are crashes and freezes, violations of internal logic and checks in the application code, memory leaks caused by such input data. Fuzzing is a type of random testing often used to test security problems in software and computer systems. (Wikipedia)

Exercise 1 – Xpdf

In this exercise we will fuzz the Xpdf PDF viewer. The goal is to find a bug for CVE-2019-13288 in XPDF 3.02.

What will you learn

After completing this exercise, you will know the basics of fuzzing with AFL, such as:

  1. Compiling the target application with instrumentation

  2. Running a fuzzer (afl-fuzz)

  3. Troubleshooting with the Debugger (GDB)

Read before you start

  1. I suggest you try to solve the exercise yourself without checking the solution. Try your best and only if you get stuck, see the example solution below.

  2. AFL uses a non-deterministic testing algorithm, so no two fuzzing sessions will ever be the same. This is why I highly recommend installing a fixed grain (-s 123). This way, your fuzzing results will be similar to those shown here, and this will allow you to complete the exercises more easily.

  3. If you discover a new vulnerability, please submit a security report to the project. If you need help or have doubts about the process, GitHub Security Lab can help you with that 🙂

Environment

All exercises were tested on Ubuntu 20.04.2 LTS. I strongly recommend that you use the same OS version to avoid different fuzzing results, and run AFL++ on bare hardware rather than virtual machines for best performance.

Download and create your goal

Let’s first get a fuzzing target. Create a new directory for the project you want to run:

cd $HOME
mkdir fuzzing_xpdf && cd fuzzing_xpdf/

To ensure your environment is completely ready, you may need to install some additional tools (namely make and gcc):

sudo apt install build-essential

Download Xpdf 3.02:

wget https://dl.xpdfreader.com/old/xpdf-3.02.tar.gz
tar -xvzf xpdf-3.02.tar.gz

Collecting Xpdf:

cd xpdf-3.02
sudo apt update && sudo apt install -y build-essential gcc
./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install

It’s time to test the build. First of all, you need to download some examples in PDF format:

cd $HOME/fuzzing_xpdf
mkdir pdf_examples && cd pdf_examples
wget https://github.com/mozilla/pdf.js-sample-files/raw/master/helloworld.pdf
wget http://www.africau.edu/images/default/sample.pdf
wget https://www.melbpc.org.au/wp-content/uploads/2017/10/small-example-pdf-file.pdf

Now we can test the pdfinfo binary:

$HOME/fuzzing_xpdf/install/bin/pdfinfo -box -meta HOME/fuzzing_xpdf/pdf_examples/helloworld.pdf

The first half is launching the tool, -box means printing within the bounds of a page, -meta Print document metadata (XML).

You should see something like this:

Tagged:         no  
Pages:          1  
Encrypted:      no  
Page size:      200 x 200 pts  
MediaBox:           0.00     0.00   200.00   200.00  
CropBox:            0.00     0.00   200.00   200.00  
BleedBox:           0.00     0.00   200.00   200.00  
TrimBox:            0.00     0.00   200.00   200.00  
ArtBox:             0.00     0.00   200.00   200.00  
File size:      678 bytes  
Optimized:      no  
PDF version:    1.7

Installing AFL++

In this course we will use the latest version of the AFL++ fuzzer. You can install everything in two ways:

  1. Local installation (recommended option)

  2. Docker image

Local installation

Install dependencies:

sudo apt-get update

sudo apt-get install -y build-essential python3-dev automake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools

sudo apt-get install -y lld-11 llvm-11 llvm-11-dev clang-11 || sudo apt-get install -y lld llvm llvm-dev clang 

sudo apt-get install -y gcc-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-plugin-dev libstdc++-$(gcc --version|head -n1|sed 's/.* //'|sed 's/\..*//')-dev

Testing and building AFL++:

cd $HOME
git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
export LLVM_CONFIG="llvm-config-11"
make distrib
sudo make install

Docker image

Installing docker:

sudo apt install docker

Download the image:

docker pull aflplusplus/aflplusplus

Start the AFLPlusPlus docker container:

docker run -ti -v $HOME:/home aflplusplus/aflplusplus

and then enter:

export $HOME="/home"

Now, if everything went well, you should be able to run afl-fuzz. Just enter afl-fuzz and you should see something like this:

afl-fuzz++4.06a based on afl by Michal Zalewski and a large online community  
  
afl-fuzz [ options ] -- /path/to/fuzzed_app [ ... ]  
  
Required parameters:  
 -i dir        - input directory with test cases  
 -o dir        - output directory for fuzzer findings  
  
Execution control settings:  
 -p schedule   - power schedules compute a seed's performance score:  
                 fast(default), explore, exploit, seek, rare, mmopt, coe, lin  
                 quad -- see docs/FAQ.md for more information  
 -f file       - location read by the fuzzed program (default: stdin or @@)  
 -t msec       - timeout for each run (auto-scaled, default 1000 ms). Add a '+'  
                 to auto-calculate the timeout, the value being the maximum.  
 -m megs       - memory limit for child process (0 MB, 0 = no limit [default])  
 -O            - use binary-only instrumentation (FRIDA mode)  
 -Q            - use binary-only instrumentation (QEMU mode)  
 -U            - use unicorn-based instrumentation (Unicorn mode)  
 -W            - use qemu-based instrumentation with Wine (Wine mode)  
 -X            - use VM fuzzing (NYX mode - standalone mode)  
 -Y            - use VM fuzzing (NYX mode - multiple instances mode)

Meet AFL++

AFL is a coverage-oriented fuzzer, meaning that it collects coverage information for each modified input to detect new execution paths and potential bugs. Given the source code, AFL can use instrumentation by inserting function calls at the beginning of each basic block (functions, loops, etc.).

To enable instrumentation for our target application, we need to compile the code using AFL compilers.

First of all, we will clean up all previously compiled object files and executables:

rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean

And now we compile xpdf using the afl-clang-fast compiler:

export LLVM_CONFIG="llvm-config-11"

CC=$HOME/AFLplusplus/afl-clang-fast CXX=$HOME/AFLplusplus/afl-clang-fast++ ./configure --prefix="$HOME/fuzzing_xpdf/install/"

make
make install

configure is located in the xpdf folder and should be run from there accordingly

You can now run the fuzzer using the following command:

afl-fuzz -i $HOME/fuzzing_xpdf/pdf_examples/ -o $HOME/fuzzing_xpdf/out/ -s 123 -- $HOME/fuzzing_xpdf/install/bin/pdftotext @@ $HOME/fuzzing_xpdf/output

Brief explanation of each flag:

  1. -i specifies the directory where we should place the input examples (aka example files)

  2. -o specifies the directory where AFL++ will store the mutated files

  3. -s specifies static random grain to use

  4. @@ this is the target command line that AFL will substitute into each input file name

So basically the fuzzer will be run by the command $HOME/fuzzing_xpdf/install/bin/pdftotext <input-file-name> $HOME/fuzzing_xpdf/output for each individual input file.

If you receive a message like “Hmm, your system is configured to send core dump notifications to an external utility…”, just do this:

sudo su
echo core >/proc/sys/kernel/core_pattern
exit

If an error occurs like mine “Whoops, your system uses on-demand CPU frequency scaling, adjusted between 1558 and 2338 MHz. Unfortunately, the scaling algorithm in the kernel is imperfect and can miss the short-lived processes spawned by afl-fuzz. To keep things moving, run these commands as root: …“, then I solved this problem like this:

export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1  
export AFL_SKIP_CPUFREQ=1

After a few minutes you should see something like this:

american fuzzy lop ++4.06a {default} (..._xpdf/install/bin/pdftotext) [fast]  
┌─ process timing ────────────────────────────────────┬─ overall results  

You can see the “uniq. crashes” value in red, showing the number of unique crashes found. You can find these crash files in the folder $HOME/fuzzing_xpdf/out/ . You can stop the fuzzer after detecting the first failure, which is what we will be working on. Depending on the performance of your machine, it may take up to one to two hours before you experience a crash.

At this stage you have already learned:

  1. How to compile a target using afl compiler with instrumentation

  2. How to run afl++

  3. How to Find Your Target’s Unique Failures

So, what is next? We don’t have any information about this error, just a program crash… It’s time to debug and fix it!

Exercise

To complete this exercise you will need:

  1. Reproduce the crash using the specified file

  2. Debug the crash to find the problem

  3. To solve a problem

Solution

Reproducing the crash

Find the file corresponding to the crash in the file located at $HOME/fuzzing_xpdf/out/ . The file name looks like this id:000000,sig:11,src:001504+000002,time:924544,op:splice,rep:16 (or something like that). Pass this file as input file pdftotext :

$HOME/fuzzing_xpdf/install/bin/pdftotext '$HOME/fuzzing_xpdf/out/default/crashes/<your_filename>' $HOME/fuzzing_xpdf/output

This will cause a segmentation fault and cause the program to crash:

Error: PDF file is damaged - attempting to reconstruct xref table...  
Error (417): Illegal character <9a> in hex string  
Error (418): Illegal character <6d> in hex string  
Error (419): Illegal character <25> in hex string  
Error (420): Illegal character <96> in hex string  
Error (421): Illegal character <9d> in hex string  
Error (423): Illegal character <50> in hex string  
Error (449): Illegal character <4e> in hex string  
Error: Missing 'endstream'  
zsh: segmentation fault

Triage

Use gdb to find out why the program crashes on this type of input. You can take a look at http://people.cs.pitt.edu/~mosse/gdb-note.html for a good short tutorial on GDB. First of all, you need to rebuild Xpdf with debug information to get a symbolic stack trace:

rm -r $HOME/fuzzing_xpdf/install
cd $HOME/fuzzing_xpdf/xpdf-3.02/
make clean
CFLAGS="-g -O0" CXXFLAGS="-g -O0" ./configure --prefix="$HOME/fuzzing_xpdf/install/"
make
make install

Now you can start GDB:

gdb --args $HOME/fuzzing_xpdf/install/bin/pdftotext $HOME/fuzzing_xpdf/out/default/crashes/<your_filename> $HOME/fuzzing_xpdf/output

Now we write inside GDB run . If everything went well, you should see the following result (or similar):

Error: Missing 'endstream'  
  
Program received signal SIGSEGV, Segmentation fault.  
_int_malloc (av=av@entry=0x7ffff7bf1c60 <main_arena>, bytes=128) at ./malloc/malloc.c:3984 

Then enter btto get a backtrace. Scroll down the call stack and you will see many calls to the “Parser::getObj” method, which seems to indicate infinite recursion. If you go to https://www.cvedetails.com/cve/CVE-2019-13288/you will see that the description matches the backtrace we received from GDB (in my case, the other vulnerability is not the one we were supposed to see).

Fixim

The last step of the exercise is to correct the error! Rebuild the target after the fix and verify that your use case no longer causes a segmentation fault. This last part is left as an exercise for the student.

Alternatively, you can download Xpdf 4.02, which has already fixed the bug, and check that the segmentation fault is gone.

Similar Posts

Leave a Reply

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