SuperAPI AI Directory

re-fuzzing

Testing & quality

View repository

Updated 10-15 21:34

About this skill

Coverage-guided fuzzing (AFL++ / libFuzzer / honggfuzz)

When to use / when not to use

  • Use: the target has a clear input surface (file parsing / library functions / network protocol parsing), and you want to automatically find crashes; source code is available for instrumentation (or no source code but you are willing to use QEMU mode); continuous regression is needed to find new bugs
  • Do not use: the target has no input parsing (pure algorithm/pure logic → symbolic execution [[re-angr]] / [[re-z3]]); there is already a crash and it only needs analysis (→ [[re-crash-triage]]); the target is a kernel/driver (follow the [[re-kernel]] domain approach, not within the scope of user-space fuzzing)
  • Do not use: there is only a single crash sample to reproduce (go directly to [[re-crash-triage]], no need to rerun fuzzing)

Tool preparation

Verify all tools before use. Fuzzing is dynamic execution; always run it inside a sandbox ([[re-analyze/platform-tips]] highest principle, see [[re-sandbox]]).

AFL++ —— the mainstay of coverage-guided fuzzing

  • Debian/Ubuntu: sudo apt install afl++
  • Fedora: sudo dnf install american-fuzzy-lop american-fuzzy-lop-clang(the package is the AFL++ fork); or compile from source to get the latest version (see below)
  • Arch: sudo pacman -S afl++
  • macOS: brew install afl++
  • Windows: inside WSL2 sudo apt install afl++(AFL++ officially supports WSL; cannot run directly on native Windows)
  • Compile from source (recommended, latest version, includes all modes):
    git clone https://github.com/AFLplusplus/AFLplusplus && cd AFLplusplus
    make distrib        # 需要 clang/LLVM 工具链
    sudo make install
    
  • Verify: afl-fuzz -h(printing usage means OK);afl-clang-fast --version

clang / LLVM —— libFuzzer host + ASAN compiler

  • Debian/Ubuntu: sudo apt install clang
  • Fedora/RHEL: sudo dnf install clang
  • Arch: sudo pacman -S clang
  • macOS: bundled with Xcode (xcode-select --install install command line tools), or brew install llvm
  • Windows: the Linux version inside WSL2; the clang-cl from native Visual Studio also works (MSVC supports /fsanitize=fuzzer)
  • Verify: clang --version

libFuzzer —— library function / single-function fuzzing (bundled with clang)

  • No separate installation needed: bundled with clang,-fsanitize=fuzzer enables it
  • Verify:
    clang -fsanitize=fuzzer -x c /dev/null -o /tmp/fztest && /tmp/fztest -runs=1
    
    output contains INFO: libFuzzer means OK (/tmp/fztest can be deleted after use)

honggfuzz —— hardware counters / multi-threaded fuzzing alternative

  • Debian/Ubuntu: no official package → compile from source:
    sudo apt install build-essential binutils-dev libunwind-dev libblocksruntime-dev clang
    git clone https://github.com/google/honggfuzz && cd honggfuzz && make
    
  • Fedora/RHEL: sudo dnf install honggfuzz
  • Arch: sudo pacman -S honggfuzz(or AUR package honggfuzz)
  • macOS: no brew formula → compile from source (requires Xcode + libblocksruntime)
  • Windows: the Linux version inside WSL2
  • Verify: honggfuzz --help

afl-cov —— coverage statistics (gcov/lcov frontend)

  • Depends on lcov/gcov: sudo apt install lcov / sudo dnf install lcov / sudo pacman -S lcov(gcov is bundled with gcc)
  • No pip package for the tool itself; git clone and run directly:
    git clone https://github.com/mrash/afl-cov && cd afl-cov
    
  • Verify: ./afl-cov -V
  • Note: afl-cov requires the target to be compiled with gcov instrumentation (gcc -fprofile-arcs -ftest-coverage), which is incompatible with AFL instrumentation——compile the same code twice: once an afl+ASAN version to run fuzzing, once a gcov version to measure coverage

Procedure

