ProFuzzer: On-the-fly Input Type Probing for Better Zero-day Vulnerability Discovery

目录

摘要

介绍

动机

总览

设计与实施

类型探测

探测

类型指导突变


摘要

现有的基于变异的模糊器倾向于在不了解其底层语法和语义的情况下随机变异程序的输入。在本文中,我们提出了一种新颖的即时探测技术(称为ProFuzzer),该技术可自动恢复并了解在模糊过程中对漏洞发现至关重要的输入字段,并智能地调整突变策略以增加击中零日目标的机会。由于这种探测透明地背负于常规的模糊测试,因此无需事先了解输入规范。在模糊测试期间,首先对各个字节进行突变,并自动分析它们的模糊测试结果,以将相关的内容链接在一起,并确定连接它们的字段的类型;这些字节按照类型特定策略进一步突变在一起,这会大大减少搜索空间。我们通常在所有应用程序中定义探针类型,从而使我们的技术应用程序不可知。我们在标准基准测试和实际应用中进行的实验表明,ProFuzzer的性能明显优于AFL及其优化版本AFLFast,以及其他先进的Fuzzer,包括VUzzer,Driller和QSYM。在两个月内,它在10个经过严格测试的程序中暴露了42个零日,生成了30个CVE。

介绍

模糊测试是一种广泛使用的软件测试技术,可以针对程序随机或有策略地生成输入以发现其漏洞。长期以来,它一直被认为是程序安全性分析中最有效的方法之一,有助于发现最具影响力的安全性法则,例如CVE-2014-0160,CVE-2017-2801和CVE-20173732 [1], [2]。这种动态分析的关键是产生具有高度代码覆盖率的输入,并且很可能会遇到隐藏在代码内部的安全漏洞。通常,这可以通过使用输入模型的知识来生成合法输入,或者通过对一组初始种子输入进行变异来完成,例如,通过随机翻转其位或策略性地将已知值替换为输入内容。然而,在以输入空间大和输入执行关系复杂为特征的现代软件的存在下,这些常规方法变得越来越不足。

挑战.更具体地说,基于生成的方法依赖于描述输入格式的语法来产生合法的测试用例。这里的语法需要先验[3],[4]或从离线[5],[6]的执行跟踪中恢复。更重要的是,尽管结构合理的语法确实减少了搜索空间,但它几乎没有提供各种输入可能对程序执行产生影响的线索:例如,一组输入是否都将导致相同的执行路径,因此仅其中之一应进行测试。此外,利用漏洞的输入甚至可能不会遵循输入语法,因为如果实现与功能无关,则实现可能会选择忽略某些输入字段(例如,图像格式转换器可能会忽略与渲染相关的字段);错误的实现甚至可能接受格式错误的输入。

基于变异的方法不需要有关输入模型的知识。相反,它使用一组合法的输入实例作为种子,并不断对其进行修改以探索各种执行路径。先前的研究已经研究了选择可能导致执行程序进入易受攻击的程序位置的正确种子的方法,以及确定可能增强代码覆盖率的关键输入字节的方法[9],[10]。但是,对于给定的种子,变异通常是随机的,即使输入数据大小适中,变异也不会缩放。

正如我们在此处看到的那样,对于高效和可扩展的模糊测试过程而言,至关重要的是深入了解目标程序的输入。这样的输入通常是结构化数据,并且可以划分为具有特定语义的数据字段序列:例如,缓冲区大小,类别指示符等。这些字段的语义对于发现漏洞非常有用,有助于模糊测试人员确定要访问的字段。变异,合法值的范围,数据字段对程序执行的影响等。利用这些信息,模糊器可以以更智能的方式操作,仅关注于最有可能导致执行路径从未见过的输入子空间。但是,此信息(字段及其语义)可能没有记录,也无法提供给模糊测试者,并且如果不进行深入的重量级分析过程,可能很难恢复。

