摘要:

灰盒模糊测试,例如american fuzzy lop(AFL),在发现软件漏洞方面非常有效,这使得它成为最先进的模糊技术。灰盒模糊测试利用程序运行期间收集的分支信息作为反馈来指导选择种子。当前的灰盒模糊处理一般采用两种方法来采集分支信息:编译时检测(AFL)和仿真(AFL扩展QEMU仿真(QAFL))。编译时检测是有效的,但它不支持二进制程序。同时,仿真支持二进制程序,但效率很低。本文提出了一种基于硬件机制(Intel处理器跟踪)的greybox模糊方法PTfuzz。我们的方法支持二进制程序,就像仿真方法一样,同时它获得了与编译时插装方法相当的性能。实验结果表明,PTfuzz可以在不做任何修改的情况下对原始二进制程序进行模糊处理,与QAFL相比,性能提高了3倍。
索引项反馈,灰盒模糊,英特尔PT,软件安全。

1.简介

计算机或智能手机上的软件已经成为我们日常生活的一部分,如网络浏览器、播放器和文档处理器。然而,软件中的漏洞仍然是普遍存在的[1],这给个人用户或企业服务带来了风险,因此,对软件安全的研究和研究正在兴起。
符号执行和模糊化是软件测试和调试技术的两个主要部分。符号执行引擎[2]-[5]直接应用于源代码,可以有效地检测漏洞。但是,符号执行会在目标程序中触发大量路径,并导致路径爆炸。相比之下,模糊化通过探索一般输入的可能值来避免路径爆炸的风险。根据程序获取的知识对模糊技术进行分类,分别为白盒模糊技术、灰盒模糊技术和黑盒模糊技术。白盒模糊器可以使用传统的程序分析技术来发现目标的特性,这可能会很耗时。同时,黑盒模糊器根本没有任何目标程序的信息。灰盒模糊器通过引入附加信息来保持黑盒的简单性,同时提高模糊器的有效性。
而且,传统的模糊器在检测错误方面已经相对过时了。在没有任何指导的情况下,盲模糊器和输入种子的随机变异不太可能击中程序中所需的分支,使得一些漏洞深藏在执行路径中,难以暴露。灰盒模糊是传统模糊技术的一个很好的扩展。在没有太多开销的情况下分析程序是一种有效的模糊尝试。Greybox fuzzers使用轻量级工具或其他机制为主模糊循环提供程序执行反馈,例如代码覆盖率。如上所述,对初始输入种子进行变异以生成测试用例来执行程序路径。例如,在利用代码覆盖反馈的模糊器中,如果某个测试用例到达一个新路径,模糊器将其作为一个新的输入种子保留在一个连续的模糊循环中,从而生成更多的种子。
近年来,灰盒模糊检测技术的研究备受关注。美国模糊Lop(AFL)是这一领域的先驱性工作[6]。它采用简单而可靠的编译时工具来提供代码覆盖率反馈信息。同时,Bóhme等人。[7] 阿法斯特介绍。改进了AFL的模糊程序,暴露了6个CVEs。此外,Rawat等人。[8] 提出了一种应用感知模糊器:VUzzer。后来,AFL被添加到QEMU的仿真后端,以支持仅二进制的模糊化。根据AFL-QEMU模式,Hertz和Newsham[9]提出了TriforceAFL。它使用QEMU全系统仿真来模糊操作系统。
然而,最近关于灰盒模糊的研究有一些共同的局限性。我们可以列出三个缺点,以往的工作如下:

  • 没有只支持二进制的模糊支持。像AFL、AFLFast和VUzzer这样的灰盒模糊器都依赖于目标程序的源代码。AFL和AFLFast使用位图(有关位图的更多信息,请参见第II-A节)跟踪基本块转换和代码覆盖率。每个基本块都有一个来自编译时检测的随机分配的id。没有源代码就无法实现这种检测。VUzzer也是如此,因为它从静态和动态分析中分析具有控制和数据流特性的程序。VUzzer是一种典型的应用感知模糊技术。

    1. 在编译时检测或源代码不可用的情况下,不能采用不支持二进制的fuzzer。不幸的是,许多软件供应商不愿意提供他们软件的源代码。这使得没有二进制的fuzzer只支持在检测漏洞和bug时没有价值。
  • 慢反馈机制。为了解决对编译时检测和源代码的依赖问题,在模糊处理中引入了动态二进制检测(Intel PIN[10])、静态重写(AFL dyninst[11])和仿真(QEMU[12])等反馈机制。如前所述,AFL通过QEMU仿真进行了扩展(本文称之为QAFL)。后来的作品如TriforceAFL,也采用QEMU来模糊操作系统。然而,AFL的Zalewski[13]指出,QEMU在模糊处理中通常的性能代价是2-5倍,这超出了本文的研究范围。当然,这种由于慢反馈机制造成的性能开销在我们的模糊化实践中是不可容忍的。人们迫切希望改善这些缓慢的机制。

  • 覆盖率反馈不准确。如上所述,greyboxfuzzer(如AFL和AFLFast)使用位图跟踪基本块转换并测量代码覆盖率。位图的每个字节表示特定边(例如从a到B)的命中计数。块A和块B的id通过运行时检测随机分配。计算从块A到块B的转换的散列值,用作位图中的键,即(A⊕B)%位图大小(⊕表示异或操作)。然而,在这个方案中可能会发生哈希冲突。假设有一条从a到B的过渡边和另一条从C到D的过渡边。当a和C的id被随机分配相同的值,并且B和D也相同时,这两个不同的块过渡将被认为是相同的,我们称之为碰撞或重叠。在这种情况下,如果从A到B的转换不是新的,并且C到D是以前从未遇到过的新路径,则可以触发从C到D的转换的测试用例将不会保存为新种子。因此,模糊循环可能会丢失一些重要的种子,可能是不完整的,无法到达程序中的深层路径。此外,AFL使用一个小位图(64KB),因此它可以驻留在缓存中以提高性能。与64KB位图相比,程序中的边数可能非常大,因此碰撞率可能相对较高。有关分支碰撞或重叠的更多讨论,请参见第II-A节。

