fuzzer

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Fuzzer Skill

Fuzzer 技能

When to Use

使用场景

Invoke when asked to fuzz a target, find memory/integer bugs via fuzzing, write a fuzz harness, or run a fuzzing campaign on a codebase.

当需要对目标进行模糊测试、通过模糊测试发现内存/整数类漏洞、编写模糊测试套(Harness),或是对代码库开展模糊测试任务时,调用本技能。

Workflow

工作流

Step 1 — Audit (MANDATORY — always run first, no exceptions)

步骤1 — 审计(强制要求 — 必须首先执行,无例外)

You MUST invoke the
audit-context-building
skill before writing any harness.
Do not skip this step even if you think you already understand the code.
Goal: read the entire codebase — all source files — before ranking anything. Do not stop after finding the first suspicious location in one directory. Cover all modules.
Look specifically for:
  • size_t → int
    or
    size_t → uint32_t
    narrowing casts
  • Unchecked length arithmetic (additions, multiplications on sizes)
  • Public API functions that accept attacker-controlled
    size_t
    / length parameters
Output: a ranked list of suspicious locations with
file:line
drawn from the full codebase. Pick the top candidate for the harness.

**在编写任何测试套之前,你必须调用
audit-context-building
技能。**即便你自认为已经理解代码,也不能跳过这一步。
目标:通读整个代码库——所有源文件——再进行风险排序。不要在某个目录中发现第一个可疑位置就停止,要覆盖所有模块。
重点排查:
  • size_t → int
    size_t → uint32_t
    的窄化转换
  • 未做检查的长度运算(针对大小的加法、乘法操作)
  • 接收攻击者可控的
    size_t
    /长度参数的公共API函数
输出:从整个代码库中提取的带有
file:line
信息的可疑位置排名列表。选择排名第一的候选作为测试套的目标。

Step 2 — Write the Harness

步骤2 — 编写测试套(Harness)

You MUST target the exact
file:line
ranked #1 by the audit. Do not target a different function, code path, or "more interesting" area based on your own judgment. The audit decides the target. You implement it.
  • Read the call path the audit provided
  • Call the exact function in that call path
  • Use the bug class it identified to pick the pattern below
Pick the pattern based on the bug class from Step 1.
Arithmetic overflow (
size_t→int
accumulation, unchecked addition on sizes): The bug is in the arithmetic — not the buffer contents. Extract a
uint32_t
claimed size from fuzz bytes and pass it directly. Use a 1-byte static stub as the data pointer. Never derive the size from the actual buffer you hand over.
c
#include <stddef.h>
#include <stdint.h>
#include <string.h>

static const uint8_t kStub[1] = {0};

/* Required by libAFLDriver.a — must be present or link fails */
int LLVMFuzzerInitialize(int *argc, char ***argv) {
    (void)argc; (void)argv;
    return 0;
}
void LLVMFuzzerCleanup(void) {}

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    uint8_t n = data[0] % MAX_CALLS;
    if (n == 0 || size < 1 + (size_t)n * 4) return 0;

    TargetState *s = TargetState_Create();
    for (uint8_t i = 0; i < n; i++) {
        uint32_t claimed;
        memcpy(&claimed, data + 1 + i * 4, 4);
        target_fn(s, (size_t)claimed, kStub);  /* claimed drives the arithmetic */
    }
    TargetState_Destroy(s);
    return 0;
}
Split-input (API takes a config/size parameter + a data payload):
c
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < N) return 0;
    /* bytes [0..N-1] → config / mode / count */
    /* bytes [N..]    → payload */
    target_fn(config, data + N, size - N);
    return 0;
}
Direct call (simple buffer + length):
c
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < 1) return 0;
    target_fn(data, size);
    return 0;
}
Rust:
rust
fuzz_target!(|data: &[u8]| { target_function(data); });
Go:
go
func FuzzTarget(f *testing.F) {
    f.Fuzz(func(t *testing.T, data []byte) { target_function(data) })
}

