fuzzer
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFuzzer 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 skill before writing any harness.
Do not skip this step even if you think you already understand the code.
audit-context-buildingGoal: 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:
- or
size_t → intnarrowing castssize_t → uint32_t - Unchecked length arithmetic (additions, multiplications on sizes)
- Public API functions that accept attacker-controlled / length parameters
size_t
Output: a ranked list of suspicious locations with drawn from the full codebase.
Pick the top candidate for the harness.
file:line**在编写任何测试套之前,你必须调用技能。**即便你自认为已经理解代码,也不能跳过这一步。
audit-context-building目标:通读整个代码库——所有源文件——再进行风险排序。不要在某个目录中发现第一个可疑位置就停止,要覆盖所有模块。
重点排查:
- 或
size_t → int的窄化转换size_t → uint32_t - 未做检查的长度运算(针对大小的加法、乘法操作)
- 接收攻击者可控的/长度参数的公共API函数
size_t
输出:从整个代码库中提取的带有信息的可疑位置排名列表。选择排名第一的候选作为测试套的目标。
file:lineStep 2 — Write the Harness
步骤2 — 编写测试套(Harness)
You MUST target the exact 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.
file:line- 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 ( accumulation, unchecked addition on sizes):
The bug is in the arithmetic — not the buffer contents. Extract a 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.
size_t→intuint32_tc
#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的漏洞类别选择模板:
算术溢出( 累加、未检查的大小加法):
漏洞出现在运算逻辑中——而非缓冲区内容。从模糊测试字节中提取类型的声明大小,并直接传入。使用1字节的静态存根作为数据指针。绝不要根据传入的实际缓冲区推导大小。
size_t→intuint32_tc
#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/nullIf not found, install it:
bash
brew install afl++ # macOS
apt-get install afl++ # Debian/UbuntuC / 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/UbuntuC / C++ 结合AFL++:
bash
AFL=$(which afl-clang-fast)
AFLDRIVER=$(dirname $(dirname $AFL))/lib/afl/libAFLDriver.aCompile 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
-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
-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
-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
-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/fuzzerRust:
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/fuzzerRust:
bash
cargo fuzz run <target_name> corpus/Go:
bash
go test -fuzz=FuzzTarget -fuzztime=12h ./...Step 5 — Report
步骤5 — 报告结果
When contains files, report:
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>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
测试套编写规则
| Rule | Why |
|---|---|
| Return 0 always | Never abort from the harness itself |
Never call | Kills the fuzzer process |
| Handle all input sizes | Fuzzer generates empty / tiny / huge inputs |
| Be fast — no logging | Target 100–1000+ exec/sec |
| Same input = same output | Determinism required for crash reproduction |
| Free all resources each call | Prevents memory exhaustion over millions of runs |
| Reset global state | Isolates each iteration |
| 规则 | 原因 |
|---|---|
| 始终返回0 | 绝不能从测试套本身触发终止 |
绝不调用 | 会终止模糊测试进程 |
| 处理所有输入大小 | 模糊测试工具会生成空/极小/极大的输入 |
| 保持高效 — 禁止日志 | 目标是达到100–1000+次执行/秒 |
| 相同输入产生相同输出 | 崩溃复现需要确定性 |
| 每次调用后释放所有资源 | 避免数百万次运行后出现内存耗尽 |
| 重置全局状态 | 隔离每次测试迭代 |
Tool Selection
工具选择
| Target | Fuzzer | Notes |
|---|---|---|
| C / C++ | AFL++ ( | Best coverage instrumentation |
| Rust | | Uses libFuzzer API under the hood |
| Go | | Native, no extra tooling |
| Any binary | AFL++ black-box ( | No source needed |
| Custom / research | LibAFL | Modular Rust fuzzing library |
| 目标语言 | 模糊测试工具 | 说明 |
|---|---|---|
| C / C++ | AFL++ ( | 覆盖插装效果最佳 |
| Rust | | 底层基于LibFuzzer API |
| Go | | 原生支持,无需额外工具 |
| 任意二进制文件 | AFL++ 黑盒模式 ( | 无需源代码 |
| 自定义/研究用途 | LibAFL | 模块化Rust模糊测试库 |