首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
二进制漏洞
发新帖
12
5
[原创]libFuzzer模糊测试引擎调研与自定义开发
发表于: 2024-10-4 15:19
13859
[原创]libFuzzer模糊测试引擎调研与自定义开发
Brinmon
9
2024-10-4 15:19
13859
# libFuzzer类库模糊测试引擎调研 ## 一、简介 **项目地址**:<a href="elink@53aK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4G2L8i4m8A6L8r3g2J5i4K6u0V1M7Y4c8Q4x3V1k6D9K9h3u0Q4x3V1k6X3N6i4A6*7k6i4t1`.">llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)</a> libFuzzer 是由 LLVM 项目开发的覆盖率引导模糊测试引擎,广泛用于自动发现软件中的漏洞和错误。它通过不断生成和测试输入数据,提高代码覆盖率,找到潜在的缺陷。libFuzzer 尤其适合测试用 C 和 C++ 编写的代码。 ## 二、libFuzzer 的模块结构 libFuzzer 是一个面向库的模糊测试引擎,广泛用于发现软件中的漏洞。了解其模块结构有助于深入理解其工作原理和扩展能力。以下是 libFuzzer 的主要模块结构: 1. **FuzzerDriver** - **功能**:这是 libFuzzer 的核心入口点,负责初始化模糊测试环境、读取输入文件、启动模糊测试循环。 - **关键函数**: - `main`: 入口函数,负责解析命令行参数并调用 `FuzzerDriver`。 - `FuzzerDriver`: 核心循环,包含模糊测试的主逻辑。 2. **Mutation** - **功能**:负责生成新的测试用例,通过变异(Mutation)已有的输入数据来发现更多潜在的漏洞。 - **关键函数**: - `Mutate`: 对输入数据进行变异。 - `CrossOver`: 将两个输入数据交叉生成新的输入。 - **相关文件**:`FuzzerMutate.cpp`, `FuzzerMutate.h` 3. **Corpus** - **功能**:管理测试用例集合(Corpus),包括收集和存储有效的测试用例。 - **关键函数**: - `AddToCorpus`: 将新的测试用例添加到集合中。 - `LoadCorpus`: 从磁盘加载测试用例集合。 - **相关文件**:`FuzzerCorpus.cpp`, `FuzzerCorpus.h` 4. **Coverage** - **功能**:收集代码覆盖率信息,以便评估测试用例的有效性和指导变异策略。 - **关键函数**: - `UpdateCoverage`: 更新覆盖率信息。 - `GetCoverage`: 获取当前的覆盖率。 - **相关文件**:`FuzzerTracePC.cpp`, `FuzzerTracePC.h` 5. **Crash Detection** - **功能**:检测程序崩溃,并记录相关的崩溃信息以便后续分析。 - **关键函数**: - `HandleCrash`: 处理崩溃事件。 - `ReportCrash`: 报告崩溃信息。 - **相关文件**:`FuzzerFork.cpp`, `FuzzerFork.h` 6. **FuzzerInterface** - **功能**:定义模糊测试引擎的接口,包括用户需要实现的目标函数。 - **关键函数**: - `LLVMFuzzerTestOneInput`: 用户提供的函数,libFuzzer 将变异后的输入传递给该函数进行测试。 - **相关文件**:`FuzzerInterface.h` 7. **Options** - **功能**:管理 libFuzzer 的配置选项,允许用户通过命令行参数或配置文件来调整模糊测试行为。 - **关键函数**: - `ParseFlags`: 解析命令行参数。 - `PrintHelp`: 打印帮助信息。 - **相关文件**:`FuzzerOptions.cpp`, `FuzzerOptions.h` 8. **Utils** - **功能**:提供各种实用工具函数,用于日志记录、内存管理等。 - **关键函数**: - `Print`: 打印日志信息。 - `AllocateMemory`: 分配内存。 - **相关文件**:`FuzzerUtil.cpp`, `FuzzerUtil.h` 9. **MainLoop** - **功能**:控制模糊测试的主循环,协调各模块的工作。 - **关键函数**: - `RunOne`: 运行一个测试用例。 - `Loop`: 主循环,反复调用 `RunOne` 进行模糊测试。 - **相关文件**:`FuzzerLoop.cpp`, `FuzzerLoop.h` ## 三、libFuzzer 功能详解 libFuzzer 的主要功能包括: - **覆盖率引导测试**:通过覆盖率信息指导输入生成,使测试尽可能覆盖更多代码路径。 - **最小化崩溃输入**:自动最小化导致崩溃的输入,便于调试。 - **多样化输入生成**:支持多种输入生成策略,包括基于变异的输入生成。 - **词典支持**:支持用户提供的词典文件,以增强输入生成的效果。 - **内存错误检测**:结合 AddressSanitizer 使用,可以检测到各种内存错误,如缓冲区溢出和使用未初始化内存等。 - **并行模糊测试**:libFuzzer可以指定行运行加速模糊测试、提高覆盖率。 - **fork模式**:它通过创建多个子进程来同时进行模糊测试,从而提高测试效率。 ## 四、libFuzzer源码解析 ### Libfuzzer的主类构成 源码位置:<a href="elink@28eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4G2L8i4m8A6L8r3g2J5i4K6u0V1M7Y4c8Q4x3V1k6D9K9h3u0Q4x3V1k6X3N6i4A6*7k6i4t1`.">llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)</a> 这个 `Fuzzer` 类的构成可以分为以下几个部分: #### 1. **构造函数与析构函数** - `Fuzzer(UserCallback CB, InputCorpus &Corpus, MutationDispatcher &MD, FuzzingOptions Options);` - `~Fuzzer();` #### 2. **公有成员函数(Public Member Functions)** - **主要功能函数:** - `void Loop(Vector<SizedFile> &CorporaFiles);` // 主循环,执行模糊测试过程 - `void ReadAndExecuteSeedCorpora(Vector<SizedFile> &CorporaFiles);` // 读取并执行种子语料 - `void MinimizeCrashLoop(const Unit &U);` // 最小化崩溃处理 - `void RereadOutputCorpus(size_t MaxSize);` // 重新读取输出语料库 - **时间与状态函数:** - `size_t secondsSinceProcessStartUp();` // 获取从进程启动到现在的秒数 - `bool TimedOut();` // 检查是否超时 - `size_t execPerSec();` // 计算每秒执行的次数 - `size_t getTotalNumberOfRuns();` // 获取总执行次数 - **静态回调函数(Static Callback Functions):** - `static void StaticAlarmCallback();` - `static void StaticCrashSignalCallback();` - `static void StaticExitCallback();` - `static void StaticInterruptCallback();` - `static void StaticFileSizeExceedCallback();` - `static void StaticGracefulExitCallback();` - **其他功能函数:** - `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);` // 崩溃情况下的内部合并步骤 - `MutationDispatcher &GetMD();` // 获取变异调度器 - `void PrintFinalStats();` // 打印最终统计信息 - `void SetMaxInputLen(size_t MaxInputLen);` // 设置最大输入长度 - `void SetMaxMutationLen(size_t MaxMutationLen);` // 设置最大变异长度 - `void RssLimitCallback();` // 内存使用限制的回调 - `bool InFuzzingThread() const;` // 检查是否在模糊测试线程中 - `size_t GetCurrentUnitInFuzzingThead(const uint8_t **Data) const;` // 获取模糊测试线程中的当前单元数据 - `void TryDetectingAMemoryLeak(const uint8_t *Data, size_t Size, bool DuringInitialCorpusExecution);` // 尝试检测内存泄漏 - `void HandleMalloc(size_t Size);` // 处理内存分配操作 - `static void MaybeExitGracefully();` // 可能会优雅退出程序 - `std::string WriteToOutputCorpus(const Unit &U);` // 将单元写入输出语料库 #### 3. **私有成员函数(Private Member Functions)** - `void AlarmCallback();` // 闹钟信号的回调 - `void CrashCallback();` // 崩溃信号的回调 - `void ExitCallback();` // 程序退出的回调 - `void CrashOnOverwrittenData();` // 数据被覆盖时崩溃的回调 - `void InterruptCallback();` // 中断信号的回调 - `void MutateAndTestOne();` // 变异并测试一个单元 - `void PurgeAllocator();` // 清理内存分配器 - `void ReportNewCoverage(InputInfo *II, const Unit &U);` // 报告新覆盖率 - `void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size);` // 打印状态并报告慢速输入 - `void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix);` // 使用前缀将单元写入文件 - `void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0, size_t Features = 0);` // 打印统计信息 - `void PrintStatusForNewUnit(const Unit &U, const char *Text);` // 打印新单元的状态信息 - `void CheckExitOnSrcPosOrItem();` // 检查是否需要退出 - `static void StaticDeathCallback();` // 处理死亡信号的回调 - `void DumpCurrentUnit(const char *Prefix);` // 转储当前单元数据 - `void DeathCallback();` // 死亡信号的回调 - `void AllocateCurrentUnitData();` // 分配当前单元数据 #### 4. **私有成员变量(Private Member Variables)** - `uint8_t *CurrentUnitData = nullptr;` // 当前单元数据指针 - `std::atomic<size_t> CurrentUnitSize;` // 当前单元大小(原子变量) - `uint8_t BaseSha1[kSHA1NumBytes];` // 基本单元的SHA1校验和 - `bool GracefulExitRequested = false;` // 是否请求优雅退出 - `size_t TotalNumberOfRuns = 0;` // 总执行次数 - `size_t NumberOfNewUnitsAdded = 0;` // 新单元的数量 - `size_t LastCorpusUpdateRun = 0;` // 上次语料库更新的运行次数 - `bool HasMoreMallocsThanFrees = false;` // 是否有比释放更多的内存分配操作 - `size_t NumberOfLeakDetectionAttempts = 0;` // 检测内存泄漏的尝试次数 - `system_clock::time_point LastAllocatorPurgeAttemptTime = system_clock::now();` // 上次内存分配器清理的时间点 - `UserCallback CB;` // 用户回调函数 - `InputCorpus &Corpus;` // 输入语料库的引用 - `MutationDispatcher &MD;` // 变异调度器的引用 - `FuzzingOptions Options;` // 模糊测试选项 - `DataFlowTrace DFT;` // 数据流跟踪对象 - `system_clock::time_point ProcessStartTime = system_clock::now();` // 进程启动时间 - `system_clock::time_point UnitStartTime;` // 当前单元开始时间 - `system_clock::time_point UnitStopTime;` // 当前单元结束时间 - `long TimeOfLongestUnitInSeconds = 0;` // 最长单元执行时间(秒) - `long EpochOfLastReadOfOutputCorpus = 0;` // 最后读取输出语料库的时间 - `size_t MaxInputLen = 0;` // 最大输入长度 - `size_t MaxMutationLen = 0;` // 最大变异长度 - `size_t TmpMaxMutationLen = 0;` // 临时最大变异长度 - `Vector<uint32_t> UniqFeatureSetTmp;` // 唯一特征集的临时存储 - `static thread_local bool IsMyThread;` // 用于标识当前线程是否是模糊测试线程 这个类主要涉及模糊测试的核心功能,包括种子语料库的处理、崩溃管理、变异调度、统计信息打印等多个方面。 ### libFuzzer的核心运行流程 Libfuzzer源码学习项目:<a href="elink@82eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6p5L8%4t1I4M7#2)9J5c8X3I4A6j5X3k6#2P5Y4A6W2M7W2)9J5k6s2N6G2M7X3E0K6K9r3!0H3">Dor1s/libfuzzer-workshop: Repository for materials of "Modern fuzzing of C/C++ Projects" workshop. (github.com)</a> libfuzzer最新源码位置:<a href="elink@dbbK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6@1M7X3g2W2i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4G2L8i4m8A6L8r3g2J5i4K6u0V1M7Y4c8Q4x3V1k6D9K9h3u0Q4x3V1k6X3N6i4A6*7k6i4t1`.">llvm-project/compiler-rt/lib/fuzzer at main · llvm/llvm-project (github.com)</a> 源码讲解:<mark class="encrypted">eaaK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3g2T1K9h3I4A6j5X3W2D9K9g2)9J5k6h3y4G2L8g2)9J5c8Y4k6A6k6r3g2G2i4K6u0r3b7W2j5I4f1V1R3@1P5e0q4J5y4K6c8H3i4K6u0r3i4K6y4r3M7%4m8E0i4K6g2X3K9h3c8Q4y4h3k6X3M7X3!0E0i4K6y4p5x3K6x3K6i4K6u0W2y4K6R3^5</mark> libFuzzer 是 LLVM 项目中的一部分,专门用于通过模糊测试来自动化测试 C/C++ 项目中的漏洞。其核心流程大致分为以下几个步骤: #### 1. **主函数入口 (`FuzzerMain.cpp`)**: 主函数入口代码如下: ```c #include "FuzzerDefs.h" extern "C" { // 该函数由用户定义,用于测试目标函数 int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size); } // extern "C" ATTRIBUTE_INTERFACE int main(int argc, char **argv) { return fuzzer::FuzzerDriver(&argc, &argv, LLVMFuzzerTestOneInput); } ``` 在这个主函数中,调用了 `fuzzer::FuzzerDriver` 函数,并传入了用户定义的 `LLVMFuzzerTestOneInput` 函数。`LLVMFuzzerTestOneInput` 是由用户编写的,用于定义模糊测试的目标函数,它接收输入数据进行测试并返回测试结果。`FuzzerDriver` 函数则负责模糊测试的核心逻辑。 #### 2. **进入核心函数 `FuzzerDriver` (`FuzzerDriver.cpp`)**: `FuzzerDriver` 是 libFuzzer 的核心函数,它的主要作用是设置和启动模糊测试。函数签名如下: ```c 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); } ``` - 核心步骤包括: - **对象初始化**:创建并初始化负责输入变异的 `MutationDispatcher`、输入语料库 `InputCorpus` 以及核心 `Fuzzer` 对象。 - `MutationDispatcher`:负责生成新的输入变异,目的是探索更多可能的代码路径。 - `InputCorpus`:管理模糊测试所使用的输入数据集合(语料库)。 - `Fuzzer`:负责执行具体的模糊测试流程,调用用户定义的 `LLVMFuzzerTestOneInput`。 - **解析命令行选项**:处理用户通过命令行传递的选项,包括模糊测试的各种配置参数。 - **处理语料库**:通过 `ReadCorpora` 函数从输入文件中读取语料库文件,并调用 `Fuzzer::Loop` 函数启动模糊测试的主循环。 - **主循环 (Fuzzer::Loop)**: `Loop` 是 libFuzzer 中用于执行模糊测试的核心循环。它会不断从语料库中提取输入数据,进行输入变异,并将变异后的输入传递给 `LLVMFuzzerTestOneInput` 进行测试。循环的目的是通过广泛探索输入空间,发现导致程序崩溃或行为异常的输入,从而发现潜在的漏洞。 - **变异引擎**: `MutationDispatcher` 是 libFuzzer 的变异引擎,它会生成各种输入数据变种,覆盖尽可能多的代码路径。变异策略包括: - 位变异、字节翻转 - 随机插入或删除数据 - 拼接不同输入的部分 通过不断生成新的变种,libFuzzer 可以更深入地挖掘目标程序中的代码路径,进而发现隐藏的错误或漏洞。 - **测试结果收集**: 每次输入变异后,libFuzzer 会将结果反馈给 `Fuzzer` 对象,判断程序是否崩溃或异常。出现崩溃时,libFuzzer 会记录触发崩溃的输入,并终止循环进行进一步分析。 # libFuzzer源码编译环境与使用 ## 源码安装 Clang 更多版本:<a href="elink@87cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6J5k6h3I4W2j5i4y4W2M7#2)9J5c8Y4c8S2k6#2)9J5c8X3I4D9N6X3#2G2M7X3N6Q4x3X3b7I4z5q4)9J5k6e0q4Q4x3X3f1^5">Release LLVM 18.1.8 · llvm/llvm-project (github.com)</a> libFuzzer的源码根据需要下载对应版本进行源码编译。 一键配置clang脚本:,该脚本在Ubuntu20上成功测试运行过: ```bash #!/bin/bash # 安装 Clang 的依赖 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}" # 检查 Clang 是否已下载 if [ -f "${CLANG_TAR}" ]; then echo "Clang 已经下载 (${CLANG_TAR})" else echo "正在下载 Clang..." wget "${CLANG_URL}" fi # 检查 Clang 是否已安装 if [ -d "${CLANG_INSTALL_DIR}" ]; then echo "Clang 已经安装在 ${CLANG_INSTALL_DIR}" else echo "正在安装 Clang..." # 创建目录 sudo mkdir -p "${CLANG_INSTALL_DIR}" echo "正在安装 解压时间比较久..." # 解压 Clang sudo tar -xf "${CLANG_TAR}" -C "${CLANG_INSTALL_DIR}" --strip-components=1 fi # 检查环境变量是否已经设置 if ! grep -q "${CLANG_INSTALL_DIR}/bin" ~/.bashrc; then echo "添加环境变量..." echo "export PATH=${CLANG_INSTALL_DIR}/bin:\$PATH" >> ~/.bashrc echo "export LD_LIBRARY_PATH=${CLANG_INSTALL_DIR}/lib:\$LD_LIBRARY_PATH" >> ~/.bashrc # 使环境变量生效 source ~/.bashrc else echo "环境变量已经配置" fi # 运行 Clang 验证安装 echo "验证 Clang 安装..." clang --version echo "如果验证失败再次运行该命令: source ~/.bashrc" ``` ## libFuzzer与覆盖率有关的命令行使用 ### libfuzzer与覆盖率有关的命令行 与覆盖率相关的选项如下: ```cpp // 是否打印新覆盖的 PC 地址 Options.PrintNewCovPcs = Flags.print_pcs; // 是否打印新覆盖的函数 Options.PrintNewCovFuncs = Flags.print_funcs; // 是否打印覆盖率信息 Options.PrintCoverage = Flags.print_coverage; // 是否打印完整的覆盖率信息 Options.PrintFullCoverage = Flags.print_full_coverage; ``` 以下是与覆盖率相关的选项的使用方法和案例说明: #### 1. **`Options.PrintNewCovPcs = Flags.print_pcs`** - **功能**: 打印新的覆盖 PC(程序计数器)地址。 - **使用方法**: 当你希望在测试过程中显示每次新发现的代码路径(对应的 PC 地址)时,可以启用此选项。 - **命令行示例**: ```bash ./woff2-2016-05-06-fsanitize_fuzzer -print_pcs=1 ``` - **案例输出**: ``` INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 2544448443 INFO: Loaded 1 modules (10708 inline 8-bit counters): 10708 [0x562f5c3345c0, 0x562f5c336f94), INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x562f5c336f98,0x562f5c360cd8), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: A corpus is not provided, starting from an empty corpus #2 INITED cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb NEW_PC: 0x562f5c220771 in ReadWOFF2Header /home/ub20/FTS/woff/SRC/src/woff2_dec.cc:987:7 #21 NEW cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 4 CrossOver-InsertByte-ChangeBit-CopyPart- NEW_PC: 0x562f5c2161a8 in ReadU32 /home/ub20/FTS/woff/SRC/src/./buffer.h:127:9 #999 NEW cov: 17 ft: 18 corp: 3/9b lim: 11 exec/s: 0 rss: 34Mb L: 4/4 MS: 3 ChangeByte-ShuffleBytes-CMP- DE: "wOF2"- NEW_PC: 0x562f5c22077c in ReadU32 /home/ub20/FTS/woff/SRC/src/./buffer.h:127:9 #1300 NEW cov: 18 ft: 19 corp: 4/17b lim: 11 exec/s: 0 rss: 34Mb L: 8/8 MS: 1 PersAutoDict- DE: "wOF2"- NEW_PC: 0x562f5c22103f in ReadWOFF2Header /home/ub20/FTS/woff/SRC/src/woff2_dec.cc:995:7 #1618 NEW cov: 19 ft: 20 corp: 5/29b lim: 14 exec/s: 0 rss: 34Mb L: 12/12 MS: 3 InsertRepeatedBytes-PersAutoDict-InsertRepeatedBytes- DE: "wOF2"- ``` #### 2. **`Options.PrintNewCovFuncs = Flags.print_funcs`** - **功能**: 打印新覆盖的函数。 - **使用方法**: 当你想知道每次新发现的函数覆盖时,可以启用此选项。 - **命令行示例**: ```bash ./woff2-2016-05-06-fsanitize_fuzzer -print_funcs=1 ``` - **案例输出**: ``` ub20@ub20:~/FTS/woff$ ./woff2-2016-05-06-fsanitize_fuzzer -print_funcs=1 INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 2688967800 INFO: Loaded 1 modules (10708 inline 8-bit counters): 10708 [0x55b819abf5c0, 0x55b819ac1f94), INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x55b819ac1f98,0x55b819aebcd8), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: A corpus is not provided, starting from an empty corpus #2 INITED cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb #10 NEW cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 32Mb L: 4/4 MS: 3 CrossOver-InsertByte-CrossOver- ... #144927 NEW cov: 29 ft: 30 corp: 14/437b lim: 1390 exec/s: 48309 rss: 47Mb L: 119/119 MS: 1 PersAutoDict- DE: "\012\000\000\000\000\000\000\000"- NEW_FUNC[1/1]: 0x55b8199a0050 in woff2::ReadBase128(woff2::Buffer*, unsigned int*) /home/ub20/FTS/woff/SRC/src/variable_length.cc:94 ... ``` #### 3. **`Options.PrintCoverage = Flags.print_coverage`** - **功能**: 打印覆盖率信息。 - **使用方法**: 启用此选项可以在 fuzzing 过程中定期打印当前的覆盖率统计信息。 - **命令行示例**: ```bash ./openssl-1.0.1f-fsanitize_fuzzer -print_coverage=1 ``` - **案例输出**: ``` UNCOVERED_FUNC: hits: 0 edges: 0/5 Init() /home/ub20/FTS/openssl-1.0.1f/target.cc:9 COVERED_FUNC: hits: 42 edges: 2/5 LLVMFuzzerTestOneInput /home/ub20/FTS/openssl-1.0.1f/target.cc:26 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:27 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:27 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f/target.cc:0 COVERED_FUNC: hits: 42 edges: 19/34 ssleay_rand_add /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:194 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:228 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:228 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:249 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:249 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:260 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:263 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:266 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:294 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:294 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:0 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:306 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:0 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:311 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:321 UNCOVERED_PC: /home/ub20/FTS/openssl-1.0.1f_crash/BUILD/crypto/rand/md_rand.c:322 ``` #### 4. **`Options.PrintFullCoverage = Flags.print_full_coverage`** - **功能**: 打印完整的覆盖率信息。 - **使用方法**: 启用此选项时,程序会在结束时输出完整的覆盖率报告,详细列出所有覆盖到的代码。 - **命令行示例**: ```bash ./woff2-2016-05-06-fsanitize_fuzzer -print_full_coverage=1 ``` - **案例输出**: ``` ub20@ub20:~/FTS/woff$ ./woff2-2016-05-06-fsanitize_fuzzer -print_coverage=1 INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 3116423339 INFO: Loaded 1 modules (10708 inline 8-bit counters): 10708 [0x5652816785c0, 0x56528167af94), INFO: Loaded 1 PC tables (10708 PCs): 10708 [0x56528167af98,0x5652816a4cd8), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: A corpus is not provided, starting from an empty corpus #2 INITED cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 32Mb #27 NEW cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 5 ChangeBit-ChangeBit-ChangeBinInt-CopyPart-CopyPart- #433 NEW cov: 17 ft: 18 corp: 3/10b lim: 6 exec/s: 0 rss: 33Mb L: 5/5 MS: 1 CMP- DE: "wOF2"- #636 REDUCE cov: 17 ft: 18 corp: 3/9b lim: 6 exec/s: 0 rss: 33Mb L: 4/4 MS: 3 ShuffleBytes-ChangeBit-EraseBytes- #884 REDUCE cov: 18 ft: 19 corp: 4/17b lim: 8 exec/s: 0 rss: 34Mb L: 8/8 MS: 3 InsertRepeatedBytes-ChangeByte-PersAutoDict- DE: "wOF2"- #1635 NEW cov: 19 ft: 20 corp: 5/29b lim: 14 exec/s: 0 rss: 34Mb L: 12/12 MS: 1 CrossOver- #3505 NEW cov: 20 ft: 21 corp: 6/58b lim: 29 exec/s: 0 rss: 35Mb L: 29/29 MS: 5 PersAutoDict-CrossOver-InsertRepeatedBytes-ChangeByte-ChangeBinInt- DE: "wOF2"- #4629 NEW cov: 21 ft: 22 corp: 7/87b lim: 38 exec/s: 0 rss: 35Mb L: 29/29 MS: 4 ChangeBit-CopyPart-ShuffleBytes-ChangeBit- #9279 REDUCE cov: 21 ft: 22 corp: 7/72b lim: 80 exec/s: 0 rss: 35Mb L: 14/29 MS: 5 EraseBytes-EraseBytes-ChangeBit-EraseBytes-ChangeBinInt- #9535 REDUCE cov: 22 ft: 23 corp: 8/86b lim: 80 exec/s: 0 rss: 35Mb L: 14/29 MS: 1 ChangeByte- #27928 REDUCE cov: 23 ft: 24 corp: 9/99b lim: 261 exec/s: 0 rss: 37Mb L: 13/29 MS: 3 ChangeBit-EraseBytes-ChangeBinInt- #31817 NEW cov: 24 ft: 25 corp: 10/120b lim: 293 exec/s: 0 rss: 37Mb L: 21/29 MS: 4 ChangeBit-EraseBytes-ChangeBit-ChangeBinInt- #40024 REDUCE cov: 24 ft: 25 corp: 10/119b lim: 373 exec/s: 0 rss: 38Mb L: 12/29 MS: 2 EraseBytes-CMP- DE: "\001\000\000\000\000\000\000\014"- #42588 NEW cov: 25 ft: 26 corp: 11/155b lim: 397 exec/s: 0 rss: 38Mb L: 36/36 MS: 4 CrossOver-CopyPart-InsertByte-ChangeBinInt- ^C==12029== libFuzzer: run interrupted; exiting COVERAGE: UNCOVERED_FUNC: hits: 0 edges: 0/19 brotli::ZopfliCreateCommands(unsigned long, unsigned long, unsigned long, std::vector<unsigned int, std::allocator<unsigned int>> const&, brotli::ZopfliNode const*, int*, unsigned long*, brotli::Command*, unsigned long*) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:437 UNCOVERED_FUNC: hits: 0 edges: 0/18 brotli::Command::Command(unsigned long, unsigned long, unsigned long, unsigned long) /home/ub20/FTS/woff/BROTLI/enc/././command.h:102 UNCOVERED_FUNC: hits: 0 edges: 0/31 brotli::ZopfliComputeShortestPath(unsigned long, unsigned long, unsigned char const*, unsigned long, unsigned long, int const*, brotli::HashToBinaryTree*, brotli::ZopfliNode*, std::vector<unsigned int, std::allocator<unsigned int>>*) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:510 UNCOVERED_FUNC: hits: 0 edges: 0/27 brotli::ZopfliCostModel::SetFromLiteralCosts(unsigned long, unsigned long, unsigned char const*, unsigned long) /home/ub20/FTS/woff/BROTLI/enc/backward_references.cc:77 UNCOVERED_FUNC: hits: 0 edges: 0/44 brotli::HashToBinaryTree::FindAllMatches(unsigned char const*, unsigned long, unsigned long, unsigned long, unsigned long, brotli::BackwardMatch*) /home/ub20/FTS/woff/BROTLI/enc/././hash.h:681 ... ``` #### 总结 - **`-print_pcs=1`**: 适用于监控每次 fuzzing 发现的新代码路径。 - **`-print_funcs=1`**: 适用于了解每次 fuzzing 发现的新函数覆盖。 - **`-print_coverage=1`**: 适用于定期检查当前覆盖率的进展情况。 - **`-print_full_coverage=1`**: 适用于获取 fuzzing 结束后的完整覆盖率报告。 这些选项可以帮助你更好地理解 fuzzing 的进展和覆盖的代码区域。 ### libFuzzer所有可用的命令行参数: 在libfuzzer中有很多命令并未实现可以自行寻找,这是在vscode匹配命令选项的正则表达式:`Options\.\w+\s*=\s*Flags\.\w+`; ``` // 设置详细程度的级别 Options.Verbosity = Flags.verbosity; // 设置最大输入长度 Options.MaxLen = Flags.max_len; ... // 是否处理 SIGINT 信号 Options.HandleInt = Flags.handle_int; // 是否处理 SIGSEGV 信号 Options.HandleSegv = Flags.handle_segv; // 是否处理 SIGTERM 信号 Options.HandleTerm = Flags.handle_term; // 是否处理 SIGXFSZ 信号 Options.HandleXfsz = Flags.handle_xfsz; // 是否处理 SIGUSR1 信号 Options.HandleUsr1 = Flags.handle_usr1; // 是否处理 SIGUSR2 信号 Options.HandleUsr2 = Flags.handle_usr2; // 是否处理 Windows 异常 Options.HandleWinExcept = Flags.handle_winexcept; // 是否启用分叉的种子组 Options.ForkCorpusGroups = Flags.fork_corpus_groups; ``` # libFuzzer自定义开发与编译 为了满足以下二次开发需求: 1. 将模糊测试的数据导出并编写接口传递到前端页面。 2. 解决 libFuzzer 无法持续性 fuzz 的问题,如遇到 crash 就停止模糊测试。 可以通过以下方式进行源码修改与编译。 ## 1. 模糊测试的覆盖率接口编写 首先,修改 `libFuzzer/FuzzerLoop.cpp`,增加用于导出模糊测试过程中数据的接口。我们可以通过直接修改 libFuzzer 的覆盖率部分,创建接口来收集并输出信息。下面是覆盖率和其他统计信息的部分代码片段: ```cpp // 返回总的程序计数器覆盖率(PC Coverage) size_t GetTotalPCCoverage() const { return TPC.GetTotalPCCoverage(); } // 返回发现的特征数量 size_t GetNumFeatures() const { return Corpus.NumFeatures(); } // 返回活跃输入的数量 size_t GetNumActiveUnits() const { return Corpus.NumActiveUnits(); } // 返回语料库大小(以字节为单位) size_t GetSizeInBytes() const { return Corpus.SizeInBytes(); } // 返回触发重点功能(Focus Function)的输入数量 size_t GetNumInputsThatTouchFocusFunction() const { return Corpus.NumInputsThatTouchFocusFunction(); } // 返回每秒执行次数 size_t GetExecPerSec() const { return execPerSec(); } // 返回临时最大变异长度 size_t GetTmpMaxMutationLen() const { return TmpMaxMutationLen; } // 返回峰值内存使用量(以MB为单位) size_t GetPeakRSSMb() const { return GetPeakRSSMb(); } // 打印模糊测试的统计信息并导出数据 void Fuzzer::PrintStats(const char *Where, const char *End, size_t Units, size_t Features) { size_t ExecPerSec = GetExecPerSec(); if (!Options.Verbosity) return; Printf("#%zd\t%s", TotalNumberOfRuns, Where); if (size_t N = GetTotalPCCoverage()) Printf(" cov: %zd", N); if (size_t N = Features ? Features : GetNumFeatures()) Printf(" ft: %zd", N); if (!Corpus.empty()) { Printf(" corp: %zd", GetNumActiveUnits()); if (size_t N = GetSizeInBytes()) { if (N < (1 << 14)) Printf("/%zdb", N); else if (N < (1 << 24)) Printf("/%zdKb", N >> 10); else Printf("/%zdMb", N >> 20); } if (size_t FF = GetNumInputsThatTouchFocusFunction()) Printf(" focus: %zd", FF); } if (size_t Lim = GetTmpMaxMutationLen()) Printf(" lim: %zd", Lim); if (Units) Printf(" units: %zd", Units); Printf(" exec/s: %zd", ExecPerSec); Printf(" rss: %zdMb", GetPeakRSSMb()); // 添加魔改标识 Printf(" [MODIFIED] Custom version of Fuzzer::PrintStats is running!\n"); // 输出结束符 Printf("%s", End); // 可以在这里添加接口将数据传递到前端 ExportDataToFrontend({ {"TotalNumberOfRuns", TotalNumberOfRuns}, {"PCCoverage", GetTotalPCCoverage()}, {"NumFeatures", GetNumFeatures()}, {"NumActiveUnits", GetNumActiveUnits()}, {"SizeInBytes", GetSizeInBytes()}, {"ExecPerSec", GetExecPerSec()}, {"PeakRSSMb", GetPeakRSSMb()} }); } // 示例数据导出函数,可将数据通过接口传递给前端 void ExportDataToFrontend(const std::map<std::string, size_t>& data) { // 通过网络接口、文件、或者其他方式将数据传递到前端页面 // 这里可以通过 JSON 格式将数据发送到 HTTP 服务器或 WebSocket } ``` 在 `Fuzzer::PrintStats` 函数中,我们已经将覆盖率、执行次数、内存使用情况等统计信息打印,并通过 `ExportDataToFrontend` 函数导出到外部系统,可以进一步传递到前端页面。 ## 2. 解决 libFuzzer 无法持续性 fuzz 的问题 要实现 libFuzzer 的持久模糊测试,并且避免在发现 crash 后终止,可以通过修改 libFuzzer 源码来调整其行为。libFuzzer 默认行为是当检测到崩溃(如非法内存访问、堆溢出等)时终止模糊测试。为了避免这种情况,目标是允许 fuzzing 继续进行而不退出。 ### 修改思路 1. **定位崩溃处理逻辑**: libFuzzer 在检测到 crash 后会调用 `LLVMFuzzerTestOneInput`,这段代码可能会引发未处理异常,导致 fuzzing 过程终止。我们可以通过修改 libFuzzer 的 crash 处理逻辑,使其在崩溃时记录问题但继续模糊测试。 2. **关键代码修改点**: 需要查找 libFuzzer 源码中的崩溃处理位置,一般在 `FuzzerLoop.cpp` 和 `FuzzerDriver.cpp` 文件中。 - `FuzzerLoop.cpp`: 此文件控制模糊测试的主循环。在这里可以捕获崩溃异常并继续模糊测试。 - `FuzzerDriver.cpp`: 此文件负责驱动整个 libFuzzer 运行,崩溃检测和异常处理可能在这里发生。 3. **持久化模糊测试的入口**: 如果你想让模糊测试保持“持久化”,即反复利用同一测试环境进行测试,可以在 `FuzzerLoop.cpp` 中引入一个持久化模式(Persistent Mode)。这种模式会允许测试继续,而不是每次退出。 ### 示例修改步骤 1. **实现持久化模式**: 在 `FuzzerLoop.cpp` 中找到模糊测试的主要循环。将其改成持久化循环模式,像这样: ```cpp while (true) { int result = LLVMFuzzerTestOneInput(Data, Size); if (result == 0) { // 记录崩溃信息,但不终止 // 继续模糊测试循环 continue; } else { // 如果检测到崩溃,记录日志或者保存问题 LogCrash(); // 但不要退出程序,继续模糊测试 continue; } } ``` 2. **捕获崩溃并继续测试**: 修改 libFuzzer 中的 `FuzzerDriver.cpp` 文件,让崩溃不会终止程序。可以在捕获崩溃的地方加入 `try-catch` 或平台相关的异常处理机制,确保在崩溃时记录并恢复。 ```cpp try { LLVMFuzzerTestOneInput(Data, Size); } catch (...) { // 捕获异常,记录崩溃,不退出 ReportCrash(); continue; // 继续模糊测试 } ``` 3. **处理信号(Signal Handling)**: 在某些情况下,libFuzzer 可能通过信号(如 SIGSEGV, SIGABRT)来处理崩溃。你可以修改信号处理函数,确保信号不会导致进程终止。 ```cpp signal(SIGSEGV, HandleSignal); signal(SIGABRT, HandleSignal); void HandleSignal(int signal) { // 捕获信号并记录信息 LogSignalCrash(signal); // 继续执行 } ``` ### 具体修改位置: 1. **`libFuzzer/FuzzerLoop.cpp`**: 主模糊测试循环部分。 2. **`libFuzzer/FuzzerDriver.cpp`**: 驱动整个模糊测试的入口。 3. **信号处理部分**:如果有需要,检查是否需要在 `libFuzzer` 中添加或修改信号处理机制。 通过以上修改,可以避免在检测到崩溃时 libFuzzer 终止程序,并且实现持久模糊测试模式。如果你有特定的需求或问题,还可以进一步定制相关代码。 ## 3. 编译自定义的 libFuzzer 编译脚本文件路径:<a href="elink@2acK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6D9L8s2k6E0i4K6u0r3L8r3I4$3L8g2)9J5k6s2m8J5L8$3A6W2j5%4c8Q4x3V1k6T1L8r3!0T1i4K6u0r3L8h3q4A6L8W2)9J5c8X3y4G2L8i4m8A6L8r3g2J5i4K6u0V1M7Y4c8Q4x3V1k6D9K9h3u0Q4x3V1k6X3N6i4A6*7k6i4u0Q4x3V1k6T1N6h3W2D9k6q4)9J5k6i4y4Z5">llvm-project/compiler-rt/lib/fuzzer/build.sh at main · llvm/llvm-project (github.com)</a> 在完成代码修改后,需要重新编译 libFuzzer 生成 `libFuzzer.a` 静态库。可以使用如下编译脚本: ```bash #!/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 ``` 将修改后的源码放置到 libFuzzer 源码目录,运行该脚本编译,生成一个 `libFuzzer.a` 静态库,用于后续的模糊测试项目链接使用。 自定义的libFuzzer的使用方式: ``` clang++ -g -O1 fuzz_target.cc libFuzzer.a -o mytarget_fuzzer ``` 这里的libFuzzer.a是自己使用项目提供的build.sh编译出来的版本,还可以添加其他编译选项自行添加! # libFuzzer模糊测试运行案例 教程地址:<a href="elink@4d9K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6Y4L8$3!0Y4L8r3g2Q4x3V1k6X3N6i4A6*7K9h3&6Y4i4K6u0r3j5X3I4G2j5W2)9J5c8X3#2S2M7%4c8W2M7W2)9J5c8Y4c8#2N6r3!0J5K9h3q4D9i4K6u0r3L8r3W2T1c8Y4g2*7P5X3g2J5g2s2g2@1L8%4u0A6j5h3I4Q4x3X3g2E0k6l9`.`.">fuzzing/tutorial/libFuzzerTutorial.md 在 master ·谷歌/模糊测试 (github.com)</a> ## 案例一:长时间无crash的案例 ##### **步骤 1:下载测试目标** ``` #使用官方案例进行测试 ub20@ub20:~$git clone https://github.com/google/fuzzer-test-suite.git FTS ub20@ub20:~$cd FTS; mkdir -p woff; cd woff; ub20@ub20:~$~/FTS/woff2-2016-05-06/build.sh ``` ##### **步骤 2:创建模糊测试目标** 文件位置:FTS/woff2-2016-05-06/target.cc ``` // Copyright 2016 Google Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); #include <stddef.h> #include <stdint.h> #include "woff2_dec.h" // Entry point for LibFuzzer. 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; } ``` ##### **步骤 3:编译模糊测试目标** ``` ub20@ub20:~/woff$ ~/Fuzz/FTS/woff2-2016-05-06/build.sh ub20@ub20:~/woff$ ~/Fuzz/FTS/woff2-2016-05-06/build.sh 正克隆到 'SRC'... remote: Enumerating objects: 1150, done. remote: Counting objects: 100% (24/24), done. remote: Compressing objects: 100% (23/23), done. remote: Total 1150 (delta 2), reused 20 (delta 0), pack-reused 1126 (from 1) 接收对象中: 100% (1150/1150), 3.48 MiB | 6.03 MiB/s, 完成. 处理 delta 中: 100% (680/680), 完成. ... #编译成功后 ub20@ub20:~/woff$ ls backward_references.o compress_fragment_two_pass.o font.o normalize.o table_tags.o woff2_dec.o bit_reader.o decode.o glyph.o seeds transform.o woff2_enc.o block_splitter.o dictionary.o histogram.o SRC utf8_util.o woff2_out.o BROTLI encode.o huffman.o state.o variable_length.o brotli_bit_stream.o encode_parallel.o literal_cost.o static_dict.o woff2-2016-05-06-fsanitize_fuzzer compress_fragment.o entropy_encode.o metablock.o streams.o woff2_common.o ``` ##### **步骤 4:运行模糊测试** ``` ub20@ub20:~/woff$ ./woff2-2016-05-06-fsanitize_fuzzer INFO: Seed: 707405402 INFO: Loaded 1 modules (11231 inline 8-bit counters): 11231 [0x799080, 0x79bc5f), INFO: Loaded 1 PC tables (11231 PCs): 11231 [0x726be0,0x7529d0), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: A corpus is not provided, starting from an empty corpus #2 INITED cov: 15 ft: 16 corp: 1/1b exec/s: 0 rss: 28Mb #9 NEW cov: 16 ft: 17 corp: 2/5b lim: 4 exec/s: 0 rss: 28Mb L: 4/4 MS: 2 CopyPart-CrossOver- #3134 NEW cov: 17 ft: 18 corp: 3/10b lim: 33 exec/s: 0 rss: 28Mb L: 5/5 MS: 5 InsertByte-ShuffleBytes-ChangeBinInt-ChangeByte-CMP- DE: "wOF2"- .... ``` ## 案例二:快速有crash的案例 ##### **步骤 1:下载测试目标** 准备对:CVE-2014-0160 进行复现 漏洞原理:<a href="elink@b57K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2j5%4y4V1L8W2)9J5k6h3&6W2N6q4)9J5c8Y4q4I4i4K6g2X3y4U0t1^5x3o6x3&6z5e0y4Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6r3g2@1j5h3W2D9M7#2)9J5c8U0p5J5z5o6M7$3y4e0V1K6y4b7`.`.">OpenSSL 心脏滴血漏洞(CVE-2014-0160)漏洞讲解(小白可懂,简单详细)-CSDN博客</a> 使用github项目已经准备好的编译脚本: ``` #下载对应目标漏洞的fuzzer项目 [AFL++ 570d9dc7fa7e] /downloads # git clone https://github.com/google/fuzzer-test-suite.git Cloning into 'fuzzer-test-suite'... remote: Enumerating objects: 3521, done. remote: Counting objects: 100% (26/26), done. remote: Compressing objects: 100% (18/18), done. remote: Total 3521 (delta 10), reused 19 (delta 8), pack-reused 3495 Receiving objects: 100% (3521/3521), 2.78 MiB | 3.69 MiB/s, done. Resolving deltas: 100% (2267/2267), done. ``` ##### **步骤 2:创建模糊测试目标** 创建一个包含 `LLVMFuzzerTestOneInput` 函数的模糊测试目标: 文件所在路径:fuzzer-test-suite/openssl-1.0.1f ```c // Copyright 2016 Google Inc. All Rights Reserved. // Licensed under the Apache License, Version 2.0 (the "License"); #include <openssl/ssl.h> // OpenSSL 库,用于 SSL/TLS 功能 #include <openssl/err.h> // OpenSSL 错误处理 #include <assert.h> // 断言 #include <stdint.h> // 定义标准整数类型 #include <stddef.h> // 定义标准数据类型 // 初始化 OpenSSL 库并设置 SSL 上下文 SSL_CTX *Init() { SSL_library_init(); // 初始化 OpenSSL 库 SSL_load_error_strings(); // 加载错误字符串 ERR_load_BIO_strings(); // 加载 BIO 错误字符串 OpenSSL_add_all_algorithms(); // 加载所有加密算法 SSL_CTX *sctx; // 创建一个新的 SSL_CTX 对象,使用 TLSv1 协议 assert(sctx = SSL_CTX_new(TLSv1_method())); /* 使用以下命令创建这两个文件: openssl req -x509 -newkey rsa:512 -keyout server.key \ -out server.pem -days 9999 -nodes -subj /CN=a/ */ // 加载服务器证书 assert(SSL_CTX_use_certificate_file(sctx, "runtime/server.pem", SSL_FILETYPE_PEM)); // 加载服务器私钥 assert(SSL_CTX_use_PrivateKey_file(sctx, "runtime/server.key", SSL_FILETYPE_PEM)); return sctx; // 返回 SSL_CTX 对象 } // fuzzer 的入口函数 extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { static SSL_CTX *sctx = Init(); // 初始化 SSL_CTX,仅在第一次调用时执行 SSL *server = SSL_new(sctx); // 创建一个新的 SSL 对象 // BIO(Basic Input/Output)对象用于处理输入和输出操作 // 内存 BIO (Memory BIO):使用内存缓冲区进行读写操作 BIO *sinbio = BIO_new(BIO_s_mem()); BIO *soutbio = BIO_new(BIO_s_mem()); // 将 BIO 对象与 SSL 对象关联,使 SSL 对象可以通过 BIO 进行 I/O 操作 SSL_set_bio(server, sinbio, soutbio); SSL_set_accept_state(server); // 将 SSL 对象设置为接受状态,准备进行 SSL/TLS 握手 // 将模糊测试输入数据写入 sinbio,以便模拟从客户端接收数据 BIO_write(sinbio, Data, Size); // 执行 SSL/TLS 握手过程 SSL_do_handshake(server); // 释放 SSL 对象,清理资源 SSL_free(server); return 0; // 返回 0 表示成功 } ``` ##### **步骤 3:编译模糊测试目标** 在项目根目录中,运行以下命令编译模糊测试目标,详细的编译选项可以查看build.sh : ``` #对目标项目进行clang插桩 [AFL++ 570d9dc7fa7e] /downloads/fuzzer-test-suite # ./openssl-1.0.1f/build.sh Cloning into 'SRC'... remote: Enumerating objects: 480247, done. ``` ##### **步骤 4:运行模糊测试** 在项目根目录中,运行以下命令开始模糊测试,成功跑出堆溢出漏洞: ``` [AFL++ 570d9dc7fa7e] /downloads/fuzzer-test-suite # ./openssl-1.0.1f-fsanitize_fuzzer INFO: Running with entropic power schedule (0xFF, 100). INFO: Seed: 528530075 INFO: Loaded 1 modules (35481 inline 8-bit counters): 35481 [0x5da29cfd2410, 0x5da29cfdaea9), INFO: Loaded 1 PC tables (35481 PCs): 35481 [0x5da29cfdaeb0,0x5da29d065840), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes INFO: A corpus is not provided, starting from an empty corpus #2 INITED cov: 402 ft: 403 corp: 1/1b exec/s: 0 rss: 41Mb #3 NEW cov: 402 ft: 404 corp: 2/3b lim: 4 exec/s: 0 rss: 41Mb L: 2/2 MS: 1 CrossOver- #45 REDUCE cov: 402 ft: 404 corp: 2/2b lim: 4 exec/s: 0 rss: 43Mb L: 1/1 MS: 2 ChangeBinInt-.... suite/BUILD/crypto/rand/rand_lib.c:169 #23239 NEW cov: 532 ft: 775 corp: 35/784b lim: 110 exec/s: 23239 rss: 387Mb L: 11/88 MS: 1 CMP- DE: "\001\000\003R"- #23380 NEW cov: 535 ft: 780 corp: 36/800b lim: 110 exec/s: 23380 rss: 387Mb L: 16/88 MS: 1 CrossOver- #23802 REDUCE cov: 535 ft: 780 corp: 36/797b lim: 110 exec/s: 23802 rss: 387Mb L: 28/88 MS: 2 ChangeBinInt-EraseBytes- ================================================================= ==2346==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000009748 at pc 0x5da29cb00a7d bp 0x7ffd011caf10 sp 0x7ffd011ca6d8 READ of size 17731 at 0x629000009748 thread T0 #0 0x5da29cb00a7c in __asan_memcpy (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x272a7c) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) #1 0x5da29cb4a42d in tls1_process_heartbeat /downloads/fuzzer-test-suite/BUILD/ssl/t1_lib.c:2586:3 #2 0x5da29cbb35c9 in ssl3_read_bytes /downloads/fuzzer-test-suite/BUILD/ssl/s3_pkt.c:1092:4 #3 0x5da29cbb7990 in ssl3_get_message /downloads/fuzzer-test-suite/BUILD/ssl/s3_both.c:457:7 ....fsanitize_fuzzer+0x1c25d3) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) #10 0x5da29ca3e760 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x1b0760) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) #11 0x5da29ca67aa2 in main (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x1d9aa2) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) #12 0x724301664d8f (/lib/x86_64-linux-gnu/libc.so.6+0x29d8f) (BuildId: 490fef8403240c91833978d494d39e537409b92e) SUMMARY: AddressSanitizer: heap-buffer-overflow (/downloads/fuzzer-test-suite/openssl-1.0.1f-fsanitize_fuzzer+0x272a7c) (BuildId: b91eb95b107c49cb91fb52d711a75150508e5122) in __asan_memcpy Shadow bytes around the buggy address: 0x629000009480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x629000009500: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x629000009580: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x629000009600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x629000009680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x629000009700: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa 0x629000009780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x629000009800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x629000009880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x629000009900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x629000009980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb ==2346==ABORTING MS: 2 EraseBytes-PersAutoDict- DE: "ECDSA"-; base unit: 1fd5cf2beed57b0a1a2a477ff6e33cbf1a23c38e 0x18,0x3,0x5,0x0,0x5,0x1,0x45,0x43,0x44,0x53,0x41, \030\003\005\000\005\001ECDSA artifact_prefix='./'; Test unit written to ./crash-96ba5b3de75e70a2400b07afcf813e645282baa2 Base64: GAMFAAUBRUNEU0E= ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#漏洞分析
#漏洞利用
#自动化挖掘
#Fuzz
#Windows
#Linux
收藏
・
12
点赞
・
5
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
一路南寻
为你点赞!
2025-10-23 03:46
PLEBFE
为你点赞!
2025-3-15 04:00
养只猫不好么
这个讨论对我很有帮助,谢谢!
2024-10-10 13:24
wogao
+2
谢谢你的细致分析,受益匪浅!
2024-10-9 16:47
nig
+1
感谢你分享这么好的资源!
2024-10-9 10:15
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
2
)
nig
雪 币:
413
活跃值:
(862)
能力值:
( LV9,RANK:170 )
在线值:
发帖
38
回帖
1471
粉丝
16
关注
私信
nig
4
2
楼
感谢分享,内容有见解,值得学习!
2024-10-9 10:15
0
XxingGoD
雪 币:
0
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
4
粉丝
0
关注
私信
XxingGoD
3
楼
感谢分享
2026-9-12 14:51
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Brinmon
9
16
发帖
24
回帖
430
RANK
关注
私信
他的文章
[原创]Fuzz挖掘sudo提权漏洞:一次堆溢出如何逆向分析出提权思路
6349
[原创]劫持SUID程序提权彻底理解Dirty_Pipe:从源码解析到内核调试
6266
[原创]深入剖析路由器FOTA固件升级流程:从解包到逆向分析
8781
[原创]IoT安全透视:D-Link DWR-932B固件全面逆向分析
9514
[原创]firmadyne源码解析-揭开固件模拟的黑盒面纱
10764
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部