你必须以审计排名第一的
file:line
位置为目标。不要根据个人判断选择不同的函数、代码路径或“更有趣”的区域。审计结果决定目标,你只需实现它。
  • 阅读审计提供的调用路径
  • 调用该路径中的具体函数
  • 根据步骤1中识别的漏洞类别,选择以下对应的代码模板
根据步骤1的漏洞类别选择模板:
算术溢出
size_t→int
累加、未检查的大小加法): 漏洞出现在运算逻辑中——而非缓冲区内容。从模糊测试字节中提取
uint32_t
类型的声明大小,并直接传入。使用1字节的静态存根作为数据指针。绝不要根据传入的实际缓冲区推导大小。
c
#include <stddef.h>
#include <stdint.h>
#include <string.h>

static const uint8_t kStub[1] = {0};

/* Required by libAFLDriver.a — must be present or link fails */
int LLVMFuzzerInitialize(int *argc, char ***argv) {
    (void)argc; (void)argv;
    return 0;
}
void LLVMFuzzerCleanup(void) {}

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    uint8_t n = data[0] % MAX_CALLS;
    if (n == 0 || size < 1 + (size_t)n * 4) return 0;

    TargetState *s = TargetState_Create();
    for (uint8_t i = 0; i < n; i++) {
        uint32_t claimed;
        memcpy(&claimed, data + 1 + i * 4, 4);
        target_fn(s, (size_t)claimed, kStub);  /* claimed drives the arithmetic */
    }
    TargetState_Destroy(s);
    return 0;
}
拆分输入(API同时接收配置/大小参数和数据载荷):
c
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < N) return 0;
    /* bytes [0..N-1] → config / mode / count */
    /* bytes [N..]    → payload */
    target_fn(config, data + N, size - N);
    return 0;
}
直接调用(简单缓冲区+长度):
c
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < 1) return 0;
    target_fn(data, size);
    return 0;
}
Rust:
rust
fuzz_target!(|data: &[u8]| { target_function(data); });
Go:
go
func FuzzTarget(f *testing.F) {
    f.Fuzz(func(t *testing.T, data []byte) { target_function(data) })
}

Step 3 — Build

步骤3 — 构建项目

Before building, locate AFL++:
bash
which afl-clang-fast || find /usr/local /opt/homebrew /tmp -name afl-clang-fast 2>/dev/null
If not found, install it:
bash
brew install afl++       # macOS
apt-get install afl++    # Debian/Ubuntu
C / C++ with AFL++:
bash
AFL=$(which afl-clang-fast)
AFLDRIVER=$(dirname $(dirname $AFL))/lib/afl/libAFLDriver.a
构建前,先定位AFL++:
bash
which afl-clang-fast || find /usr/local /opt/homebrew /tmp -name afl-clang-fast 2>/dev/null
如果未找到,执行安装:
bash
brew install afl++       # macOS
apt-get install afl++    # Debian/Ubuntu
C / C++ 结合AFL++:
bash
AFL=$(which afl-clang-fast)
AFLDRIVER=$(dirname $(dirname $AFL))/lib/afl/libAFLDriver.a

Compile target sources into instrumented static library

Compile target sources into instrumented static library

mkdir -p build find <src-dir> -name ".c" | while read f; do $AFL
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
<include-flags> -c "$f" -o "build/$(basename $f .c).o" done ar rcs build/libtarget.a build/
.o
mkdir -p build find <src-dir> -name ".c" | while read f; do $AFL
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
<include-flags> -c "$f" -o "build/$(basename $f .c).o" done ar rcs build/libtarget.a build/
.o

Compile harness + link

Compile harness + link

$AFL
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
harness.c build/libtarget.a $AFLDRIVER
-o build/fuzzer

**Rust:**
```bash
cargo fuzz build <target_name>

$AFL
-g -O1 -fsanitize=address,undefined -fno-omit-frame-pointer
harness.c build/libtarget.a $AFLDRIVER
-o build/fuzzer

**Rust:**
```bash
cargo fuzz build <target_name>