使用类型探测进行模糊测试。为了解决挑战并使用丰富的输入语义提升当今的模糊技术,我们提出了一种新的智能模糊技术,称为ProFuzzer,它通过称为“探测”的轻量级随机模糊过程自动发现输入字段及其语义,以指导种子突变的在线进化。这种探测完全背负于常规的模糊测试,并立即执行。更具体地说,ProFuzzer通过使一组种子变异来对目标程序执行两阶段模糊测试。在第一阶段(即探测阶段),它会按顺序逐字节进行探测,即每次更改一个字节,针对目标程序枚举其值,然后移至下一个字节。此顺序的模糊测试步骤还会收集有关不同字节值下目标的执行路径的信息。信息会自动在线分析以恢复数据字段(将相关字节链接在一起)并确定其字段类型:例如,特定内容导致突变的同一异常路径的连续字节可以分组为字段。在我们的研究中,我们确定了6种字段类型,这些字段类型对内容影响程序执行的方式进行建模,例如大小,循环数和枚举,这些字段只有几个有效值可以正确解释输入中的后续内容。这些类型与应用程序无关,描述了模糊相关的语义。数据字段及其类型的发现为第二阶段的模糊测试提供了指导,在此阶段,ProFuzzer对每个字段进行突变以利用可能导致攻击的值(例如,大数据量可能会利用缓冲区溢出漏洞),并根据字段类型探索合法值,以实现更好的覆盖范围。

我们在AFL上实施该设计[11]。我们将ProFuzzer与AFL,AFLFast [7],最先进的程序分析辅助模糊器VUzzer [12]和两个最新的混合模糊器Driller [13]和QSYM [14]进行了比较4具有20个已知漏洞和两个标准基准的应用程序(LAVA-M [15]和Google模糊测试套件[16])。结果表明,ProFuzzer能够以更少的时间发现更多的错误。例如,对于4个实际应用程序,ProFuzzer可以在24小时内发现18个错误,而其他Fuzzer的最佳结果仅为15个。此外,ProFuzzer每次错误所需的平均时间仅为所需时间的18-73%由其他的模糊测试。可以在第五节中找到更多详细信息。

发现。我们还在10种流行的现实世界开源应用程序上运行了ProFuzzer两个月,其中包括用于图像处理的软件(例如OpenJPEG),音频和视频解码器(例如LibAv),PDF阅读器(例如MuPDF)和压缩库(例如ZzipLib)。我们的研究结果发现了42个零日漏洞,例如堆溢出,无限循环等。一旦被利用,大多数漏洞将带来严重后果。例如,可以通过非常小的制作的图像文件触发Exiv2(CVE-2017-17725)中的堆缓冲区读取漏洞,从而导致4字节的信息泄漏,从而有助于进一步利用(例如,绕过地址空间随机化)。请注意,即使Exiv2已经过广泛的测试[17],但这种脆弱的能力仍然落空了。我们已将调查结果报告给所有受影响的软件供应商。到目前为止,他们中的大多数人已经承认我们发现的问题是真实的和重要的。我们已经获得30份CVE,并且正在对其他报告的问题进行调查。

贡献。我们有以下贡献:

•新的智能模糊技术。我们设计了一个高效的智能模糊器,它通过轻量级的探测来发现输入字节与程序行为之间的关系,以指导后续针对性的种子突变。这种方法将语义知识发现无缝透明地集成到模糊处理过程中,不仅提高了模糊处理的准确性,而且提高了其性能,而不会降低适用性。

•发现0day漏洞。在实际的应用程序上运行我们的模糊器,我们在两个月内发现了42个0day漏洞。我们的发现表明,这种在线语义发现和变异指导技术可以改善现有技术。

动机

我们使用OpenJPEG [18]来解释传统的模糊技术和ProFuzzer的思想问题。 OpenJPEG是一个开源图像编解码器库,可将各种图像格式转换为JPEG2000。它已广泛集成到用于图像处理的商品软件中。

基于变异的模糊测试。基于变异的模糊测试会在不了解种子输入语义的情况下对其进行变异。这样,突变很大程度上是随机应用的。该方法简单且与应用程序无关。但是,由于缺少输入语义,它无法有效工作。图1a显示了OpenJPEG的种子输入。我们使用最广泛使用的基于突变的模糊处理工具AFL对具有5个已知漏洞的旧版本OpenJPEG库进行模糊处理,并且在24小时后仅发现了其中3个。我们发现62个字节中只有24个字节(图1a中突出显示)有助于改善覆盖范围。图1b显示了24小时内每个字节上的突变数。超过60%的突变没有帮助。根本原因是,在没有输入规范的情况下,基于突变的模糊测试没有得到适当的指导。目前已经进行了推断输入语义以改善模糊的工作。 AFL提供了一个简单的离线文件格式分析器实用程序(AFL-analyze [19])来推断输入语法,以及一个内置的字典构造功能[20]来标识应用程序特定的关键字。但是,由于其简单性,它们的有效性有限(请参阅V-C部分)。

基于规范的模糊测试。类似AFL的方法的自然增强是使用输入规范(例如,输入语法和语义约束)来指导模糊测试。确实,先前对基于符号执行的测试的研究表明,输入语法可以大大提高模糊测试的有效性[21]。但是,这些方法通常很难部署。首先,规格并非总是可用。其次,规范可能是隐含的,需要大量的人工来提取。这极大地影响了模糊技术的适用性。最重要的是,模糊测试是一个二进制过程。它与实际的实现高度相关,实际的实现可能与其输入规范有不小的偏差。应用程序通常仅支持部分规范,有时规范将包括一些保留的字节,其功能在各种实现/扩展中进行了自定义[22]。例如,图1a中从偏移量0x1c-0x1d开始的字节用于表示颜色深度。根据规范[23],它们可能的值为0x01、0x04、0x08、0x10、0x18和0x20。但是,当前版本的OpenJPEG不支持0x01或0x04。最后,这些规范不是面向模糊测试的,因此,许多生成的输入对于模糊测试仍然是多余的。也就是说,这些输入对程序有效,但不能改善路径覆盖范围或漏洞发现。

ProFuzzer的基本概念。 ProFuzzer受到两个重要观察的启发。首先,对于模糊测试,不需要全面,特定于应用程序且语义丰富的输入规范。例如,图1a中从偏移量0x26到0x2d的字节表示图像的分辨率。根据文件格式规范,它们是重要的输入字段。但是,OpenJPEG在图像转换期间会跳过这些字节。因此,识别字段不会带来太多的模糊感,因为它们的内容不会影响转换期间的执行路径。相反,更有用的信息是识别对于模糊测试至关重要的特殊类型的字段。例如,如果我们知道字段描述了缓冲区大小,则可以应用定制的变异规则来找出是否存在易受攻击的缓冲区。其次,可以通过直接观察模糊过程来理解输入,并可以发现其字段和数据类型,特别是输入内容的更改方式以及响应该更改的程序执行路径的更改。这样,依靠重量分析的现有输入格式逆向工程技术(例如,REWARDS [24],TIE [25],Howard [26])既没有必要,也没有成本效益。相反,我们相信可以通过对先前的模糊结果进行即时语义分析来识别输入字段及其类型,然后指导将来的输入突变,从而将输入理解无缝地集成到模糊过程中。

根据观察结果,我们设计了两阶段的模糊技术ProFuzzer。在第一阶段,它通过按字节的突变来探查种子输入的输入字段和字段类型。特别是,根据目标程序在突变下的行为变化(例如执行路径更改),ProFuzzer可以识别关键的输入字段。然后在第二阶段中使用字段和字段类型信息来指导在线输入突变。不同的领域类型具有不同的优先级,并且需要采取各种变异策略。例如,我们的方法避免了对原始数据类型的字段进行变异,因为原始数据类型的变异导致目标程序经过相同的路径。对于具有缓冲区大小类型的字段,遵循特定的突变模式(例如,优先考虑边界值),将属于同一字段的多个字节一起突变。相反,即使AFL具有某些预先定义的突变模式,例如将连续输入字节的随机子序列更改为特殊值(例如0xFF),也很难将其应用于正确的子序列。

借助可感知的场域突变,ProFuzzer可以提高模糊测试的有效性和性能。我们在OpenJPEG上运行ProFuzzer 24小时,发现所有5个已知漏洞,并且路径覆盖率比AFL高60%。我们还计算了图1c中每个字节在24小时内经历的突变数。请注意,相同字段中的连续字节具有相同的突变计数。具体而言,由于62个输入字节中的数据对于模糊测试很重要,因此只有24个输入字节发生了突变。例如,ProFuzzer探测并确定在0x0e到0x11之间存在一个偏移量域,该偏移量域确定了后续数据字节(来自文件头)的偏移量。偏移场对于漏洞检测非常重要,因为它们会对内存行为产生重大影响。这些信息可以使ProFuzzer避免盲目模糊,而可以使用特定的突变策略(例如,将偏移字段的值设置为当前位置和文件结尾之间的差)来策略性地探索字段的输入空间。再举一个例子,在512次探测运行之后,位于0x1a和0x1b的字节被识别为原始数据,因此这些字节上没有进一步的突变,而它们被AFL突变了407,170次。

总览

图2

ProFuzzer的体系结构如图2所示。它由四个组件组成:探测引擎,变异引擎,执行引擎和报告引擎。