Execute in order; record the path of each step's artifacts (harness source code, corpus directory, fuzz output directory) for reference by [[re-crash-triage]] / reports.

  1. Target and instrumentation selection:
    • Source code compilable → AFL++ compiler wrapper + ASAN:
      CC=afl-clang-fast CXX=afl-clang-fast++ AFL_USE_ASAN=1 ./configure && make
      # 简单单文件目标:
      afl-clang-fast -fsanitize=address -g -O1 -o target target.c
      
    • No source code → -Q QEMU mode (afl-fuzz -Q ...), no instrumentation needed but 2-5 times slower
    • Target is a library function → write a fuzz target (harness) that reads a file and feeds it to the parsing API:
      /* fuzz_target.c —— 以 libpng 的 png_read 入口为例 */
      #include <stdint.h>
      extern int my_parse(const char *data, size_t len); /* 目标解析入口 */
      int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
        my_parse((const char *)data, size);
        return 0;
      }
      
      Compile with libFuzzer:clang -fsanitize=fuzzer,address -g -O1 -o fuzz_target fuzz_target.c target_lib.c Compile the same harness with AFL++:afl-clang-fast -fsanitize=fuzzer,address -o fuzz_target_afl fuzz_target.c(afl-clang-fast intercepts -fsanitize=fuzzer and links the bundled driver), run afl-fuzz -i in -o out -- ./fuzz_target_afl @@
    • Network target → see pitfall 5: the harness calls the parsing function directly (data comes from a file), do not fuzz the entire service process
  2. Corpus initialization:
    mkdir in out
    # 每个种子是目标可正常解析的最小有效输入(别拿空文件起步)
    cp seed_sample.bin in/
    afl-cmin -i in -o in_min -- ./target @@   # 去重:只留覆盖不同路径的种子
    
    • Corpus quality matters more than quantity: 10 small samples covering different parsing branches > 1000 large files of the same kind (see pitfall 2)
    • Starting from an empty corpus / pure empty file: the first few thousand execs are all in the EOF branch, and the parsing logic is not covered for a long time
  3. afl-fuzz run parameters:
    afl-fuzz -i in_min -o out -m none -t 1000+ -x dict.txt -- ./target @@
    
    • -m none: the default memory limit kills ASAN programs by mistake, must add (see pitfall 4)
    • -t 1000+: give a sufficient initial timeout (+ means it adapts as the path grows)
    • @@ placeholder = the file path generated by fuzz; if input goes through stdin, remove @@
    • Multi-core multi-instance:-M main main instance + -S secondary1 and other secondary instances (sharing the same out directory);afl-whatsup out to see the summary
    • Resume:afl-fuzz -i - -o out ...(-i - reuse the existing output directory)
    • Run duration: only 24h+ is statistically meaningful, don't stop early; after a crash appears do not interrupt, analyze separately ([[re-crash-triage]] can use afl-tmin to work on out/crashes)
  4. Coverage (afl-cov):
    • Real-time viewing during the run:afl-whatsup out——paths_total growth + execs_done high = normal
    • gcov-instrumented target (compile separately from the fuzz version):
      gcc -fprofile-arcs -ftest-coverage -g -O0 -o target_gcov target.c
      
    • Summary:
      ./afl-cov -d out --live --coverage-cmd "cat AFL_FILE | ./target_gcov" --code-dir .
      
      Output is in out/cov/, HTML report out/cov/web/(generated by lcov/genhtml)
    • Alternative without gcov:afl-showmap -o /tmp/map -- ./target @@ see which blocks a single input covers; compare the maps of two inputs to determine whether a new path is covered
  5. Dictionary / structured input:
    • Dictionary -x dict.txt: keywords (magic numbers, delimiters, keywords) reduce the cost of cross-format mutation; AFL++ ships with many format dictionaries (AFLplusplus/dictionaries/ contains png/pdf/jpeg/tiff etc., directly -x reference)
    • Structured input (header + length + data type formats): the initial seeds must cover different structural variants; give common values for length fields in the dictionary; for advanced use, use a custom mutator (AFL++ AFL_CUSTOM_MUTATOR_LIBRARY, Python example in utils/python_mutators/), maintaining checksum/length consistency

Cross-domain combination

  • [[re-vuln]]: step 2 of this gateway workflow always calls this skill (the foundational atom for vulnerability discovery domain coverage)
  • [[re-crash-triage]]: the crash inputs produced by this skill (out/crashes/) are handed to it for analysis; minimization/deduplication (cmin/tmin) is done within its flow
  • [[re-ctf]]: CTF pwn / challenge binary fuzzing references this skill
  • [[re-binary-core]]: before writing a harness, use decompilation to confirm the parsing entry point and API semantics ([[re-ghidra]] / [[re-ida]])
  • [[re-netcap]]: seed corpus source for network target fuzzing (real traffic packet capture)
  • [[re-sandbox]] / [[re-analyze/platform-tips]]: fuzzing is dynamic execution, by default run inside a sandbox

Common pitfalls and traps

  • No instrumentation, no coverage: Symptom —afl-fuzz execs skyrocket when running but the paths count doesn't move and it never crashes; Cause — the target isn't instrumented (release binary / compiler isn't an afl-* wrapper / @@ wrong arguments passed, fuzzing an unrelated program); Countermeasure — before starting afl-showmap -o /tmp/map -- ./target @@ verify that coverage changes;afl-fuzz the startup output will warn about missing instrumentation; if there's no source code, switch to -Q QEMU mode
  • Corpus size imbalance: Symptom — slow startup, low exec rate per round, or early on completely failing to reach the parsing logic; Cause — too many and too large seeds (queue bloat) or all empty files/the same format variant; Countermeasure —afl-cmin deduplicate down to a few dozen minimal samples covering different branches; keep one representative for each different format variant
  • Crash deduplication (cmin/tmin): Symptom —out/crashes/ piling up tens of thousands of files that are actually the same bug; Cause — the fuzzer saves every triggering input to disk without distinguishing by crash point; Countermeasure — use afl-tmin to minimize one by one (afl-tmin -i crash -o crash.min -- ./target @@), then merge duplicates by ASAN error type + backtrace stack (see [[re-crash-triage]])
  • sanitizer misconfiguration: Symptom — real crashes aren't reported (missed detection) or the fuzz process gets killed by mistake (false crash); Cause — ASAN not enabled (missed AFL_USE_ASAN=1 / -fsanitize=address)、-m none not set causing ASAN to be killed by the memory limit, ASAN conflicting with an old glibc version; Countermeasure — unify AFL_USE_ASAN=1 + -m none;-t set generously; first manually run a known crashing input to confirm ASAN can report it
  • Network targets need a harness: Symptom — fuzzing a network service binary, input comes from a socket, and@@ files simply can't be fed into the parsing logic, coverage stays at 0 forever; Cause — the network program reads an fd, not a file, so the fuzz channel is misaligned; Countermeasure — write a harness that feeds the file contents directly to the parsing function (what's being fuzzed is the parser, not the network stack); for real traffic, use [[re-netcap]] to capture packets as seeds