本文针对greybox模糊技术的上述缺点,提出了PTfuzz,它是一种基于Intel处理器跟踪反馈的改进型模糊器。英特尔PT[14]是英特尔处理器的新功能。它可以公开程序控制流信息的精确和详细的跟踪,例如条件跳转和无条件跳转。特别是PT可以精确地跟踪每个基本块的地址,因此在PTfuzz中,我们遵循AFL的思想,利用PT的这一特性来测量基本块之间的转换,为fuzzing循环提供准确的覆盖反馈信息。同时,PTfuzz能够模糊任何纯二进制软件,因为我们直接从处理器中获取执行信息,完全不依赖任何源代码。在第五节中,我们进一步展示了PTfuzz的性能开销。实验表明,PT是一种比QAFL和TriforceAFL更快的反馈机制。
本文的主要贡献概括如下:

  • 仅二进制模糊。我们提出了一个新的greybox fuzzer来模糊任何只包含二进制的软件,而不需要任何源代码。在源代码不可用的情况下,编译时检测和彻底的程序分析是不可能的,像AFL、AFLFast和VUzzer这样的模糊器将毫无用处。我们的方法可以像往常一样优雅地处理这些情况和模糊二进制文件。
  • 快速反馈机制。我们引入了一种更快的反馈机制。如前所述,虽然之前的工作都试图解决源代码依赖的问题,但它们都有相当大的性能开销,特别是QAFL和TriforceAFL。我们直接利用CPU的快速硬件反馈,以比QAFL快得多的方式处理仅二进制的模糊。根据我们的实验,我们的模糊器的性能开销比QAFL小得多。
  • 准确的覆盖率反馈。我们提出了一种更精确的代码覆盖率反馈测量方法。编译时检测和基本块的随机id分配将不准确地测量代码覆盖率。我们使用基本块的实际运行时地址来跟踪基本块之间的转换,并可以提供运行代码的实际控制流信息。
  • PTfuzz。基于这些见解,我们实现了一个名为PTfuzz的原型(https://github.com/hunter-ht-2018/ptfuzzer)。实验表明,PTfuzz能够快速、准确地处理二元模糊问题。

    2.背景

    A.位图(BITMAP)

    1)位图定义
    如上所述,greyboxfuzzer(如AFL、AFL和Syzkaller)利用代码覆盖率反馈来决定一个测试用例是作为新种子保存还是丢弃。如果一个测试用例执行一个新的程序路径,它将被保存为一个种子来生成更多的测试用例。但是如何决定一个测试用例是否走上了一条新的道路呢?有几种选择。总区块(TBL)、新区块(NBL)、总分支(TBR)和新分支(NBR)覆盖范围。(在本文中,分支和基本块转换是相同的。)Total表示命中块或分支的总数,new表示需要新块或分支。例如,在TBL中,如果测试用例T1命中基本块A,而T2命中块A和B,那么我们可以决定T2执行一个新路径。在NBL中,如果T3击中基本块A,而T4击中块B,那么我们可以决定T4执行一个新路径。但是,由于T3和T4的命中块数相同,T4在TBL中不被视为执行新路径。TBR和NBR也是如此。
    AFL的Zalewski[6]对4种方法进行了试验,发现丁腈橡胶的性能最好。因此在AFL中,如果一个测试用例碰到一个新的分支,它被认为是在执行一条新的路径,并将被保存为一个种子。每个命中分支都记录在一个称为位图的特定内存空间中。例如,如果有一个从a到B的分支,AFL首先读取bitmap[(a⊕B)%bitmap_SIZE]中的值,看它是否为0,然后将该值加1。因此,通过简单地读取位图(最初设置为0),AFL知道hit分支是否是新的,并决定保存或放弃测试用例。
    2)位图中的值表示什么?
    此外,位图中记录的命中分支数是比较不同模糊技术的代码覆盖率的有用指标[15]。换句话说,命中的分支越多,代码覆盖率就越高。然而,Zalewski[6]自己声称,在AFL中,碰撞或重叠的分支总数为20000个时为14%,50000个时为30%。因此,对于AFL和AFLFast,由于分支的碰撞和重叠,指标可以相对较低。稍后在第五节中的实验也将证明AFL具有较低的hit分支值。因此我们可以得出结论,像AFL和AFLFast这样的模糊器由于反馈不准确而导致代码覆盖率较低。而且他们也无法暴露程序中隐藏得很深的错误和漏洞。

    B.灰盒模糊中的反馈

    在本节中,我们将详细讨论以前的灰盒模糊技术。我们将根据每个模糊器所采用的反馈机制进行分类,并指出一些常见的局限性。这些局限性是我们提出PTfuzz的动力。
    image.png
    对于灰盒模糊机来说,内置反馈机制是决定模糊性能的关键因素。我们在表1中总结了各种反馈机制,并对其进行了如下讨论。

  • 编译时检测是一种用特殊编译器将源代码编译成二进制文件并检测所需内容的方法。以前的作品,如Vulcan[16]、alto[17]和Diablo[18]都是这一领域的领先作品。AFL、AFLFast和Syzkaller[19]等模糊器采用编译时工具来利用它们的反馈机制。它相对快速稳定,但编译时工具的缺点也很明显。如上所述,它们通过记录基本块之间的转换向主模糊循环提供代码覆盖率反馈。但是,基本块的地址是随机分配的值,而不是运行时的确切地址。这就是为什么我们声称它不是一个精确的反馈机制。此外,编译目标程序需要源代码。因此在编译时检测中不能使用纯二进制模糊。

  • 动态二进制检测可以通过检测代码在运行时分析二进制文件的行为。插入后的检测代码作为原始代码的一部分工作。别针和发电机是最著名的作品。这种反馈机制在模糊化中的应用可以处理纯二进制的模糊化,并且由于运行时检测的缘故,是准确的。但动态二进制检测的最大问题是开销很大。在模糊化作业中,它比编译时检测慢。
  • 静态重写通过为每个基本块插入回调和在程序入口点[11]中插入初始化回调来检测二进制文件。AFL dyninst使用静态重写,并采用这种技术模糊二进制文件。所以静态重写只能处理二进制的模糊任务,而且速度相对较快。然而,静态重写有一个主要的缺点,那就是它不稳定,充满了危险[13]。
  • 像QEMU这样的仿真是通过动态二进制转换来仿真cpu的,它可以同时进行用户模式和系统仿真。fuzzer能够在QEMU的帮助下运行用户级进程并捕获程序执行流。QAFL和TriforceAFL利用QEMU,可以对运行时反馈准确的纯二进制软件进行模糊处理。然而,由于QEMU的体系结构,与没有QEMU执行相比,这些方法在2-5x的开销上比较昂贵。这是模糊二进制文件无法忍受的开销,无法应用于实际实践。
  • 英特尔分支跟踪存储是处理器的一项硬件功能。BTS机制允许用户将分支跟踪保存在指定的缓冲区中。它还可以提供运行时信息,而不依赖于任何源代码。BTS类似于PT,但在CPU中设置BTS标志会大大降低处理器的性能[21]。换句话说,我们不能使用具有如此大执行开销的模糊器。
  • 英特尔处理器跟踪也是处理器的一项硬件功能,但它比BTS先进得多。PT能够准确地跟踪程序控制流信息,并以此为基础记录基本的块转换。最重要的是,如表1所示,PT可以在没有太多性能开销的情况下完成任务。

