/ AFL

AFL / WinAFL Tips and Tricks

Hi there

As you all know there are so many tutorials online explaining how to use AFL online, some of them introduce some really cool tricks that helps AFL or WinAFL to find more bugs or to fuzz faster.
The goal of this blog post is to collect these tricks in one location along with refernces to original posts if you need the full infromation, I will also use this as a reference for me when I perform fuzzing and I hope it can help you too :)

Note : You can check my Fuzzer Series in this blog or watch the two videos from Gynvael to know more about genetic fuzzing 1 2

Before Fuzzing

Better Coverage

You can find the original blog post containing the coverage tips Here By Brendan Dolan-Gavitt

Trick 1 : Fixing Instrumentation Problems

Brendan noticed that there are some cases when AFL decides not to instrument some branches as a workaround to fix a problem with instrumentation, however it also affects other platforms (he was using linux), to fix it you can comment the code that skips the instrumentation (in afl-as) or add the following flags when compiling (the flags might affect performance):
-fno-align-labels -fno-align-loops -fno-align-jumps

Trick 2 : Better Clang Instrumentation

Clang instrumentation works at LLVM level, LLVM uses an intermediate language that is less sophisticated than assembly which means it produces less basic blocks than assembly level, so with optimization enabled multiple condtional blocks might be merged into one bigger block, a workaround is to set "AFL_DONT_OPTIMIZE" envrionment variable before compiling your target (also might affect performance).

Trick 3 : Unrolling Constants

Due to the way feedback fuzzing works sometimes the algorithm wouldn't be able to get past "atomic", large-search-space checks such as:

if (strcmp(header.magic_password, "h4ck3d by p1gZ")) goto terminate_now;

So to solve this problem you can instrument your memcmp and strcmp functions for example you can do:

/* Naive instrumented memcmp(). */

inline int my_memcmp(char* ptr1, char* ptr2, int len)
  __attribute__((always_inline));

inline int my_memcmp(char* ptr1, char* ptr2, int len) {

  while (len--) if (*(ptr1++) ^ *(ptr2++)) return 1;
  return 0;

}

#define memcmp my_memcmp

Trick 4 : A Dictionary

Instead of rolling constants you can add a dictionary to help AFL find more branches for example in the previous trick the string h4ck3d by p1gZ was hard to predict so we can add it to our dictionary. To find the strings you can simply run the strings command against the binary executable you are fuzzing, also you can use the script provided by Brendan:\

#!/bin/bash

objdump -d "${1}" | grep -Eo '\$0x[0-9a-f]+' | cut -c 2- | sort -u | while read const; do echo $const | python -c 'import sys, struct; sys.stdout.write("".join(struct.pack("<I" if len(l) <= 11 else "<Q", int(l,0)) for l in sys.stdin.readlines()))' > testcases/$const; done
i=0; strings "${1}"| while read line; do echo -n "$line" > testcases/string_${i} ; i=$[ $i + 1 ] ; done

Minimizing Corpus for Better Performance

You can find the original blog post containing the minimization tipsHere

Tip 1: Remove Unnecessary Test Cases

The tool afl-cmin looks in a folder and runs the test cases then compare the coverage and remove the test cases that doesn't introduce new blocks, to run it you can do:

afl-cmin -i testcases/ -o testcasesmin/ -- ./target @@

Tip 2: Minimize Test Cases

The binary files contains some data that doesn't affect the coverage, luckly for us AFL introduces a tool called afl-tmin which takes a test case file (only one file) and removes the bytes that doesn't affect the coverage, Brandon Perry provided a nice script that minimizes all of your test cases:

# for i in *; do afl-tmin -i $i -o $i.min -- ~/parse; done;
# mkdir ~/testcases && cp *.min ~/testcases

Also I found that this tool is useful to minimize the crashing files to make the crash analysis much easier.

Persistent Mode with __AFL_LOOP()

Afl provides what is known as persistent mode, persistent mode doesn't need to restart the application for each test case, to use it let's assume we have the following code:

int main(int argc, char** argv) {
  Params p = ParseArgs(argc, argv);

  if (argc > 1) {
    std::ifstream fin;
    fin.open(argv[1]);
    parse(fin);
  } else {
    parse(std::cin);
  }

  return 0;
}

To enable persistent mode we use __AFL_LOOP() function as the following:

if (argc > 1) {
  std::ifstream fin;
  fin.open(argv[1]);
  parse(fin);
} else {
  while (__AFL_LOOP(1000)) {
    parse(std::cin);
  }
}

During Fuzzing

Tip 1: Find More Blocks/Edges

In a research by provided by checkpoint where they found 50 adobe CVEs in 50 days, they mentioned that they generated the coverage for the test cases in AFL queue using dynamo rio:

[dynamoriodir]\bin32\drrun.exe -t drcov — harness.exe testcase

Later they used lighthouse which is and IDA Pro plugin that allows yout to visualize coverage, then they analyzed large functions that wasn't covered and looked for test cases that can trigger them which is an awesome trick.

After Crashing

Tip 1: Easier Crash Analysis

You can use crashwalk which has an AFL mode to analyze your crashes easily, in windows you can use BugId.

Tip 2: Minimize Crashing File

In the same way you use afl-tmin to minimize the test cases you can use it to make the crashing file much smaller, afl-tmin automatically detects that the executable is crashing and runs in crashing mode.

At the end I wish you a fruitefull fuzz sessions, and sorry for any typos.

Buy Me a Coffee at ko-fi.com
AFL / WinAFL Tips and Tricks
Share this

Subscribe to Fady's Technical Blog