libFuzzer模糊测试引擎调研与自定义魔改
字数 2378 2025-08-22 12:22:54
libFuzzer模糊测试引擎深度解析与自定义开发指南
一、libFuzzer概述
1.1 基本介绍
libFuzzer是LLVM项目开发的覆盖率引导(Coverage-guided)模糊测试引擎,专门用于自动发现C/C++代码中的漏洞和错误。其核心特点包括:
- 基于LLVM的轻量级内联(in-process)模糊测试引擎
- 无需外部工具,直接链接到测试目标
- 使用覆盖率反馈指导输入变异
- 支持与AddressSanitizer、MemorySanitizer等Sanitizer工具集成
项目地址:llvm-project/compiler-rt/lib/fuzzer
1.2 核心优势
- 高效性:内联执行,避免了进程启动开销
- 智能变异:基于覆盖率反馈指导变异方向
- 集成性:与LLVM工具链深度集成
- 灵活性:支持多种变异策略和自定义扩展
二、架构与核心模块
2.1 模块结构
FuzzerDriver模块
- 功能:核心入口点,负责初始化环境和主循环
- 关键函数:
main():入口函数FuzzerDriver():核心循环逻辑
Mutation模块
- 功能:生成新测试用例
- 关键函数:
Mutate():输入数据变异CrossOver():交叉生成新输入
- 相关文件:
FuzzerMutate.cpp,FuzzerMutate.h
Corpus模块
- 功能:管理测试用例集合
- 关键函数:
AddToCorpus():添加新测试用例LoadCorpus():从磁盘加载语料库
- 相关文件:
FuzzerCorpus.cpp,FuzzerCorpus.h
Coverage模块
- 功能:收集代码覆盖率信息
- 关键函数:
UpdateCoverage():更新覆盖率GetCoverage():获取当前覆盖率
- 相关文件:
FuzzerTracePC.cpp,FuzzerTracePC.h
Crash Detection模块
- 功能:检测和处理程序崩溃
- 关键函数:
HandleCrash():处理崩溃事件ReportCrash():报告崩溃信息
- 相关文件:
FuzzerFork.cpp,FuzzerFork.h
FuzzerInterface模块
- 功能:定义用户接口
- 关键函数:
LLVMFuzzerTestOneInput():用户提供的测试函数
- 相关文件:
FuzzerInterface.h
2.2 核心类结构
class Fuzzer {
public:
// 构造函数与析构函数
Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, FuzzingOptions Options);
~Fuzzer();
// 主循环控制
void Loop(Vector<SizedFile> &CorporaFiles);
void ReadAndExecuteSeedCorpora(Vector<SizedFile> &CorporaFiles);
// 崩溃处理
void MinimizeCrashLoop(const Unit &U);
// 状态查询
size_t secondsSinceProcessStartUp();
bool TimedOut();
size_t execPerSec();
size_t getTotalNumberOfRuns();
// 回调函数
static void StaticAlarmCallback();
static void StaticCrashSignalCallback();
// 变异与测试
void ExecuteCallback(const uint8_t *Data, size_t Size);
bool RunOne(const uint8_t *Data, size_t Size, bool MayDeleteFile = false,
InputInfo *II = nullptr, bool *FoundUniqFeatures = nullptr);
// 语料库管理
void Merge(const Vector<std::string> &Corpora);
void CrashResistantMergeInternalStep(const std::string &ControlFilePath);
private:
// 内部实现细节...
uint8_t *CurrentUnitData = nullptr;
std::atomic<size_t> CurrentUnitSize;
UserCallback CB;
InputCorpus &Corpus;
MutationDispatcher &MD;
FuzzingOptions Options;
// ...其他成员变量
};
三、核心运行流程
3.1 主入口流程
- 入口函数 (
FuzzerMain.cpp):
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size);
int main(int argc, char **argv) {
return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput);
}
- FuzzerDriver核心 (
FuzzerDriver.cpp):
int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
auto *MD = new MutationDispatcher(Rand, Options);
auto *Corpus = new InputCorpus(Options.OutputCorpus);
auto *F = new Fuzzer(Callback, *Corpus, *MD, Options);
// 解析命令行选项
// ...
// 读取并处理语料库
auto CorporaFiles = ReadCorpora(*Inputs, ParseSeedInuts(Flags.seed_inputs));
F->Loop(CorporaFiles);
}
3.2 主循环流程 (FuzzerLoop.cpp)
- 初始化测试环境
- 加载初始语料库
- 进入主循环:
- 从语料库选择输入
- 应用变异策略生成新输入
- 执行测试并收集覆盖率
- 评估输入有效性并更新语料库
- 定期输出统计信息
- 处理崩溃和超时
3.3 变异策略
libFuzzer提供多种变异策略:
- 位/字节级变异
- 数据插入/删除
- 交叉变异
- 基于字典的变异
- 特殊值插入
四、编译与使用
4.1 编译环境配置
推荐使用以下脚本配置Clang环境:
#!/bin/bash
# 安装依赖
sudo apt-get --yes install curl subversion screen gcc g++ cmake ninja-build golang \
autoconf libtool apache2 python-dev pkg-config zlib1g-dev libgcrypt20-dev \
libgss-dev libssl-dev libxml2-dev ragel nasm libarchive-dev make automake \
libdbus-1-dev libboost-dev autoconf-archive libtinfo5
# 定义Clang版本
CLANG_VERSION="18.1.8"
CLANG_TAR="clang+llvm-${CLANG_VERSION}-x86_64-linux-gnu-ubuntu-18.04.tar.xz"
CLANG_URL="https://github.com/llvm/llvm-project/releases/download/llvmorg-${CLANG_VERSION}/${CLANG_TAR}"
CLANG_INSTALL_DIR="/usr/local/clang-${CLANG_VERSION}"
# 下载和解压
wget "${CLANG_URL}"
sudo mkdir -p "${CLANG_INSTALL_DIR}"
sudo tar -xf "${CLANG_TAR}" -C "${CLANG_INSTALL_DIR}" --strip-components=1
# 设置环境变量
echo "export PATH=${CLANG_INSTALL_DIR}/bin:\$PATH" >> ~/.bashrc
echo "export LD_LIBRARY_PATH=${CLANG_INSTALL_DIR}/lib:\$LD_LIBRARY_PATH" >> ~/.bashrc
source ~/.bashrc
4.2 libFuzzer编译
使用官方提供的build.sh脚本:
#!/bin/sh
LIBFUZZER_SRC_DIR=$(dirname $0)
CXX="${CXX:-clang}"
for f in $LIBFUZZER_SRC_DIR/*.cpp; do
$CXX -g -O2 -fno-omit-frame-pointer -std=c++17 $f -c &
done
wait
rm -f libFuzzer.a
ar r libFuzzer.a Fuzzer*.o
rm -f Fuzzer*.o
4.3 基本使用方式
编译测试目标:
clang++ -g -O1 fuzz_target.cc libFuzzer.a -o mytarget_fuzzer
五、高级功能与自定义开发
5.1 覆盖率相关选项
| 选项 | 功能 | 示例 |
|---|---|---|
-print_pcs=1 |
打印新覆盖的PC地址 | ./fuzzer -print_pcs=1 |
-print_funcs=1 |
打印新覆盖的函数 | ./fuzzer -print_funcs=1 |
-print_coverage=1 |
打印覆盖率信息 | ./fuzzer -print_coverage=1 |
-print_full_coverage=1 |
打印完整覆盖率报告 | ./fuzzer -print_full_coverage=1 |
5.2 自定义开发案例
案例1:导出覆盖率数据
修改FuzzerLoop.cpp添加数据导出接口:
// 返回覆盖率统计信息
size_t GetTotalPCCoverage() const { return TPC.GetTotalPCCoverage(); }
size_t GetNumFeatures() const { return Corpus.NumFeatures(); }
size_t GetNumActiveUnits() const { return Corpus.NumActiveUnits(); }
// 修改PrintStats函数添加数据导出
void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units, size_t Features) {
// ...原有统计打印逻辑
// 添加数据导出
ExportDataToFrontend({
{"TotalNumberOfRuns", TotalNumberOfRuns},
{"PCCoverage", GetTotalPCCoverage()},
{"NumFeatures", GetNumFeatures()},
{"ExecPerSec", GetExecPerSec()}
});
}
// 数据导出函数
void ExportDataToFrontend(const std::map<std::string, size_t>& data) {
// 实现数据导出逻辑
}
案例2:实现持久化模糊测试
修改崩溃处理逻辑:
// 修改主循环实现持久化
while (true) {
try {
int result = LLVMFuzzerTestOneInput(Data, Size);
if (result == 0) {
continue; // 正常执行,继续
} else {
LogCrash(); // 记录崩溃但不退出
continue;
}
} catch (...) {
ReportCrash(); // 捕获异常并记录
continue; // 继续执行
}
}
// 修改信号处理
signal(SIGSEGV, HandleSignal);
signal(SIGABRT, HandleSignal);
void HandleSignal(int signal) {
LogSignalCrash(signal); // 记录信号
// 不退出,继续执行
}
六、实战案例
6.1 WOFF2字体解析器测试
测试目标:WOFF2字体解析器漏洞挖掘
测试代码:
#include <stddef.h>
#include <stdint.h>
#include "woff2_dec.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
std::string buf;
woff2::WOFF2StringOut out(&buf);
out.SetMaxSize(30 * 1024 * 1024);
woff2::ConvertWOFF2ToTTF(data, size, &out);
return 0;
}
编译运行:
clang++ -g -O1 -fsanitize=address,fuzzer woff_target.cc woff2_dec.o -o woff_fuzzer
./woff_fuzzer -print_coverage=1
6.2 OpenSSL心脏滴血漏洞(CVE-2014-0160)复现
测试目标:OpenSSL 1.0.1f TLS心跳扩展实现
测试代码:
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <stdint.h>
#include <stddef.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// 初始化SSL
SSL_CTX *ctx = SSL_CTX_new(TLSv1_method());
SSL *ssl = SSL_new(ctx);
// 处理心跳数据
// 这里模拟漏洞触发条件
if (size > 0) {
SSL_process_heartbeat(ssl, data, size);
}
// 清理
SSL_free(ssl);
SSL_CTX_free(ctx);
return 0;
}
七、最佳实践与技巧
-
语料库管理:
- 使用小型但多样化的初始语料库
- 定期合并新发现的测试用例
- 使用
-merge=1选项合并语料库
-
性能优化:
- 设置合理的
-max_len限制输入大小 - 使用
-fork=N进行并行模糊测试 - 结合AddressSanitizer检测内存错误
- 设置合理的
-
调试技巧:
- 使用
-verbosity=1增加输出详细程度 - 对崩溃输入使用
-minimize_crash=1进行最小化 - 结合GDB调试崩溃用例
- 使用
-
高级选项:
# 控制变异策略 -cross_over=1 # 启用交叉变异 -dict=wordlist.dict # 使用字典指导变异 -use_counters=1 # 使用计数器指导变异 # 资源控制 -max_total_time=3600 # 设置最大运行时间(秒) -rss_limit_mb=2048 # 设置内存限制
八、总结
libFuzzer作为LLVM生态中的重要工具,具有以下特点:
- 高效性:内联执行和智能变异策略
- 可扩展性:支持自定义变异和反馈机制
- 集成性:与Sanitizer工具完美配合
- 灵活性:丰富的配置选项和扩展接口
通过本文介绍的自定义开发方法,可以实现:
- 定制化数据收集和展示
- 持久化模糊测试模式
- 特定领域的优化变异策略
- 与其他系统的集成对接
掌握libFuzzer的核心原理和扩展方法,可以显著提升软件安全测试的效率和深度。