综上所述,使用英特尔处理器跟踪反馈扩展的fuzzers能够在纯二进制fuzzing中准确地记录基本块转换。而且,它可以非常快。基于PT,我们的PTfuzz成功地解决了以往工作中存在的问题。

C.英特尔处理器跟踪

在本节中,我们将简要介绍英特尔处理器跟踪。随着Broadwell体系结构和新一代核心处理器的出现,英特尔提出了一种新的硬件功能,称为处理器跟踪[14],它是英特尔体系结构的扩展,可以捕获有关程序执行的跟踪数据。Intel PT只会对使用精心设计的硬件设备跟踪的程序造成最小的性能开销。以前的硬件功能,如Intel Last Branch Record,也会执行程序跟踪,但其输出存储在特殊的寄存器中,而不是主内存中。Intel PT利用内存空间存储跟踪数据,因此连续PT跟踪仅限于主内存大小。因此,我们可以使用PT的输出特性来执行连续的模糊作业和跟踪程序执行。
具体地,PT的输出以数据包的格式被收集[21]。数据包按其功能可分为两类:基本执行信息包和控制流信息包。包流边界(PSB)、时间戳计数器(TSC)和其他相关包是基本的执行信息,它们反映了程序的一般运行状态。在PT的初始实现中,控制流跟踪包由软件解码器进行处理。控制流信息包括运行时的时间、程序流等信息。
基本块是一个连续的代码段,没有跳转或分支。在本文中,为了捕获基本块之间的转换,我们需要关注程序流信息,例如跳转目标和分支执行/不执行。我们需要一个专门设计的解码器来解码存储空间中的程序流跟踪数据。Intel PT指定可以将程序流更改为流指令更改(COFI)的指令。介绍了三种COFI指令:直接传输COFI、间接传输COFI和远传输COFI。
此外,Intel PT还引入了4个特定的数据包来跟踪COFI指令:

  • 已取未取(TNT)包。TNT包中的特定位可以指示在条件跳转中是否执行分支。因此,它用于跟踪条件分支的方向。
  • 目标IP(TIP)数据包。TIP记录跳转或传输指令的目标指令指针(IP)。IP值存储在此数据包中的特定位中。具体来说,根据应用场景的不同,TIP可以分为TIP、TIP.PGE、TIP.PGD和TIP.FUP。
  • 流更新包(FUP)。当发生诸如中断或陷阱之类的异步事件时,我们需要FUP来提供源IP地址,因为TIP在这些事件中不起作用。
  • 模式包。模式提供了重要的程序执行信息,它有多种格式来表示执行模式。