探测引擎。探测引擎的任务是根据给定二进制文件的种子输入生成类型模板。对于每个种子输入,ProFuzzer将遍历所有字节。对于每个字节,它遍历所有可能的值(0x00至0xff),并收集相应的执行文件。然后,从这些配置文件中提取一些指标(例如,由突变引起的边缘覆盖率变化),并将其用作字节的特征。然后,ProFuzzer将具有相似功能的连续字节分组到字段中。最后,它根据可能值的变化与观察到的特征的相应变化之间的关系来确定每个字段的类型(例如,幻数字段可以通过所有突变都发生在原始值中的模式来表征)。种子输入导致输入无效,因此执行时间短)。

变异引擎。突变引擎使用所探查的模板(即字段和字段类型)来指导后续的突变。指南从两个方面给出:首先是以具有成本效益的方式扩大覆盖范围。例如,ProFuzzer可以避免对原始数据字段进行模糊处理,而将注意力集中在确定后续数据解释的变异字段上。这将使我们能够到达原本很难到达的代码区域。在第二方面,领域信息可以用来指导漏洞利用的产生。例如,缓冲区大小字段是许多漏洞的根本原因。众所周知,某些突变模式对于规模领域至关重要。

 

执行引擎和报表引擎。我们使用标准的AFL执行和报告引擎。每次探测引擎或变异引擎请求执行应用程序时,执行引擎都会派生一个新进程。执行期间,它会自动收集执行配置文件。报表引擎监视执行情况,尤其是崩溃或挂起。

假设。 我们假定目标应用程序具有以下属性。 首先,它的执行是确定性的:也就是说,给定相同的输入,应用程序的多次执行都遵循相同的执行路径并产生相同的结果。 其次,可获得合理大小的初始有效种子输入。 ProFuzzer的重点是类型探测和基于类型的突变,而不是种子选择。 因此,现有的种子输入生成/选择技术[27]是ProFuzzer的补充。 第三,如果对某些字节的验证失败,则执行将迅速终止,这意味着异常执行的执行路径比正常执行路径短。 我们在第V-A节中进行了一项实证研究,以验证上述假设。

设计与实施

模糊相关的输入字段类型。如前所述,与应用程序无关的输入字段类型的知识可以为模糊测试提供强有力的指导。为此,我们确定了6种输入字段类型,包括断言,原始数据,枚举,循环计数,大小和偏移量。这些类型涵盖了流行应用程序的大多数输入内容,例如图像处理工具,文档阅读器和压缩实用程序。最重要的是,这些类型以独特的方式影响程序的执行,因此对于增强程序模糊化的有效性非常有用。在下面对这些类型的讨论中,我们以图1a为例。对于每种类型,我们提供OpenJPEG中BMP转换组件的简化代码段,以处理该类型的输入。稍后,我们将展示如何识别这些类型以及如何在模糊测试中利用它们。

•断言。断言类型的输入字段只有一个有效值,可以使程序正确执行。其他值将导致程序终止并出现错误。示例映像中偏移量0x00和0x01处的字节形成一个声明字段。如下面的代码片段所示,OpenJPEG检查该字段以确定它是否等于0x4d42,这是BMP文件的幻数。在模糊测试期间,任何声明字段的内容都不应更改。否则,测试用例将无效。

•原始数据。对于原始数据类型的输入字段,其内容不影响程序的执行(例如,其控制流和内存访问)。请注意,原始数据是特定于实现的。在我们的示例图像中,从偏移量0x26到0x2d的字节是图像分辨率,这对于图像渲染应用程序很重要。但是它们是OpenJPEG的原始数据,因为它仅转换格式而不渲染图像。在模糊测试期间,我们不需要更改原始数据字段。

•枚举。枚举类型的输入字段的特征是一小套有效值,其他值导致程序错误地终止。一个示例是示例图像中从偏移量0x1c到0x1d的字段。这些字节代表图像的颜色深度,根据文件格式规范,其可能值为0x01、0x04、0x08、0x10、0x18和0x20。但是,在当前的OpenJPEG实现中,仅支持0x08、0x10、0x18和0x20。如下面的代码片段所示,将调用不同的处理函数来处理不同的有效颜色深度。在模糊测试中,我们只想测试有效值。

循环计数。 循环计数类型的输入字段决定了循环结构(例如for / while循环或递归调用)中代码片段应执行的时间。 循环计数值对路径计数有实质性影响,而对路径本身的影响则微不足道。 示例图像中偏移量0x36处的字节是循环计数的实例。 它是根据文件格式指定的原始数据字段。 但是,它表示RGB值,这样在进行颜色变换(例如,修改的普查和离散小波变换)之后,其值会影响变量bpno,如以下代码片段所示。 变量又决定循环中需要编码的位数。

