This guide walks you through fuzzing a target with source code available. For binary-only targets, see the Binary-only fuzzing documentation.
Before you start, be aware of common sense risks of fuzzing:
- Your CPU will run hot and needs adequate cooling
- Targets may grab gigabytes of memory or fill disk space
- Heavy I/O can reduce SSD/HDD lifespan - consider using
AFL_TMPDIR with a RAM-disk
- Don’t fuzz on systems where data loss is unacceptable
Overview
Fuzzing with AFL++ involves three steps:
- Instrument the target - Compile with AFL++ compilers to add instrumentation
- Prepare seeds - Collect and optimize initial input corpus
- Run afl-fuzz - Execute the fuzzer to find bugs
Step 1: Instrument your target
Select the AFL++ compiler
Choose the best compiler for your setup:
LTO mode (best)
If you have clang/clang++ 11 or newer:CC=afl-clang-lto CXX=afl-clang-lto++
See LTO mode documentation for details. LLVM mode (good)
If you have clang/clang++ 3.8 or newer:CC=afl-clang-fast CXX=afl-clang-fast++
See LLVM mode documentation for details. GCC_PLUGIN mode (fallback)
It is highly recommended to have the newest LLVM version possible installed. Anything below LLVM 9 is not recommended.
Compile the target
For configure-based build systems:
CC=afl-clang-fast CXX=afl-clang-fast++ ./configure --disable-shared
make clean all
For CMake-based projects:
mkdir build && cd build
cmake -DCMAKE_C_COMPILER=afl-cc -DCMAKE_CXX_COMPILER=afl-c++ ..
make
For Meson build system:
CC=afl-cc CXX=afl-c++ meson
Important compilation tips:
- Always compile libraries as static, not shared (use
--disable-shared)
- If the build system complains about stderr output, set
export AFL_QUIET=1
- If configure fails with AFL++ compiler, set
export AFL_NOOPT=1 during configure only
- Disable
-Werror if the build system treats warnings as errors
Advanced: Enable CMPLOG (recommended)
CMPLOG helps solve complex comparisons and magic bytes:
# Compile a CMPLOG-instrumented binary
AFL_LLVM_CMPLOG=1 afl-clang-fast target.c -o target-cmplog
# Compile the regular fuzzing binary
afl-clang-fast target.c -o target
You’ll use the CMPLOG binary with the -c flag during fuzzing.
Advanced: Enable LAF-INTEL (alternative)
LAF-INTEL splits complex comparisons:
export AFL_LLVM_LAF_ALL=1
afl-clang-fast target.c -o target
CMPLOG is usually more effective than LAF-INTEL. Use LAF-INTEL when targets heavily transform input before comparison.
Gather valid input files for your target:
- Example files from the target’s test suite
- Sample files from bug reports
- Files downloaded from the internet
- Files generated by running the target normally
AFL++ includes example seed files in the testcases/ directory for common formats.
Create seeds directory
mkdir seeds
cp /path/to/valid/inputs/* seeds/
Optional: Minimize the corpus
Remove redundant inputs that don’t provide new coverage:
# If target reads from a file
afl-cmin -i seeds -o seeds_unique -- ./target @@
# If target reads from stdin
afl-cmin -i seeds -o seeds_unique -- ./target
The @@ symbol is replaced by AFL++ with the path to the test input file.
Optional: Minimize individual files
Shorter inputs fuzz faster:
mkdir input
cd seeds_unique
for i in *; do
afl-tmin -i "$i" -o "../input/$i" -- /path/to/target @@
done
This step is optional but can improve fuzzing efficiency.
Step 3: Run afl-fuzz
System configuration
Before fuzzing, configure your system for optimal performance:
This command is required before the first fuzzing session after each reboot. If you can’t run it with root privileges, set export AFL_SKIP_CPUFREQ=1 to skip the check.
For permanent boot-time optimizations:
sudo afl-persistent-config
These optimizations improve fuzzing performance but decrease system security. Use strong firewall rules and only expose SSH if you apply these settings.
Basic fuzzing
Start fuzzing with the minimal configuration:
afl-fuzz -i seeds -o output -- ./target @@
Breaking this down:
-i seeds: Input directory with seed files
-o output: Output directory (created automatically)
--: Separates afl-fuzz options from target command
./target: Your instrumented binary
@@: Placeholder for input filename (omit if target reads from stdin)
With CMPLOG
If you compiled a CMPLOG binary:
afl-fuzz -i seeds -o output -c ./target-cmplog -- ./target @@
With a dictionary
Dictionaries help with verbose formats (SQL, HTTP, etc.):
afl-fuzz -i seeds -o output -x /path/to/dictionary.txt -- ./target @@
AFL++ includes dictionaries for common formats in dictionaries/ directory.
Setting memory limits
Limit memory usage to prevent OOM and find malloc failures:
afl-fuzz -i seeds -o output -m 512 -- ./target @@
This sets a 512MB memory limit. Adjust based on your target’s needs.
Full example with recommended options
afl-fuzz -i seeds -o output \
-c ./target-cmplog \
-x dictionaries/sql.dict \
-m 512 \
-- ./target @@
Understanding the UI
When afl-fuzz runs, you’ll see a status screen:
┌─────────────────────────────────────────────────────────┐
│ process timing │ overall results │ cycle progress │
│ last new find │ total crashes │ map coverage │
│ total execs │ saved crashes │ stability │
│ exec speed │ total tmouts │ strategy info │
└─────────────────────────────────────────────────────────┘
Key metrics:
- last new find: Time since last new path was discovered
- total crashes: Number of unique crashes found
- exec speed: Executions per second (higher is better)
- map coverage: Percentage of instrumentation points hit
Analyzing results
Finding crashes
Crashes are saved in the output directory:
ls output/default/crashes/
Each crash file is named with metadata: id:NNNNNN,sig:NN,src:NNNNNN,...
Reproducing crashes
Test a crash manually:
# If target reads from stdin
cat output/default/crashes/id:000000* | ./target
# If target reads from a file
./target output/default/crashes/id:000000*
Debugging crashes
Use GDB to investigate:
gdb ./target
(gdb) run < output/default/crashes/id:000000*
Or generate core dumps:
ulimit -c unlimited
./target < output/default/crashes/id:000000*
gdb ./target core
Finding hangs
Inputs that cause timeouts are saved in:
Stopping and resuming
Stop fuzzing
Press Ctrl+C or send SIGINT to afl-fuzz.
Resume fuzzing
Reuse the same command but replace -i seeds with -i -:
afl-fuzz -i - -o output -- ./target @@
Alternatively, set AFL_AUTORESUME=1 to auto-resume:
export AFL_AUTORESUME=1
afl-fuzz -i seeds -o output -- ./target @@
Multi-core fuzzing
If you want to fuzz seriously, use multiple CPU cores. A single instance is just for hobby fuzzing!
Start the main fuzzer
afl-fuzz -i seeds -o output -M main -- ./target @@
Start secondary fuzzers
In separate terminals (or screen/tmux sessions):
afl-fuzz -i seeds -o output -S secondary1 -- ./target @@
afl-fuzz -i seeds -o output -S secondary2 -- ./target @@
afl-fuzz -i seeds -o output -S secondary3 -- ./target @@
Recommended variations for secondaries:
- One with CMPLOG:
-c target-cmplog -l 2AT
- One with sanitizers (ASAN/UBSAN)
- Different power schedules:
-p fast, -p explore, -p exploit
- Some with MOpt:
-L 0
- Some with old queue cycling:
-Z
Check fuzzing status
Monitor all instances:
afl-whatsup output/
# Or just the summary
afl-whatsup -s output/
Next steps
This quickstart covers the basics. For professional fuzzing campaigns, read the complete fuzzing in depth guide.
To improve your fuzzing:
- Use persistent mode for 2-20x speed improvement
- Use libfuzzer harnesses with
LLVMFuzzerTestOneInput()
- Implement custom mutators for domain-specific formats
- Enable sanitizers to find non-crashing bugs
- Read about best practices for network services and GUI programs
Resources: