Skip to content

Latest commit

 

History

History
232 lines (172 loc) · 6.75 KB

File metadata and controls

232 lines (172 loc) · 6.75 KB
title Fuzzing
type technique
tags
binary
exploitation
fuzzing
git-poc
phase exploitation
date_created 2026-05-08
date_updated 2026-05-08
sources
git-raptor

Fuzzing

What it is

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.

Prerequisites

  • 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)

AFL++ Setup

Instrumented compile (preferred)

# 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

Binary-only mode (no source)

# QEMU mode — slower but works without recompiling
afl-fuzz -Q -i seeds/ -o out/ -- ./binary @@

System configuration

# 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 once

Corpus Strategy

Pick 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.

Dictionary-assisted fuzzing

# 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"

Running AFL++

# 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

Timeout Selection

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.


Parallel Fuzzing

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.


Monitoring Progress

# 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

When Stuck (No Crashes)

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 @@

Crash Prioritization

After fuzzing, you'll have multiple crash files. Triage in this order:

1. By signal (most exploitable first):

  1. SIGSEGV at controlled address (0x4141…) — buffer overflow controlling RIP
  2. SIGABRT from malloc/free — heap corruption
  3. SIGSEGV at low address — null ptr deref (usually DoS)
  4. 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 -rn

3. 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).


Duration Profiles

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 Integration Notes

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>&1

Output Structure

out/
├── 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)