偏移量。偏移量类型的输入字段确定程序可以从中访问输入文件中后续数据的位置。偏移量域对于模糊测试很重要,因为错误的偏移量值可能会导致程序访问范围之外的数据(例如,文件末尾),从而导致后续验证检查出错并终止。在有效范围内,不同的偏移会导致访问不同的后续数据。如果访问的数据对控制流有影响(即非原始数据),则其不同的值可能导致执行采用不同的路径。例如,示例图像中从偏移量0x0a到0x0d的字段是偏移量的一个实例,它指向程序从中加载图像数据的位置。在我们的例子中,它从文件的第54个字节开始。如以下代码片段所示,如果将偏移量设置为大于54,则fseek()会超出文件末尾,从而导致异常。在模糊测试期间,需要以与之前的位置转换突变相一致的方式来更改偏移字段(例如,添加数据字段)。

Size.大小类型的输入字段确定程序应从输入文件读取以进行进一步处理的数据量。与偏移量类型类似,如果大小值导致任何超出范围的数据访问,程序将终止并显示错误;在有效范围内,由于处理了不同的数据,不同的大小可能会导致不同的执行路径。偏移量和大小之间的区别在于,如果将大小值设置为零,则必须发生错误,而将偏移量设置为0可能不会引起类似的问题。示例图片中从0x16到0x19的字节是size的一个实例,代表了图片的高度。正确的大小是0x02。如果大小设置为零或大于0x02(例如0x03),则程序将终止并显示错误消息。在模糊测试期间,大小字段突变受总输入大小限制。

请注意,这些字段类型不同于编程语言中的传统类型或程序员定义的类型。我们类型系统的重要设计标准包括与应用无关和模糊相关。这六种类型可能并不全面。输入字节可能不属于任何一个。在这种情况下,ProFuzzer会退回到AFL进行的常规随机突变。

类型探测

ProFuzzer会通过逐字节突变并分析各个突变字节对程序执行的影响,由ProFuzzer自动发现字段类型。 具体来说,给定一个程序,ProFuzzer在探测过程中一次更改其输入一个字节,枚举该字节的所有256个值以收集相应的256个执行文件。 这些配置文件存储在紧凑的哈希数组中,该数组保持各个控制流边缘的执行频率(即,控制从基本块到另一个的转移)。 我们称这种运行时轮廓边缘向量的表示。 在我们的系统中,每个边缘向量都用于提取许多特征,以表征正在探测的字节。 然后,利用这些功能将多个字节分组到一个字段中,并进一步确定该字段的类型。

定义。 为了便于讨论,我们引入以下定义。 让I作为输入– len字节序列,每个字节的范围从0x00到0xff,T_{I}是函数execute的输出,该函数在I上运行目标程序并返回边沿向量。 我们有:

I=\left \{0x00,0x01,...,0xff \right \}^{len},T_{I}=execute(I)

I_{\left [ i \right ]:v}为函数替换的结果,该函数将I的第i个字节替换为值v

 I_{\left [ i \right ]:v}=substitude(I,i,v)

对于在偏移量i处突变为值v的输入字节,我们基于边缘矢量T_{I}T_{I\left [ i \right ]:v}定义了两个度量:覆盖相似度S_{I\left [ i \right ]:v}和频率差D_{I\left [ i \right ]:v}。 前者着重于测量边缘覆盖率的相似度(即不考虑边缘被取的次数),而后者则测量是否占优势的边缘具有不同的执行频率。 特别地,S_{I\left [ i \right ]:v}计算为两个向量的边缘覆盖交集的大小除以它们的并集大小。D_{I\left [ i \right ]:v} 计算为具有不同频率的边缘数量与具有不同覆盖率(即,一个覆盖但不被另一个覆盖)的边缘数量之比。

作为示例,在下面,我们显示示例图像输入的边缘矢量T_{I}T_{I\left [ 0x1c \right ]:0x20} ,其对应的输入在偏移量0x1c处有所不同,原始值为0x18,突变值为0x20。 我们可以看到,两个向量在具有索引13的边缘具有相同的覆盖范围,在边缘583和13052处具有不同的覆盖范围。对于边缘34093,两个执行都覆盖了边缘,但是频率不同。 边缘覆盖相交和并集的大小分别为1727和1766。 因此,覆盖相似度为0.978(1727/1766)。 不同的边缘频率和不同的覆盖范围分别为1和39。 因此,频率差为0.026(1/39)。 这两个指标表明这两个执行非常相似,不同之处主要在于边缘覆盖率而不是边缘频率。

 STEPI:特征提取。 我们为每个字节i提取两个特征,作为两个元组,每个元组的大小为256:F_{i}^{S}表示[0x00,0xff]范围内的变异值的覆盖相似度,称为覆盖特征;F_{i}^{D}表示256个突变值的频率差,称为频率特征。

