Honggfuzz 的环境搭建与使用案例总结

前言

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)
1copy_string堆缓冲区溢出(heap-overflow)long65.txt(超长字符串)第14行
2concat_strings堆缓冲区溢出(heap-overflow)special_chars.txt(特殊字符)第23行
3repeat_string堆缓冲区溢出(heap-overflow)覆盖率文件(中间变异用例)第38行
4substring段错误(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项核心任务:

  1. 输入桥梁:接收模糊测试工具(honggfuzz)生成的变异输入(通常是文件或内存数据)。
  2. 参数转换:将原始输入解析为目标函数所需的参数(如字符串、整数、结构体等)。
  3. 执行触发:调用目标程序中可能存在漏洞的函数(如字符串处理、协议解析函数),将解析后的参数传入,触发潜在漏洞。

简单来说,测试桩的使命是“让模糊工具的变异输入能够被目标程序理解并执行”。

2 测试桩的设计原则

为确保模糊测试效率和漏洞发现能力,测试桩设计需遵循以下原则:

  1. 高覆盖率
    需覆盖目标程序中所有可能存在漏洞的接口(本示例项目中涉及字符串拷贝、拼接、解析等),避免遗漏潜在风险点。

  2. 输入解析灵活性
    模糊工具生成的输入是二进制数据流,测试桩需将其灵活拆分为多个参数(如将一段数据拆分为两个字符串,分别传给concat_strings的两个参数)。

  3. 无干扰性
    自身代码应避免引入内存泄漏、崩溃等问题(如正确释放动态内存),否则会干扰对目标程序漏洞的判断。

  4. 兼容性
    需适配模糊工具的调用方式(如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 测试桩的优化技巧

  1. 参数多样化
    数值参数(如repeat_timessub_start)应基于输入数据动态生成(而非固定值),增加触发边界条件(如负数、超大值)的概率。

  2. 覆盖异常路径
    主动构造可能触发异常的输入(如空字符串、超长字符串)

  3. 减少冗余操作
    避免在测试桩中添加复杂逻辑(如日志打印、结果验证),以免拖慢测试效率。模糊测试的核心是“触发崩溃”,而非“验证正确性”。

  4. 内存安全
    测试桩自身需严格管理内存(如正确释放分配的缓冲区),避免因自身漏洞干扰测试结果(例如,测试桩的内存泄漏可能被误认为是目标库的漏洞)。

四、测试闭源程序

上述示例代码在编译后,build 目录下生成文件:libstringutils.so,假设实际项目中没有该 so 文件的源码,如何进行对其进行 Fuzz 测试呢?

闭源 / 无源码的二进制库,优先选择 QEMU 模式。

QEMU 模式属于黑盒插桩(black-box instrumentation),可直接对二进制库进行模糊测试,无需通过逆向工程分析其内部逻辑或获取源码。例如,若需测试该库的字符串处理函数(如 strcpystrcmp 等)是否存在漏洞,可通过 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 系统中的应用,核心内容如下:

  1. 环境搭建:通过安装依赖工具(git、clang 等)、克隆源码并编译安装,完成 Honggfuzz 部署,可通过版本验证确认安装成功。

  2. 实战流程:以字符串处理库为测试目标,先准备多样化初始测试用例(正常字符串、空字符串、特殊字符等);编译目标项目时启用 AddressSanitizer 增强漏洞检测;运行 Honggfuzz 时指定输入输出目录、线程数等参数,对编译好的程序进行模糊测试。

  3. 测试桩作用:作为 Honggfuzz 与目标程序的适配层,负责接收输入、转换参数并调用目标函数,其设计需遵循高覆盖率、输入灵活解析等原则,以保障测试效果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

介一笔记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值