因此,借助于TNT、TIP和FUP包,我们能够编写解码器来捕获程序执行中的基本块转换。更具体地说,我们需要记录基本块的地址。当COFI指令发生时,我们需要在位图中记录基本块之间的控制流转换。例如,当存在从A到B的间接跳转指令(例如JMP(FF/4)、CALL(FF/2))时,解码器可以识别这属于TIP包并读取其中的IP地址。然后位图[(A⊕B)%位图大小]增加1,我们完成记录转换。

D.符号执行与模糊化

软件中的漏洞仍然很常见,使个人用户或企业用户处于危险之中。为了检测软件中的漏洞和错误,我们可以检查源代码并匹配某个已知模式,这是一种静态分析方法。但是,如果不执行一段代码,很难找到许多类漏洞,如功能正确性错误。在代码执行过程中暴露错误称为动态方法。关于代码执行的问题,关于符号执行与更轻量级的模糊化技术相比,已经有了很多争论[7]。
符号执行和模糊化是软件测试和调试技术的两个主要部分。
符号执行引擎可以直接应用于源代码。他们使用程序分析来解释应用程序,用符号变量对用户输入建模,跟踪条件跳转产生的约束,并采用约束求解来创建有趣的输入,以覆盖特定的程序路径。象Veritesting[22]、Firmalice[23]、under-constrainted execution[24]这样的符号执行引擎近来受到广泛关注。同时,随着近年来计算能力的提高,伴随执行(也称为动态符号执行)也越来越流行。有很多高性能的工具:EXE[2]、KLEE[3]、Mayhem[4]和S2E[5]。由于符号执行的内部工作结构,两种符号共扼执行都存在着著名的路径爆炸问题。
此外,符号执行工具能够自动分析具有符号值和大量约束求解的程序[3]。它会在目标程序中触发大量路径,并导致路径爆炸。同时,模糊技术是一种有效的测试技术,可以暴露漏洞和漏洞。给定一个初始输入种子,可以通过简单的突变生成一堆新的测试用例,以便尽可能多地练习和覆盖程序路径。如今,大多数漏洞都是由特别轻量级的fuzzer暴露的,这些fuzzer不利用密集的程序分析[6]。
相比之下,模糊化通过探索一般输入的可能值,试图捕捉特定值并驱动程序分区之间的执行流,避免了路径爆炸的风险。现有的fuzzing,如Dowsing[25]、BROG[26]、Flayer[27]和BuzzFuzz[28],缺乏精确的信息,使得fuzzing无法解决程序中不同的条件跳转和更深层次的执行。Driller[1]将符号执行和模糊化结合起来,提出了一种混合方法来解决上述问题。
同时,根据程序获取的知识对模糊技术进行分类。通常,白盒模糊器具有目标程序的全部信息,并且可以使用传统的程序分析技术来揭示目标的属性。包括Smart-Fuzz[29]、BuzzFuzz[28]、Vuzzer[8]和TaintScope[30]在内的白盒模糊器实现了预期的性能,并可应用于实际场景。灰盒模糊器使用特定的反馈信息来增强“盲”模糊化的过程。这种模糊方法试图通过引入附加信息来保持黑盒的简单性,同时提高模糊器的有效性。AFL[31]和AFLFast[7]是灰盒模糊器最成功的代表。同时,黑盒模糊器根本没有任何目标程序的信息。最近,一些新的思想被应用到黑盒模糊器中,Radamsa[32]、zzuf[33]和peach[34]在这方面做了显著的工作。
这种模糊器的分类是基于与目标程序的交互,而它也可以分为非核模糊器和核模糊器,这取决于它是否可以用于模糊核[19]。