为了测量覆盖特征F_{i}^{S}的集中趋势,我们将其中值\alpha _{i} 定义为:

为了易于理解,我们通常将覆盖范围特征F_{i}^{S}可视化为条形图,例如,图3。x轴表示从0x00到0xff的字节值。 y轴表示与字节值相对应的coverage相似性度量。 我们向读者介绍附录A中示例图像中每个字节的覆盖范围。

第二步:现场识别。 字段识别的目标是将相同输入类型的连续字节分组为一个字段。 观察

到目标程序倾向于对整个输入字段而不是单个字节执行验证检查。 凭直觉,如果连续字节属于同一个字段,则很有可能它们共享与无效(变异)值相对应的大部分变异特征。 具体来说,尽管字段中的每个字节可能具有多个有效值(例如,枚举字段),这些值通常会导致执行路径不同,从而导致度量不同,但许多无效值总会导致相同的终止路径,但例外情况是 最短路径。 因此,这些无效值必须导致相同的覆盖范围和频率指标。 因此,如果满足条件(6),我们将字节从i到j的偏移量分组为一个字段。

注意,最小覆盖相似度值对应于由无效突变引起的最短路径。 在我们的示例中考虑偏移量为0x1c和0x1d的字节。 偏移量为0x1c处字节的覆盖范围特征如图3c所示。 尽管该字节可能具有多个与原始执行具有较高覆盖相似度的有效值(例如,具有0.978相似度的值0x20和具有0.959相似度的值0x10),但是当给出无效值时,其最小覆盖度相似度为0.068,与 字节在0x1d处的最小相似度。 回想一下,源代码在这两个字节上有一个switch case语句,这样对任何一个字节的所有无效突变都会导致相同的失败路径。

步骤III:字段类型标识。 给定一组连续的字节作为输入字段,下一步是确定字段的类型。

断言字段。 对于从i到j偏移的字段,如果它满足条件(7),我们认为它是断言字段。 该条件意味着,对于偏移量为x∈[i,j]的任何字节,该字节都只有一个值v,该值会诱发相似性得分1。对于其他任何值,相似性得分均小于中间值。

直觉是断言字段中的任何字节都只有一个有效值,该值允许程序正常运行。 其他值将导致相同的带异常终止路径,从而导致与原始执行的相似度较低。 中间值\alpha _{x}可用于区分原始执行和错误执行。

例如,图1a中偏移量为[0x00,0x01]的字段是一个断言字段。图3a展示了字节在0x00处的相似性。 如我们所见,只有0x42的值会导致相似度得分为1的正常执行,其他值会导致相似度得分为0.059的异常。

原始数据字段。 对于从i到j偏移的字段,如果它满足条件(8),我们认为它是原始数据字段。 该条件意味着,对于偏移量为x∈[i,j]的任何字节,其与原始执行相比的相似性分数对于所有值始终为1。

直觉是原始数据字段的值一定不会影响程序的控制流。 例如,图1a中偏移量为[0x26,0x2d]的字段被视为原始数据字段。 它表示图像分辨率,它对图像转换的控制流没有影响。 图3b显示了字节0x26的相似性特征。

枚举字段。 枚举字段满足条件(9),这意味着在偏移量x∈[i,j]处存在一个字节,并且集合VS是集合{0x00,...,0xff}的适当子集,子集大小大于1所示,对于VS中的任何值v,其相似性得分均大于中值,而对于不在VS中的任何字节u,其相似性得分均小于中值。

直觉是枚举字段具有少量有效值。 对于那些有效值,相似度得分相对较高,因为程序可以正确进行。 但是,对于其他值,相似度得分非常低,因为控制流最有可能转移到错误处理代码。

图1a中[0x1c,0x1d]处的字段是一个枚举字段(指示颜色深度)。 图3c可视化了字节0x1c的覆盖范围。 该图还告诉我们,OpenJPEG的当前实现支持四种图像格式。

循环计数字段。 循环计数字段满足条件(10),这意味着覆盖范围相似性得分的方差小于β,这是一个预定义的值,表明数据变化较小[28],[29]。 频率差异的平均值大于1,这意味着与覆盖差异相比,频率差异占主导地位。

