摘要:定向灰盒模糊测试致力于检测程序中的目标代码,广泛应用于补丁测试、候选漏洞确认及已知漏洞复现等多种场景。然而,我们发现现有定向模糊测试工具普遍缺乏有效的输入变异策略,通常依赖于基于AFL策略的随机性与经验主义方法,这在定向测试环境中效率较低。
本文提出IDFUZZ------一种面向定向模糊测试的智能输入变异解决方案。我们的核心思路是借助神经网络模型,从历史变异输入中学习并提取有用经验,以引导输入变异向目标代码推进。我们在模型构建与模型训练中引入了多项创新技术,有助于建立一个能够充分捕捉如何覆盖与目标相关的已探索及未探索代码经验的模型。此外,我们设计了一种基于模型梯度细化的引导方案,利用经验定位关键输入字段,并开发了定向输入变异策略。
**历史变异输入:**程序内部的历史变异,其有效性依赖于程序逻辑的稳定性。
我们将IDFUZZ实现为一个输入变异模块,可与大多数开源主流定向模糊测试工具兼容。在评估中,IDFUZZ在谷歌模糊测试基准套件上将现有定向模糊测试工具的漏洞复现速度显著提升了2.48倍以上 。此外,我们证实IDFUZZ帮助现有定向模糊测试工具减少了91.86% 的无效变异。最后,借助IDFUZZ,我们在经过充分测试的真实软件中检测到6个 先前未知的漏洞(迄今为止已分配4个 CVE编号),并发现1个高危漏洞的不完整修复案例。
引言
模糊测试已成为网络空间漏洞发现中应用最广泛的技术之一。根据测试目标差异,模糊测试通常可分为两大类:覆盖引导式模糊测试旨在提升程序代码的覆盖率,而定向模糊测试则专注于抵达代码中的特定目标位置。近年来,定向灰盒模糊测试因在崩溃复现、漏洞验证和补丁测试等场景中的广泛应用,受到越来越多的关注。研究背景
众多定向模糊测试解决方案已被提出,这些方案通常利用与目标代码的距离或路径信息作为反馈,以调度种子输入或优化输入执行过程 。例如,部分研究通过距离度量来优先处理更接近目标代码的输入,并对其施加更多变异操作。另一些研究则利用路径可达性来筛选输入并节省执行时间,还有工作通过在输入执行过程中跳过收集无关代码的覆盖率信息,以缩小演化方向的范围。然而,当前针对定向模糊测试中变异步骤的研究仍显不足 。现有的定向灰盒模糊测试工具通常采用基于AFL方法的变异策略,即在变异过程中随机选择偏移量和长度。但定向模糊测试的目标是抵达具有特定控制流要求的目标代码位置,这通常需要对特定输入字段(关键字段)进行精准变异才能接近目标位置。因此,随机选择偏移量和长度的变异策略缺乏方向性,在定向测试中往往会导致大量无效变异。近期研究数据显示,超过65.1%的变异输入未能接近目标位置。现存问题
输入变异/生成技术在覆盖引导式模糊测试领域已得到广泛研究,但为定向模糊测试寻找有效的输入变异方案仍然面临独特挑战。当前两种主流优化方法------相关性分析和混合执行------在应用于定向模糊测试任务时均存在显著局限性。
- 相关性分析技术(如污点追踪)旨在将影响输入字节与给定约束条件相匹配。这类技术在处理早期完整性检查(例如魔数校验)时极为有效,可显著提升覆盖引导式模糊测试效率。然而,当面临多重约束时,这些技术常遭遇过污点和欠污点的问题。由于定向模糊测试的目标常位于程序深层,且受复杂约束条件保护,相关性分析方法在处理定向测试任务中的大量约束时,往往面临计算成本高昂或精度不足的困境。
- **混合执行技术利用约束求解器解析路径约束,可有效协助模糊测试工具突破严格约束。**然而在实际程序中,通往定向模糊测试目标的路径可能数量众多,这常导致混合执行面临高昂开销及路径爆炸等可扩展性问题。
因此,定向模糊测试亟需一种能处理多种约束的轻量级、可扩展方法。本文提出一种新颖的定向模糊测试变异策略,其核心思路在于:历史上生成的变异输入蕴含着可引导输入向目标代码逼近的宝贵经验。例如,若输入a经变异生成更接近目标的输入b(例如b沿目标路径前进了一个基本块),通过对比a与b之间的字节级差异,便可提取出趋近目标的经验(即关键字段的位置信息)。当对其他受相同约束的输入进行变异时,可借助此类经验直接修改输入的关键字段,从而减少无效尝试。这种利用历史输入经验的数据驱动方法,使我们无需显式求解约束条件,即可高效定位各类约束对应的关键字段。为了利用历史上变异输入的经验来制定输入变异策略,我们需要回答以下问题:
- **问题一:此类经验是否适用于未来的变异操作?**我们期望将从输入a和b中获得的经验应用于其他输入的变异,即通过识别关键字段的偏移量和长度,对其进行变异以促使输入更接近目标。然而,不同输入的关键字段可能存在显著差异。即使是文件格式的相同部分,大多数情况下在不同输入中的偏移量也各不相同。因此,从a和b获取的经验通常仅适用于a这一特定输入。
- **问题二:此类经验能否提升定向模糊测试的效率?**假设输入a与目标的距离为d,输入b与目标的距离为d-1。从a和b中获取的经验,理论上最多只能揭示如何从距离d缩短至d-1的方法,但无法为生成距离小于d-1的输入提供指引。换言之,我们获得的知识仅有助于引导变异抵达已实现的距离,而不能为探索未覆盖的代码区域提供方向。由于覆盖新代码是抵达目标的必要前提,因此这类经验能否推动定向模糊测试的进展仍存疑问。
针对问题一,我们发现虽然从单一输入对中提取可泛化经验较为困难,但从海量模糊测试输入的整体数据中,可以提取输入字节组织形式与输入距离之间的关联模式。这是因为输入字节与距离的相关性本质上反映了被测程序的执行逻辑。被测程序将输入字节读入变量,生成执行路径,并决定距离值。被测程序的执行逻辑具有确定性和稳定性,使得输入字节与距离之间形成独立于具体输入的强关联模式。尽管传统程序分析方法难以总结此类模式,但我们发现神经网络模型凭借其在模式识别方面的强大潜力,具备学习这种模式的显著能力。
程序的执行逻辑是连接"输入数据"与"执行结果"的稳定桥梁 。具体来说,程序代码一旦编译,其逻辑就是固定的。由于逻辑是确定的,特定的输入子节分布必然会推到出特定的执行路径和距离值。传统的程序分析在面对复杂的嵌套循环、算数运算或大规模路径时,很难用简单的公式总结出这种关联模式。--通过神经网络学习漏洞触发模式
我们采用简单的多层感知机模型来学习输入字节与距离之间的关联模式,该方案在成本效益上符合任务需求。我们以模糊测试输入的字节值作为模型输入。为提供更丰富的特征以优化模型拟合效果,我们并未直接使用距离值作为模型输出,而是设计了一种创新的分支编码技术,将相关分支行为编码为模型输出。我们将相关分支行为定义为程序控制流图中目标代码支配基本块(简称dom-BB)的覆盖信息。根据定义,dom-BB是必须按序覆盖才能抵达目标代码的基本块序列。因此,将其覆盖信息编码为模型输出,不仅包含"执行路径是否接近目标"(即距离)的信息,还通过使模型学习如何按序覆盖这些dom-BB,蕴含了"如何更接近目标"的指导信息。此外,我们开发了自适应数据集生成技术,能够在线从海量模糊测试输入中识别并采样出少量高质量训练数据集。
TransferFuzz从 Base Binary 中提取"历史轨迹"作为外部导向知识,侧重于在"种子选择"阶段利用模拟退火寻找全局优解;而 IDGF 则在模糊测试过程中实时生成"分支经验"作为内部导向知识,侧重于在"变异执行"阶段利用神经网络梯度实现局部精准打击。
针对问题二,我们发现经验可通过两个方面助力定向模糊测试。首先,引导生成可抵达已探索代码区域的输入本身就具有重要价值。这是因为一旦抵达目标,我们就能利用现有可达输入的经验生成更多可达输入,从而加速目标漏洞的复现过程。其次,我们发现某些已覆盖分支的经验可用于推测如何抵达未探索代码区域。我们观察到,同一条件语句对应的兄弟控制流边共享相同的关键字段。通过分支编码将条件语句下所有分支的覆盖信息关联起来,模型就能从已探索的兄弟边学习如何抵达未探索边。例如,若目标为特定的switch分支,即使该目标分支尚未被探索,我们仍可从其他已探索的分支推断出switch语句对应的关键字段。因此,我们的方法不仅能保留接近目标的输入,还能持续引导生成更接近目标的输入。神经网络模型完成训练后,它将模拟以下方程中的目标函数:
模型的输出向量会按照这些关卡在程序中的先后执行顺序进行排列。为了识别哪些字节是控制程序的关键,我们引入了数学上的偏导数概念。具体来说,通过计算某个分支行为对特定输入字节的导数,我们能够直观地衡量该字节的变动对程序分支走向的影响力。这个数值越大,就意味着该字节对该关卡的控制力越强,从而为后续的精准变异提供了明确的数字化指标。在确定需要对哪些输入字节进行变异时,我们可以指定当前输入未覆盖的首个支配基本块,进而识别对其产生影响的字节。对这些字节实施变异能够引导程序执行流向该支配基本块,从而逐步接近目标代码。
基于这一理论基础,我们进一步设计了新型梯度过滤技术,以消除模型梯度在现实场景中的噪声干扰。此类噪声源于实际场景中字节分布的不均匀性,会显著降低关键字段识别的准确性。随后,我们采用基于密度的噪声应用空间聚类算法对输入字节的梯度值进行分组处理,以此识别关键字段的偏移量与长度。依托这两项技术,我们开发出能够高效识别并变异关键字段的智能变异策略,从而引导输入向量向目标代码演进。
我们将该方法实现为名为IDFUZZ的输入变异模块,可替代现有定向模糊测试工具中基于AFL的变异策略。通过将IDFUZZ集成到四种先进定向模糊测试工具中,并在广泛用于评估定向测试工具的谷歌模糊测试基准套件上进行实验。结果显示,IDFUZZ将目标漏洞复现速度提升超过2.48倍,性能优于现有优化变异方案。此外,我们发现IDFUZZ在仅增加6%开销的情况下,帮助现有定向测试工具减少91.86%的无效变异。借助集成IDFUZZ的AFLGo工具,我们在主流实际软件中检测到6个新漏洞(其中4个已获CVE编号)及1个不完整修复案例。
研究动机
我们通过一个真实案例来阐述本研究的动机。代码片段1展示了jhead程序读取JPEG格式输入文件的部分代码。该示例中,第4行代码检查文件的魔数字节,第5-29行通过for循环顺序读取每个JPEG区段。在循环内部,第8-15行过滤区段前的0xff填充字节,并读取决定区段类型的标记字节。随后第18-28行通过switch语句根据标记字节的数值对区段进行进一步处理。本例中的目标位于第24行的"case M_COM"分支。
1 static int SectionsRead;
2 int ReadJpegSections(FILE* infile, ReadMode_t ReadMode) {
3 int a = fgetc(infile);
4 if (a != 0xff || fgetc(infile) != M_SOI) return FALSE;
5 for(;;) {
6 int prev = 0;
7 int marker = 0;
8 for (a=0;;a++) {
9 marker = fgetc(infile);
10 if (marker != 0xff && prev == 0xff) break;
11 if (marker == EOF) {
12 ErrFatal("Unexpected end of file");
13 }
14 prev = marker;
15 }
16 Sections[SectionsRead].Type = marker;
17 SectionsRead += 1;
18 switch(marker) {
19 case M_SOS: ...
20 case M_DQT: ...
21 case M_DHT: ...
22 ...
23 case M_COM:
24 // target
25 ...
26 case M_SOF15: ...
27 default: ...
28 }
29 }
30 return TRUE;
31 }
定向模糊测试存在的问题
现有的有向模糊方法可以概括为三个主流:优化输入优先级、优化输入执行和优化输入生成。
- 优化输入优先级排序。当前主流研究以AFLGo为代表,通过适应度指标度量种子输入与目标的距离,并优先对距离目标更近的种子进行变异。在此案例中,这些方法虽能优先处理到达switch语句的种子,但在种子变异过程中无法识别"M_COM"标记字节,导致大量变异操作浪费在其他无关字节上。即便少数输入成功抵达"case M_COM"分支,其变异策略仍对此成功毫无感知:未来变异不可达输入时无法利用此经验排除无效变异,甚至可能错误地变异可达输入中的标记字节,致使执行路径偏离目标。
- 优化输入执行过程。FuzzGuard运用神经网络模型预测输入的可达性并剪除不可达输入。Beacon通过提前终止不可达输入的执行来节省时间开销。MC2采用蒙特卡洛执行重构定向模糊测试,依据程序分支的执行频率估算目标可达输入。SelectFuzz与DAFL通过选择性插桩确保输入执行的反馈仅包含相关代码的覆盖信息,从而避免进化方向偏离目标。尽管这些方法能有效筛除大量不可达输入或缩小搜索空间,但均无法直接生成抵达"case M_COM"的输入。
- 优化输入生成机制。Halo通过分析已执行输入来推断抵达目标所需的条件,并依据这些条件将输入字段变异为特定数值。WAFLGo选择距离目标最近的控制流边作为目标边,并采用变异掩码技术确保仅变异不会偏离目标边的输入字节。Halo与WAFLGo均无法推断标记字节的偏移量。两者依赖于顺序测试每个字节,这种方法与污点追踪类似,同样面临过污点和欠污点问题。Halo仅当存在已抵达"case M_COM"分支的输入时,才能推断出抵达该分支的条件。
约束求解存在的问题
另一种可行的解决方案是利用约束求解技术(例如相关性分析或混合执行)来绕过switch语句。相关性分析技术在识别标记字节时面临挑战,因为其偏移量可能随不同输入而变化:由于第8行的循环在标记字节前检查填充字节,污点追踪可能遭遇过污点问题,而输入状态关系分析则可能因填充字节在"着色"过程中被错误修改而导致识别不准确。对于混合执行而言,其固有的可扩展性问题限制了在大型程序中复现漏洞的有效性,尤其在本例涉及嵌套for循环的程序结构中更为明显。
智能定向模糊测试引出
在此案例中,现有方法难以抵达"case M_COM"分支。即便成功生成少量可达输入后,仍难以生成更多此类输入。我们认为这源于其变异策略缺乏经验感知能力:未能充分利用模糊测试过程中积累的变异经验。我们的核心思路是通过提取变异输入中的经验来定位标记字节并直接对其执行变异操作,具体从两个层面提取经验:
- 在第一层面,当部分输入成功抵达"case M_COM"分支后,我们通过分析这些成功输入与受阻于switch语句的输入,提取出标记字节是抵达"case M_COM"关键字段的经验认知。
- 在第二层面,若"case M_COM"分支尚未被触及,那些受阻于switch语句的输入会选择其他分支。虽然现有方法通常不关注这些输入选择的具体分支(为其分配相同距离值或统一视为不可达),但抵达这些分支的关键字段同样也是标记字节,这暗示着推断"M_COM"关键字段的可能性。通过捕捉并学习这些输入在switch语句处呈现的不同分支行为,我们发现所有分支(包括"M_COM")的关键字段都是标记字节。
"经验"的本质是什么?我们通过模型学习到的经验来识别关键输入字段,这里的"经验"本质上是模型输出(即相关分支行为)相对于输入(即输入字节)的梯度信息。在此案例中,我们采用分支编码技术将switch语句的不同分支行为编码为公式(1)中不同的br值,准确表征了三种执行路径:1未抵达switch语句、2抵达switch并进入"case M_COM"分支、3抵达switch但进入其他分支。我们的模型能有效学习输入字节与这些编码分支行为之间的关联。模型训练完成后,我们计算给定输入对应的模型梯度。理论上,对应标记字节的梯度∂br/∂xj具有最大绝对值,因此可确定标记字节的偏移量为j。值得庆幸的是,即使标记字节在不同输入中的偏移量存在差异,该方法仍能有效定位标记字节,正如第5.4节实验所验证。然而,在实现高效定向变异的过程中,我们仍需应对两大挑战。
研究内容

IDFUZZ的工作流程可分为三个阶段:模型初始化、模型训练和梯度引导定向变异,如图1所示。在模型初始化阶段,我们使用轻量级静态分析来提取目标代码的dom BB,并开发一个分支编码器,该编码器可以将输入在这些dom BB处的分支行为(即相关分支行为)编码为张量作为模型输出。在模型训练阶段,我们使用自适应数据集生成器来收集模糊输入,使用分支编码器对其执行路径进行编码,并生成模型训练集。在模型训练之后,我们将种子输入输入到模型中并计算模型梯度。通过梯度滤波和DBSCAN聚类技术,我们识别关键输入字段并引导定向种子突变。我们将在下面介绍三个阶段的细节。