| title | Fuzzing | ||||
|---|---|---|---|---|---|
| type | technique | ||||
| tags |
|
||||
| phase | exploitation | ||||
| date_created | 2026-05-08 | ||||
| date_updated | 2026-05-08 | ||||
| sources |
|
Fuzzing is coverage-guided dynamic testing that automatically mutates inputs, executes a binary, and detects crashes. It finds bugs that static analysis misses (incorrect runtime assumptions, state-dependent paths, subtle memory math). [[aflplusplus]] is the de facto standard for binary fuzzing; [[libfuzzer]] for in-process library fuzzing.
- Target binary and source (preferred) or binary-only mode via QEMU
- AFL++ installed (
apt install afl++or build from source) - Seed corpus (even 1–2 valid inputs)
# C/C++ with AFL instrumentation + ASAN
CC=afl-clang-fast CXX=afl-clang-fast++ \
CFLAGS="-fsanitize=address -g" CXXFLAGS="-fsanitize=address -g" \
./configure && make
# Or direct compile
afl-clang-fast -fsanitize=address -g -o binary_fuzz source.c# QEMU mode — slower but works without recompiling
afl-fuzz -Q -i seeds/ -o out/ -- ./binary @@# Required on Linux for performance
echo core | sudo tee /proc/sys/kernel/core_pattern
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
sudo afl-system-config # sets all recommended kernel params at oncePick seed strategy based on what the binary processes:
| Binary type | Seed strategy |
|---|---|
| File format parser (PDF, ZIP, PNG…) | 2–5 valid files of that format |
| Text protocol (HTTP, SMTP, FTP) | Valid protocol messages |
| Binary protocol | Raw binary captures (Wireshark) |
| Simple string input | 3–5 short strings exercising different branches |
| Complex parser (JSON, XML, YAML) | Structure-aware seeds + dictionary |
# Minimize corpus — removes redundant seeds before fuzzing
afl-cmin -i raw_seeds/ -o minimized_seeds/ -- ./binary_fuzz @@
# Minimize individual test case
afl-tmin -i crashing_input -o minimized_crash -- ./binary_fuzz @@Seed count: 3–10 seeds is usually optimal. More seeds slow mutation; fewer miss coverage.
# AFL++ ships dicts for common formats
afl-fuzz -x /usr/share/afl/dictionaries/http.dict -i seeds/ -o out/ -- ./binary @@
# Custom dict: one token per line, quoted
# "SELECT"
# "UNION"
# "\x00\x01"# Basic run (stdin mode)
afl-fuzz -i seeds/ -o out/ -- ./binary_fuzz
# File input mode (%% = path to temp file)
afl-fuzz -i seeds/ -o out/ -- ./binary_fuzz @@
# With timeout and memory limit
afl-fuzz -i seeds/ -o out/ -t 2000 -m 512 -- ./binary_fuzz @@
# Parallel — master + secondary instances (one per core)
afl-fuzz -M main -i seeds/ -o out/ -- ./binary_fuzz @@ # terminal 1
afl-fuzz -S slave1 -i seeds/ -o out/ -- ./binary_fuzz @@ # terminal 2
afl-fuzz -S slave2 -i seeds/ -o out/ -- ./binary_fuzz @@ # terminal 3| Binary execution speed | AFL++ timeout flag |
|---|---|
| Fast (< 1ms per run) | -t 100 |
| Normal (1–10ms) | -t 1000 (default) |
| Slow (10–100ms) | -t 5000 |
| Very slow (> 100ms) | -t 30000+ |
If you're not sure, run time ./binary < seeds/seed1 and multiply by 5–10.
| CPU cores | Setup |
|---|---|
| 1 | Single instance, no -M/-S |
| 2–4 | 1 master + (N-1) secondaries |
| 8+ | 1 master + (cores - 1) secondaries; consider splitting targets |
Secondaries use different mutation strategies and share crashes/queue with master automatically.
# Real-time stats (in afl-fuzz terminal)
# Key metrics:
# cycles done — times through full corpus; 1+ = queue explored
# map coverage — % of edges hit; low = poor corpus
# total crashes — unique crashes found
# exec speed — execs/sec; < 100/sec = too slow
# Check coverage across all instances
afl-whatsup out/ # summary of all parallel instances| Problem | Fix |
|---|---|
| Map coverage < 5% | Improve seeds — use valid format examples |
| Exec speed too slow | Reduce timeout; disable ASAN for discovery phase |
| Coverage plateau | Add dictionary (-x) or switch to QEMU + persistent mode |
| Binary needs network/device | Use preeny (desock.so) to intercept socket calls |
# Check what coverage exists (without crashes)
afl-showmap -i seeds/ -o /dev/null -- ./binary_fuzz @@After fuzzing, you'll have multiple crash files. Triage in this order:
1. By signal (most exploitable first):
- SIGSEGV at controlled address (0x4141…) — buffer overflow controlling RIP
- SIGABRT from malloc/free — heap corruption
- SIGSEGV at low address — null ptr deref (usually DoS)
- SIGABRT from assert — logic error (rarely exploitable)
2. Dedup by stack hash:
# Many crashes are the same bug triggered differently
# Group by GDB backtrace hash or ASAN stack trace
for f in out/main/crashes/id:*; do
gdb -batch -ex run -ex bt ./binary_fuzz < "$f" 2>&1 | grep "^#" | md5sum
done | sort | uniq -c | sort -rn3. Minimize before analysis:
afl-tmin -i out/main/crashes/id:000001 -o minimized_001 -- ./binary_fuzz @@4. Analyze top 5–10 unique crashes (see crash-analysis.md for workflow).
| Profile | Duration | Goal |
|---|---|---|
| Quick check | 10 minutes | Verify fuzzing setup works; obvious bugs |
| Bug-finding | 1–4 hours | Find most common bugs in reachable code |
| Thorough | 24+ hours | Deep path coverage; rare condition bugs |
| Continuous | Days/weeks | CI fuzzing; regression detection |
ASAN + fuzzing = best coverage of memory errors. Trade-off: ~2–5× slower execution.
Strategy: Run first discovery phase without ASAN (speed), then re-run interesting inputs with ASAN build (classification).
# Discovery phase — fast, no ASAN
afl-clang-fast -O2 -o binary_fast source.c
afl-fuzz -i seeds/ -o out_fast/ -- ./binary_fast @@
# Classification — replay crashes with ASAN
./binary_asan < out_fast/main/crashes/id:000001 2>&1out/
├── main/ # master instance
│ ├── queue/ # mutated inputs that increase coverage
│ ├── crashes/ # unique crashing inputs
│ │ └── id:000001,sig:11,...
│ ├── hangs/ # timeout-triggering inputs
│ └── fuzzer_stats # stats file
└── slave1/ # secondary instance (same structure)