这种情况背后的直觉是,循环计数值对边缘频率有实质性影响,而对边缘覆盖率的影响可忽略不计。 图1a中偏移量0x36处的字节是循环计数字段。 它确定循环中需要编码的位数。 图3d可视化了字节的覆盖范围特征。 我们可以看到,这些值的相似性得分非常接近,这意味着它们的覆盖率差异很小。 但是,当我们计算不同频率的边缘数与不同覆盖率的边缘数之比时,平均值为7.355,表明前者占优势

偏移字段。 偏移量字段需要满足(11),这意味着在偏移量x∈[i,j]处存在一个字节,以使覆盖范围相似度值0(即,当字节更改为0时,覆盖范围与原始执行的相似度)大于中间范围,并且范围[0x00,0xff]中也存在值t ,这样有两个小于t的值u和v具有不同的相似性评分,而任何大于t的值w始终具有小于中值的相似性评分。。

直觉是,偏移字段的有效范围由其余输入的长度(即,从字段旁边的字节到结尾)的长度来界定。 将字段更改为大于界限的任何值可能会导致超出范围的访问,并因此导致异常。 将字段更改为小于界限的任何值都会导致访问不同的后续数据,进而可能导致不同的执行路径。 这样,存在两个这样的值u和v,它们的覆盖相似度度量是不同的。 此外,将该字段更改为0很可能是有效的,因为这意味着该程序将在该字段之后立即访问后续数据。

例如,图1a中[0x0a,0x0d]处的字节是一个偏移字段,它指向程序从中加载图像数据的位置。 图3e可视化了字节0x0a的覆盖范围。 我们可以看到0x36是边界。 超出界限时,覆盖范围相似性指标会很低,因为程序会因错误而终止,从而导致与原始执行的实质性差异。 在边界内,不同的偏移值会导致不同的相似性指标。 值0的覆盖相似度大于中间范围。

size字段。大小域需要满足类似于(11)的条件,除了在偏移量x∈[i,j]处存在一个字节,以使值0的覆盖相似度小于中间范围。直觉上,大小域与偏移域具有相似的特性,因为也存在一个由剩余输入的长度决定的界限。区别在于将偏移量字段更改为0可能会导致成功执行,而将尺寸字段更改为0则通常会导致提前终止。

例如,图1a中[0x16,0x19]处的字节是指示图像高度的大小字段。图3f可视化了字节0x16的覆盖范围。注意,由于我们使用小图像作为输入,因此值0x02是边界。大于边界的值具有非常小的相似性分数,表明提早终止,并且值0的分数低于中间范围。

我们将大小域与偏移域区分开来,因为它们具有不同的突变策略。例如,当将偏移字段增加1时,我们在该字段之后立即插入一个字节,以便在变异运行中访问相同的原始后续数据。相反,当将大小字段增加1时,我们在输入的末尾插入一个字节。如果输入以断言字段结尾,则将字节添加到该字段之前。

在某些情况下,偏移量/大小字段可能紧邻原始数据字段。确定这些字段的边界可能很棘手,因为对偏移量/大小字段的最小字节进行更改可能不会导致任何执行更改,从而使其与原始数据字节无法区分。我们通过将识别出的大小/偏移字节与它们相邻的原始字节分组来解决该问题,只要分组字节表示的值不超过种子输入的大小即可。

我们的实验表明,我们的领域探测非常精确,平均假阳性率只有5.2%,假阴性率只有4.5%(请参阅第V-C节)。

探测

在模糊化过程中,给定新的(突变的)输入,ProFuzzer首先尝试重用被探测的模板,该模板的执行跟踪与输入的执行跟踪类似。 如果没有这样的模板,我们将变异输入视为新的种子输入,并像探测原始种子输入一样重新探测其字段。 在重新进行探测之前,ProFuzzer会利用AFL内置的输入微调功能来尽可能减小输入大小,并对小尺寸输入进行优先排序。

挑战在于新的种子输入可能不会导致正常执行,而是导致异常执行。 因此,重新探测的基本思想是,当对新的种子输入进行突变导致执行时间比种子更长时,我们认为重新探测的突变正接近于有效输入,因此,我们将当前种子替换为变异的种子并继续。 请注意,执行时间越长,表明变异的种子会通过更多的验证检查

重新探测特征提取。 由于种子执行的无效性,重新探测必须基于测量执行长度变化的不同执行特征。 对于偏移量为i的字节(其突变为值v),我们基于边缘矢量T_{I}T_{I\left [ i \right ]:v}定义了重测指标R_{I\left [ i \right ]:v} 。 度量标准度量执行期间获取的边数之间的比率,即TI 中和TIi:v 中具有非零计数的边数。 然后,我们提取偏移量为i的字节的重探测特征FiR 作为256个元素的元组,每个元素对应一个重探测度量。

