0x00 前言

由于想要了解一下大语言模型在模糊测试领域的应用研究,所以经 Th3r 师傅的推荐来阅读一下这篇论文。不过由于本人英语很垃圾,所以就是拿小绿鲸一边翻译一边阅读。

论文摘要:LLAMAFUZZ 是一篇研究在模糊测试中引入 LLM 的非常经典的论文。本文主要提出了通过 LLM 对种子进行生成,并且利用 LLM 的推理能力对种子进行结构化变异。通过 LLM 增强结构化数据模糊测试的能力。论文基于 LLM 预训练获得的数据格式转换知识生成有效新输入,并通过配对变异种子进行微调,使其高效掌握结构化格式与变异策略。

0x01 简介

这里就不对模糊测试的概念做介绍了,直接进入重点。

传统的模糊测试工具(以下简称 fuzzer)难以有效的生成结构化数据。比如 AFL++ 虽然结合了多种变异策略(如位翻转、字节插入删除等)和先进调度策略(如覆盖率引导的模糊测试),但是在处理需要严格结构化输入的应用程序时,其盲目的随机位级变异往往会破坏数据格式的完整性,导致产生大量无效的输入种子。因此,想要变异到一个对程序高覆盖率的种子来测试出 bug 需要非常长的时间。

典型案例:测试PDF解析器时,AFL++ 需要平均 17 小时才能生成可通过基础校验的变异样本,而结构化感知方法仅需 23 分钟。

因此,为了加快这个过程,honggfuzz 提出共享文件语料库,让 fuzzer 在多进程、多线程上运行,提高吞吐量,在有限的时间中生成更多的测试用例。但是,由于复杂的结构需求,一味的增加吞吐量和添加更多的随机突变策略会在突变结构化种子时产生瓶颈。AFL++ 和 honggfuzz 需要大量的尝试来变异有效的结构化种子。另一方面,由于随机化策略, fuzzer 表现出不稳定的结果。为了减轻这种不确定性,评估模糊测试需要重复试验以进行公平比较。然而,现实世界的 bug 是稀缺的, 即使是十次重复的试验也不能确保检测到 bug。

因为依赖随机性和覆盖率信息的 fuzzer 缺乏对测试种子的结构性意识。为了生成有价值的结构化二元种子,已经提出了使用预定义语法来创建结构化数据的专用 fuzzer。Gramatron 重构了语法结构,使其支持从输入状态空间的无偏采样,并允许更激进的突变操作。它将(Search-based Testing)与基于语法的模糊测试(Grammar-based Fuzzing) 相结合,在测试用例生成过程中同时优化探索路径与语法合法性。然而,Gramatron 依赖于 Chomsky 范式Greibach 范式,需要在其基础上附件规范来构建语法自动机,同时主要聚焦于 JSON 格式。另一种方法是基于块的变异。WEIZZ 提出了一种能够自动识别未知的基于块的二进制格式并进行有效变异的机制,适用于缺乏明确语法定义的目标。然而,WEIZZ 在处理如 JSON、XML 或编程语言这类强语法约束格式时存在困难。

因此,fuzzer 开发人员面临着使用通用 fuzzer 和专用 fuzzer 之间的权衡。通用的 fuzzer 虽然用途广泛,但往往难以有效地处理结构化的种子。另一方面,专向的 fuzzer 可以生产出高质量的结构化种子,但这种专向会限制其灵活性和适用性。 此外,依赖语法规则生成种子需要广泛的领域知识,这可能成为其广泛使用的障碍。因此,需要一种更好的方法来利用通用和专用模糊器的优势。

为了解决上述问题,论文提出使用大语言模型来增强模糊中的变异过程。下图提供了 LLAMAFUZZ 体系结构的概述。通过对不同数据集的LLM 进行预训练,LLM 可以学习复杂的数据转换模式和数据格式信息, 这对结构化数据突变至关重要。此外对 LLM 进行微调, 以学习特定的结构化种子模式和变异结构化种子,试图在通用 fuzzer 和专业 fuzzer 之间找到平衡。

系统架构图
图 1:LLAMAFUZZ 架构图。

