news 2026/5/16 6:42:59

AI驱动模糊测试:用大语言模型自动生成Fuzzing Harness

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI驱动模糊测试:用大语言模型自动生成Fuzzing Harness

1. 项目概述:当模糊测试遇上大语言模型

最近在搞自动化安全测试,特别是模糊测试这块,发现一个挺有意思的项目,叫google/oss-fuzz-gen。这项目名一出来,懂行的朋友估计眼睛就亮了:Google、OSS-Fuzz、Gen(生成),这几个词凑一块,基本就锁定了它的核心——用生成式AI来搞模糊测试。

简单来说,oss-fuzz-gen是 Google 开源的一个研究项目,它试图解决模糊测试领域一个老大难问题:如何自动、高效地生成高质量的模糊测试驱动(Fuzzing Harness)。传统的模糊测试,无论是 AFL、libFuzzer 还是 OSS-Fuzz 平台本身,都高度依赖一个写好的“驱动”程序。这个驱动就像个“翻译官”,负责把模糊器随机生成的、乱七八糟的输入数据,转换成目标函数能“听懂”的调用。写这个驱动是个技术活,得懂目标库的API、数据结构、调用顺序,费时费力,还容易出错,直接制约了模糊测试的覆盖率和自动化程度。

oss-fuzz-fuzz-gen的思路很直接:既然大语言模型(LLM)在代码生成和理解上已经这么猛了,能不能让它来当这个“翻译官”的自动生成器?项目基于 Google 自家的 Gemini 系列模型,训练了一个专门针对 C/C++ 库生成模糊测试驱动的模型。你给它一个目标库的头文件(.h)或者一些示例代码,它就能尝试理解这个库的接口,然后生成一个可以直接编译、链接到 libFuzzer 进行模糊测试的驱动源码。

这玩意儿要是真能成熟落地,意义可就大了。对于开源项目维护者,意味着接入 OSS-Fuzz 这类持续模糊测试平台的门槛会大大降低,安全漏洞的发现可以更早、更自动化。对于安全研究员,相当于多了一个不知疲倦的“初级模糊测试工程师”,能快速对大量目标进行初步的漏洞挖掘。当然,它目前还是个研究项目,离“开箱即用、全自动搞定”还有距离,但其中展现的思路、方法和遇到的挑战,对我们理解AI在软件安全领域的应用边界,非常有价值。

2. 核心原理与技术栈拆解

要理解oss-fuzz-gen怎么工作,得先拆开看看它的“技术栈”。这不像调用一个现成的API那么简单,背后是一套组合拳。

2.1 核心组件:从代码理解到驱动生成

项目的核心是一个基于 Transformer 架构的代码生成模型。但它不是通用的代码补全模型(比如 Codex),而是经过了特定任务的精调(Fine-tuning)。这个“特定任务”就是:给定库代码的上下文,生成一个有效的 libFuzzer 风格的模糊测试驱动

它的输入通常包括:

  1. 目标库的头文件内容:这是最主要的上下文,模型需要从中提取函数签名、数据结构定义、宏、枚举等。
  2. 可选的示例代码或文档片段:帮助模型理解这些API的典型用法和调用顺序。
  3. 任务描述(Prompt):明确指示模型“生成一个libFuzzer测试驱动”。

