mcpField
L
libafl

libafl

github.com/trailofbits测试与质量7.9k5.2k 次安装今天更新
代码仓库

关于 libafl

LibAFL 是用 Rust 编写的模块化模糊测试库,面向需要构建定制模糊测试器或开展研究的开发者。它既可替代 libFuzzer,也能组合组件实现自定义反馈、变异和调度。

主要功能

  1. 01以 Rust 库形式构建模糊测试器
  2. 02提供 libFuzzer 兼容模式
  3. 03模块化反馈与变异组件
  4. 04支持多核模糊测试
  5. 05提供语料库管理与统计

适用场景

  1. 01为现有 libFuzzer 测试目标运行模糊测试
  2. 02定制输入变异和反馈机制
  3. 03针对目标代码并行运行测试活动
  4. 04开展新型模糊测试技术研究

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trailofbits/skills --skill libafl

安装后在 Claude Code 中输入/libafl确认已加载。

附带文件agents/openai.yamlassets/trail-of-bits-mark.svg

详细介绍

LibAFL

LibAFL 是一个模块化的模糊测试库,实现了 AFL++ 等基于 AFL 的模糊测试器所具备的功能。与传统模糊测试器不同,LibAFL 以 Rust 库的形式提供了所有功能,且支持模块化和自定义。它既可以作为 libFuzzer 的直接替代品,也可以作为库,用于从头构建自定义模糊测试器。

适用场景

模糊测试器最适用场景复杂度
libFuzzer快速设置、单线程低
AFL++多核、通用用途中
LibAFL自定义模糊测试器、高级功能、研究高

选择 LibAFL 的场景:

  • 需要自定义变异策略或反馈机制
  • 标准模糊测试器不支持目标架构
  • 想要实现新颖的模糊测试技术
  • 需要对模糊测试组件进行细粒度控制
  • 正在开展模糊测试研究

快速开始

只需进行少量设置,LibAFL 即可作为 libFuzzer 的直接替代品:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // Call your code with fuzzer-provided data
    my_function(data, size);
    return 0;
}

构建 LibAFL 的 libFuzzer 兼容层:

git clone https://github.com/AFLplusplus/LibAFL
cd LibAFL/libafl_libfuzzer_runtime
./build.sh

编译并运行:

clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz
./fuzz corpus/

安装

前置条件
  • Clang/LLVM 15-18
  • Rust(通过 rustup 安装)
  • 其他系统依赖项
Linux/macOS

安装 Clang:

apt install clang

也可以通过 apt.llvm.org 安装特定版本:

wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15

配置 Rust 环境:

export RUSTFLAGS="-C linker=/usr/bin/clang-15"
export CC="clang-15"
export CXX="clang++-15"

安装 Rust:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

安装其他依赖项:

apt install libssl-dev pkg-config

要使用 libFuzzer 兼容模式,请安装 nightly Rust:

rustup toolchain install nightly --component llvm-tools
验证

构建 LibAFL 以验证安装:

cd LibAFL/libafl_libfuzzer_runtime
./build.sh
# Should produce libFuzzer.a

编写 Harness

在直接替代模式下,LibAFL harness 遵循与 libFuzzer 相同的模式:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // Your fuzzing target code here
    return 0;
}

使用 LibAFL 作为 Rust 库构建自定义模糊测试器时,harness 逻辑会直接集成到模糊测试器中。完整模式请参阅下文的“编写自定义模糊测试器”部分。

另请参阅: 有关详细的 harness 编写技巧,请参阅 harness-writing 技能。

使用模式

LibAFL 支持两种主要使用模式:

1. libFuzzer 直接替代

使用 LibAFL 替代 libFuzzer,并沿用现有 harness。

编译:

clang++ -DNO_MAIN -g -O2 -fsanitize=fuzzer-no-link libFuzzer.a harness.cc main.cc -o fuzz

运行:

./fuzz corpus/

建议用于长时间测试:

./fuzz -fork=1 -ignore_crashes=1 corpus/
2. 将自定义模糊测试器用作 Rust 库

使用 LibAFL 组件构建完全自定义的模糊测试器。

创建项目:

cargo init --lib my_fuzzer
cd my_fuzzer
cargo add [email protected] [email protected] [email protected] [email protected] \
  --features "[email protected]/libfuzzer,[email protected]/sancov_pcguard_hitcounts"

配置 Cargo.toml:

[lib]
crate-type = ["staticlib"]

编写自定义模糊测试器

另请参阅: 有关详细的 harness 编写技巧、处理复杂输入的模式以及高级策略,请参阅 fuzz-harness-writing 技能。

模糊测试器组件

LibAFL 模糊测试器由模块化组件组成:

  1. 观察器 - 收集执行反馈(覆盖率、耗时)
  2. 反馈 - 判断输入是否有趣
  3. 目标 - 定义模糊测试目标(崩溃、超时)
  4. 状态 - 维护语料库和元数据
  5. 变异器 - 生成新输入
  6. 调度器 - 选择要进行变异的输入
  7. 执行器 - 使用输入运行目标
基本模糊测试器结构
use libafl::prelude::*;
use libafl_bolts::prelude::*;
use libafl_targets::{libfuzzer_test_one_input, std_edges_map_observer};

#[no_mangle]
pub extern "C" fn libafl_main() {
    let mut run_client = |state: Option<_>, mut restarting_mgr, _core_id| {
        // 1. Setup observers
        let edges_observer = HitcountsMapObserver::new(
            unsafe { std_edges_map_observer("edges") }
        ).track_indices();
        let time_observer = TimeObserver::new("time");

        // 2. Define feedback
        let mut feedback = feedback_or!(
            MaxMapFeedback::new(&edges_observer),
            TimeFeedback::new(&time_observer)
        );

        // 3. Define objective
        let mut objective = feedback_or_fast!(
            CrashFeedback::new(),
            TimeoutFeedback::new()
        );

        // 4. Create or restore state
        let mut state = state.unwrap_or_else(|| {
            StdState::new(
                StdRand::new(),
                InMemoryCorpus::new(),
                OnDiskCorpus::new(&output_dir).unwrap(),
                &mut feedback,
                &mut objective,
            ).unwrap()
        });

        // 5. Setup mutator
        let mutator = StdScheduledMutator::new(havoc_mutations());
        let mut stages = tuple_list!(StdMutationalStage::new(mutator));

        // 6. Setup scheduler
        let scheduler = IndexesLenTimeMinimizerScheduler::new(
            &edges_observer,
            QueueScheduler::new()
        );

        // 7. Create fuzzer
        let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

        // 8. Define harness
        let mut harness = |input: &BytesInput| {
            let buf = input.target_bytes().as_slice();
            libfuzzer_test_one_input(buf);
            ExitKind::Ok
        };

        // 9. Setup executor
        let mut executor = InProcessExecutor::with_timeout(
            &mut harness,
            tuple_list!(edges_observer, time_observer),
            &mut fuzzer,
            &mut state,
            &mut restarting_mgr,
            timeout,
        )?;

        // 10. Load initial inputs
        if state.must_load_initial_inputs() {
            state.load_initial_inputs(
                &mut fuzzer,
                &mut executor,
                &mut restarting_mgr,
                &input_dir
            )?;
        }

        // 11. Start fuzzing
        fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
        Ok(())
    };

    // Launch fuzzer
    Launcher::builder()
        .run_client(&mut run_client)
        .cores(&cores)
        .build()
        .launch()
        .unwrap();
}

编译

详细模式

手动指定所有插桩标志:

clang++-15 -DNO_MAIN -g -O2 \
  -fsanitize-coverage=trace-pc-guard \
  -fsanitize=address \
  -Wl,--whole-archive target/release/libmy_fuzzer.a -Wl,--no-whole-archive \
  main.cc harness.cc -o fuzz
编译器包装器(推荐)