本文主要做出了一下贡献:

  • 提出了一种 LLM 增强的突变策略,该策略可以应用 于基于二进制和基于文本的数据格式,只需微调步骤。
  • 提供了通用 fuzzer 和专用 fuzzer 之间的解决方案, 可以学习结构化种子模式并对结构化种子进行突变。
  • 提供了经验证据,证明 LLM 可以增强突变过程, 从而有利于模糊测试以提高代码覆盖率。
  • 提供了实验解释来说明 LLM 如何增强模糊过程。
  • 设计了一种轻量级的异步方法来利用 LLM 和模糊器, 允许 LLAMAFUZZ 轻松部署在单 GPU 或多 GPU 上。

0x02 背景

介绍基于变异的模糊测试和覆盖率引导的灰盒模糊测试的背景。然后,提供了一些结构化数据模糊测试的当前解决方案。

2.1 基于变异的模糊测试

变异策略的目标是从给定的种子中生成新的测试用例,以发现以前未覆盖的程序区域。变异可以是随机的,即对种子进行任意更改。AFL++ 作为最先进的模糊测试工具,它使用三个阶段进行变异:

  • 确定性阶段:包括不同长度的比特翻转,对小整数的加减操作,以及已知的 “有趣” 整数;
  • 扰乱阶段:随机多次选择变异操作符,并将其应用于种子内的多个随机位置;
  • 拼接阶段:将来自两个不同种子的片段组合创建一个新的测试用例,并进一步在扰乱阶段中对其进行变异。

基于变异的策略可以更广泛地探索输入空间,但其内在的随机性也带来了更大的不确定性和效率低下。因此,传统的灰盒模糊测试工具很难生成有效输入,往往需要指数级的时间才能深入探索创新行为。

本文提出一种利用 LLM 的方法,基于已有种子进行结构感知的变异。由于 LLM 能理解输入种子的结构,并在保持其有效性的前提下进行修改,因此该方法在加速漏洞发现、提升覆盖路径数量以及发现漏洞的总量方面展现出了显著效果。

2.2 覆盖率引导的灰盒模糊测试

为克服基于变异的模糊测试中固有的随机性问题,研究人员提出使用位图(bit-map)记录覆盖信息,作为反馈来更有效地指导模糊测试过程 。由于漏洞通常隐藏在尚未被覆盖的执行路径中,因此扩大执行路径的覆盖范围被认为是提升模糊测试性能的合理方向。

对于一个被测试的程序和一组初始种子,覆盖引导的灰盒模糊测试主要包含以下四个阶段:

  1. 种子队列:从种子池中选择一个种子用于变异;
  2. 种子变异:使用多种变异策略对选中的种子进行变异,生成新的测试用例;
  3. 执行阶段:将当前测试用例输入目标程序执行;
  4. 行为监测:将每个新的种子送入插桩过的程序中执行,并通过覆盖率指标进行评估;如果该种子触发了新的代码覆盖路径,则将其添加回种子队列,供进一步模糊测试使用。

随着模糊测试循环的不断进行,更多的代码分支将被探索,从而有更大概率触发潜在的漏洞。

系统架构图
图 2:样本输入和LLM突变结果的PNG示例。

2.3 结构化种子变异

覆盖引导的灰盒模糊测试在发现现实世界程序中的漏洞方面已被证明是有效的。然而,随着软件开发的日益复杂,越来越多的程序采用具有特殊格式的高度结构化数据,这对传统模糊测试技术提出了巨大挑战。传统的模糊测试工具主要在比特级别进行变异,在处理结构化数据时往往需要大量尝试才能实现有效变异。

基于语法的模糊(Grammar-based fuzzing)提供了一种解决方案,通过人工指定的语法生成结构良好的种子。这种方法保证了输入在具备语法多样性的同时仍是语法合法的。

研究发现,以下三种语法感知的变异操作符在发现深层次漏洞时尤其有效:

  1. 随机变异(Random mutation):选取一个随机的非叶子、非终结符节点,并生成一个新的上下文无关文法派生子树;
  2. 随机递归展开(Random recursive unrolling):查找递归的产生式规则,并将其展开最多 $n$ 次;
  3. 拼接(Splicing):将两个输入拼接在一起,同时保持其语法的合法性。

这些结构化变异策略大幅提升了模糊测试在处理复杂输入格式时的有效性与效率。