3.模型概述

在本节中,我们将介绍我们的模型和PTfuzz的模糊化步骤。PTfuzz作为一种灰盒模糊器,遵循AFL的模糊结构。它从初始种子文件开始,并尝试生成新种子以尽可能多地执行程序路径。如图1所示,PTfuzz主要包含两个相关部分:主fuzzing循环和PT基础设施。
image.png
主模糊循环用作父线程,其作业包括:

  • 配置。该部分包括屏幕显示初始化、输入种子文件定位、信号设置、定时器设置等配置。此配置步骤对于以后的模糊化步骤至关重要。
  • 预构建COFI映射并写入MSR寄存器。将加载目标二进制文件并转储指令以构造第II-C节中提到的用于解码的COFI映射。并写入特定的MSR寄存器,为PT进程设置ip过滤。
  • 装载种子。输入种子按顺序列在队列中。每次执行完最后一个种子后,队列中的下一个种子将自动加载以进行变异。
  • 变异。变异意味着改变一个种子的某些部分来产生几个测试用例。可分为确定性突变和非确定性突变。确定性变异包括:具有不同长度和步进的顺序位翻转、加或减小数字以及插入已知整数。非确定性变异包括叠加位翻转、插入、删除和拼接。例如,有一个种子,其内容为“10011”。程序分析器需要读取“10010”才能执行以下代码。显然,这个种子不能通过程序解析器。当对第一个位执行位翻转时,此种子将变为测试用例“10010”。这个测试用例通过了程序解析器并执行了下面的代码。
  • 执行。在生成突变或测试用例之后,它们被作为程序执行的输入。
  • fork。在执行开始时,fork()调用子线程作为处理器跟踪基础结构执行。这个子线程在执行完成后被捕获,解码后的跟踪数据将被发送到父线程。
  • 保存或放弃。测试用例执行结束后,主模糊循环需要根据PT基础设施提供的信息来决定是否保存或放弃这个用例。保存的测试用例将被放入种子队列中以进行连续的模糊处理。
  • 显示和报告。当运行模糊时,需要屏幕显示来监视模糊状态。此外,在模糊化结束后,主模糊化循环将创建有关各种运行信息的详细报告。

如上所述,处理器跟踪基础结构是通过fork()由主模糊循环创建的。主要完成以下任务:

  • 启用PT。为了执行程序执行的连续跟踪,需要启用处理器跟踪。在fork()操作之后,PT基础结构在程序执行开始时启用PT。
  • 记录跟踪数据。启用PT后,它将不停地捕获程序执行信息。并且PT基础设施指定了一定的内存空间来存储启用PT后的跟踪数据。因此,PT可以在这个特定的空间中写入跟踪数据。
  • 解码跟踪数据。我们的目的是在程序执行中找到基本的块转换,因此PT基础设施解码原始跟踪数据并捕获TNT、TIP和FUP包,如第II-C节和其他相关信息所述。基本的块转换被写入位图,这样主模糊循环就可以访问它并做出决策。
  • 禁用PT。执行结束后,PT被禁用,PT将不记录任何内容。

然后详细描述模型的运行步骤。如图2所示,PTfuzz具体工作如下:

  • (1) 主模糊循环配置屏幕显示初始化、定位输入种子文件、信号设置、计时器设置和其他相关事件。
  • (2) 将目标二进制文件加载到内存中,并转储文本部分中的指令以生成COFI映射。修改了CPU中的MSR寄存器,设置了ip过滤。
  • (3) 种子队列中的一个种子已加载,准备好进行变异。
  • (4) 这个种子被赋予变异引擎来执行确定性和非确定性的变异,生成几个测试用例。
  • (5) 将突变后的测试用例作为目标程序的输入,逐一执行。
  • (6) 在执行开始时,主模糊循环通过fork()调用一个子线程,因此处理器跟踪基础结构是活动的。
  • (7) PT基础设施在PT分叉后立即启用它。
  • (8) PT跟踪数据被记录到特定的内存空间中。
  • (9) 对跟踪数据进行解码以显示基本的块转换,并更新位图,以便主模糊循环可以访问它来进行决策。
  • (10) 执行完成后禁用PT。
  • (11) 这个子进程被捕获,因此PT基础设施不存在。
  • (12) 如果测试用例根据PT反馈触发了一个新的基本块转换,主模糊循环将保存这个特定的测试用例并将其放入种子队列中;如果没有检测到新的转换,则丢弃这个测试用例。

在步骤(12)之后,主模糊循环加载队列中的下一个种子并开始下一个模糊循环。PTfuzz要求用户首先提供初始种子。本文不讨论如何选择初始种子,因为这部分超出了我们的研究范围。此外,PTfuzz可以及时报告程序崩溃、执行速度和命中分支数。最后,PTfuzz以用户的停止操作或预先设定的运行时间结束。

4.实现

基于第三节讨论的模型概述,我们实现了原型PTfuzz。我们将在下面描述我们实现的一些细节。

  • 正在构建COFI地图。如第II-C节所述,流指令变更(COFI)是处理器跟踪中的控制流序列。当目标程序加载时,我们可以转储它来提取文本部分的所有指令和ip地址的范围。在我们的实现中,我们采用Python-CLE 1加载二进制文件,capstone 2转储文本部分。对PT跟踪包进行解码需要COFI映射,PTfuzz通过预先构建COFI映射可以节省相当多的解码时间,并大大减少执行开销。
  • 编写msr并设置ip过滤。如英特尔的编程指南[21]所述,我们可以通过设置某些MSR寄存器来过滤PT数据包的生成。在PTfuzz中,我们只需要解码目标二进制文件的ip范围内的PT包,而不包括一些库或系统调用。因此,我们遵循msr-tools3来编写msr寄存器,并将PT包的生成限制为目标二进制文件。通过这样做,PTfuzz的解码可以避开大量无关的PT包,获得明显的性能改进。
  • 在模糊化中启用和禁用PT。如英特尔系统编程指南中所述,我们必须将型号特定寄存器(MSR)的特定位设置为1,以启用英特尔PT[21]。启用PT后,它将跟踪cpu中的任何执行信息。但是,设置MSR不能直接由用户级隔室完成。所以在PTfuzz中,我们使用系统调用ioctl()来执行设置MSR的操作。具体来说,主模糊循环将调用一个子线程来启用带有ioctl()的PT并开始跟踪程序执行。在模糊迭代结束后,子线程将通过ioctl()禁用PT。
  • 处理器跟踪输出。以前关于PT的工作,如Simple PT[35],无法执行连续跟踪,因为它们将跟踪信息存储在文件中。写入文件可能很慢,无法与主模糊循环进行及时交互。所以在PTfuzz中,我们在perf_event[36]中使用mmap_page特性。此功能配置特定的内存空间以存储跟踪数据。PTfuzz在这个内存中记录跟踪信息。访问内存比访问文件快得多,并且与主模糊循环的交互是及时的。
  • 正在解码PT信息。当PT信息被记录到存储器中后,我们需要对其进行解码以找到基本的块转换。英特尔已经提出了自己的PT解码器库[37]。但是,它是一个通用解码器,不适合我们的需求,因为它没有提供跟踪基本块转换的API。因此,我们基于系统编程指南[21]为PTfuzz编写了一个新的解码器。我们的解码器是一个特殊用途的解码器,它只关注程序执行中的基本块转换。它访问指定内存空间中的信息,并对跟踪数据进行解码,以找到主模糊循环所需的内容。

    5.实验

    在本节中,我们将讨论我们的PTfuzz与AFL和QAFL相比的实验结果。AFL是一项特殊的工作,它使用编译时工具实现应用程序感知的模糊器。与之相比,PTfuzz能够实现纯二进制的模糊化,并且比AFL具有更高的代码覆盖率。QAFL表示只支持二进制的模糊技术。通过与QAFL的比较,可以看出PTfuzz在模糊任务中的速度相对较快。
    image.png
    实验设置:实验在桌面上进行,英特尔酷睿i7 3.4GHz 8核CPU,8GB RAM运行Ubuntu16.04。我们的目标项目是有意选择的。我们有来自GNU Binutils[38]的cxfilt、nm、objdump、readelf、size和strings,以及来自LAVA-M数据集的base64、md5sum、uniq。Binutils是广泛使用的二进制工具集合。我们有图像处理工具gif 2tiff和tiffinfo from TIFF[39]。此外,还包括mpg321[40]和tcpdump[40]。因此我们总共有10个目标程序来进行实验。表2显示了这些程序的输入参数。对于每个程序,我们分别使用PTfuzz、AFL和QAFL对其进行24小时模糊处理。此外,我们为每个实验记录了3个指标:

  • 碰撞。这是执行程序时的唯一崩溃数。而崩溃是由导致被测程序接收致命信号(例如SIGSEGV、SIGILL、sigabt)的独特测试用例造成的。这是一个广泛使用的指示符[7]、[8]、[41]和[42],用来决定一个模糊器是否具有良好的模糊性能。

  • 速度。我们在exe/s中测量每个fuzzer的执行速度,以演示fuzzing开销。此指示器表示每秒执行的测试用例数。
  • 分支。如第II-A节所述,命中分支数(基本块转换)是衡量模糊器代码覆盖率的一个重要指标。分支越多,代码覆盖率越高。覆盖更多的程序代码肯定会导致更深层的漏洞和bug。