创建 LibAFL 编译器包装器,以自动处理插桩。

创建 src/bin/libafl_cc.rs:

use libafl_cc::{ClangWrapper, CompilerWrapper, Configuration, ToolWrapper};

pub fn main() {
    let args: Vec<String> = env::args().collect();
    let mut cc = ClangWrapper::new();
    cc.cpp(is_cpp)
      .parse_args(&args)
      .link_staticlib(&dir, "my_fuzzer")
      .add_args(&Configuration::GenerateCoverageMap.to_flags().unwrap())
      .add_args(&Configuration::AddressSanitizer.to_flags().unwrap())
      .run()
      .unwrap();
}

编译并使用:

cargo build --release
target/release/libafl_cxx -DNO_MAIN -g -O2 main.cc harness.cc -o fuzz

另请参阅: 有关详细的 Sanitizer 配置、常见问题和高级标志,请参阅 address-sanitizer 和 undefined-behavior-sanitizer 技能。

运行测试

基本运行
./fuzz --cores 0 --input corpus/
多核模糊测试
./fuzz --cores 0,8-15 --input corpus/

这会运行 9 个客户端:1 个使用核心 0,另外 8 个使用核心 8-15。

使用选项
./fuzz --cores 0-7 --input corpus/ --output crashes/ --timeout 1000
文本用户界面(TUI)

启用图形统计信息视图:

./fuzz -tui=1 corpus/
解读输出
输出含义
corpus: N找到的有趣测试用例数量
objectives: N找到的崩溃/超时数量
executions: N目标调用总次数
exec/sec: N当前执行吞吐量
edges: X%代码覆盖率百分比
clients: N并行模糊测试进程数量

模糊测试器会发出两种主要事件:

  • UserStats - 定期发送的心跳事件,包含当前统计信息
  • Testcase - 发现新的有趣输入

高级用法

提示与技巧
提示帮助
使用 -fork=1 -ignore_crashes=1在首次崩溃后继续进行模糊测试
使用 InMemoryOnDiskCorpus在重启后保留语料库
使用 -tui=1 启用 TUI更直观地查看进度
使用特定版本的 LLVM避免兼容性问题
正确设置 RUSTFLAGS防止链接错误
崩溃去重

避免存储同一 bug 导致的重复崩溃:

添加回溯观察器:

let backtrace_observer = BacktraceObserver::owned(
    "BacktraceObserver",
    libafl::observers::HarnessType::InProcess
);

更新执行器:

let mut executor = InProcessExecutor::with_timeout(
    &mut harness,
    tuple_list!(edges_observer, time_observer, backtrace_observer),
    &mut fuzzer,
    &mut state,
    &mut restarting_mgr,
    timeout,
)?;

使用哈希反馈更新目标:

let mut objective = feedback_and!(
    feedback_or_fast!(CrashFeedback::new(), TimeoutFeedback::new()),
    NewHashFeedback::new(&backtrace_observer)
);

这样可以确保只保存回溯唯一的崩溃。

字典模糊测试

使用字典引导模糊测试器针对特定标记进行测试:

从文件添加标记:

let mut tokens = Tokens::new();
if let Some(tokenfile) = &tokenfile {
    tokens.add_from_file(tokenfile)?;
}
state.add_metadata(tokens);

更新变异器:

let mutator = StdScheduledMutator::new(
    havoc_mutations().merge(tokens_mutations())
);

硬编码标记示例(PNG):

state.add_metadata(Tokens::from([
    vec![137, 80, 78, 71, 13, 10, 26, 10], // PNG header
    "IHDR".as_bytes().to_vec(),
    "IDAT".as_bytes().to_vec(),
    "PLTE".as_bytes().to_vec(),
    "IEND".as_bytes().to_vec(),
]));

另请参阅: 有关详细的字典创建策略和特定格式的字典, 请参阅 fuzzing-dictionaries 技术技能。

自动标记

自动从程序中提取魔数值和校验和:

在编译器包装器中启用:

