LibAFL模糊测试技能Skill libafl

这个技能涉及使用LibAFL库进行高级模糊测试,用于构建自定义模糊测试器,发现软件漏洞和提升代码覆盖率。关键词包括:模糊测试、LibAFL、自定义fuzzer、漏洞挖掘、安全测试、代码覆盖率、软件测试。

漏洞挖掘 0 次安装 0 次浏览 更新于 3/14/2026

名称: libafl 类型: 模糊测试器 描述: > LibAFL 是一个用于构建自定义模糊测试器的模块化模糊测试库。适用于高级模糊测试需求、自定义突变器或非标准模糊测试目标。

LibAFL

LibAFL 是一个模块化模糊测试库,实现了基于 AFL 的模糊测试器如 AFL++ 的功能。与传统模糊测试器不同,LibAFL 以 Rust 库的形式提供所有功能,模块化且可定制。它可以用作 libFuzzer 的替代品,或作为从头开始构建自定义模糊测试器的库。

何时使用

模糊测试器 最适合 复杂度
libFuzzer 快速设置,单线程
AFL++ 多核心,通用目的
LibAFL 自定义模糊测试器,高级功能,研究

选择 LibAFL 当:

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

快速开始

LibAFL 可以作为 libFuzzer 的替代品,设置简单:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // 使用模糊测试器提供的数据调用代码
    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
# 应该生成 libFuzzer.a

编写测试套件

当使用替代模式时,LibAFL 测试套件遵循与 libFuzzer 相同的模式:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // 您的模糊测试目标代码在此
    return 0;
}

当使用 LibAFL 作为 Rust 库构建自定义模糊测试器时,测试套件逻辑直接集成到模糊测试器中。参见下面的“编写自定义模糊测试器”部分以了解完整模式。

另见: 有关详细测试套件编写技术,请参阅 harness-writing 技术技能。

使用模式

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

1. libFuzzer 替代品

使用 LibAFL 作为 libFuzzer 的替代品,配合现有测试套件。

编译:

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 libafl@0.13 libafl_targets@0.13 libafl_bolts@0.13 libafl_cc@0.13 \
  --features "libafl_targets@0.13/libfuzzer,libafl_targets@0.13/sancov_pcguard_hitcounts"

配置 Cargo.toml:

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

编写自定义模糊测试器

另见: 有关详细测试套件编写技术、处理复杂输入的策略和高级策略,请参阅 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. 设置观察者
        let edges_observer = HitcountsMapObserver::new(
            unsafe { std_edges_map_observer("edges") }
        ).track_indices();
        let time_observer = TimeObserver::new("time");

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

        // 3. 定义目标
        let mut objective = feedback_or_fast!(
            CrashFeedback::new(),
            TimeoutFeedback::new()
        );

        // 4. 创建或恢复状态
        let mut state = state.unwrap_or_else(|| {
            StdState::new(
                StdRand::new(),
                InMemoryCorpus::new(),
                OnDiskCorpus::new(&output_dir).unwrap(),
                &mut feedback,
                &mut objective,
            ).unwrap()
        });

        // 5. 设置突变器
        let mutator = StdScheduledMutator::new(havoc_mutations());
        let mut stages = tuple_list!(StdMutationalStage::new(mutator));

        // 6. 设置调度器
        let scheduler = IndexesLenTimeMinimizerScheduler::new(
            &edges_observer,
            QueueScheduler::new()
        );

        // 7. 创建模糊测试器
        let mut fuzzer = StdFuzzer::new(scheduler, feedback, objective);

        // 8. 定义测试套件
        let mut harness = |input: &BytesInput| {
            let buf = input.target_bytes().as_slice();
            libfuzzer_test_one_input(buf);
            ExitKind::Ok
        };

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

        // 10. 加载初始输入
        if state.must_load_initial_inputs() {
            state.load_initial_inputs(
                &mut fuzzer,
                &mut executor,
                &mut restarting_mgr,
                &input_dir
            )?;
        }

        // 11. 开始模糊测试
        fuzzer.fuzz_loop(&mut stages, &mut executor, &mut state, &mut restarting_mgr)?;
        Ok(())
    };

    // 启动模糊测试器
    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

另见: 有关详细消毒器配置、常见问题和高级标志,请参阅 address-sanitizerundefined-behavior-sanitizer 技术技能。

运行活动

基本运行

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

多核心模糊测试

./fuzz --cores 0,8-15 --input corpus/

这运行 9 个客户端:一个在核心 0,八个在核心 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 防止链接错误

崩溃去重

避免存储来自同一错误的重复崩溃:

添加回溯观察者:

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 头
    "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 平衡速度和持久性
消毒器 2-5倍减速,对发现漏洞至关重要
优化级别 -O2 速度和覆盖率之间的平衡

调试模糊测试器

以单进程模式运行模糊测试器以更容易调试:

// 用直接调用替换启动器
run_client(None, SimpleEventManager::new(monitor), 0).unwrap();

// 注释掉:
// 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
模糊测试慢 消毒器启用 预期 2-5倍减速,对发现漏洞必要
环境变量干扰 CCCXXRUSTFLAGS 设置 构建 LibAFL 项目后取消设置
无法附加调试器 多进程模糊测试 以单进程模式运行(见调试部分)

相关技能

技术技能

技能 使用案例
fuzz-harness-writing 编写有效测试套件的详细指导
address-sanitizer 模糊测试期间的内存错误检测
undefined-behavior-sanitizer 未定义行为检测
coverage-analysis 测量和改善代码覆盖率
fuzzing-corpus 构建和管理种子语料库
fuzzing-dictionaries 为格式感知模糊测试创建字典

相关模糊测试器

技能 何时考虑
libfuzzer 更简单设置,不需要 LibAFL 的高级功能
aflpp 无需自定义模糊测试器开发的多核心模糊测试
cargo-fuzz 用较少设置模糊测试 Rust 项目

资源

官方文档

示例和教程