实验结果和详细讨论如下。
A.源代码可用模糊
在本节中,我们将详细比较PTfuzz和AFL的实验结果。如上所述,在用AFL模糊化程序之前,我们必须用AFL自己的特殊编译器AFL clang-fast编译程序。编译器插入随机分配的值作为基本块的地址。但我们的PTfuzz不需要经过这些步骤。所以这10个程序在AFL上运行时是插装程序,而不是在PTfuzz上。
image.png
如表3所示,PTfuzz在10个目标程序(cxxfilt、nm、size、gif2tiff、tiffinfo、mpg321和tcpdump)中发现比AFL更多的唯一崩溃,在2个程序(readelf和strings)中发现相同数量的崩溃。然而,对于每秒执行的测试用例,PTfuzz的速度比AFL慢7%左右,这是由于我们实现了PT解码器。(我们详细讨论了这个问题,并在第六节中提供了关于这个问题的未来工作)但是在模糊化任务中,7%的开销相对来说是可以接受的。在hit分支中,PTfuzz在所有10个程序中的分支都比AFL多。这个指示器也证明了PTfuzz可以覆盖程序中更多的代码和路径,并且能够暴露更深层的错误。
此外,当输入文件和参数都与PTfuzz相同时,AFL不能正常地模糊gif2tiff,只能在屏幕上产生“奇数,检查语法”。根据AFL的文献[43],这些信息表明AFL中的fuzzing出错了,我们应该立即停止fuzzing。我们假设这个问题是由于AFL在编译gif2tiff时使用的仪器造成的。AFL的特殊编译器AFL clang fast可能会破坏程序的一些内部特性,我们无法用AFL模糊它。更重要的是,这有力地证明了AFL不能用于所有可用的源代码模糊任务,但是我们的PTfuzz可以很好地处理这个问题。
image.png
PTfuzz和AFL的比较有力地证明了PTfuzz能够发现更多的程序崩溃,实现更高的代码覆盖率,并且在暴露漏洞方面更有效。这一结果的主要原因是,我们克服了AFL编译时工具的内部缺陷,并使用基本块的实际运行时地址,而不是随机分配的值。PTfuzz能够准确地捕捉程序执行中的基本块转换,并为主模糊循环提供有用的反馈。总之,PTfuzz在崩溃暴露能力和代码覆盖率方面优于AFL,开销为7%,这可以通过代码优化来消除。
B.二元模糊
在本节中,我们在没有源代码的情况下,对PTfuzz、AFL和QAFL中的10个程序的二进制文件进行了实验。我们仍然在表4中列出了3个指标,崩溃、速度和分支。这些二进制文件是由其他计算机用普通gcc编译器编译的,我们的桌面上只有10个二进制文件,没有任何源代码。
首先,我们将在这个表中比较PTfuzz和AFL。当源代码可用时,这与实验不同,因为我们根本无法用AFL的特殊编译器编译这10个程序。正如我们所看到的,AFL不能在这些只包含二进制的模糊作业中做任何事情,并且当源代码不可用时它是没有价值的。但是,我们的PTfuzz可以毫无例外地模糊所有这些二进制文件。在这种只使用二进制的模糊化场景中,PTfuzz绝对优于AFL。
然后我们将检验PTfuzz和QAFL之间的比较。通常,PTfuzz在10个程序(cxxfilt、nm、objdump、size、gif2tiff、tiffinfo、mpg321和tcpdump)中暴露的崩溃比QAFL多,在2个程序(readelf和strings)中暴露的崩溃也相同。在执行速度方面,在所有10个实验中,PTfuzz都比QAFL快。尤其对于objdump,PTfuzz的执行速度比QAFL快24倍。在10个程序中,PTfuzz比QAFL命中更多的分支,这意味着PTfuzz在fuzzing中的代码覆盖率总是高于QAFL。
此外,当模糊mpg321和tcpdump时,QAFL会在fork服务器启动后卡住。(启动fork服务器是AFL和QAFL模糊化中的一个基本步骤)我们假设这个问题是由于QAFL的执行速度慢,不能继续执行后面的步骤。然而,我们的PTfuzz在fuzzing作业中并没有遇到这种情况。这个问题也证明了QAFL的执行速度慢。
因此,通过比较AFL和QAFL在纯二进制模糊方面的性能,我们可以发现PTfuzz在很多方面都优于AFL和QAFL。PTfuzz能够在AFL不能的地方模糊二进制文件,因为PTfuzz不需要像AFL那样专门编译目标程序。此外,PTfuzz的运行速度比QAFL快得多,暴露了更多的崩溃,覆盖了更多的程序路径。其内在原因是我们放弃了QAFL中采用的QEMU仿真,而将精力集中在处理器跟踪上,这样可以快速、准确地模糊二进制文件。因此,在纯二进制的模糊任务中,PTfuzz比AFL和QAFL更容易发现深层的错误和漏洞。
C.LAVA-M数据集实验
在本节中,我们将比较PTfuzz、AFL和QAFL在LAVA-M数据集上的实验结果。LAVA是一种通过向目标程序中注入真正的bug来生成地面真实语料库的技术[42]。作者通过注入4个常见的GNU程序创建LAVA-M数据集:base64、md5sum、uniq和who。最近的工作,如VUzzer[8],倾向于使用LAVA-M数据集作为基准来比较不同模糊器的性能。我们分别对base64、md5sum、uniq和who进行了实验,实验结果见表5。具体来说,在表中,由于AFL不能模糊原始二进制文件,所以对AFL的目标程序进行编译时检测,而不对PTfuzz和QAFL进行检测。
image.png
如表所示,PTfuzz在base64和uniq中暴露的崩溃比AFL和QAFL多。在执行速度方面,PTfuzz在所有目标程序中都优于QAFL,但比AFL慢,这与以前的实验相同。此外,在所有的实验中,PTfuzz比AFL和QAFL能够覆盖更多的边缘,这意味着在精确的覆盖反馈下,PTfuzz能够更精确地模糊目标程序。在md5sum的实验中,我们无法在PTfuzz、AFL和QAFL中对其进行模糊处理,因为它在第一次输入时崩溃,而不允许程序执行更多的其他输入。在VUzzer中也出现了同样的现象,所以我们在md5sum的实验结果中标记了“-”。
根据该表,PTfuzz在LAVA-M数据集中发现崩溃时表现不佳,AFL和QAFL也是如此。原因是,注入LAVA-M的bug都是通过对从输入中复制的值与硬编码的魔法字节进行条件检查来保护的。因此,如果没有静态分析工具(如在VUzzer中),PTfuzz就无法轻松地恢复在保护注入的错误的检查中使用的预期值。
总之,我们在本节中对广泛使用的基准LAVA-M数据集进行了实验。通过与AFL和QAFL的比较,可以证明PTfuzz在这种情况下具有更好的模糊性能。而且我们的PTfuzz可以对除md5sum之外的大多数目标程序进行模糊化,因此我们可以得出结论:PTfuzz可以扩展到大多数模糊情况。

