文章目录
前言
Honggfuzz 是一款由 Google 开发的高效模糊测试(Fuzzing)工具,主要用于发现软件中的内存安全漏洞(如缓冲区溢出、使用-after-free 等)。以下是在 Ubuntu 系统中搭建 Honggfuzz 环境及使用案例的详细步骤。
本文所有操作基于 Ubuntu 22.04.5 LTS
一、环境搭建
1. 安装依赖工具
Honggfuzz 依赖部分系统工具和库,首先安装:
sudo apt update
sudo apt install -y git build-essential libssl-dev clang llvm binutils-dev libunwind8-dev
2. 克隆 Honggfuzz 源码
从 GitHub 克隆官方仓库:
git clone https://github.com/google/honggfuzz.git
cd honggfuzz
3. 编译并安装
使用 make
编译(默认支持 clang,推荐使用 clang 以获得更好的覆盖率支持):
make
sudo make install # 安装到 /usr/local/bin 目录
验证安装是否成功:
honggfuzz --version
若输出版本信息,则安装成功。
二、实战模拟
使用 Honggfuzz 对字符串处理库进行模糊测试的完整流程
编写一个模拟漏洞的 C++ 项目:vulnerableCppDemo,使用 CMake 编译
项目示例源码上传到:资源地址
1 准备初始测试用例
当前项目的测试桩已经内置
#include "stringutils.h"
#include <fstream>
#include <iostream>
#include <vector>
#include <cstring>
#include <cstdint>
// 从文件读取输入数据
std::vector<uint8_t> read_input_file(const char* filename) {
std::ifstream file(filename, std::ios::binary);
if (!file.is_open()) {
return {};
}
return std::vector<uint8_t>(
(std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>()
);
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "用法: " << argv[0] << " <输入文件路径>" << std::endl;
return 1;
}
// 读取输入(二进制数据,保留原始字节)
std::vector<uint8_t> input_data = read_input_file(argv[1]);
if (input_data.empty()) {
input_data.push_back(0x00);
}
// 提取原始字节(用于生成参数,避免字符串截断)
size_t data_len = input_data.size();
const uint8_t* data = input_data.data();
// --------------------------
// 1. 测试 copy_string(缓冲区溢出)
// --------------------------
// 直接使用全部输入作为源字符串(保留原始字节,包括\x00)
char* copied_str = stringutils::copy_string(reinterpret_cast<const char*>(data));
stringutils::free_string(copied_str);
// --------------------------
// 2. 测试 concat_strings(空指针解引用)
// --------------------------
// 分割输入为两部分,可能为空;随机将其中一个参数设为nullptr
size_t split = data_len / 2;
const char* str_a = reinterpret_cast<const char*>(data);
const char* str_b = reinterpret_cast<const char*>(data + split);
if (data_len >= 1 && (data[0] % 20 == 0)) {
str_a = nullptr;
}
if (data_len >= 2 && (data[1] % 20 == 0)) {
str_b = nullptr;
}
char* concat_str = stringutils::concat_strings(str_a, str_b);
stringutils::free_string(concat_str);
// --------------------------
// 3. 测试 repeat_string(整数溢出)
// --------------------------
int32_t repeat_times = 1;
if (data_len >= 4) {
repeat_times = *reinterpret_cast<const int32_t*>(data);
}
const char* base_str = reinterpret_cast<const char*>(data + (data_len / 3));
char* repeated_str = stringutils::repeat_string(base_str, repeat_times);
stringutils::free_string(repeated_str);
// --------------------------
// 4. 测试 substring(越界访问)
// --------------------------
int32_t start_pos = 0;
if (data_len >= 8) {
start_pos = *reinterpret_cast<const int32_t*>(data + 4);
}
// 子串长度:用第8-11字节生成大整数(可能超过字符串长度)
int32_t sub_length = 0;
if (data_len >= 12) {
sub_length = *reinterpret_cast<const int32_t*>(data + 8);
}
// 基础字符串使用输入的前半部分
const char* sub_base = reinterpret_cast<const char*>(data);
char* sub_str = stringutils::substring(sub_base, start_pos, sub_length);
stringutils::free_string(sub_str);
return 0;
}
需要创建一些基础测试用例,用于 Honggfuzz 生成变异输入。这些用例应尽可能多样化,覆盖不同的字符串场景:
# 创建存放测试用例的目录
mkdir -p inputs
# 生成正常字符串用例
echo "normal string" > inputs/normal.txt
echo "test concat part1" > inputs/concat1.txt
echo "test concat part2" > inputs/concat2.txt
# 生成空字符串用例(测试空指针处理)
touch inputs/empty.txt # 空文件,对应空字符串输入
# 生成超长字符串(65字节)
printf "A%.0s" {1..65} > inputs/long65.txt
# 生成边界长度字符串(64字节)
printf "B%.0s" {1..64} > inputs/boundary64.txt
# 生成特殊字符用例(包含控制字符、非ASCII字符)
echo -e "$%^&*\x00\x01\x7Fáéí" > inputs/special_chars.txt
# 生成重复字符串基础用例
echo "x" > inputs/short_str.txt
# 生成子串测试用例
echo "0123456789abcdef" > inputs/sub_base.txt
说明:初始测试用例是模糊测试的起点,Honggfuzz 会基于这些用例进行变异(如修改字符、增减长度、插入特殊符号等),因此覆盖越多场景的初始用例,越容易触发边缘路径的漏洞。
2 编译目标项目
我们需要编译包含漏洞的字符串处理库及其测试程序。确保编译时启用内存错误检测工具(如 AddressSanitizer),以便 Honggfuzz 能捕获内存漏洞:
# 进入项目目录(假设项目名为 vulnerableCppDemo)
cd vulnerableCppDemo
# 创建并进入构建目录
mkdir build && cd build
# 生成 Makefile(CMake 配置已包含 -fsanitize=address 等选项)
cmake ..
# 编译项目,生成可执行文件 stringutils_tester
make
说明:
- 编译时启用
AddressSanitizer
(通过 CMake 配置中的-fsanitize=address
)是关键,它能帮助 Honggfuzz 检测出缓冲区溢出、使用已释放内存等内存安全问题。 - 生成的
stringutils_tester
是测试桩程序,负责读取 Honggfuzz 生成的输入并调用目标库的接口。
3 运行 Honggfuzz 进行模糊测试
使用 Honggfuzz 对编译好的程序stringutils_tester
进行测试,监控并记录触发漏洞的输入:
# 运行 Honggfuzz,参数说明见下文
honggfuzz -i ../../inputs -o hfuzz_results --timeout 3000 -n $(nproc) --sanitizers --verbose -- ./stringutils_tester ___FILE___
核心参数解析:
-i ../../inputs
:指定初始测试用例目录(对应步骤 1 创建的 inputs 文件夹)。-o hfuzz_results
:指定输出目录,用于保存崩溃用例、日志和统计信息。-n $(nproc)
:使用与 CPU 核心数相同的并行线程,最大化测试效率。--sanitizers
:启用 sanitizer 支持(与编译时的-fsanitize=address
配合,增强漏洞检测能力)。--verbose
:输出详细日志,便于跟踪测试过程。./stringutils_tester ___FILE___
:指定测试程序及输入占位符(___FILE___
表示 Honggfuzz 生成的测试用例文件路径)。
默认行为说明:
- Honggfuzz 默认不会在发现崩溃后停止(
--exit_upon_crash
默认为 false),会持续运行并记录所有触发漏洞的用例。 - 测试过程中,终端会实时输出进度(如测试用例数量、覆盖率增长、崩溃信息等)。
- 测试过程中,手动 Ctrl + C 停止后再次执行,会继承之前的结果,除非重新走整个测试流程。
4 查看测试结果
测试结束后(可通过 Ctrl + C
手动停止),在执行目录下查看文件如下几类:
这里直接查看HONGGFUZZ.REPORT.TXT
文件,honggfuzz 已成功触发了 4个不同的漏洞,分别对应 stringutils
库中的4个核心函数。每个崩溃报告都清晰显示了漏洞类型、触发位置和根因,具体分析如下:
序号 | 函数名 | 漏洞类型 | 触发文件(ORIG_FNAME) | 代码位置(stringutils.cpp) |
---|---|---|---|---|
1 | copy_string | 堆缓冲区溢出(heap-overflow) | long65.txt (超长字符串) | 第14行 |
2 | concat_strings | 堆缓冲区溢出(heap-overflow) | special_chars.txt (特殊字符) | 第23行 |
3 | repeat_string | 堆缓冲区溢出(heap-overflow) | 覆盖率文件(中间变异用例) | 第38行 |
4 | substring | 段错误(SEGV)+堆缓冲区溢出 | 覆盖率文件(中间变异用例) | 第66行 |
=====================================================================
TIME: 2025-08-23.07:41:25
=====================================================================
FUZZER ARGS:
mutationsPerRun : 5
externalCmd : NULL
fuzzStdin : FALSE
timeout : 1 (sec)
ignoreAddr : (nil)
ASLimit : 0 (MiB)
RSSLimit : 0 (MiB)
DATALimit : 0 (MiB)
wordlistFile : NULL
dynFileMethod :
fuzzTarget : ./stringutils_tester ___FILE___
CRASH:
DESCRIPTION: AddressSanitizer: heap-buffer-overflow on address 0x502000000054 at pc 0x7ffff74545a9 bp 0x7fffffffdb80 sp 0x7fffffffd328
ORIG_FNAME: long65.txt
FUZZ_FNAME: /home/jerry/code/vulnerable-cpp-demo/build/SIGABRT.PC.7ffff74545a9.STACK.1591c7c013.CODE.-6.ADDR.0.INSTR.push___$0x0.fuzz
PID: 951463
SIGNAL: SIGABRT (6)
PC: 0x7ffff74545a9
FAULT ADDRESS: 0x0
INSTRUCTION: push___$0x0
STACK HASH: 0000001591c7c013
STACK:
<0x00007ffff74545a8> [func:__interceptor_strcpy file:../../../../src/libsanitizer/asan/asan_interceptors.cpp line:439 module:]
<0x00007ffff7fae257> [func:stringutils::copy_string(char const*) file:/home/jerry/code/vulnerable-cpp-demo/src/stringutils.cpp line:14 module:]
<0x0000555555556d89> [func:main file:/home/jerry/code/vulnerable-cpp-demo/src/tester.cpp line:41 module:]
<0x00007ffff6c29d8f> [func:__libc_start_call_main file:../sysdeps/nptl/libc_start_call_main.h line:58 module:]
<0x00007ffff6c29e3f> [func:__libc_start_main_impl file:../csu/libc-start.c line:392 module:]
<0x00005555555565c4> [func:_start file: line:0 module:/home/jerry/code/vulnerable-cpp-demo/build/stringutils_tester+0x25c4]
=====================================================================
=====================================================================
TIME: 2025-08-23.07:41:27
=====================================================================
FUZZER ARGS:
mutationsPerRun : 5
externalCmd : NULL
fuzzStdin : FALSE
timeout : 1 (sec)
ignoreAddr : (nil)
ASLimit : 0 (MiB)
RSSLimit : 0 (MiB)
DATALimit : 0 (MiB)
wordlistFile : NULL
dynFileMethod :
fuzzTarget : ./stringutils_tester ___FILE___
CRASH:
DESCRIPTION: AddressSanitizer: heap-buffer-overflow on address 0x5020000000a0 at pc 0x7ffff743daa7 bp 0x7fffffffdb70 sp 0x7fffffffd318
ORIG_FNAME: special_chars.txt
FUZZ_FNAME: /home/jerry/code/vulnerable-cpp-demo/build/SIGABRT.PC.7ffff743daa7.STACK.8d4f4ddd.CODE.-6.ADDR.0.INSTR.push___$0x0.fuzz
PID: 951496
SIGNAL: SIGABRT (6)
PC: 0x7ffff743daa7
FAULT ADDRESS: 0x0
INSTRUCTION: push___$0x0
STACK HASH: 000000008d4f4ddd
STACK:
<0x00007ffff743daa6> [func:__interceptor_strlen file:../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc line:389 module:]
<0x00007ffff7fae2a2> [func:stringutils::concat_strings(char const*, char const*) file:/home/jerry/code/vulnerable-cpp-demo/src/stringutils.cpp line:23 module:]
<0x0000555555556ee4> [func:main file:/home/jerry/code/vulnerable-cpp-demo/src/tester.cpp line:61 module:]
<0x00007ffff6c29d8f> [func:__libc_start_call_main file:../sysdeps/nptl/libc_start_call_main.h line:58 module:]
<0x00007ffff6c29e3f> [func:__libc_start_main_impl file:../csu/libc-start.c line:392 module:]
<0x00005555555565c4> [func:_start file: line:0 module:/home/jerry/code/vulnerable-cpp-demo/build/stringutils_tester+0x25c4]
=====================================================================
=====================================================================
TIME: 2025-08-23.07:41:33
=====================================================================
FUZZER ARGS:
mutationsPerRun : 5
externalCmd : NULL
fuzzStdin : FALSE
timeout : 1 (sec)
ignoreAddr : (nil)
ASLimit : 0 (MiB)
RSSLimit : 0 (MiB)
DATALimit : 0 (MiB)
wordlistFile : NULL
dynFileMethod :
fuzzTarget : ./stringutils_tester ___FILE___
CRASH:
DESCRIPTION: AddressSanitizer: heap-buffer-overflow on address 0x502000000054 at pc 0x7ffff743daa7 bp 0x7fffffffdb70 sp 0x7fffffffd318
ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov
FUZZ_FNAME: /home/jerry/code/vulnerable-cpp-demo/build/SIGABRT.PC.7ffff743daa7.STACK.9872cff.CODE.-6.ADDR.0.INSTR.push___$0x0.fuzz
PID: 951600
SIGNAL: SIGABRT (6)
PC: 0x7ffff743daa7
FAULT ADDRESS: 0x0
INSTRUCTION: push___$0x0
STACK HASH: 0000000009872cff
STACK:
<0x00007ffff743daa6> [func:__interceptor_strlen file:../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc line:389 module:]
<0x00007ffff7fae323> [func:stringutils::repeat_string(char const*, int) file:/home/jerry/code/vulnerable-cpp-demo/src/stringutils.cpp line:38 module:]
<0x0000555555556f96> [func:main file:/home/jerry/code/vulnerable-cpp-demo/src/tester.cpp line:76 module:]
<0x00007ffff6c29d8f> [func:__libc_start_call_main file:../sysdeps/nptl/libc_start_call_main.h line:58 module:]
<0x00007ffff6c29e3f> [func:__libc_start_main_impl file:../csu/libc-start.c line:392 module:]
<0x00005555555565c4> [func:_start file: line:0 module:/home/jerry/code/vulnerable-cpp-demo/build/stringutils_tester+0x25c4]
=====================================================================
=====================================================================
TIME: 2025-08-23.07:41:40
=====================================================================
FUZZER ARGS:
mutationsPerRun : 5
externalCmd : NULL
fuzzStdin : FALSE
timeout : 1 (sec)
ignoreAddr : (nil)
ASLimit : 0 (MiB)
RSSLimit : 0 (MiB)
DATALimit : 0 (MiB)
wordlistFile : NULL
dynFileMethod :
fuzzTarget : ./stringutils_tester ___FILE___
CRASH:
DESCRIPTION: AddressSanitizer: SEGV on unknown address 0x50203836018e (pc 0x7ffff6d9d985 bp 0x7fffffffdb80 sp 0x7fffffffd308 T0)
ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov
FUZZ_FNAME: /home/jerry/code/vulnerable-cpp-demo/build/SIGABRT.PC.7ffff6d9d985.STACK.2b99642b7.CODE.-6.ADDR.0.INSTR.vpcmpeqb_(%rdi),%ymm0,%ymm1.fuzz
PID: 951860
SIGNAL: SIGABRT (6)
PC: 0x7ffff6d9d985
FAULT ADDRESS: 0x0
INSTRUCTION: vpcmpeqb_(%rdi),%ymm0,%ymm1
STACK HASH: 00000002b99642b7
STACK:
<0x00007ffff6d9d985> [func:UNKNOWN file: line:0 module:/lib/x86_64-linux-gnu/libc.so.6+0x19d985]
<0x00007ffff745bc57> [func:MaybeRealStrnlen file:../../../../src/libsanitizer/asan/asan_interceptors.cpp line:58 module:]
<0x00007ffff745bc57> [func:__interceptor_strncpy file:../../../../src/libsanitizer/asan/asan_interceptors.cpp line:482 module:]
<0x00007ffff7fae43e> [func:stringutils::substring(char const*, int, int) file:/home/jerry/code/vulnerable-cpp-demo/src/stringutils.cpp line:66 module:]
<0x0000555555557093> [func:main file:/home/jerry/code/vulnerable-cpp-demo/src/tester.cpp line:95 module:]
<0x00007ffff6c29d8f> [func:__libc_start_call_main file:../sysdeps/nptl/libc_start_call_main.h line:58 module:]
<0x00007ffff6c29e3f> [func:__libc_start_main_impl file:../csu/libc-start.c line:392 module:]
<0x00005555555565c4> [func:_start file: line:0 module:/home/jerry/code/vulnerable-cpp-demo/build/stringutils_tester+0x25c4]
=====================================================================
=====================================================================
TIME: 2025-08-23.07:41:48
=====================================================================
FUZZER ARGS:
mutationsPerRun : 5
externalCmd : NULL
fuzzStdin : FALSE
timeout : 1 (sec)
ignoreAddr : (nil)
ASLimit : 0 (MiB)
RSSLimit : 0 (MiB)
DATALimit : 0 (MiB)
wordlistFile : NULL
dynFileMethod :
fuzzTarget : ./stringutils_tester ___FILE___
CRASH:
DESCRIPTION: AddressSanitizer: heap-buffer-overflow on address 0x502000006983 at pc 0x7ffff745be47 bp 0x7fffffffdb80 sp 0x7fffffffd328
ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov
FUZZ_FNAME: /home/jerry/code/vulnerable-cpp-demo/build/SIGABRT.PC.7ffff745be47.STACK.1517c598d2.CODE.-6.ADDR.0.INSTR.push___$0x0.fuzz
PID: 952113
SIGNAL: SIGABRT (6)
PC: 0x7ffff745be47
FAULT ADDRESS: 0x0
INSTRUCTION: push___$0x0
STACK HASH: 0000001517c598d2
STACK:
<0x00007ffff745be46> [func:__interceptor_strncpy file:../../../../src/libsanitizer/asan/asan_interceptors.cpp line:484 module:]
<0x00007ffff7fae43e> [func:stringutils::substring(char const*, int, int) file:/home/jerry/code/vulnerable-cpp-demo/src/stringutils.cpp line:66 module:]
<0x0000555555557093> [func:main file:/home/jerry/code/vulnerable-cpp-demo/src/tester.cpp line:95 module:]
<0x00007ffff6c29d8f> [func:__libc_start_call_main file:../sysdeps/nptl/libc_start_call_main.h line:58 module:]
<0x00007ffff6c29e3f> [func:__libc_start_main_impl file:../csu/libc-start.c line:392 module:]
<0x00005555555565c4> [func:_start file: line:0 module:/home/jerry/code/vulnerable-cpp-demo/build/stringutils_tester+0x25c4]
=====================================================================
三、测试桩
在模糊测试中,测试桩(也称为“模糊驱动程序”)是连接模糊测试工具(如honggfuzz)与目标程序的关键组件。对于未集成libFuzzer等标准接口的项目,测试桩是实现模糊测试的核心载体。
1 测试桩的核心作用
测试桩本质是一个“适配层”,主要承担3项核心任务:
- 输入桥梁:接收模糊测试工具(honggfuzz)生成的变异输入(通常是文件或内存数据)。
- 参数转换:将原始输入解析为目标函数所需的参数(如字符串、整数、结构体等)。
- 执行触发:调用目标程序中可能存在漏洞的函数(如字符串处理、协议解析函数),将解析后的参数传入,触发潜在漏洞。
简单来说,测试桩的使命是“让模糊工具的变异输入能够被目标程序理解并执行”。
2 测试桩的设计原则
为确保模糊测试效率和漏洞发现能力,测试桩设计需遵循以下原则:
-
高覆盖率
需覆盖目标程序中所有可能存在漏洞的接口(本示例项目中涉及字符串拷贝、拼接、解析等),避免遗漏潜在风险点。 -
输入解析灵活性
模糊工具生成的输入是二进制数据流,测试桩需将其灵活拆分为多个参数(如将一段数据拆分为两个字符串,分别传给concat_strings
的两个参数)。 -
无干扰性
自身代码应避免引入内存泄漏、崩溃等问题(如正确释放动态内存),否则会干扰对目标程序漏洞的判断。 -
兼容性
需适配模糊工具的调用方式(如honggfuzz通过命令行参数传递测试用例文件路径)。
3 测试桩的完整实现步骤(以文章示例项目为例)
1. 明确目标接口
目标库stringutils
包含4个可能存在漏洞的函数,测试桩需调用这些函数:
// stringutils.h(目标库头文件)
char* copy_string(const char* src); // 字符串拷贝(可能有缓冲区溢出)
char* concat_strings(const char* a, const char* b); // 字符串拼接(可能有空指针解引用)
char* repeat_string(const char* s, int times); // 字符串重复(可能有整数溢出)
char* substring(const char* s, int start, int len); // 子串提取(可能有越界访问)
void free_string(char* ptr); // 释放内存
2. 编写测试桩代码(tester.cpp)
测试桩需读取honggfuzz生成的测试用例文件,解析后调用上述接口:代码在章节二的步骤1中已经全部展示。
3. 编译测试桩(与项目代码联动)
测试桩必须与目标库一起编译,才能正确调用其函数。以CMake为例,CMakeLists.txt
配置如下:
cmake_minimum_required(VERSION 3.10)
project(stringutils_fuzz)
# 启用调试符号和AddressSanitizer(关键:检测内存漏洞)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -fsanitize=address -O1")
# 编译目标库(stringutils)
add_library(stringutils STATIC src/stringutils.cpp)
# 编译测试桩(链接目标库)
add_executable(stringutils_tester fuzz/tester.cpp)
target_link_libraries(stringutils_tester stringutils)
编译后生成可执行文件:
mkdir build && cd build
cmake ..
make # 生成 stringutils_tester
4. 用honggfuzz调用测试桩
测试桩编译完成后,通过honggfuzz启动模糊测试:
honggfuzz -i ../inputs -o ../results -n $(nproc) -- ./stringutils_tester ___FILE___
___FILE___
:honggfuzz自动替换为生成的测试用例文件路径,由测试桩的main
函数接收并解析。
4 测试桩的优化技巧
-
参数多样化
数值参数(如repeat_times
、sub_start
)应基于输入数据动态生成(而非固定值),增加触发边界条件(如负数、超大值)的概率。 -
覆盖异常路径
主动构造可能触发异常的输入(如空字符串、超长字符串) -
减少冗余操作
避免在测试桩中添加复杂逻辑(如日志打印、结果验证),以免拖慢测试效率。模糊测试的核心是“触发崩溃”,而非“验证正确性”。 -
内存安全
测试桩自身需严格管理内存(如正确释放分配的缓冲区),避免因自身漏洞干扰测试结果(例如,测试桩的内存泄漏可能被误认为是目标库的漏洞)。
四、测试闭源程序
上述示例代码在编译后,build
目录下生成文件:libstringutils.so
,假设实际项目中没有该 so 文件的源码,如何进行对其进行 Fuzz 测试呢?
闭源 / 无源码的二进制库,优先选择 QEMU 模式。
QEMU 模式属于黑盒插桩(black-box instrumentation
),可直接对二进制库进行模糊测试,无需通过逆向工程分析其内部逻辑或获取源码。例如,若需测试该库的字符串处理函数(如 strcpy
、strcmp
等)是否存在漏洞,可通过 QEMU 模式加载库并对其接口输入进行变异,观察是否触发崩溃(如缓冲区溢出、空指针引用等)。
1 编译 Honggfuzz 定制化 QEMU
Honggfuzz 的 QEMU 模式依赖定制化的 QEMU 二进制文件,需先编译:
# 进入 Honggfuzz 的 qemu_mode 目录
cd honggfuzz/qemu_mode
# 编译支持的目标架构(这一步要科学上网,实际是clone操作)
make qemu_bin
# 编译完成后,QEMU 二进制文件会生成在如下路径(以 x86_64 为例)
# honggfuzz-qemu/x86_64-linux-user/qemu-x86_64
2 确认目标库信息
libstringutils.so
已放在当前目录,确认其架构和符号表:
3 准备测试程序
QEMU 模式需通过一个调用 libstringutils.so
的可执行程序作为测试入口( test.c
),该程序需满足:
- 接收输入数据(如从文件或标准输入读取)。
- 调用
libstringutils.so
中的目标函数(这里示例代码写一部分要测试的函数)。
#include <stdio.h>
#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
// 定义函数指针类型(根据 nm 提取的函数名推测)
typedef char* (*str_trim_t)(const char*); // 去除首尾空格
typedef char* (*str_concat_t)(const char*, const char*); // 拼接字符串
typedef char* (*str_repeat_t)(const char*, int); // 重复字符串
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "用法: %s <输入文件>\n", argv[0]);
return 1;
}
// 加载闭源库
void* handle = dlopen("./libstringutils.so", RTLD_LAZY);
if (!handle) {
fprintf(stderr, "加载库失败: %s\n", dlerror());
return 1;
}
// 解析函数指针(忽略不存在的函数)
str_trim_t str_trim = (str_trim_t)dlsym(handle, "str_trim");
str_concat_t str_concat = (str_concat_t)dlsym(handle, "str_concat");
str_repeat_t str_repeat = (str_repeat_t)dlsym(handle, "str_repeat");
// 读取模糊测试输入(Honggfuzz 生成的变异数据)
FILE* f = fopen(argv[1], "rb");
if (!f) {
perror("打开输入文件失败");
dlclose(handle);
return 1;
}
fseek(f, 0, SEEK_END);
size_t len = ftell(f);
fseek(f, 0, SEEK_SET);
char* input = malloc(len + 1);
fread(input, 1, len, f);
input[len] = '\0';
fclose(f);
// 调用库函数(传入输入数据,覆盖所有导出函数)
if (str_trim) {
char* res = str_trim(input);
if (res && res != input) free(res); // 释放库分配的内存
}
if (str_concat) {
size_t half = len / 2;
char* part1 = input;
char* part2 = input + half;
char* res = str_concat(part1, part2);
if (res) free(res);
}
if (str_repeat) {
int times = (len % 10) + 1;
char* res = str_repeat(input, times);
if (res) free(res);
}
// 清理资源
free(input);
dlclose(handle);
return 0;
}
4 编译测试程序
无需插桩(QEMU 会动态插桩),直接编译:
gcc test.c -o test_wrapper -ldl # -ldl 用于动态加载库
5 准备初始输入语料库
参考章节二的步骤1
# 创建存放测试用例的目录
mkdir -p inputs
# 生成正常字符串用例
echo "normal string" > inputs/normal.txt
echo "test concat part1" > inputs/concat1.txt
echo "test concat part2" > inputs/concat2.txt
6 启动 QEMU 模式测试
# 启动测试
honggfuzz -i ./inputs -o hfuzz_results --timeout 5000 -- /home/jerry/tools/honggfuzz/qemu_mode/honggfuzz-qemu/x86_64-linux-user/qemu-x86_64 test.c ___FILE___
文章总结
本文围绕 Honggfuzz 展开,重点介绍其在 Ubuntu 系统中的应用,核心内容如下:
-
环境搭建:通过安装依赖工具(git、clang 等)、克隆源码并编译安装,完成 Honggfuzz 部署,可通过版本验证确认安装成功。
-
实战流程:以字符串处理库为测试目标,先准备多样化初始测试用例(正常字符串、空字符串、特殊字符等);编译目标项目时启用 AddressSanitizer 增强漏洞检测;运行 Honggfuzz 时指定输入输出目录、线程数等参数,对编译好的程序进行模糊测试。
-
测试桩作用:作为 Honggfuzz 与目标程序的适配层,负责接收输入、转换参数并调用目标函数,其设计需遵循高覆盖率、输入灵活解析等原则,以保障测试效果。