Fuzzing with AFL++

American fuzzy lop (AFL) is a software fuzzer that employs genetic algorithms in order to efficiently increase code coverage of the test cases.
It’s been used to identify a number of vulnerabilities in major free software projects such as Bash, PHP and OpenSSL.

AFL can be installed in Kali Linux with the following command:

sudo apt install afl++

However, the default Kali package does not support binary instrumentation. As such, it’s better to install from source using;

sudo apt-get update
sudo apt-get install -y build-essential python3-dev automake cmake git flex bison libglib2.0-dev libpixman-1-dev python3-setuptools cargo libgtk-3-dev
# try to install llvm 12 and install the distro default if that fails
sudo apt-get install -y lld-12 llvm-12 llvm-12-dev clang-12 || 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
sudo apt-get install -y ninja-build # for QEMU mode
git clone https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
make distrib
sudo make install

Next, to improve fuzzing performance, you will want to run afl-system-config.

sudo afl-system-config 
[sudo] password for kali: 
This reconfigures the system to have a better fuzzing performance.
WARNING: this reduces the security of the system!

Settings applied.

It is recommended to boot the kernel with lots of security off - if you are running a machine that is in a secured network - so set this:
  /etc/default/grub:GRUB_CMDLINE_LINUX_DEFAULT="ibpb=off ibrs=off kpti=0 l1tf=off mds=off mitigations=off no_stf_barrier noibpb noibrs nopcid nopti nospec_store_bypass_disable nospectre_v1 nospectre_v2 pcid=off pti=off spec_store_bypass_disable=off spectre_v2=off stf_barrier=off srbds=off noexec=off noexec32=off tsx=on tsx_async_abort=off arm64.nopauth audit=0 hardened_usercopy=off ssbd=force-off"

If you run fuzzing instances in docker, run them with "--security-opt seccomp=unconfined" for more speed.

Fuzzing Applications with Source Code Available

To demonstrate it’s ability to find software security flaws, we will use the following vulnerable application:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void processData(char* input) {
    char buffer[20];
    strcpy(buffer, input);
}

 
int main( int argc, char *argv[] ) {
    FILE    *textfile;
    char    *text;
    long    numbytes;
    
    if( argc == 2 ) {
      printf("Filename is %s\n", argv[1]);
    }
 
    textfile = fopen(argv[1], "r");
    if(textfile == NULL)
        return 1;
     
    fseek(textfile, 0L, SEEK_END);
    numbytes = ftell(textfile);
    fseek(textfile, 0L, SEEK_SET);  
 
    text = (char*)calloc(numbytes, sizeof(char));   
    if(text == NULL)
        return 1;
 
    fread(text, sizeof(char), numbytes, textfile);
    fclose(textfile);
 
    printf(text);
    processData(text); 
    return 0;
}

Create a Makefile in the same directory as the application.

CFLAGS ?= -g -w

all: read_file.c
        $(CC) ${CFLAGS} -o readfile read_file.c

clean:
        rm -f readfile

Use AFL to compile the application:

CC=afl-clang-fast AFL_HARDEN=1 make
afl-clang-fast -g -w -o readfile read_file.c
afl-cc++4.00c by Michal Zalewski, Laszlo Szekeres, Marc Heuse - mode: LLVM-PCGUARD
SanitizerCoveragePCGUARD++4.00c
[+] Instrumented 5 locations with no collisions (hardened mode) of which are 0 handled and 0 unhandled selects.

Create directories to store the test data and output:

mkdir testcases
mkdir findings

And add a sample file the application should be able to read:

cat testcases/readme.txt 
this is a file

Then run AFL. The two @ symbols at the end of the line designate that our program takes a filename as an input parameter:

afl-fuzz -i testcases -o findings  ./readfile @@

Very quickly, you should see that AFL has detected a number of crashes.

If we look in our findings directory, we can see one unique entry. Running our vulnerable application against this file will verify the crash.

ls
id:000000,sig:06,src:000000,time:38,execs:78,op:havoc,rep:8  README.txt

./readfile findings/default/crashes/id\:000000\,sig\:06\,src\:000000\,time\:38\,execs\:78\,op\:havoc\,rep\:8 
Filename is findings/default/crashes/id:000000,sig:06,src:000000,time:38,execs:78,op:havoc,rep:8
*** buffer overflow detected ***: terminated
Aborted

It’s worth noting that the effectiveness of our fuzzing efforts are directly based on how accurate our input data is. For instance, if we patch the vulnerable application to check to see if the input file contains some text before processing the data;

        if (strncmp(text, "BORDERGATE", 10) == 0 ){
            processData(text); 
        }

With our existing input file, fuzzing will fail. Creating a new input file starting with the text “BORDERGATE” would allow AFL to continue finding the overflow vulnerability

Fuzzing Without Source Code

If you compiled AFL using its source code, you should be able to run AFL in QEmu mode to fuzz binary files. This has the added benefit of also being able to test executables built for other platforms (such as ARM).

afl-fuzz -Q -i testcases -o findings  ./readfile @@

Closing Thoughts

AFL is a great fuzzer. But like any fuzzer, it has its constraints. When fuzzing files which implement checksums, the checksum code should be commented out. In addition, fuzzing network protocols requires quite a bit of work as it does not natively support interacting with network sockets.