2.4 大语言模型

在近期的研究中,预训练的 LLM 在自然语言任务上表现出了令人印象深刻的表现,包括自然语言理解、推理、自然语言生成、多语言处理以及事实性判断等方面。

通过无监督学习,LLM 在海量文本数据上进行预训练,从而具备了广泛的通用知识。此外,凭借数十亿甚至数万亿的参数规模,LLMs 不仅能够捕捉上下文种的语言模式,还能够更深入地理解文本数据,例如文件中的格式信息和结构块信息。

这种能力使得 LLMs 的应用超越了传统自然语言处理(NLP)任务,其通用性已在多个领域得到验证,如视觉分类、蛋白质序列生成、代码生成。

在这种通用能力的基础上,LLMs 在解析和处理不同数据结构方面的内在能力,使其在模糊测试的变异阶段表现出特别的优势。

例如,CHATFUZZ 利用 LLMs 直接生成测试种子,但其应用目前仅限于面向文本格式的目标程序,如 JSON 和 XML。

此外,psamez 等人展示了压缩语言模型(Compressed-Language Models)可以理解由压缩文件格式编码的文件内容。

在实验中,LLMs 能够生成有价值的测试种子,这些种子在探索新的路径上起到了关键作用,从而帮助获得更好的边覆盖率(edge coverage)。

0x03 设计思路

3.1 系统架构

本文提出了 LLAMAFUZZ,一个基于 LLM 的灰盒模糊工具,旨在高效变异结构化数据。如图 1 所示,包括两个阶段:

  1. 首先,利用成对的结构化数据对 LLM 进行微调,以使其能够理解底层的数据结构和变异转换方式;
  2. 然后,将 LLM 集成到模糊测试流程中,使其能够基于现有输入生成结构良好的种子。

关键在于微调阶段,它使得 LLM 能够理解目标数据的结构和变异逻辑,从而通过微调实现对各种数据格式的适应。

工作流程包括三个部分:

  1. 微调准备:训练数据来自多种来源,包括 FuzzBench 实验数据和 AFL++ 的实验数据。此外,我们还引入了一种数据转换方法,允许 LLM 生成各种数据格式。
  2. 用于变异的 LLM 微调。我们介绍了对 LLM 进行微调,然后利用 LLM 进行结构感知的变异操作。
  3. 融合 fuzzer 和 LLM:我们展示了一种异步集成的方法,使 fuzzer 与 LLM 之间可以进行异步通信,提升整体效率和并发能力。

3.2 微调准备

首先在多样化的大规模未标注语料库上进行生成式预训练(generative pre-training),然后针对特定任务进行判别式微调(discriminative fine-tuning)。

在基础模型的选择上,采用 llama-2-7b-chat-hf,该模型在大约 2 万亿个 token 上进行了预训练。

微调所用的数据来自于真实的模糊测试过程,利用这些数据训练 LLM 理解结构化数据的模式和变异方式,从而使其能够在保持原始结构的同时,对给定种子进行有效修改,生成具有价值的新测试种子。

3.2.1 微调数据收集

为了使 LLM 能够理解数据结构并生成结构化的测试种子,首先需要构建一个训练数据集。

具体而言,从 FuzzBench 的实验数据和 AFL++ 的模糊测试数据中收集有价值的种子,满足:

  1. 能触发新的新的路径;
  2. 拥有不同的命中次数;
  3. 能触发程序崩溃。

这些标准的理由很直观:

  • 提升代码覆盖率可以帮助 fuzzer 探索更多未访问的路径,因为漏洞往往隐藏在尚未执行的路径中。
  • 拥有不同命中次数的种子虽然不一定提升覆盖率,但可能以不同的方式执行程序,进而发现已覆盖路径中的新漏洞;
  • 而触发崩溃的种子本身就具备极高的价值,是模糊测试的直接目标。

3.2.2 数据转换和预处理

