ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

LibFuzzer 语料库精简技巧:如何用最小的 Corpus 跑出最大覆盖率

LibFuzzer 语料库精简技巧:如何用最小的 Corpus 跑出最大覆盖率 LibFuzzer 语料库精简技巧如何用最小的 Corpus 跑出最大覆盖率语料库膨胀模糊测试效能的隐形杀手在基于 LLVM 编译器工具链的覆盖率引导模糊测试Coverage-guided Fuzzing中LibFuzzer 凭借其与目标进程同进程运行In-process、无需进程派生开销的极致性能已成为开源社区如 OSS-Fuzz与企业安全开发的首选引擎。然而随着 Fuzzer 运行数十小时或数天语料库Corpus中的种子文件数量往往会从几十个迅速膨胀到数万个。这种语料库膨胀Corpus Bloat会带来严重的效能衰退测试吞吐量断崖式下跌大量种子文件包含冗余的代码路径覆盖Fuzzer 耗费宝贵的 CPU 时间对高度相似的种子进行重复变异降低了探索新分支的概率。种子单文件体积过大某些意外变异生成的庞大输入如数十 MB会导致单次LLVMFuzzerTestOneInput执行耗时从微秒级恶化到毫秒级。CI/CD 回归测试超时在代码提交流水线中如果直接拉取未经精简的几十 GB 语料库进行回归验证将严重拖慢持续集成效率。掌握语料库精简Corpus Minimization / Distillation技术用最小的测试用例集合达成最大化的代码覆盖率是高级模糊测试工程师的必备功底。语料库精简的算法机理贪心集合覆盖LibFuzzer 的语料库合并与精简-merge1模式在底层本质上是求解一个加权集合覆盖问题Weighted Set Cover Problem[原始膨胀语料库 (Thousands of Files)] │ ▼ ┌────────────────────────────────────────┐ │ 1. 覆盖率与执行耗时采样 │ │ - 逐用例执行收集 PC-Table 覆盖集 │ │ - 记录单文件体积与执行微秒数 │ └─────────────────┬──────────────────────┘ │ ▼ ┌────────────────────────────────────────┐ │ 2. 贪心集合覆盖算法 (Greedy Set Cover) │ │ - 优先挑选具有独占新增覆盖的种子 │ │ - 在覆盖相同时选择文件更小、耗时更短│ └─────────────────┬──────────────────────┘ │ ▼ [精简后最小核心语料库 (Minimal Distilled Corpus)]1. 评价指标的加权排序算法不会盲目随机挑选用例。对于覆盖了相同基本块Basic Block的两个种子 $A$ 和 $B$优先选择**文件体积更小Smaller File Size**的用例在体积相近时优先选择**执行耗时更短Lower Execution Time**的用例。这种策略确保了保留下来的种子在后续变异时能维持最轻量的内存占用与最高频的执行吞吐。语料库精简工程化脚本与贪心算法实现以下 Python 代码实现了 LibFuzzer 底层核心的贪心集合覆盖精简算法Greedy Corpus Distillation并结合真实的 LibFuzzer 命令行参数构建了自动化精简流水线import os import shutil import subprocess import time from typing import List, Dict, Set class CorpusDistillationEngine: 语料库覆盖率贪心精简引擎 def __init__(self, target_binary: str): self.target_binary target_binary def run_libfuzzer_native_merge(self, raw_corpus_dir: str, minimized_corpus_dir: str): 调用 LibFuzzer 原生 -merge1 模式进行工业级语料库合并与精简 os.makedirs(minimized_corpus_dir, exist_okTrue) print(f[*] 启动 LibFuzzer 原生精简流水线...) print(f - 源语料库目录: {raw_corpus_dir}) print(f - 目标精简目录: {minimized_corpus_dir}) start_time time.time() # 执行 LibFuzzer 合并命令: ./fuzzer_target -merge1 minimized_dir raw_dir cmd [self.target_binary, -merge1, minimized_corpus_dir, raw_corpus_dir] try: res subprocess.run(cmd, stdoutsubprocess.PIPE, stderrsubprocess.STDOUT, textTrue) elapsed time.time() - start_time print(f[] 原生合并执行完毕耗时: {elapsed:.2f}s) # 统计精简前后文件数量对比 raw_count len(os.listdir(raw_corpus_dir)) min_count len(os.listdir(minimized_corpus_dir)) print(f[] 语料库精简比: {raw_count} - {min_count} (精简率: {(1 - min_count / max(raw_count, 1)) * 100:.2f}%)) except FileNotFoundError: print(f[!] 错误: 未找到目标 Fuzzer 二进制文件 {self.target_binary}) staticmethod def simulate_greedy_set_cover(test_cases: List[Dict]) - List[Dict]: 仿真贪心集合覆盖算法逻辑 test_case 格式: {id: str, size: int, covered_pcs: Set[int]} all_target_pcs: Set[int] set() for tc in test_cases: all_target_pcs.update(tc[covered_pcs]) covered_so_far: Set[int] set() selected_corpus: List[Dict] [] while covered_so_far ! all_target_pcs: best_candidate None best_new_coverage_count 0 # 遍历寻找单位体积下带来最多新增覆盖率的用例 for tc in test_cases: new_pcs tc[covered_pcs] - covered_so_far if not new_pcs: continue if len(new_pcs) best_new_coverage_count: best_new_coverage_count len(new_pcs) best_candidate tc elif len(new_pcs) best_new_coverage_count and best_candidate is not None: # 覆盖新增相同时优先选体积更小的用例 if tc[size] best_candidate[size]: best_candidate tc if best_candidate is None: break selected_corpus.append(best_candidate) covered_so_far.update(best_candidate[covered_pcs]) return selected_corpus if __name__ __main__: print([] 模拟语料库贪心集合覆盖精简计算...) # 模拟 5 个种子文件及其覆盖的基本块编号 mock_corpus [ {id: seed_1 (庞大冗余), size: 4096, covered_pcs: {101, 102, 103}}, {id: seed_2 (轻量精简), size: 64, covered_pcs: {101, 102}}, {id: seed_3 (独立分支), size: 128, covered_pcs: {103, 104}}, {id: seed_4 (全覆盖大包), size: 8192, covered_pcs: {101, 102, 103, 104, 105}}, {id: seed_5 (边界深层), size: 256, covered_pcs: {105, 106}} ] selected CorpusDistillationEngine.simulate_greedy_set_cover(mock_corpus) print(f[] 贪心算法挑选的最优精简子集:) for s in selected: print(f └── 选用: {s[id]:20} | 文件大小: {s[size]:5} Bytes | 覆盖块: {s[covered_pcs]})单用例体积精简与崩溃去重技巧除了对语料库进行宏观集合精简针对触发异常的单个大用例还需要进行微观的用例体积精简Testcase Minimization1. 崩溃用例自动精简-minimize_crash1当 Fuzzer 挖掘到一个 100KB 的导致堆溢出崩溃的种子时其中可能 99% 的字节都是无关噪音。使用 LibFuzzer 提供的原生精简指令./fuzzer_target -minimize_crash1 -exact_artifact_pathminimized_poc.bin raw_crash_input.bin引擎会通过二分法Binary Search与连续字节块删除在不断重放目标并确认崩溃调用栈Crash Stack Hash一致的前提下将输入自动精简到触发 Bug 的最小字节集如仅剩 12 字节极大降低漏洞分析与 PoC 编写的人工成本。2. 持续化语料库维护Corpus Hygiene定期排毒每隔 12~24 小时执行一次-merge1剔除变异过程中累积的低价值冗余种子。清理超时种子将执行耗时超过平均值 10 倍的畸形慢速种子移入隔离区避免慢速用例污染变异调度池。
返回列表