cc.add_pass(LLVMPasses::AutoTokens)

在模糊测试器中加载自动标记:

tokens += libafl_targets::autotokens()?;

验证标记区段:

echo "p (uint8_t *)__token_start" | gdb fuzz
性能调优
设置影响
多核模糊测试随核心数增加而线性提速
InMemoryCorpus速度更快,但不会持久化
InMemoryOnDiskCorpus兼顾速度与持久化
Sanitizer速度降低 2-5 倍,但对发现 bug 至关重要
优化级别 -O2平衡速度与覆盖率
调试模糊测试器

以单进程模式运行模糊测试器,以便更轻松地调试:

// Replace launcher with direct call
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();

// Comment out:
// Launcher::builder()
//     .run_client(&mut run_client)
//     ...
//     .launch()

然后使用 GDB 调试:

gdb --args ./fuzz --cores 0 --input corpus/

实际示例

示例:libpng

使用 LibAFL 对 libpng 进行模糊测试:

1. 获取源代码:

curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/
apt install zlib1g-dev

2. 设置编译器包装器:

export FUZZER_CARGO_DIR="/path/to/libafl/project"
export CC=$FUZZER_CARGO_DIR/target/release/libafl_cc
export CXX=$FUZZER_CARGO_DIR/target/release/libafl_cxx

3. 构建静态库:

./configure --enable-shared=no
make

4. 获取测试框架:

curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc

5. 链接模糊测试器:

$CXX libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz

6. 准备种子:

mkdir seeds/
curl -o seeds/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png

7. 获取字典(可选):

curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict

8. 开始模糊测试:

./fuzz --input seeds/ --cores 0 -x png.dict
示例:CMake 项目

将 LibAFL 集成到 CMake 构建系统中:

CMakeLists.txt:

project(BuggyProgram)
cmake_minimum_required(VERSION 3.0)

add_executable(buggy_program main.cc)

add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2)

构建未插桩二进制文件:

cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build . --target buggy_program

构建模糊测试器:

export FUZZER_CARGO_DIR="/path/to/libafl/project"
cmake -DCMAKE_C_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cc \
      -DCMAKE_CXX_COMPILER=$FUZZER_CARGO_DIR/target/release/libafl_cxx .
cmake --build . --target fuzz

运行模糊测试:

./fuzz --input seeds/ --cores 0

故障排除

问题原因解决方案
覆盖率没有增加插桩失败确认使用了编译器包装器,并检查是否包含 -fsanitize-coverage
模糊测试器无法启动语料库为空,没有有趣的输入提供能够触发代码路径的种子输入
使用 libafl_main 时出现链接器错误未链接运行时使用 -Wl,--whole-archive 或 -u libafl_main
LLVM 版本不匹配LibAFL 要求 LLVM 15-18安装兼容的 LLVM 版本并设置环境变量
Rust 编译失败Rust 或 Cargo 版本过旧使用 rustup update 更新 Rust
模糊测试速度慢已启用 Sanitizer预计会慢 2-5 倍,但这是发现 bug 所必需的
环境变量干扰已设置 CC、CXX、RUSTFLAGS构建 LibAFL 项目后取消设置
无法附加调试器正在进行多进程模糊测试使用单进程模式运行(参见“调试模糊测试器”部分)

相关技能

技术技能
技能使用场景
fuzz-harness-writing编写有效测试框架的详细指南
address-sanitizer在模糊测试期间检测内存错误
undefined-behavior-sanitizer检测未定义行为
coverage-analysis衡量并提升代码覆盖率
fuzzing-corpus构建和管理种子语料库
fuzzing-dictionaries为格式感知模糊测试创建字典
相关模糊测试工具
技能适用场景
libfuzzer配置更简单,且不需要 LibAFL 的高级功能
aflpp无需开发自定义模糊测试工具即可进行多核模糊测试
cargo-fuzz以较少的配置对 Rust 项目进行模糊测试

资源

官方文档
示例和教程

评论

支持 Markdown