6.讨论

A.限制

虽然我们的PTfuzz比AFL和QAFL有更好的模糊性能,但是我们的方法有一些局限性。

  • 硬件和操作系统要求。英特尔处理器跟踪是继英特尔核心处理器(宽井体系结构)之后的新功能,因此PTfuzz不能在使用旧版本英特尔CPU的计算机上运行。此外,我们的处理器跟踪输出空间(ToPA[21])的实现依赖于perf_event[36]中名为mmap_page的功能,并且此功能仅在内核4.1.x之后可用。因此PTfuzz需要新版本的内核才能运行。但是,这个ToPA实现问题可以通过手动指定一个内存空间作为ToPA来解决,这与使用mmap_page完全相同。
  • 解码器的实现。如第五节所示,ptfuzzislowerthanflinfuzzingjobs,通过分析我们的PTfuzz代码,我们发现通过执行类似流水线的解码,可以极大地加速我们对记录的处理器跟踪信息的解码。换句话说,在我们目前的实现中,PTfuzz仅在测试用例完成执行后才解码ToPA中的测试用例的跟踪数据。所以在执行开始和执行完成之间,解码器没有任何关系。我们可以在测试用例执行时执行解码,而不是在执行完成后执行解码,从而改进PTfuzz。
  • 仅在Linux平台上。目前我们的PT fuzz只能模糊Linux程序,因为我们的PT实现依赖于Linux系统。不过,处理器跟踪最近增加了Windows支持,以帮助在Windows上运行PT,4因此,我们计划在未来将PT fuzz移植到Windows平台,并以同样的方式对Windows应用程序进行fuzz

    7.结论

    本文主要研究greybox fuzzing,以揭示软件中的缺陷和漏洞。同时,我们考察了以前的灰盒绒毛,找出了它们的一些共同缺点。有些fuzzer不能支持纯二进制的fuzzing,有些代码覆盖率低,有些代码开销大。为了解决这些限制,我们引入了一种greybox模糊技术,并在Intel处理器跟踪技术的帮助下实现了一个原型PTfuzz。我们以相对较快的执行速度准确地记录了程序执行中的基本块转换,并且实现了比以前的模糊器更高的代码覆盖率。实验结果表明,PTfuzz在碰撞、速度和分支3个指标上均优于AFL和QAFL。结果有力地证明了PTfuzz在模糊化作业方面更有效,并且能够暴露程序中更深层的错误和漏洞。