Step 4 — Run

步骤4 — 运行模糊测试

C / C++ with AFL++:
bash
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:symbolize=0 \
AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \
$(which afl-fuzz) \
  -i corpus/ \
  -o findings/ \
  -- build/fuzzer
Rust:
bash
cargo fuzz run <target_name> corpus/
Go:
bash
go test -fuzz=FuzzTarget -fuzztime=12h ./...

C / C++ 结合AFL++:
bash
ASAN_OPTIONS=abort_on_error=1:detect_leaks=0:symbolize=0 \
AFL_SKIP_CPUFREQ=1 AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1 \
$(which afl-fuzz) \
  -i corpus/ \
  -o findings/ \
  -- build/fuzzer
Rust:
bash
cargo fuzz run <target_name> corpus/
Go:
bash
go test -fuzz=FuzzTarget -fuzztime=12h ./...

Step 5 — Report

步骤5 — 报告结果

When
findings/default/crashes/
contains files, report:
CRASH FOUND
  Input:   findings/default/crashes/id:000000,...
  Signal:  sig:06 (SIGABRT = sanitizer) or sig:11 (SIGSEGV)

Reproduce (with symbolized output):
  UBSAN_OPTIONS=print_stacktrace=1 \
  ASAN_OPTIONS=detect_leaks=0:print_stacktrace=1 \
  ./build/fuzzer findings/default/crashes/id:000000,...

Sanitizer output:
  <paste UBSan/ASan stacktrace here>

Root cause: <file>:<line> — <one sentence description>
If no crashes after the time budget: report paths found and unique inputs in corpus.

findings/default/crashes/
目录下存在文件时,按以下格式报告:
CRASH FOUND
  Input:   findings/default/crashes/id:000000,...
  Signal:  sig:06 (SIGABRT = sanitizer) or sig:11 (SIGSEGV)

Reproduce (with symbolized output):
  UBSAN_OPTIONS=print_stacktrace=1 \
  ASAN_OPTIONS=detect_leaks=0:print_stacktrace=1 \
  ./build/fuzzer findings/default/crashes/id:000000,...

Sanitizer output:
  <paste UBSan/ASan stacktrace here>

Root cause: <file>:<line> — <one sentence description>
若在预算时间内未发现崩溃:报告已发现的代码路径以及语料库中的唯一输入。

Harness Rules

测试套编写规则

RuleWhy
Return 0 alwaysNever abort from the harness itself
Never call
exit()
Kills the fuzzer process
Handle all input sizesFuzzer generates empty / tiny / huge inputs
Be fast — no loggingTarget 100–1000+ exec/sec
Same input = same outputDeterminism required for crash reproduction
Free all resources each callPrevents memory exhaustion over millions of runs
Reset global stateIsolates each iteration

规则原因
始终返回0绝不能从测试套本身触发终止
绝不调用
exit()
会终止模糊测试进程
处理所有输入大小模糊测试工具会生成空/极小/极大的输入
保持高效 — 禁止日志目标是达到100–1000+次执行/秒
相同输入产生相同输出崩溃复现需要确定性
每次调用后释放所有资源避免数百万次运行后出现内存耗尽
重置全局状态隔离每次测试迭代

Tool Selection

工具选择

TargetFuzzerNotes
C / C++AFL++ (
afl-clang-fast
)
Best coverage instrumentation
Rust
cargo-fuzz
Uses libFuzzer API under the hood
Go
go test -fuzz
Native, no extra tooling
Any binaryAFL++ black-box (
afl-fuzz @@
)
No source needed
Custom / researchLibAFLModular Rust fuzzing library
目标语言模糊测试工具说明
C / C++AFL++ (
afl-clang-fast
)
覆盖插装效果最佳
Rust
cargo-fuzz
底层基于LibFuzzer API
Go
go test -fuzz
原生支持,无需额外工具
任意二进制文件AFL++ 黑盒模式 (
afl-fuzz @@
)
无需源代码
自定义/研究用途LibAFL模块化Rust模糊测试库