AFL++ (American Fuzzy Lop plus plus) is the most widely deployed coverage-guided fuzzer in the security research community. It's a community-maintained fork of Michal Zalewski's original AFL with significant improvements to mutation strategies, instrumentation, scheduling, and performance.
Installing AFL++
sudo apt-get install -y build-essential python3-dev cmake git \
flex bison libglib2.0-dev libpixman-1-dev
git clone https://github.com/AFLplusplus/AFLplusplus
cd AFLplusplus
make distrib
sudo make installInstrumenting Your Target
AFL++ needs coverage feedback from the target binary. Recompile with AFL++'s compiler wrapper, which instruments every branch at compile time:
# With AddressSanitizer (recommended)
AFL_USE_ASAN=1 CC=afl-cc ./configure --disable-shared
make -j$(nproc)
# Or compile a single file directly
afl-cc -o my_parser my_parser.cThe default backend is PCGUARD (based on LLVM SanitizerCoverage). For targets you cannot recompile, QEMU mode (afl-fuzz -Q) instruments binaries dynamically at runtime.
Building a Seed Corpus
Coverage-guided fuzzing needs a starting corpus — a set of valid, small inputs that exercise diverse code paths. Use afl-cmin to remove redundant inputs:
# Remove redundant inputs that cover the same code paths
afl-cmin -i seeds/ -o seeds_min/ -- ./my_parser @@
# Minimize individual seeds for faster fuzzing
afl-tmin -i seeds_min/sample.bin -o seeds_opt/sample.bin -- ./my_parser @@Running AFL++
# Single-core run
afl-fuzz -i seeds/ -o findings/ -- ./my_parser @@
# Multi-core: one main + N-1 secondary instances
afl-fuzz -i seeds/ -o findings/ -M main -- ./my_parser @@
afl-fuzz -i seeds/ -o findings/ -S worker1 -- ./my_parser @@
afl-fuzz -i seeds/ -o findings/ -S worker2 -- ./my_parser @@The @@ placeholder is replaced with each generated input path. Crashes are written to findings/default/crashes/ and the corpus to findings/default/queue/.
Reading the Status Screen
Key metrics to watch on the AFL++ TUI:
- exec speed: executions per second. Under 100/s means you need persistent mode or the target is too slow.
- total paths: unique code paths discovered. Should grow steadily; stagnation means you need a better corpus or dictionary.
- crashes: total crashes found. Each file in the crashes directory is a unique reproducer.
- map density: how full the coverage bitmap is. Over 70% may require a larger map size.
Persistent Mode: 10–100x Faster
Persistent mode is the single biggest performance improvement. Instead of forking a new process for each input, AFL++ reuses the same process across many inputs:
#include "afl-fuzz.h"
int main(int argc, char **argv) {
// One-time initialization here
__AFL_INIT();
while (__AFL_LOOP(1000)) {
ssize_t len = read(0, buf, sizeof(buf));
parse_input(buf, len);
}
return 0;
}Triaging Crashes
# Reproduce with AddressSanitizer output
ASAN_OPTIONS=abort_on_error=1 ./my_parser findings/crashes/id:000001,*
# Or use GDB for targets built without ASan
gdb -ex run -ex bt --args ./my_parser findings/crashes/id:000001,*AddressSanitizer prints a detailed report: error type, allocation site, and full stack trace. AFL++ crash filenames include a signal indicator and an edge hash for first-pass deduplication.
Scaling with Managed Fuzzing
Running AFL++ on a single machine finds bugs, but real campaigns run on dozens or hundreds of cores for days or weeks. The infrastructure overhead — provisioning instances, syncing corpora, collecting and deduplicating crashes — is substantial. Managed fuzzing platforms handle all of that with crash reports delivered to a dashboard in real time.