🤥
模糊测试通常是自动化的,但也经常由专家辅助,他们以贪婪的方式插入工作流程中寻找漏洞。在本文中,我们提出了"Homo in Machina"或者HM-fuzzing,其中分析引导手动工作,最大化效益。作为这一范例的一个示例,我们介绍了区块分析。区块分析使用整个程序的支配关系分析来估计到达新代码的效用,并将其与指示该代码所保护的严重缺乏覆盖的边的动态分析相结合。这将导致一份优先级排序的区块列表,即语义上划分且根据当前考虑的输入集合而大部分不可达的程序的大型未覆盖部分。人类可以直接使用这种区块的分类和排序来集中手动工作,找到或创建使这些区块在未来模糊测试中可访问的输入。我们评估了区块分析对OSS-Fuzz语料库中的七个项目的影响,在某些项目中与AFL++相比覆盖率提高了高达94%,中位数为13%。我们还观察到,确定区块是非常稳定的,因此可以在模糊测试活动的早期进行,最大化影响的潜力。
模糊测试,或称为fuzzing,是当今最有效的自动化漏洞发现技术。它被广泛应用于工业界[7],[34],[35],[38],并在近年来进行了大量研究[28]。然而,作为一种动态测试技术,模糊测试在根本上存在一定的限制——如果代码不执行,那么模糊测试就无法发现该代码中的漏洞。覆盖率通常以已执行至少一次的基本块或边的数量来衡量,这是揭示未知漏洞的必要条件。需要注意的是,这并不是充分条件;仅仅因为已执行包含漏洞的块,并不意味着在一般情况下满足程序状态的附加约束条件时将触发该漏洞。然而,当使用模糊测试来发现新的漏洞时,实现高覆盖率仍然是一个至关重要的障碍需要克服。
不幸的是,即使使用简单的基本块或边覆盖度量标准,模糊测试也无法覆盖被测试程序的大部分代码。实证研究反复显示,覆盖率通常在相对较短的时间内就会达到一个稳定水平,通常在24小时之内[21],[42]。最近的研究从理论的角度探讨了这一现象,假设了一个经验性的幂律来描述实现新覆盖的难度——随着时间的推移,实现新覆盖的难度呈指数级增加[32]。已经提出了许多方法(部分或全部)来克服这个"覆盖障碍":(i) 种子选择和修剪,以减少在不太可能产生新覆盖的输入上的浪费性试验 [9],[10];(ii) 更复杂的模糊测试配置调度算法[12],[14];(iii) 改进变异操作符调度[27];(iv) 输入区域优先级[5],[6],[15],[18],[23];(v) 使用共享执行辅助传统灰盒变异模糊测试的混合模糊测试方法[13],[22],[31];以及(vi) 从人类输入中学习[16],等等。尽管进行了大量研究,但在逐渐覆盖新代码以及在不同模糊测试活动间的持续改进仍然是一个未解决且核心的问题。
在这项工作中,我们提出了Homo in Machina或HM-fuzzing,它将增量模糊测试的结果呈现给人类,并由人类帮助指导模糊测试,以实现比单独进行模糊测试时更大的覆盖率。目前,人类主要通过分析覆盖报告并提供新的种子或修改测试框架来介入模糊测试活动。HM-fuzzing旨在改善这个关键但不常研究的手动方面的真实模糊测试,基于一个重要的观点,即虽然人类相对于自动化技术更擅长于覆盖新代码,但他们在应用这种专业知识上有相对有限的预算。
HM-fuzzing的核心思想是利用人类的专业知识和洞察力,通过增量模糊测试的结果为人类提供准确的信息,帮助人类找到新的代码路径和漏洞,并指导模糊测试工具进一步探索。相比自动化方法,人类可以更好地理解程序的内部逻辑和结构,并且能够根据经验和直觉来选择更有潜力的输入。然而,鉴于人类的时间和精力有限,HM-fuzzing还需要考虑如何有效地利用人类的专业知识,以及何时和如何与自动化模糊测试工具进行交互。
HM-fuzzing试图将人类和自动化技术紧密结合起来,以充分发挥两者的优势,从而更好地提高模糊测试的覆盖率和效果。通过在现实世界的模糊测试中改进人类参与的方式,HM-fuzzing有望在发现新的代码路径和漏洞方面取得更好的成果。
作为HM-fuzzing的一个示例,我们介绍了隔室分析(compartment analysis)。隔室分析旨在在模糊测试活动期间定期运行,分析到目前为止实现的增量覆盖率。分析的目标是识别隔室或者受到覆盖边界相邻块支配的代码[2],如果这些代码被覆盖,很可能会在给定固定资源预算的条件下最大化整体覆盖率。测试程序的跨过程控制流图(ICFG)用于识别在考虑内部和跨过程边缘时,支配最多基本块的节点。根据当前的模糊测试语料库,进一步过滤这些隔室,只保留覆盖严重不足或完全未覆盖的隔室。然后,结合ICFG使用动态数据流分析方法,向分析人员提供有关条件依赖于输入或模糊测试框架的某些特定部分的额外建议。分析使用加权函数对这些隔室进行排序,并向安全分析师返回重要的部分。然后,分析师可以利用从隔室中提取的信号来调整测试框架或增加输入队列,以“解锁”这些隔室。这种技术不仅可以提高剩余模糊测试活动的覆盖率,还可以为将来的活动提供帮助。隔室分析系统地使安全分析师能够明智地干预,帮助模糊测试工具覆盖它们很可能无法自行覆盖的代码。
我们的评估对来自Google FuzzBench语料库[38]的一组程序和相应的模糊测试框架应用了隔室分析。总体结果表明,隔室分析在提高覆盖率方面非常有效,在七个案例中至少有六个案例的增益达到了10%,其中一个案例的增益达到了94%。人力工作估计每个程序需要几个小时,考虑到所获得的收益,这是相当可观的。
为了推进开放科学和可重复性研究,我们将在发表后公开我们的原型实现和数据[1]。总结起来,我们的论文做出了以下贡献:
这项工作的最终目标是帮助模糊测试工具覆盖它们在模糊测试活动中无法到达和充分覆盖的代码。然而,需要认识到代码难以覆盖的原因有多种。覆盖引导的模糊测试工具卡住的最明显原因之一是由于复杂的程序逻辑,这些逻辑不适合增量进展或转化为可满足的公式。另外,模糊测试工具可能由于在如何加载和执行待测程序方面的选择而无法覆盖代码。
Problem: 考虑图1,它展示了以file_trycdf为根的“file”实用程序的调用子图。这段特定的代码负责识别和汇总Microsoft组合文档文件(CDF) [2]。对于最先进的模糊测试工具来说,覆盖处理CDF的代码是非常困难的,除非提供一个CDF文件作为种子示例。CDF是一种高度结构化和复杂的文件格式,本质上在一个文件中编码了类似FAT的文件系统。要在没有有效示例的情况下探索这段代码,模糊测试工具需要从其他种子中合成一个有效的CDF文件。尽管模糊测试工具已经以自动合成某些文件格式的实例而闻名[11],但它很难为像CDF这样高度结构化的输入实现这样的技巧。
从模糊测试工具的角度来看,合成一个CDF文件对应于满足一个复杂的符号化公式,该公式代表了导致进入图1所示的文件的CDF代码的路径约束。即使借助于专门针对此目的设计的工具(例如SMT求解器),满足复杂的路径约束对于模糊测试工具来说也是非常困难的,并且是导致模糊测试覆盖问题的一个重要原因。
Problem: 另一方面,考虑到列表1,它显示了从libxml2 XML解析库的libFuzzer测试框架的代码片段的差异。在这种情况下,我们可以看到测试框架的原始版本将一个空的选项位集传递给函数。虽然这可能看起来无害,但实际上,由于这个选择,会确定性地排除处理由该参数指示的选项的所有代码,这些选项由xmlReadMemory函数处理。换句话说,由于测试框架将选项设置为固定值,libFuzzer永远不会覆盖libxml2中的一个相当大的子集。请注意,这个问题不仅限于libFuzzer,而是普遍适用于整个程序测试,其中命令行或环境变量要么被初始化为默认值,要么不发生变化。
绝大多数先前的工作主要集中在解决这两个问题中的第一个问题,即需要引入新的输入类型或类别来解决覆盖问题[5],[6],[9],[10],[12]–[16],[18],[22],[23],[27],[31]。我们不知道是否有系统地解决第二个问题的相关工作,该问题是由于缺少源输入而有效地阻碍了覆盖率的提升,无论是命令行选项、环境变量还是其他许多意想不到的输入来源。
解决这些问题,当自动化手段失败时,是人类分析师的工作。为了使模糊测试器能够覆盖新的CDF解析代码,分析师可能会仔细阅读未覆盖源代码的相关部分,确定语料库中缺少这种类型的输入,并通过一些谷歌搜索获取一些新的种子。为了对libxml2的SAX1接口进行模糊测试,分析师可能需要深入研究目标程序的源代码,发现libxml2中的选项特定代码没有被覆盖。掌握这些知识后,对测试框架代码的简要调查将提出一个修复方案。在这两种情况下,这些都是需要专家进行的任务,他们需要理解程序的功能、实现方式、输入结构以及如何找到新的种子。这些对于能够将不同技能融合到整体问题解决工作流程中的人类分析师来说是非常适合的。
虽然执行模糊测试活动的安全分析师会进行这种手动分析,但他们很少能获得相关指导。然而,这种有针对性的人工干预可以极大地帮助自动化模糊测试工具的效果。因此,我们提出以下研究问题。
Research Question: 模糊测试工具是否可以系统地提供这种缺失的指导,并以输入队列扩充或测试框架修改的形式向安全分析师建议高价值的干预措施,以改进自动化模糊测试的覆盖率?
规则定义的分析工作流程如图2所示。首先,在FUZZ步骤中,目标程序会在某个种子语料库上进行模糊测试,期间跟踪累积边的计数,并在活动结束时将其作为LLVM分析数据文件(*.profraw)输出,同时还输出一个改善覆盖率或导致崩溃的输入语料库,即模糊测试的“队列”。然后,队列会在第一个EXECUTE步骤中重新执行,以获取动态调用图,记录在callgraph.log中遍历的间接调用边。这些动态的间接调用数据和边的计数值会在ANALYZE步骤中使用,以识别不同的隔离区并根据第III-B节中的描述计算权重。该步骤的输出是一个隔离区列表,会通过另一个EXECUTE步骤运行,其中会运行经过dfsan插桩的程序版本,以根据控制它们的输入源诊断隔离区保护条件(参见第III-D节)。这将生成一个类似于表格I中示例的隔离区排名报告,人类分析师将根据该报告指导自己的工作,并根据需要向模糊测试队列中添加新的种子或调整测试框架以引入新的变异输入。
在模糊测试中,收集覆盖率数据是一个重要的环节,尤其是对于最流行的一种模糊测试形式——基于覆盖率导向的模糊测试。关于在模糊测试期间收集和存储覆盖率信息的方法已经得到了深入研究 [20],[30],[40],[41],并且研究者们致力于选择合适的敏感性和效率水平以识别新的覆盖率。尽管不同的技术存在一些差异,但一般来说,基于覆盖率导向的模糊测试工具会跟踪记录哪些代码块或边缘已经被覆盖,并生成保存在语料库中的输入与其对整体覆盖率的贡献之间的映射关系 [28]。
在传统的模糊测试工作流程中,研究人员可以通过生成覆盖率报告来评估模糊测试工具的进展情况。GCC和Clang都支持对二进制文件进行插桩,并以代码区域为基础生成源代码覆盖率的HTML报告,该指标比行号更精确。这份报告是通过编译带有插桩的二进制文件并在模糊测试语料库上执行该二进制文件来生成的。虽然该报告会突出显示具有有限或零覆盖率的函数,但它并不指示模糊测试工具在各个代码区域上所花费的资源平衡情况。为了分析可能未被充分探索的代码区域,需要在模糊测试期间收集更详细的性能分析信息。为了支持在模糊测试过程中收集性能分析信息,AFL++通过添加一个额外的分叉服务器(图2中的cov_fuzzer)对其进行了修改。这个分叉服务器简单地在插桩的二进制文件上执行模糊测试工具生成的每个修改后的输入。由于性能分析是与默认的分叉服务器并行进行的,因此模糊测试的减速非常小,但计算开销大约增加一倍,因为程序将运行两次。
在模糊测试过程中,分区的排名或优先级是基于解锁分区的奖励上限确定的。在模糊测试活动中,奖励就是额外的新覆盖。排名是通过对跨过程控制流图(ICFG)进行静态分析,结合在初始模糊测试期间获得的性能分析覆盖信息来确定的。确定分区权重的函数如清单2所述。函数getBlockWeight输入一个从ICFG导出的支配树DT和要分析的基本块B,并返回总权重。为了确定超过函数调用的指令,函数getCallsWeight以深度优先的顺序遍历调用图,将从𝔻𝕋中的调用点唯一可达到的所有函数的权重求和。一个分区的总权重是从该分区的入口块唯一可达到的LLVM中间表示(IR)指令的总和。
用户定义的参数MAX_EXEC_COUNT允许分析人员在需要时查看未执行的代码和/或欠饱和的代码。为了评估的目的,我们选择仅查看在模糊测试期间没有覆盖到的分区。因此,MAX_EXEC_COUNT的低值(例如50)是合适的。然而,在连续模糊测试环境中,分析人员可能对未充分模糊测试的分区或模糊测试资源不足的分区感兴趣。在这种情况下,分析人员可以将MAX_EXEC_COUNT设置为更高的值,以捕捉他们希望考虑的相对欠饱和程度。
尽管大部分的ICFG可以在编译时确定,但间接调用点会阻止精确的全程序分析。有一些可用的工具[29],[37]可以进行调用图分析,但它们往往会对间接调用点的调用图边缘进行过度近似。这是由于函数原型的重叠造成的,特别是在C程序中,函数原型是确定间接调用边缘的主要手段。例如,在PROJ4中,函数pj_init_ctx包含了几个间接调用点,其中一个接受一个指针参数并返回一个指针。在我们选择的基准测试的分析中,间接调用点可以占据ICFG中总节点数的高达11%,并且在边缘中占比更大。此外,即使经过四天的模糊测试,仍然可能有许多间接调用未被发现,如表II所示。我们选择不依赖静态分析来解决间接调用,因为误报很常见,可能会损害人们对工具输出的信任或降低工作效率。对于大部分未覆盖的代码位于间接调用目标后面的程序,请注意我们可以将没有任何传入直接调用边的每个函数都视为可能的分区,并计算其权重为𝑊𝜃(𝑏),其中𝑏是函数的入口基本块。
LLVM DataFlowSanitizer (DFSan)是一种强大的动态数据流分析工具,已广泛用于辅助模糊测试[18],[25],[26],[39]。通过使用选项-fsanitize=dataflow编译二进制文件,可以启用DFSan插桩,但需要额外的工作来标记输入源。可以通过LLVM转换Pass或手动修改源代码来标记输入源,方法是添加DFSan函数以创建污点标签并将它们适当地分配给数据源。此外,当调用外部库函数时,DFSan还依赖于用户补充的ABI列表,以确定如何处理污点传播。虽然动态数据流分析非常强大,但由于隐式数据流、对完整ABI列表的依赖以及复杂的I/O操作,它也可能非常脆弱。
在HMfuzzing中,我们使用DFSan作为一种最佳努力方法,以帮助分析人员理解分区列表以及如何解锁分区。数据流分析的结果将通过与在进入分区入口块之前紧接着执行的条件相关联的标签的并集来为每个分区进行标记。目前,HMfuzzing只使用了两个标签:(1) 输入数据标签和(2) 测试桩数据标签。在FuzzBench和OSS-Fuzz中的所有基准测试中,都使用了libFuzzer的标准API调用约定,通过编译DFSan日志记录二进制文件的转换过程来进行输入数据的标记。标记受测试桩控制的数据由分析人员完成,通常只需要在由模糊测试桩初始化的任何选项、标志和默认值上添加函数调用即可,这通常需要很少的工作量。
在讨论HM-Fuzzing中人类的角色之前,了解一下在没有分区分析的情况下需要人力工作量是有帮助的。根据社区的指南,评估模糊测试活动的推荐方式是通过代码覆盖率报告。不幸的是,尽管代码覆盖率报告的HTML界面易于浏览,但一个规模适中的软件项目的概览提供了大量的信息。概览包含了源文件列表,以及每个文件的各种统计数据,如函数、行或分支覆盖率。确定从哪里开始并不是一项简单的任务。然而,通过少量的脚本编写,可以生成一个按未覆盖源代码行数排序的函数列表。假设分析人员从包含最多未覆盖行数的函数开始,下一步就是确定在该(可能很大的)函数内部模糊测试器卡住的位置。分析人员必须审查已识别函数中的各种“段落”:已覆盖的行、未覆盖的行、注释以及由于编译选项而未编译的代码。表III显示了分析人员需要检查的行数,以开始解决前十个未覆盖源代码行数最多的函数的覆盖问题。例如,在freetype2中,分析人员需要手动检查多达1,896行源代码,以确定如何和在哪里进行干预。
通过进行分区分析,安全工程师或开发者/维护者可以使用加权的分区列表来优先考虑人工努力以改善模糊测试的覆盖范围。分区列表(见表格I中的示例)包含了数据流分析的结果,这些结果提示了通过输入或测试用例修改可能解锁某个分区的方法。在此阶段,工程师对受测试程序的专业知识和/或熟悉程度将推动下一步的操作。在某些情况下,分区列表中提供的符号名称可能为工程师提供足够的信息,以找到一个解决方案的种子。工程师可能需要检查导致被锁定分区的边缘的源代码和目标代码块的上下文信息。利用上下文信息,工程师可以得出结论:提供的种子中不包括特定的文件格式,或者必须生成新的模糊测试用例(参见第V-C节),以覆盖独占条件代码。
HM-fuzzing利用了LLVM强大的转换和分析库。其性能数据收集使用了LLVM的性能引导优化和基于源码的代码覆盖率。数据流分析使用LLVM DFSan来传播对变异输入和受控数据进行标记。对AFL++的补丁大约有200行C代码,用于添加一个额外的fork-server,使每个变异输入都可以独立地、并行地执行,而不受其他启用的fork-server的影响。分区分析和DFS转换的代码分别约为800行和400行C++代码。此外,我们编写了Python脚本,用于在一组输入上执行DFS二进制程序,并打印带有数据流标签注释的最终分区列表,以及解锁分区的可能输入。这些脚本可以帮助分析人员快速评估候选输入,并识别代表解决方案的输入。
为了量化HM-fuzzing的价值,我们设计了一个实验,通过将分区分析应用于一组代表性程序与基准模糊测试活动进行比较,来测量其在覆盖范围上的差异。为了进行评估,我们构建了一个程序测试集,其中包括七个程序、模糊测试用例以及作为Google FuzzBench语料库[38]的一部分发布的种子。我们所选的特定程序代表了各种复杂度的不同输入文件格式,并且包括文本和二进制输入格式。表格IV提供了每个所选基准测试的详细信息。
我们选择了AFL++ [33]作为一个典型的灰盒变异模糊测试工具的最新示例。AFL++是AFL的一个分支,它结合了社区和学术界的一些改进(无论是基于AFL还是非AFL),并且在Google FuzzBench的排名中始终位于顶部或接近顶部。
我们的实验结构旨在模拟现实世界中模糊测试活动的工作流程。对于对照组,我们使用未修改的模糊测试用例和种子集,从FuzzBench获取,并在未经干预的情况下运行AFL++ 24小时。在实验组中,我们运行AFL++ 12小时,暂停模糊测试,并基于分析报告的前20个最重要的结果进行分区分析,以获得此时点的增量覆盖范围。然后我们继续模糊测试12小时。根据社区关于测试随机化算法的指导,每个实验组重复50次 [21]。所有实验都在配置有Intel Xeon E5-2670 2.5 GHz CPU和256 GB RAM的服务器上进行,主机操作系统为Ubuntu 16.04。
在模糊测试了12小时之后,作者对基准进行了分区分析,以收集所需的分析数据。大约花费了8个小时来审查每个基准的前20个分区。分区按从最大权重到最小的贪婪最优方式进行审查。当确定一个分区需要额外的输入时,可以通过搜索特定的文件格式或手动创建基于其他输入的新输入来找到这些输入。对于模糊测试用例,只进行了一些轻微的修改,例如修改选项或标志值,以便与默认的模糊测试用例进行覆盖率比较。
我们的实验结果概述如表 IV 所示,而图 3 则呈现了基线运行(蓝色)和实验运行(黄色)之间的覆盖率对比箱线图。箱线图上标注了中位数值,以及每个图底部中位数覆盖率的原始值和百分比增加。总体而言,除了 libjpeg-turbo 外,当应用分区分析时,所有目标都显示出了显著的覆盖率增益。这些增益范围从适度(例如 libpcap 的10%)到显著(例如 proj4 的94%)不等。proj4 展示了最多的解决方案,通过16次干预成功实现了对报告的阻塞边界父函数的完全覆盖。在表中可以观察到解决方案数量与新覆盖范围之间存在明显的正相关关系。然而,即使解锁率较低(2-3),两个基准也实现了适度的覆盖率增加(10-14%)。这表明即使成功干预的数量很少,仍然可以获得显著的覆盖率增益。尽管模糊测试具有固有的随机性,但在模糊测试12小时至96小时期间,前20个分区大多保持稳定。根据第 III-B 节描述的分析结果,这九次试验中的 14 到 17 个顶级分区是相同的。
在实验结果中,覆盖率改善的来源是多种多样的。例如,对于 lcms 和 freetype2,测试用例的污点传播到阻塞分区的条件语句中起到了作用。在前一种情况下,很快就发现模糊测试的测试用例使用 Intent 和 flags 变量的默认值直接导致了对两个分区的访问被阻塞。为了解决这个问题,我们修改了 lcms 的测试用例,使用了不同的选项。在图 3 中,分别显示了具有和没有修改以移除这两个分区访问的 lcms 测试用例的覆盖率差异,标有 lcms 和 lcms_mod 的箱线图。有趣的是,修改后的测试用例的中位数覆盖率实际上从3320降低到2732个区域(-18%)。在调查中,明确了应该同时使用两组选项来进行模糊测试,以获得最佳覆盖率。未修改和修改后的 lcms 测试用例之间的覆盖率并集为4104个区域,相比未修改的测试用例增加了24%。实际上,这个观察结果表明,通常情况下,将库选项(或更广泛地说,程序参数和环境变量)添加到被测试程序的变异输入范围中,以系统地减少这种无法覆盖的代码的来源可能是有效的。
在 freetype2 的情况中,我们发现一个包含测试用例标记数据的分区影响了阻塞条件语句。然而,考虑到分析师的时间预算,决定将精力集中在输入集合的修改上会更好。这个决定是由于存在多个分区符号引用了模糊测试工具无法变异合成的字体文件格式。在分区分析期间进行的其余干预措施采取了输入集合增加的形式。
在 libxml2 中,我们的原型系统确定了20个未覆盖的分区,并按潜在覆盖影响进行了排名。通过检查我们的分析结果,我们确定通过添加4个新的简单种子,可以解锁5个新的分区。此外,我们确定应该修改测试环境以启用 SAX1 选项(如前述的清单1所示)。结合另外两个基于 SAX1 的种子,这个修改解锁了额外的12个分区。
这些改变导致了中位数代码区域覆盖率的39%增加,结合新的种子和修改的模糊测试环境。图4中显示了其中两个分区,它们都存在于函数 xmlStringLenDecodeEntities 中。图中左侧的分区由一个额外的种子解锁,但右侧的分区只部分解锁。分区167的权重由257个未执行的指令组成,其中主要是入口块,还有属于函数 xmlParseStringEntityRef 的267个指令,该函数是被唯一调用的。这个边缘在清单3中表示,它检查实体内是否有字符'&',该字符前缀是先前定义的实体。这个输入展示了稍微复杂一些的约束条件,因为实体必须首先有一个有效的定义,然后才能有有效的引用。在这种情况下,AFL++ 能够生成包含有效实体的 XML 输入,但是无法生成一个一个有效实体 𝑒1 后面紧跟着引用 𝑒1 的另一个实体 𝑒2 的序列。清单4中显示了一个满足这些约束条件的示例输入。
额外的分区391无法通过修改测试环境或提供额外的种子完全解锁。要完全解锁这个分区,需要引用外部实体,例如文件系统中的另一个文件,并成功加载该外部实体。通过使用新的种子,模糊测试工具能够取得一些进展,将具有1176个未执行指令的锁定分区391减少到具有882个权重的分区423。这部分未覆盖的代码属于未执行的函数xmlLoadEntityContent,该函数是由xmlStringLenDecodeEntities唯一调用的。
proj4的分区分析展示了间接调用分析的潜在有效性。在分区分析的第一次迭代中,未包括间接调用目标(即源代码中没有静态调用引用的函数)。当在分区分析中添加间接调用目标后,初始分析中的前20个分区中有14个被替换为新的分区,其入口块是一个间接调用目标的函数入口。通过间接调用可到达的仅通过间接调用才能到达的未覆盖代码的比例在图5中体现出来。如图所示,通过静态代码分析可以到达39个函数,而264个函数没有相应的调用点。大部分未覆盖的函数("No-Calls")只是proj4能够进行转换的154个可能的地图投影。尽管到达这些投影所需的变异相对简单,但在96小时的模糊测试后,AFL++仍无法覆盖这些函数。使用包括间接调用目标的分区分析,可以使用函数名作为所需输入投影描述的指示来轻松生成解决方案输入。例如,+proj=omerc表示需要进行与斜方面墨卡托投影的转换。
我们进行了额外的模糊测试活动,使用固定参数(种子、二进制文件),但持续时间不同:6小时、12小时、48小时和96小时。在图6中,显示了每个基准测试的相对程序覆盖范围在各种持续时间下的情况。这些替代实验表明,HM-fuzzing在24小时内的性能通常至少与基线模糊测试器在96小时后的性能相当。结果与图3中报告的成功情况非常类似。对于文件、freetype2、libpcap、libxml2和proj4基准测试来说,在24小时的测试中使用分区分析比模糊测试96小时更有利。对于其他基准测试(lcms和libjpeg-turbo),在24小时的测试期间进行分区分析提供的结果与在48小时的模糊测试中相当,而无需干预。此外,与使用分区分析进行24小时的模糊测试相比,进行完整的96小时模糊测试(没有分区分析)只略微好一点(增加约1%)。
虽然我们相信HM-fuzzing可以通过显著扩展被测试程序的覆盖范围,为真实世界的模糊测试工作流程提供有价值的贡献,但该技术并非万能,其也存在一些限制,我们将在下面进行讨论。
我们认为HM-fuzzing能够大大扩展程序测试范围,从而对真实世界的模糊测试工作流程做出宝贵贡献。然而,这项技术并非万灵药,它存在一些限制,我们将在下文中讨论这些限制。
有效性与人类专业知识成正比:区分性分析的整个目的是将人类专业知识高效地融入到模糊测试循环中,以最大程度地利用人类的时间。也就是说,将可能产生最大影响的程序点呈现给安全分析师。然而,在手头信息的基础上确定适当的干预措施留给分析师决策。尽管我们的经验表明通常会有丰富的信息可用,无论是符号名称还是区分性分析提示(例如基于污点的限制性分析),但解释这些信息可能受分析师对被测试程序的熟悉程度的限制,包括其目的、设计、实现和预期输入。我们认为,在开发人员将模糊测试作为其测试流程的组成部分进行测试(如SAGE [7]或ClusterFuzz [34]),或者在安全分析师针对固定目标进行长期模糊测试活动(如OSS-Fuzz [35]或FuzzBench [38])的情况下,可以合理地期望具备某种程度的熟悉度。
简而言之,使用区分性分析需要结合分析师的专业知识和对被测试程序的了解,才能发挥其最大的效果。区分性分析提供了丰富的信息,但分析师需要根据自身知识和经验来解释和利用这些信息来确定适当的干预措施。
有些程序确实是难以攻克的目标:根据我们迄今为止使用这项技术的经验,一个程序与一般计算机科学知识的主流路径偏离得越远,对于普通分析师来说,很难迅速确定适当的输入集添加或测试框架修改。
某些程序可能具有复杂的设计、独特的实现或非传统的输入要求。这些程序可能在结构上或语义上与常规程序有较大差异,使得对其进行模糊测试变得更加困难。对于这样的程序,普通分析师可能需要更多的时间和专业知识来理解其工作原理、逻辑和漏洞潜在点。
因此,面对这些"难靶"(hard targets)的程序,我们可能需要依赖更有经验和专业知识的安全分析师来研究并找到有效的干预措施。他们可能需要深入了解程序的内部机制、特殊的输入要求以及可能存在的漏洞类型。这还强调了在进行区分性分析时,分析师的专业知识和经验的重要性。
总之,对于偏离主流计算机科学知识路径的程序,一般分析师可能会面临更大的困难,在短时间内确定合适的输入集添加或测试框架修改方法。而对于这类程序,需要更有经验和专业知识的安全分析师介入才能取得进展。
让我们来考虑 libjpeg-turbo 这个案例,它是一个对区分性分析效果最不明显的测试程序。在区分性分析表中,核心函数负责图像解码的函数(如 jinit_master_decompress)是造成最大影响的函数。特别是发现,在修改库参数以控制解码图像时离散余弦变换的方式之后,这些模块才能被激活。然而,尽管研究员投入了约16个小时来寻找这些影响点,但在这个目标上取得的显著进展并不多。尽管进行实验的分析师并非JPEG图像解码的专家,但也不能期望普通分析师具备该领域的专业知识。因此,很明显有一些目标并不适合使用区分性分析。事实上,这个经验与来自FuzzBench的覆盖率数据相吻合,该数据显示自动化模糊测试工具在处理libjpeg-turbo中的复杂路径约束时也面临困难。
从这个案例中可以看出,对于某些程序,无论是通过区分性分析还是自动化模糊测试工具,都难以取得预期的结果。这可能是因为这些程序具有复杂的逻辑和路径约束,普通分析师和自动化工具都难以快速理解和处理。对于这样的目标,可能需要更有专业知识和经验的专家来深入研究和分析。他们可能需要深入了解特定领域的知识,并进行更为精细和复杂的干预措施。
因此,虽然区分性分析是一种有用的技术,但并不是适用于所有情况。对于那些涉及复杂路径约束或特定领域知识的程序,需要将区分性分析作为一种辅助手段,并结合专家的知识和经验进行分析和漏洞挖掘。
与libjpeg-turbo相比,我们来看看"file"实用程序。在区分性分析表中,最为重要的函数包括file_trycdf、file_zmagic、file_tryelf和file_fsmagic等符号。这些名称立即让许多安全分析师联想到与之相关的输入文件类型:分别是Microsoft Word文档、压缩文件、ELF文件或文件系统映像。在这方面,"file"实用程序确实比libjpeg-turbo更适合普通分析师进行区分性分析。
"file"实用程序的函数命名能够直接提示出与输入文件类型相关的信息,对于分析人员来说非常直观。这样的命名使得分析人员能够快速了解每个区分的含义,而无需具备深入的专业知识。因此,对于普通分析师来说,"file"实用程序明显是更适合进行区分性分析的候选对象。
总结起来,不同的程序适合的分析方法也会有所不同。对于有明显命名规律并且可以直接关联到输入文件类型的程序(如"file"实用程序),区分性分析是一种更加直观且容易理解的方法。而对于具有更复杂逻辑和路径约束的程序(如libjpeg-turbo),可能需要更高级的专业知识和经验,以及结合区分性分析等方法来进行详细的研究和分析。
略
本文作者:Du4t
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!