为了构建通用的种子变异模型,将二进制输入文件转换为统一的十六进制表示。进行这种转换的原因如下:

  1. 目标适配多种数据格式:希望 LLAMAFUZZ 能够处理各种不同格式的数据。然而,为每种格式定制读取方式在实践中并不可行,因此我们需要一种统一的数据读取方式。
  2. 适配 LLM 的输入特性:传统 fuzzer 对二进制种子进行比特级操作,而 LLM 则通常以自然语言为输入。因此,有必要将训练数据转换为 LLM 可以理解的格式。
  3. 保持转换效率:数据的转换过程必须高效和快速,因为缓慢的转换过程会直接影响模糊测试的吞吐量。

与其它编码方式(如 Base64)相比,十六进制编码更直观、易于实现,并且便于从二进制文件中直接转换。

需要注意的是:这种数据转换仅适用于基于二进制的数据。 对于文本类型的数据,仅在种子前添加 prompt,因此已有研究表明 LLM 能很好地处理基于文本的种子。

系统架构图
图 3:数据集预处理的工程流程。

如图 3 所示,数据转换方法包括以下几个步骤:

  1. 初始转换:首先,将二进制种子文件转换为十六进制表示。
  2. 生成 token:随后,每两个连续的十六进制字符将组成一个 token,从而减少输入字符串的 token 总数。
  3. 添加提示词:最后,我们为每条微调数据添加 prompt,以使 LLM 更清除其任务目标。

除了数据转换之外,还引入了噪声数据到训练集中,以降低过拟合和训练数据重放的风险。

在训练数据的组织形式上,每条数据都是一对种子:原始种子与其对应的变异版本。

这种结构设计旨在帮助 LLM 学习:

  • 种子的变异转换模式。
  • 以及数据格式的底层结构。

3.3 针对变异的微调 LLM

本节描述了对 LLM 进行微调,然后利用 LLM 进行结构感知突变的方法。

3.3.1 微调

对预训练模型进行微调是一种常见的策略,用于使模型在特定下游任务中表现得更加专业。同样,在利用通用 LLM 进行结构化数据变异时,监督式微调也是必要的。这个过程建立在预训练模型的一般理解之上,并通过监督学习使其适应特定的任务。在监督微调期间,LLM 可以根据特定任务损失得出的梯度来调整权重。因此,我们可以使用监督微调来教 LLM 理解输入语法和输出变异。

进行微调的第一步是准备有效的提示,图 3 右侧的 Pair 1到 Pair n为模型提供了对结构化数据进行变异的示例和相应的变异结果。在这个提示符中,fuzzer 以十六进制表示提供了当前的结构化数据和期望的变异结果。随后,强调格式关键字,允许 LLM 从预训练的知识中获取对格式的一般理解来进行变异。不过,LLM 可能偶尔会产生随机输出,不过这种行为相对罕见,不会对整个 Fuzzing 过程产生重大影响。由于这些响应通常是无效的,因此它们很容易被 fuzzer 忽略,而不会影响结果。

图 2 展示了一个由 LLM 变异的 PNG 种子示例,其中用红色突出显示的字节是 LLM 完成的突变。在这个例子中,PNG 文件被构造成 PNG 签名、IHDR、gAMA和原始数据块。签名通常被称为文件头,由固定的 8 个字节组成,标志着文件的开始。紧跟着签名的 IHDR 块是至关重要的,因为它包含基本的图像信息,如宽度、高度、位深、颜色类型、压缩方法、滤波方法和隔行方法,gAMA 块指定图像和所需显示输出之间的关系。后续块在本质上通常是辅助的。在 IHDR 之后,PNG 文件包含几个辅助块。

在这个例子中,LLM 选择性地修改 IHDR、gAMA和数据块。它不仅保留了原始数据格式的完整性,还引入了有效的修改,增强了种子触发漏洞的潜力。

3.3.2 生成

下一步是将来自 fuzzer 的当前种子结合提示词作为 LLM 的输入。提示词用于引导 LLM 生成与其微调阶段所实验格式保持一致的变异内容。LLM 生成响应后,输出结果会被解析并转换回二进制格式。我们期望生成的内容是对原始种子的结构化变异,即在保持原始输入结构完整性的前提下,产生有意义的变异种子。

3.4 整合fuzzer和LLM

对于灰盒 fuzzer 来说,速度是至关重要的,它能够每秒执行数百甚至数千个种子。任何额外集成流程(如基于 LLM 的变异机制)都可能影响整体吞吐量,进而降低模糊测试的效能。尤其是 LLM 生成过程存在显著的性能瓶颈:其速度更慢且计算密集,主要依赖大量 CPU 资源。