输出则是一个完整的.c.cc文件,包含:

  • LLVMFuzzerTestOneInput函数:这是 libFuzzer 的标准入口点。
  • 对目标库头文件的引用(#include)。
  • 解析输入数据(const uint8_t *data, size_t size)并构造调用参数的逻辑。
  • 调用一个或多个目标库API的代码。
  • 必要的错误处理和资源清理(如果生成了的话)。

2.2 关键技术创新点

  1. 代码上下文表征:如何让模型“读懂”C/C++头文件是个挑战。项目很可能采用了类似 Tree-sitter 的解析器,先将代码转化为抽象语法树(AST),再结合令牌(Token)序列,形成丰富的代码表征。模型不仅要理解语法,还要理解语义,比如某个参数的类型是FILE*,那在驱动里可能需要用fmemopen或临时文件来模拟。

  2. 约束引导的生成:纯粹靠概率生成代码,很容易产出语法错误或逻辑荒谬的驱动。oss-fuzz-gen必然引入了约束。比如:

    • 语法约束:确保生成的C/C++代码能通过基础编译检查。
    • API使用约束:基于头文件信息,确保函数调用时参数类型匹配。
    • 模糊测试特定约束:生成的驱动必须包含LLVMFuzzerTestOneInput函数,并且要尝试去“消费”输入数据,不能直接忽略datasize
  3. 基于真实数据集的训练:模型的训练数据不是凭空造的,很可能来源于 OSS-Fuzz 项目中成千上万个已经存在的、人工编写的优质模糊测试驱动。模型从这些真实、有效的样本中学习“一个好的模糊测试驱动长什么样”、“针对某种类型的API应该如何构造输入”。这是它区别于通用代码生成模型的核心。

  4. 与编译工具链的集成:生成驱动不是终点,还要能编译、能运行。项目需要集成 Clang 编译器,对生成的代码进行即时编译和链接测试。如果编译失败,这个失败的信号(错误信息)可以反馈回去,用于改进模型或筛选结果,形成一个“生成-编译-验证”的闭环。

2.3 技术栈推测

根据项目性质和 Google 的技术背景,其技术栈可能包含:

  • 模型框架:大概率基于 JAX 或 TensorFlow,使用 Google 内部的 Gemini 模型作为基座。
  • 代码处理:libClang 或 Tree-sitter 用于解析C/C++代码,提取结构化信息。
  • 训练基础设施:Google 的 TPU 集群,用于处理海量的代码数据。
  • 评估管道:与 OSS-Fuzz 基础设施集成,自动在隔离的沙箱环境中运行生成的驱动,评估其代码覆盖率和崩溃发现能力。

注意:以上部分细节是结合模糊测试和AI代码生成领域的常见实践进行的合理推测,因为作为研究项目,其论文或文档可能不会披露全部工程细节。但理解这个框架,对于我们自己尝试类似思路或评估其输出至关重要。

3. 实操:尝试使用与生成驱动解析

虽然oss-fuzz-gen主要供内部研究,但我们可以通过其开源代码和示例,理解如何使用以及如何评判它生成的驱动。假设我们已经按照项目README,配置好了必要的Python环境、模型权重(或访问API的权限)和编译工具链。

3.1 基本使用流程

一个典型的使用命令可能类似于:

python generate_harness.py \ --header_file /path/to/target_lib.h \ --output /path/to/generated_harness.cc \ --model_path /path/to/checkpoint

这个过程背后经历了几个阶段:

  1. 上下文收集:脚本会读取target_lib.h,可能还会在相同目录下搜索相关的.c文件或简单的使用示例,一起打包作为模型的输入上下文。
  2. 提示工程:将原始代码和任务指令按照预定义的模板组合成最终的提示(Prompt),送给模型。这个模板可能强调了“生成libFuzzer驱动”、“使用提供的输入数据”等关键点。
  3. 模型推理与生成:模型进行推理,自回归地生成令牌序列,直到产出完整的源代码文件。
  4. 后处理与输出:对生成的源代码进行简单的格式化,然后写入指定的输出文件。

3.2 生成驱动代码深度解析

假设我们有一个简单的目标库mylib.h,里面就一个函数:

// mylib.h int decode_buffer(const unsigned char *input, int input_len, char *output, int output_capacity);

一个理想的、由oss-fuzz-gen生成的驱动可能如下:

// generated_harness.cc #include <cstddef> #include <cstdint> #include <cstdlib> #include "mylib.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { if (size < 1) { return 0; // 模糊测试器认为没有进展 } // 使用输入数据的一部分作为输入长度 int input_len = static_cast<int>(data[0]) % (size - 1) + 1; if (input_len > size - 1) { input_len = size - 1; } const unsigned char* input = data + 1; // 为输出分配缓冲区,大小基于输入或固定值 int output_capacity = input_len * 2; // 启发式策略 if (output_capacity < 1) output_capacity = 1; char* output = static_cast<char*>(malloc(output_capacity)); if (output == nullptr) { return 0; } // 调用目标函数 int result = decode_buffer(input, input_len, output, output_capacity); // 清理资源 free(output); // libFuzzer 期望我们忽略返回值,除非是特殊的错误 return 0; }

我们来拆解这个生成的驱动,看看模型的“思考”过程:

  1. 基础框架:正确包含了必要的头文件和extern "C"(因为 libFuzzer 是C接口),定义了标准的LLVMFuzzerTestOneInput函数签名。这是模糊测试驱动的“宪法”,模型必须遵守。

  2. 输入验证if (size < 1) return 0;这是一个非常经典的模糊测试驱动技巧。libFuzzer 可能会提供空输入,如果我们的驱动逻辑至少需要1个字节来工作(比如这里需要1个字节来定义input_len),那么对于空输入就直接返回0,告诉模糊器“这次运行没产生新覆盖”,避免无意义的执行。

  3. 数据解析策略:这是驱动生成最核心、最体现“智能”的部分。模型需要从datasize这块原始的、无结构的字节数组中,构造出目标函数所需的参数。

    • 对于input_len:模型选择用第一个字节data[0]来动态决定长度,并取模限制在合理范围内(size - 1)。这是一种非常合理的启发式方法,既能产生变化的长度,又能防止越界访问。
    • 对于input指针:直接指向data[1]之后的数据。这样,data[0]控制长度,后续数据作为实际输入内容,结构清晰。
    • 对于output缓冲区:模型知道output是一个输出缓冲区,需要预先分配。它采用了input_len * 2的启发式策略,这是一个安全且常见的做法,因为解码后的数据可能比原始数据长。同时检查了malloc是否成功。
  4. 函数调用:正确调用了decode_buffer,传递了所有构造好的参数。

  5. 资源清理:记得free分配的output缓冲区,避免了内存泄漏。这对于长时间运行的模糊测试至关重要。

  6. 返回值:正确返回 0。在 libFuzzer 中,除非遇到需要特殊处理的错误(通常用 -1 表示),否则都返回 0。

这个例子展示了模型在理解API签名(参数类型、用途)和模糊测试特定约束方面的能力。它没有生成“万能”的固定值,而是设计了一套从随机字节流中“解析”出有意义参数的逻辑。

3.3 实操中的关键检查点

当你拿到一个生成的驱动时,不要急着上大规模模糊测试,先做这几步检查:

  1. 编译测试:用与目标库相同的编译器和标志进行编译。这是第一道关卡。

    clang -g -fsanitize=fuzzer,address generated_harness.cc -lmylib -o fuzzer_executable

    如果编译失败,查看错误信息。常见的错误包括:缺少头文件、类型不匹配、语法错误。这些错误可以反馈给生成流程(如果支持的话)。

  2. 链接测试:确保能正确链接到目标库(-lmylib)。

  3. 最小化功能测试:写一个简单的测试程序,用固定的、合法的输入调用生成的驱动,看目标函数是否被正确调用,程序是否正常结束(不崩溃)。这能发现一些基础的逻辑错误。

  4. 初始模糊测试运行:用 libFuzzer 以极短的运行时间(如5秒)和极小的语料库启动一次模糊测试。

    ./fuzzer_executable -max_total_time=5 ./corpus

    观察是否有立即崩溃,以及代码覆盖率(如果安装了gcov或使用-fsanitize-coverage)的初始增长情况。如果运行5秒就发现崩溃,那这个驱动已经成功了;如果覆盖率纹丝不动,说明驱动可能没有有效“消费”输入数据。

4. 优势、局限与评估指标

任何技术都有其适用边界,oss-fuzz-gen这类AI生成的模糊测试驱动也不例外。我们需要客观地看待它的能力和不足。

4.1 核心优势

  1. 自动化与规模化:这是最大的优势。面对一个拥有数百个API的大型库,人工为每个有潜力的函数编写驱动是巨大的工程。AI模型可以批量、自动地生成大量驱动候选,极大提升了启动模糊测试的初始速度。
  2. 减少领域知识依赖:编写一个好的模糊测试驱动需要对目标库和模糊测试都有深入理解。AI模型通过从海量现有驱动中学习,封装了这部分知识,使得即使对某个库不熟悉的安全研究员或开发者,也能快速获得一个可用的起点。
  3. 启发式数据构造:如前面的例子所示,模型能生成相对智能的数据解析逻辑(如用第一个字节控制长度),这比简单的随机分割或固定值分配更有效,能更快地探索到程序的深层状态。
  4. 代码风格一致性:生成的驱动在代码风格上通常是统一的,便于后续的维护和审查。

4.2 当前主要局限与挑战

  1. API理解深度不足:模型主要依赖头文件的语法信息,缺乏对API语义的深层理解。例如:
    • 参数间复杂约束:函数A的输出必须是函数B的输入,且顺序不能错。模型可能生成颠倒顺序的调用。
    • 状态依赖:某些函数调用前必须初始化某个全局上下文,或者需要在特定状态(如“已连接”、“已打开”)下调用。模型生成的驱动可能遗漏这些初始化步骤。
    • 资源生命周期管理:对于返回句柄或需要配对使用的API(如create_context/destroy_context),模型可能只生成创建调用,忘记销毁,导致资源泄漏,这在长时间模糊测试中会是问题。
  2. 难以生成复杂输入结构:如果目标函数需要一个复杂的、嵌套的数据结构(如一个解析JSON或特定协议报文的函数),仅从随机字节流构造出合法结构的概率极低。模型生成的驱动可能无法有效触及核心解析逻辑。
  3. “正确性”陷阱:模型倾向于生成“编译通过且看起来合理”的代码,但这不一定是“有效”的模糊测试驱动。一个驱动如果总是用固定的小范围值调用API,或者错误处理路径过早返回,其模糊测试效果可能为零。
  4. 反馈循环缺失:目前的研究型生成多是“一次性”的。一个理想的系统应该能将模糊测试运行时的反馈(如代码覆盖增长点、发现的崩溃)重新用于指导模型生成更有效的驱动,形成闭环优化。但这在工程上非常复杂。

4.3 如何评估生成的驱动?

不能只看编译是否通过。我们需要一套更细致的评估指标:

  1. 编译与链接成功率:基础指标,但权重不高。
  2. 初始代码覆盖率:使用一个小的、合法的种子输入集运行驱动,测量其对目标库代码的行覆盖率、函数覆盖率。覆盖率越高,说明驱动触及的代码路径越多,潜力越大。可以使用llvm-covgcov来测量。
  3. 模糊测试效率:在固定时间(如1小时)内,使用 libFuzzer 运行该驱动,观察:
    • 唯一崩溃/超时/错误发现数量:直接的安全收益。
    • 代码覆盖率增长曲线:覆盖率是否快速上升并趋于平稳?还是几乎不动?
    • 执行速度:每秒能执行多少次LLVMFuzzerTestOneInput调用?这会影响模糊测试的吞吐量。
  4. 驱动代码质量:人工审查代码,检查是否有明显的资源泄漏、未定义行为、无效的输入消费逻辑等。

一个高质量的AI生成驱动,应该在“初始代码覆盖率”和“模糊测试效率”上表现良好。它可能不如经验丰富的人类专家写的驱动那么精妙,但作为一个“零成本”的起点,如果能达到人工驱动70%的效果,其投入产出比就已经非常惊人了。

5. 实战经验:集成到现有流程与调优思路

如果你打算在真实项目中尝试或借鉴oss-fuzz-gen的思路,以下是一些从实战角度出发的经验和思考。

5.1 集成到CI/CD或安全测试流程

不要指望AI生成驱动能完全替代人工。更现实的定位是作为“模糊测试驱动生成的第一阶段”“辅助工具”

一个可行的集成流程如下:

  1. 目标选择:针对新引入的、或尚未有模糊测试覆盖的核心C/C++库。
  2. 批量生成:使用oss-fuzz-gen或类似工具,为其主要公共API批量生成驱动候选。
  3. 自动化筛选: a.编译过滤:自动编译所有生成驱动,过滤掉编译失败的。 b.基础运行过滤:用一个极小的、合法的种子语料(甚至可以是空输入)运行每个可执行驱动几秒钟。过滤掉那些立即崩溃(可能是驱动本身有bug)或覆盖率完全为零的驱动。
  4. 人工审查与精选:对通过筛选的驱动进行快速人工代码审查。重点看:资源管理是否正确?输入消费逻辑是否合理?是否调用了关键的API组合?挑选出最有潜力的几个。
  5. 投入模糊测试集群:将精选后的驱动加入到持续的模糊测试任务中(如OSS-Fuzz、内部Fuzzing平台),进行长时间(24小时以上)的测试。
  6. 结果监控与迭代:监控这些驱动的崩溃发现情况和覆盖率增长。对于效果好的驱动,可以将其代码作为模板保存;对于效果差的,分析原因(是API理解问题还是数据构造问题),这些反馈可以用于改进生成模型或提示词。

5.2 针对复杂库的调优与提示工程

对于复杂的库,直接给个头文件可能不够。你需要为模型提供更多“上下文线索”,这类似于给AI“喂小灶”。

  1. 提供使用示例:在项目目录中放置一个简单的example_usage.c文件,展示如何正确初始化、调用API、清理资源。模型在生成时可以参考这个文件的代码模式和结构。
  2. 提供API分组信息:如果库的API有明显的模块划分(如编码、解码、网络、文件),可以尝试分模块生成驱动。给模型的提示词可以更具体:“为libfoo的编解码模块生成一个模糊测试驱动,重点测试encode_stringdecode_buffer函数。”
  3. 定制化提示模板:研究oss-fuzz-gen的提示词模板。你可以尝试修改它,加入更明确的指令,例如:
    • “确保在调用process_data之前先调用init_engine。”
    • “注意,ctx指针需要最后用free_context释放。”
    • “尝试使用输入数据的前4个字节作为一个32位整数,用来控制循环次数。”
  4. 后处理脚本:编写脚本对生成的驱动进行自动化的“修补”。例如,如果发现模型总忘记释放某些资源,可以写一个脚本,在生成代码后自动搜索特定的API调用模式,并插入对应的清理代码。

5.3 常见问题与排查实录

在实际尝试中,你可能会遇到以下典型问题:

问题1:生成的驱动编译失败,错误是“未定义的引用”。

  • 排查:这通常是链接问题,不是驱动代码本身的问题。检查你的编译命令是否正确链接了目标库(-l参数)。确保库文件路径在链接器的搜索路径中。
  • 心得:将编译和链接命令封装在一个脚本或Makefile里,避免手动输入错误。先确保你能用一个最简单的、手写的测试程序成功编译链接目标库,再用完全相同的编译链接选项去编译生成的驱动。

问题2:驱动能编译运行,但模糊测试覆盖率几乎不增长。

  • 排查
    1. 检查输入消费:在LLVMFuzzerTestOneInput函数开头加一句printf(“Size: %zu\n”, size);,运行一下看看模糊器提供的size是否总是0或很小。libFuzzer可能会从很小的输入开始。确保你的驱动逻辑能处理极小输入(比如直接return 0)。
    2. 检查驱动逻辑:是不是驱动里的某个条件判断过于严格,导致绝大部分随机输入都被提前return 0了?比如if (size != 100) return 0;。模型有时会生成这种无意义的硬编码检查。
    3. 检查API调用是否真的执行:在调用目标API前后加打印,或者用调试器单步跟踪,确认函数确实被调用到了。
  • 解决:如果问题出在驱动逻辑,你需要修改生成模型的输入(提供更好的示例),或者手动修改这个有问题的驱动。一个常见的修复是:将过于严格的检查改为概率性的或基于输入数据的动态检查。

问题3:驱动运行导致内存泄漏,长时间模糊测试后内存耗尽。

  • 排查:使用 AddressSanitizer 来编译和运行驱动。-fsanitize=address,fuzzer。运行很短时间,ASan就会报告内存泄漏点。
  • 解决:找到泄漏的资源(通常是malloc的内存、fopen的FILE指针、或库分配的句柄),在驱动中确保释放。这可能需要你深入理解目标库的API,然后手动修补生成的驱动,或者为模型提供更明确的资源管理示例。

问题4:生成的驱动只测试了某个API的皮毛,没有组合多个API或进行状态ful测试。

  • 分析:这是当前AI生成驱动的普遍局限。模型倾向于为单个API生成简单的测试。
  • 应对策略:不要期望一个驱动解决所有问题。你可以:
    • 为复杂的、有状态的操作手动编写驱动。
    • 尝试让模型生成“序列化”测试:在提示词中要求“生成一个驱动,它先调用A,再调用B,使用第一次调用的结果作为第二次调用的输入”。
    • 将AI生成的驱动视为“单元模糊测试”,再辅以人工编写的“集成模糊测试”。

最后,保持合理的期望。oss-fuzz-gen代表了一个充满希望的方向,但它不是银弹。它的最佳用途是作为“力量倍增器”,帮助安全团队和开发者快速建立模糊测试的初始覆盖,发现那些明显的、浅层的漏洞,从而让人类专家可以更专注于深度的、逻辑复杂的漏洞挖掘。将AI的“广度”与人类的“深度”结合,才是提升软件安全性的正道。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/16 6:42:04

Java应用性能监控(APM)原理与实践:从字节码增强到全链路追踪

1. 项目概述&#xff1a;从“蜜蜂”视角看应用性能监控最近在梳理团队的技术栈&#xff0c;发现一个挺有意思的现象&#xff1a;大家对于应用性能监控&#xff08;APM&#xff09;的认知&#xff0c;往往还停留在“出了问题再查日志”的阶段。直到我深度体验并拆解了hao117/bee…

作者头像 李华
网站建设 2026/5/16 6:40:12

ISDN PRI外线故障排查实战指南

在实际运维案例中&#xff0c;工程师不怕故障一直出现&#xff0c;就怕偶尔出问题。比如客户反馈打外线时&#xff0c;偶尔会出现断线的情况。当然可以通过MST或Trace命令去跟踪&#xff0c;但如果故障发生频率过低&#xff0c;抓日志往往很难。我们通常需要先检查线路质量&…

作者头像 李华
网站建设 2026/5/16 6:35:23

基于CircuitPython与USB HID协议的自定义物理宏键盘制作指南

1. 项目概述&#xff1a;打造你的专属物理快捷键键盘如果你经常需要在电脑上重复执行某些操作&#xff0c;比如视频剪辑时频繁切换工具、直播时快速切换场景&#xff0c;或者玩游戏时希望有更带感的实体按键&#xff0c;那么自己动手做一个完全自定义的USB键盘控制器&#xff0…

作者头像 李华
网站建设 2026/5/16 6:30:02

Hermes开发者工具集:模块化架构、核心功能与自托管部署实践

1. 项目概述&#xff1a;一个面向开发者的高效工具集最近在和一些做后端开发的朋友交流时&#xff0c;大家普遍提到一个痛点&#xff1a;日常开发中&#xff0c;经常需要一些零散但高频的工具&#xff0c;比如快速生成一个API接口的模拟数据、格式化一段复杂的JSON、或者对数据…

作者头像 李华
网站建设 2026/5/16 6:23:02

算法题(栈)

一、题目1、字符串解码&#xff08;LC 394&#xff09;2、最小栈&#xff08;LC 155&#xff09;3、有效的括号&#xff08;LC 20&#xff09;二、题解1、字符串解码&#xff08;LC 394&#xff09;&#xff08;1&#xff09;分析这道题需要把带方括号和数字的编码字符串进行解…

作者头像 李华