字段类型重新标识。 在下文中,我们将说明如何使用新功能来探究断言字段和枚举字段。 由于篇幅所限,无法重新探测其他领域。

•重新声明断言字段。 对于i处的字节,如果满足条件(13),则将其视为声明字段。 该条件意味着该字节存在一个且只有一个值v,该值导致比原始执行更长的执行时间。 对于任何其他值,执行时间都小于或等于原始执行时间。 识别出这样的值后,我们用识别出的值替换字节,以使执行进一步进行。 按照第IV-A节中描述的相同方法,将多个这样的字节分组到一个断言字段中。

重新探测枚举字段。 对于偏移量为i的字节,如果满足条件(14),则认为该字节属于枚举字段。 该条件意味着存在一个集合VS,它是集合{0x00,...,0xff}的适当子集,其大小大于1,因此对于VS中的任何值v,结果执行时间都比原始执行时间长 。 对于其他任何值,执行时间都小于或等于原始值。 可以将多个此类字节分组到一个枚举字段。

类型指导突变

探测场用于指导突变。该指南从两个方面提供。在第一个方面,它将突变限制为字段类型的所有合法值,以实现更好的覆盖范围。我们称之为探索突变。在第二个方面,ProFuzzer对每个字段进行突变,以利用可能导致潜在攻击的一组特殊值(针对特定字段类型)。我们称其为利用突变。在模糊过程中,ProFuzzer会交错这两种模式。从直觉上讲,它尝试利用当前种子涵盖的路径。如果该过程没有结果,它将开始通过产生更多种子来探索更多的覆盖范围,依此类推。

探索突变。拥有字段信息的一个重要优点是我们可以在字段级别而不是字节级别应用突变,从而大大减少了无效突变的数量。此外,我们在探索阶段采用了以下突变策略。 (1)我们不允许对原始数据字段进行任何更改。 (2)对于一个枚举域,我们分配一个在其有效范围内的值(请参阅第IV-A节)的可能性很大(在我们的研究中为0.9),而在该范围之外的值则分配的可能性很小(0.1)以探索其他探测期间遗漏了有效值。特别是,我们利用类似于VUzzer [12]的技术从主题程序中提取常量(例如,数字和字符串),以使那些与字段大小相同的值成为候选突变。 (3)即使对于断言字段,我们不应该在模糊化过程中更改其内容,但在探测字段归类错误的情况下(例如,将枚举字段视为断言)。 (4)对于偏移量字段,当突变将其值增加(或减少)X时,我们会在字段后立即插入(或删除)X字节。 (5)对于一个大小域,当突变将其值增加(或减小)X时,我们将在最后一个非断言域的末尾插入(或删除)X字节。插入的数据是随机生成的。 (6)对于循环计数字段,我们执行二进制搜索以识别其最小和最大有效值。 (7)对于无法归类为六种类型之一的其他字段,我们执行默认的AFL随机每字节突变。

剥削突变。我们手动分析了一百多个已知漏洞的PoC,发现给定字段类型通常具有少量值,可能导致利用。此类模式与应用程序无关。由于我们已经认识到了领域和领域类型,因此利用这些模式可以帮助有效地发现新的漏洞。特别是,对于每个PoC,我们使用ProFuzzer探查其字段模板,然后对字段类型和导致剥削的相应值进行了手动研究。当前,该过程需要领域知识和手动工作。我们将剥削规则的自动学习从大量的PoC留给我们的未来工作。表I列出了我们发现的大小,偏移量和循环计数字段。由于篇幅所限,其他的被省略了。考虑规则1,该规则指定我们可以将大小字段更改为程序找到的任何大小字段的最大值。直观地,各种大小值对应于我们不关心其特定于应用程序类型的不同数据结构。突变实质上迫使程序将后续数据解释为不同的数据结构(具有更大的大小)。如果程序具有与类型相关的错误,例如类型混淆[30],则该突变将有助于揭示问题。规则2、3、4、7、8有类似的想法。规则9意味着通过指定的突变,我们迫使程序开始在输入结束后开始读取。请注意,在开发过程中,我们的目标是暴露错误。因此,当增加偏移量时,我们不需要用新的字节来修补输入。

 

原创文章 46 获赞 23 访问量 3万+

猜你喜欢

转载自blog.csdn.net/zhang14916/article/details/105792604