为了解决这一速度不匹配的问题,论文设计了 LLM 与 fuzzer 之间的异步通信机制。具体实现流程如下:首先将当前种子快速转换为十六进制格式;随后 fuzzer 在向 LLM 发送种子的同时,持续接收 LLM 返回的新种子;LLM 完成种子变异后立即回传新种子供后续测试。该异步架构完全消除了等待延迟,使得 fuzzer 能够持续保持高速运行状态,无需阻塞等待 LLM 完全变异任务。通过将高速测试流程与低速 LLM 变异过程解耦,在不损失测试效率的前提下,成功实现了 LLM 对 fuzzer 的能力增强。

0x04 实验

论文扩展 AFL++ 实现了 LLAMAFUZZ,通过异步方法在 fuzzer 和 LLM 之间进行通信。通过修改 AFL++ 源代码以纳入 LLAMAFUZZ 的功能,如消息队列和种子评估。为了解决 fuzzer 和 LLM 之间的生成速度不匹配问题,将消息队列长度限制为 30。这种调整确保了 LLM 总是在最近的种子上进行变异,保持了变异的实时性和有效性。

为了收集足够多样的微调数据,并满足 LLM 最大 token 限制,将十六进制对的最大长度设置为 4096。这个限制对应于 LLM 的最大 token 容量。论文选择了 llama2-7b-chat-hf,这是最先进的 LLM 之一,在硬件上功能强大且高效。

实验结果表明,LLAMAFUZZ 在多个基准测试程序和真实世界目标程序上均表现出明显优于传统 fuzzer 的测试效果,特别是在代码路径探索和触发崩溃方面。

未来的研究可以进一步探索 LLM 如何从已有种子的表现中学习其与新行为触发的关系,从而增强模糊测试过程的智能决策能力。在 fuzzing 中,能触发新的覆盖路径或错误状态的种子被视为有价值的,因此,理解种子与代码覆盖提升之间的联系对于提升整个测试流程的效率至关重要。

类似于传统的随机测试工具如 Csmith,其通过系统地生成覆盖 C 语言大子集的合法测试程序,并规避未定义行为,有效提升了对 C/C++ 编译器的测试覆盖率。未来,结合语义感知与结构化输入的 LLM 可望在更复杂场景下生成类似高质量测试用例。

0x05 总结

  • Fuzzing:Fuzzing 是一种自动化的随机软件测试技术,用于发现目标程序 或应用程序中的漏洞和错误。

    • 白盒:白盒 fuzzer 利用程序分析来提高代码覆盖率,以探索某些代码区域,这可以有效地揭示复杂逻辑中的漏洞。
    • 黑盒:黑盒 fuzzer 的目标是二进制程序,它不知道程序结构。黑盒 fuzzer 因为随机生成测试输入,所以执行量很高,但它只触及表面。
    • 灰盒:灰盒 fuzzer 结合了白盒模糊器的有效性和黑盒模糊器的效率,它利用插桩从目标程序获得反馈,从而产生更有价值的种子,提高代码覆盖率。
  • 结构数据变异 Fuzzing:在需要结构化输入的应用程序中,上述方法的盲目随机变异策略往往会破坏数据格式的完整性,导致生成大量低效和无效的种子。结构化数据变异的语法引导 fuzzer 以准确识别目标输入格式。

  • 机器学习增强 Fuzzing:目前机器学习增强 Fuzzing 的研究主要在两个方面:

    • 利用机器学习模型作为生成器。
    • 利用机器学习模型来引导 Fuzzing 过程。

输入种子变异是直接影响模糊测试性能的灰盒模糊测试关键步骤。虽然随机比特级变异在许多情况下有效,但是很多情况下在测试结构化要求的目标时面临着巨大的挑战。这些因为当前基于变异的 fuzzer 需要极多的尝试来变异有效的高度结构化数据,且变异高度依赖随机性。

本文提出利用大语言模型学习结构化数据的模式并进行种子变异。基于该理论构建了 LLAMAFUZZ,并证明大语言模型在结构感知变异中具有高效性和有效性。