Offensive Fuzzing
Fuzzer Types
| Type | Coverage | Speed | Tools |
|---|---|---|---|
| BlackBox | Poor | Fast | Peach, Boofuzz |
| GreyBox | Good | Fast | AFL++, Honggfuzz, libFuzzer, WinAFL |
| Snapshot | Good | Fastest | Nyx, wtf, Snapchange |
| WhiteBox | Best | Slow | KLEE, QSYM, SymSan |
| Ensemble | Best | Fast | AFL++ + Honggfuzz + libFuzzer |
GreyBox sub-variants: Directed (AFLGo, UAFuzz), Grammar (AFLSmart, Tlspuffin), Concolic (QSYM, Driller), Kernel (syzkaller, kAFL, wtf).
Core Workflow
Research target → Choose analyses → Build harness → Seed corpus → Instrument → Fuzz → Triage crashes → Report
1. Research Target
- Map all input surfaces (files, network, IPC, syscalls, IOCTL)
- Identify high-value areas: previously patched code, complex parsers, newly added code, input ingestion points
- For kernel modules: look beyond
copy_from_user— DMA-BUF ops, page fault handlers, VM operation structs, allocation callbacks
2. Instrument and Build
bash# AFL++ (preferred for GreyBox) CC=afl-clang-fast CXX=afl-clang-fast++ cmake -DCMAKE_BUILD_TYPE=Release .. && make -j # libFuzzer + ASan/UBSan (C/C++) cmake -DCMAKE_CXX_FLAGS="-fsanitize=fuzzer,address,undefined -O1 -g" .. # CmpLog build for hard compares AFL_LLVM_CMPLOG=1 CC=afl-clang-fast CXX=afl-clang-fast++ make clean all
Windows (MSVC): Project Properties → C/C++ → Address Sanitizer: Yes (/fsanitize=address)
3. Write Harness
libFuzzer (C++):
cpp#include <cstdint> #include <cstddef> extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { parse_or_process(data, size); return 0; }
Honggfuzz HF_ITER (persistent mode — preferred for large targets):
cpp#include "honggfuzz.h" int main(int argc, char** argv) { initialize_target(); // runs once for (;;) { size_t len; uint8_t *buf; HF_ITER(&buf, &len); FILE* s = fmemopen(buf, len, "r"); target_function(s); fclose(s); reset_target_state(); } }
AFL++ persistent mode (__AFL_LOOP):
cppwhile (__AFL_LOOP(10000)) { // re-read input and process }
macOS IPC (Mach message fuzzing):
cvoid *lib_handle = dlopen("libexample.dylib", RTLD_LAZY); pFunction = dlsym(lib_handle, "DesiredFunction");
4. Build Seed Corpus
- Pull from target's test suite, bug reports, and real-world samples
- Web-crawl (Common Crawl) for file formats; filter by MIME type
- Minimize:
afl-cmin -i raw_corpus -o seeds -- ./target @@ - Trim inputs:
afl-tmin -i crash -o crash.min -- ./target @@
5. Launch Fuzzing
AFL++ parallel (primary + secondary with cmplog):
bashafl-fuzz -M f1 -i seeds -o findings -x dict.txt -- ./target @@ afl-fuzz -S s1 -i seeds -o findings -c 0 -- ./target @@
libFuzzer:
bash./target_libfuzzer corpus/ -max_total_time=3600 -workers=4
Binary-only (QEMU):
bashafl-fuzz -Q -i seeds -o findings -- target.exe @@
Snapshot (AFL++ Nyx):
bashNYX_MODE=1 AFL_MAP_SIZE=1048576 afl-fuzz -i seeds -o findings -- ./target_nyx @@
Ensemble (AFL++ + Honggfuzz sharing corpus):
bash# Terminal 1 afl-fuzz -M fuzzer1 -i seeds -o sync_dir -- ./target @@ # Terminal 2 ../honggfuzz/honggfuzz -i sync_dir/fuzzer1/queue -W sync_dir/hfuzz \ --linux_perf_ipt_block -t 10 -- ./target ___FILE___
6. Monitor and Unstick
If progress stalls:
- Enable CmpLog:
-c 0on AFL++ secondaries - Add dictionary:
-x dict.txtorAFL_TOKEN_FILE - Switch to directed fuzzing (AFLGo) targeting specific BBs/functions
- Use concolic assistance (QSYM, Driller) on hard branches
- Snapshot the target to increase exec/s
AFL_MAP_SIZE=1048576,-L 0for MOpt scheduler
7. Triage Crashes
bash# 1. Minimize afl-tmin -i crash -o crash.min -- ./target @@ # 2. Symbolize ASAN_OPTIONS=abort_on_error=1:symbolize=1 ./target crash.min 2>asan.log # 3. Hash + bucket ./cov-tool --bbids ./target crash.min > cov.hash ./bucket.py --key "$(cat cov.hash)" --log asan.log --out triage/
Sanitizer env quick reference:
ASAN_OPTIONS=abort_on_error=1:symbolize=1:detect_stack_use_after_return=1 UBSAN_OPTIONS=print_stacktrace=1:halt_on_error=1 TSAN_OPTIONS=halt_on_error=1:history_size=7 MSAN_OPTIONS=poison_in_dtor=1:track_origins=2
Oracle Selection
| Bug Class | Oracle |
|---|---|
| Memory safety | ASan, HWASan (AArch64, lower overhead) |
| Uninitialized reads | MSan |
| Concurrency | TSan |
| Undefined behavior | UBSan |
| Type safety | TypeSan |
| Heap hardening | Scudo Hardened Allocator |
| Logic bugs | Differential / idempotency oracles |
| Kernel memory | KASAN, KMSAN, KCSAN |
| Kernel UB | KUBSan (CONFIG_UBSAN_TRAP=y) |
| CFI | KCFI (-fsanitize=kcfi, Clang 18) |
| Binary-only | QASAN (QEMU+ASan), DynamoRIO |
Property oracle patterns:
- Idempotency:
f(x) == f(f(x)) - Differential: compare two impls, bucket on output mismatch
- Invariants: monotonic lengths, checksum equality, schema validation post-parse
Specialized Targets
Kernel (Linux) — syzkaller
json{ "target": "linux/arm64", "http": ":56700", "workdir": "/path/to/workdir", "kernel_obj": "/path/to/kernel", "image": "/path/to/rootfs.ext3", "sshkey": "/path/to/id_rsa", "procs": 8, "enable_syscalls": ["openat$module_name", "ioctl$IOCTL_CMD", "mmap"], "type": "qemu", "vm": { "count": 4, "cpu": 2, "mem": 2048 } }
- Limit
enable_syscallsto deepen coverage on specific subsystems - Use
syz-extractto pull constants for custom modules - Enable
CONFIG_KASAN=y,CONFIG_KCFI=y,CONFIG_DEBUG_INFO_BTF=y - Use
kcovfilters andsyz_cover_filterto direct coverage - Network fuzzing: inject via
TUN/TAP+ pseudo-syscalls (syz_emit_ethernet) - Crash decode:
./scripts/decode_stacktrace.sh vmlinux ... < dmesg.log
syzkaller repro:
bashsyz-execprog -repeat=0 -procs=1 -cover=0 -debug target.repro
EDR / Windows Scanning Engines
WTF snapshot harness skeleton (mpengine.dll / mini-filter):
cppg_Backend->SetBreakpoint("nt!KeBugCheck2", [](Backend_t *Backend) { const uint64_t BCode = Backend->GetArg(0); Backend->Stop(Crash_t(fmt::format("crash-{:#x}", BCode))); });
FilterConnectionPort fuzzing:
cppHANDLE hPort; FilterConnectCommunicationPort(L"\\PortName", 0, NULL, 0, NULL, &hPort); FilterSendMessage(hPort, fuzzData, sizeof(fuzzData), NULL, 0, &bytesReturned);
IOCTL fuzzing pattern:
cppHANDLE hDev = CreateFile(L"\\\\.\\DeviceName", GENERIC_READ|GENERIC_WRITE, ...); DeviceIoControl(hDev, ioctlCode, inputBuf, inputLen, outBuf, outLen, &ret, NULL);
- Take snapshots after initialization, right before parse/dispatch loop
- Use IDA Lighthouse for coverage visualization
- Monitor:
DRIVER_VERIFIER_DETECTED_VIOLATION (0xc4),IRQL_NOT_LESS_OR_EQUAL (0xa) - WinDbg:
.symfix; !analyze -v; k; !heap -p -a @rax
Cross-platform mpengine.dll on Linux (loadlibrary + HF_ITER + Intel PT):
cpp// Bypass Lua VM to avoid stability issues insert_function_redirect((void*)luaV_execute_address, my_lua_exec, HOOK_REPLACE_FUNCTION); for (;;) { HF_ITER(&buf, &len); ScanDescriptor.UserPtr = fmemopen(buf, len, "r"); __rsignal(&KernelHandle, RSIG_SCAN_STREAMBUFFER, &ScanParams, sizeof ScanParams); }
Rust
bash# Full Rust fuzzing pipeline cargo test # 1. property tests cargo +nightly miri test # 2. UB via interpreter cargo +nightly careful test # 3. runtime bounds checks cargo fuzz run fuzz_target_1 -- -max_total_time=3600 # 4. libFuzzer crashes RUSTFLAGS="--cfg loom" cargo test --release # 5. concurrency (if needed) cargo fuzz coverage fuzz_target_1 # 6. coverage report
Focus unsafe blocks on: Vec::from_raw_parts, unchecked indexing, transmute size mismatches, pointer arithmetic, FFI integer truncation.
Embedded / Binary-Only
- LibAFL: Modular Rust framework; Unicorn engine, snapshot module, LBRFeedback (zero-instrumentation on Intel), SAND decoupled sanitization
- Retrowrite / QASAN: Binary rewriting for coverage + ASan without source
- Nautilus: Grammar-based fuzzing for structured formats
Language Ecosystems
- Go 1.18+:
go test -fuzz=Fuzz -run=^$ ./... - Python: Atheris (CPython native extension fuzzing)
- Rust:
cargo-fuzzorhonggfuzz-rs - JS engines: Fuzzilli with extended instrumentation (
__builtin_return_address(0)for PC tracking) - Wasm runtimes:
wasmtime-fuzz,waflfor differential fuzzing across V8/Wasmer/Wasmtime - Smart contracts: Echidna, Foundry-fuzz (Solidity); Move-Fuzz (Aptos/Sui)
CI/CD Integration
yaml- name: Build with afl-clang-fast run: CC=afl-clang-fast make -j - name: Fuzz (smoke, 15 min) run: timeout 15m afl-fuzz -i seeds -o findings -- ./target @@ || true - name: Upload crashes if: always() uses: actions/upload-artifact@v4 with: path: findings/**/crashes/*
Use ClusterFuzzLite for persistent continuous fuzzing; cache corpora between runs.
Crash Analysis Quick Reference
Linux:
bashulimit -c unlimited && sysctl -w kernel.core_pattern=core.%e.%p gdb -q ./target core.* -ex 'bt' -ex 'info reg' -ex q addr2line -e ./target 0xDEADBEEF
Windows:
powershell# Enable local dumps New-Item 'HKLM:\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps' -Force # PageHeap gflags /p /enable target.exe /full
Kernel KASAN/KMSAN:
bashdmesg -T | egrep -i 'kasan|kmsan' -A 60 ./scripts/decode_stacktrace.sh vmlinux /lib/modules/$(uname -r)/build < dmesg.log
Reproducibility: pin CPU governor, disable ASLR only where safe, fix RNG seeds, save input sequences in persistent mode, record binary hashes and sanitizer options with every crash.
Tool Index
| Tool | Use Case |
|---|---|
| AFL++ | General GreyBox, CmpLog, MOpt, Nyx |
| Honggfuzz | Intel PT, crash detection, HF_ITER |
| libFuzzer | In-process, source available |
| syzkaller | Linux/Windows kernel syscall fuzzing |
| wtf | Snapshot fuzzing, Windows targets |
| Nyx | AFL++ snapshot mode (Intel PT) |
| Snapchange | AWS snapshot fuzzing |
| LibAFL | Custom Rust fuzzing framework |
| AFLGo | Directed fuzzing to target BB/function |
| kAFL | Kernel + OS fuzzing |
| Jackalope | Binary coverage-guided (Windows/macOS) |
| cargo-fuzz | Rust libFuzzer integration |
| Atheris | Python fuzzing |
| Nautilus | Grammar-based fuzzing |
| AFLTriage | Automated crash triage |
| afl-cov | Coverage analysis for AFL++ |
| ClusterFuzz | Distributed fuzzing infrastructure |

