论文阅读1:Path Transitions Tell More: Optimizing Fuzzing Schedules via Runtime Program States

路径转换告诉更多:通过运行时程序状态优化模糊调度

---------------------------------------------------------------------------------------------

​​​​​​​论文链接:Path transitions tell more | Proceedings of the 44th International Conference on Software Engineering (acm.org)

---------------------------------------------------------------------------------------------

Abstract

覆盖率引导的灰盒模糊测试(Coverage-guided Greybox Fuzzing, CGF)是目前最成功、应用最广泛的bug搜索技术之一。优化CGF主要采用两种方法:

  1. 通过推断输入字节与路径约束之间的关系来减少输入的搜索空间;

  2. 制定模糊过程(如路径转换)和建立概率分布以度优化能量调,即每个种子产生的输入数量。

然而,前者的推断结果较为主观,推断结果可能包含路径约束外的额外字节,从而限制了路径约束解析、代码覆盖率发现和缺陷暴露的效率;而后者只关注种子的能量调度,而不关注种子中字节的调度。

在本文中,我们提出了一种轻量级模糊方法Truzz,以优化现有的覆盖导向灰盒模糊器(CGFs)。为了解决前面提到的两个挑战,Truzz识别了与验证检查(Validation checks)相关的字节(即,保护错误处理代码的检查),并保护这些字节不被频繁修改,使大多数生成的输入能够去检查程序的功能代码,而不是被验证检查拒绝直接退出程序以字节为单位确定关系(约束变量与字节的关系)可以缓解fuzzer在推断字节约束关系时加载额外字节的问题。此外,提出的Truzz内部路径转换可以有效地将种子优先级排序为新路径,收获许多新边,并且新路径可能属于具有许多未发现代码行的代码区域。

1. Introduction

CGF(覆盖率引导的灰盒模糊测试)通常维持一个无限循环,在这个循环中,通过突变种子产生新的输入。如果一个新的输入探索了新的代码覆盖范围(例如,新的边缘),该输入将作为一个新的种子保留,并在进一步的回合中突变。

例如,一个输入包含8个字节,但只有1个字节与约束相关,if(x[5]==0xee)。如果CGFs可以推断出字节--约束关系,那么突变可以只集中在相关的字节x[5]上。因此,CGF就不用去尝试256^{8}个新输入,而是最多只需要生成256个新输入来解决约束。

根据我们的经验,现有的推理方法不能区分嵌套的外部条件和内部条件。例如,推断出的字节可能与整个嵌套条件相关,例如:

if(x[5] == 0xee) 
{
    if(x[6] == 0xff)
    {
        …
    }
}

也就是说,当推断过程打算推断内部条件(即x[6])的相关字节时。推断结果将错误地包含与整个条件相关的字节(即x[5]和x[6])。因此,当模糊处理要解决内部条件时,模糊处理可能会改变字节x[5],这将改变外部条件的分支这样会导致尽管解决了内部条件(x[6]==0xff),但外部条件被改变,导致生成的输入将永远不会达到内部条件,因为x[5]的突变违反了外部条件。

另一方面,另一类解决方案通过制定模糊过程来优化能量调度。因为CGFs的目标是发现更多的代码覆盖率,一个优化能量调度的最新解决方案是将路径转换 建模为Markov chain或Multi-Armed Bandit。能量调度决定了分配给每个种子的突变数量

一个新输入s',其执行路径为P_s',这个新输入s'由种子s突变而来,且种子s的执行路径为P_s;然后,在路径 P_s 和 P_s' 之间有一个路径转换

该模型建立了路径转换的概率分布,并根据该分布优化了能量调度。一个基本的动机是将更多的能量分配给那些更有可能发现新覆盖路径的种子也就是说他们更喜欢包含新路径在内的路径转换(突变)。例如:对于基于Markov chain的AFLFast来说,AFLFast更喜欢执行较少频繁路径的种子,即具有较低转移概率的种子。然而现有的模型只关注种子的能量调度,而忽略了字节的能量调度。事实上,在路径转换中包含了更多的执行状态信息,可以利用这些信息优化对于字节的能量调度。我们观察到,转换的两个执行路径之间的差异暗示了被测试程序(put)的属性。具体来说,如果路径转换导致路径明显缩短,则该转换可能与验证检查相关。在该研究中,我们遵循You et al中的定义:如果一个路径约束看守了error-handling code(错误处理代码),那么它是一个验证检查;否则它是非验证检查。检查error-handling code的执行路径通常比检查函数功能代码的路径短得多,原因在于error-handling code通常会导致程序的终止。

例如下图:如果一个输入验证检查Ⅰ(validation check Ⅰ)失败,则该输入会进入长度为10边(edges)的errorhandling code Ⅰ当中。

另一方面:如果一个输入通过了验证检查Ⅰ(validation check Ⅰ)和验证检查Ⅱ(validation check Ⅱ),则执行长度至少为100边。

此外,如果路径转换发现了一个包含新边的执行路径,代表了它可能会探索一个未发现的区域。直观的说,路径中覆盖的更多新边,更有可能会使我们找到更多未被发现的后代,因为它可能涉及更多的新代码区域,因此我们应该更多地关注这些发现更多新边的种子,直到它们被彻底测试。考虑到模糊测试活动的时间预算有限,对于那些执行路径包含更多未发现的后代基本块的种子,给予更高的优先级可能会进一步提高代码的覆盖率

例如上图中:如果执行路径ACFJLN中的新边为CF、FJ、JL和LN,那么路径ACFJLN的未被发现的后代基本块有I、K、M、O和P。

再例如执行路径ABDG中的新边为BD、DG,那么路径ABDG可以到达的未被发现后代基本块为H。

因此执行路径为新路径ACFJLN的输入更有可能探索更大的未发现代码区域。

在本文中,我们提出了一种轻量级的Truzz方法来提高CGF的性能。不同于现有的CGF模糊器,我们的Truzz不需要复杂的静态分析和动态分析就可以确定与验证相关的字节。具体来说,Truzz打算保护与验证检查相关的字节,并优先考虑那些更有可能探索更大的新代码区域的种子它推断输入字节和验证检查之间的关系。

根据前面的观察,如果某些字节的突变导致执行明显更短的路径,那么这些字节可能与验证检查相关。为了确定这样的关系,Truzz为每个字节计算一个标量(适应度),度量一个字节与验证检查相关的可能性。然后根据适应度评分,Truzz将较低的突变概率分配给与验证相关的字节,使它们不容易被突变(之所以不直接锁定他们不被突变是因为,为了以确保模糊测试仍然会生成验证检查失败的输入,并覆盖error-handling code代码区域,而不是完全忽略它。 。此外,为了对种子进行优先级排序,Truzz根据第一次保留种子时新发现的边缘的数量对每个种子进行排名。优先级最高种子将被挑选出来在下一轮的fuzzing中进行突变。

实验结果表明,我们的方法与现有的CGFs相结合,可以进一步提高CGFs的性能。我们的方法可以帮助缓解前面提到的字节约束关系推断不精确的问题。该算法注重字节调度的细粒度,提高了覆盖发现的性能。同时,我们的方法将计算资源引导到函数代码而不是errorhandling code,这样输入就能探索更长的路径。由于时间预算有限,我们的方法中的种子优先级进一步提高了能量调度的性能。

我们通过将Truzz应用于6个最先进的模糊器,即NEUZZ , GreyOne , AFL,MOPT、FuzzFactory、AFLFast,并且在8个目标程序上进行fuzz测试。 实验结果表明,配备truzz的模糊器比普通模糊器发现更多的代码覆盖率和bug。我们将我们的贡献总结如下。

  1. 我们首先提出了一个轻量级的通用字节调度和种子优先级框架Truzz,以提高CGFs的性能,利用路径转换中的路径差异,因为这种差异可以指示目标程序的属性,例如,验证检查和未发现的代码区域。

  2. 我们根据有效的输入生成来评估我们的方法。实验结果表明,在Truzz的帮助下,平均有16.14%的输入可以通过验证检查并流入函数代码。

  3. 我们用6个最先进的模糊器(AFL、AFLFast、NEUZZ、MOPT、FuzzFactory和GreyOne)实现了我们的框架,并与普通模糊器进行了性能比较。平均多出24.75%的边缘覆盖率。此外,配备truzz的模糊器暴露了8个目标程序中的13个错误,其中6个没有被普通模糊器识别出来。

2. Related Work And Motivation

在本节中,我们介绍了覆盖引导灰盒模糊(CGF)的相关工作。然后试图通过展示当前存在的一些CGFs的弱点来与我们的方法对比,体现我们的优势:

2.1 Coverage-guided Greybox Fuzzing

CGF的基本思想是探索更多的代码覆盖范围,从而触发隐藏在某些代码区域的bug。一般的CGF有一个种子语料库,其中包含初始输入和模糊处理期间从生成的输入中保留的“有趣的”输入。初始输入通常是根据被测程序(put)的输入规范手动创建的,或者是从Internet收集的。“有趣的”输入是那些试图在模糊处理期间发现新的代码覆盖率的输入。具体来说,在每一轮fuzzing过程中,CGF从种子语料中选择一个种子,并通过对种子中的字节进行突变产生新的输入。大多数cgf随机选择要突变的字节或选择与某个路径约束相关的字节。当一个生成的输入发现了新的代码覆盖范围(例如,新的边缘),CGF将保留输入作为一个新的种子。包含选择、突变和保留的循环确保了代码覆盖发现的有效性。

大多数CGF通过插桩位图来表示新边缘,来获取覆盖率信息。位图是一个紧凑的向量,记录当前执行中发现的边。每条边都有一个标识符,它也是位图的索引。当检查到一条边时,位图的对应元素将使值变为1。因此如果元素的值为0,则不检查相应的边;否则,如果一个元素的值非零,则检查相应的边。因此,如果位图的对应元素在当前执行中从0翻转到非0,则确定一个新边。

2.1.1 Path Constraints 路径约束

提高模糊处理效率的主要途径是路径约束,特别是紧约束,比如如果(x [5] == 0 xee)。许多fuzzer利用符号执行来解决路径约束^{[1]},但执行速度明显较慢。因为与路径约束相关的字节通常是输入的一小部分字节,其他一些模糊器利用污染分析来构建输入字节和路径约束之间的关系^{[2]}。污点跟踪可以识别出可能影响程序某些操作的输入字节。在解决路径约束时,模糊只需要改变相关的字节,以提高通过约束条件的效率,然而污染分析仍然会引入限制性能改进的高开销。T-Fuzz利用程序转换来删除路径检查,以便模糊处理能够探索深层代码^{[3]},但它需要许多工程工作来转换程序。

针对路径约束提出了更轻量级的解决方法,该方法根据执行状态的变化推断输入字节和路径约束之间的关系^{[4]}。例如,NEUZZ为输入字节和分支行为之间的关系建立了深度学习模型。分支行为表示满足或不满足路径约束的执行状态。模型很有可能包含特定路径约束的额外字节,因为它们近似地构建了关系。类似地,GreyOne的推断方法是,如果字节的突变导致约束变量的值发生变化,则这些字节与约束变量相关。当该约束变量在路径约束中被选中时,即该约束变量是执行路径中相关分支约束的变量,则该变量对应的字节也与路径约束相关。然而,与外部条件相关的字节的变化也会影响与内部条件相关的变量的值(见下图)因此,与内部条件相关的字节将包括额外的字节。由于这种额外的字节,fuzzing可能会使与外部条件相关的字节发生变异,从而改变外部条件的分支,从而降低了求解内部条件路径约束的有效性。

 Validation check. 如果一个输入没有通过验证检查,它将被困在错误处理代码中

2.1.2 Power Schedule Optimization 能量调度优化

提高CGF性能的另一种方法是通过制定模糊过程来优化模糊调度。对于一般的cgf(例如AFL),它们为每个种子分配几乎相同的能量。然而不同的种子有不同的潜力发现新的覆盖。因此在制定模糊处理过程的基础上,CGFs为更有可能发现新覆盖度的种子分配了更多的能量。能量调度的优化是基于路径转换公示的,CGF通过观察每个突变的结果来构建路径转换的概率分布。将更多的能量分配给那些有更高可能性转换到未被发现路径的种子。然而他们只关注了种子的能量调度而忽略了字节的调度。

2.2 Motivating Example 例子

Truzz基于路径转换之间的差异改进CGFs的性能。在本节中,我们将展示一个这项研究的示例,即对字节调度的需求。如下图所示:

图中代码第二行是一个验证检查(validation check),因为它守卫第八行中的error-handling code。为了测试功能代码(第4行和第6行),fuzzing必须满足第二行中的验证检查。另一方面,如果一个种子已经满足了第二行中的条件,那么后面的fuzzing活动不应该去改变与该条件相关的字节。因此一些最先进的fuzzer通过推断字节-约束关系来提高求解路径约束的效率。例如,上图中的输入长度为5字节,一个随机突变可能会产生256^{5}个新输入,其中生成的输入大多数都不能通过第二行的验证检查。然而如果模糊器可以推断出输入的第一个字节是与第二行约束相关的唯一字节,那么它最多只需要256个突变就可以绕过约束。

问题是推理约束的相关字节时,可能会包含用于该路径约束的额外字节。原因在于推理依赖于执行状态的变化,而这些变化可能会显式或隐式的受到某些字节的影响。例如:在上图第三行if判定的条件,该条件显式的受到变量b的影响,并且隐式的收到变量a的影响。因此对于NEUZZ和GreyOne,输入input中与第3行条件相关的字节为[0:4],即输入的[0:4]字节与约束变量a和b都相关。这就导致fuzzer在解析第三行约束时会突变字节input[0],并且导致生成的输入最终都流向检查代码error_handler(),而不是流向函数functionalities。因此为了检验函数功能,满足验证检查的input[0]字节不应该被突变。

Truzz通过基于路径转换识别与验证检查相关的字节来缓解上述问题。当Truzz改变相关的字节时,它将保护与验证检查相关的字节(例如上图中的input[0])。这样以来,大多数生成的输入会通过验证检查进入函数代码,而避免被困在error-handling code中。同时,利用路径转换来确定种子的优先级,进一步提高了CGFs的效率。

3. Methodology Of Truzz

根据前面观察到的路径转换的差异暗示了程序的内部状态,我们将Truzz设计为一个轻量级框架,以提高cgf的代码覆盖率和效率。我们的Truzz是轻量级的,因为它没有利用复杂的污染分析来获取相关的字节或方法来精确定位error-handling code区域。如图所示,Truzz由两个核心组件组成,字节分析和种子优先级。

Truzz对每个种子执行字节分析,以识别与验证检查(validation check)相关的字节。如果某些字节的突变导致执行路径显著缩短,则可以确定这是与验证相关的字节由于与验证相关的字节在输入的所有字节中占比是稀疏的,因此Truzz利用二分法来提高识别这些字节过程的效率。

注意,字节分析(Byte analysis)只对每个种子执行一次,这带来了很小的开销。在突变阶段,与验证相关的字节受到保护,以便能够生成的输入大多数都能够通过验证检查,从而进一步检查程序的功能代码,而不是被验证检查拒绝。然后在种子优先级中,Truzz在将每个种子添加到种子语料库时对它们进行排序。那些新发现边缘越多的种子被标记为更高的等级,拥有更高等级的种子其优先级越高。字节分析(Byte analysis)与种子优先排序(Seed priorities)相结合,旨在通过“开发与探索”的平衡来优化fuzzing中的能量调度。字节分析(Byte analysis)更多地关注于“开发(exploit)”,因为它在对功能代码的模糊上投入了更多精力。另一方面,种子优先级(Seed priorities)倾向于"探索(explore)",因为它倾向于那些具有更大潜力去发现更多未被发现代码区域的种子。

3.1 Byte Analysis 字节分析

在这个阶段,Truzz为输入字节建立一个概率分布,并根据该分布选择要突变的字节这个概率是根据路径转换中两个执行路径之间的差异获得的。本质上,如果一个字节更可能与验证检查处约束变量相关,则该字节将以较小的概率被选择去突变因此,在模糊处理(fuzzing)期间,与验证相关的字节受到保护,这样生成的输入更有可能探索程序的功能函数代码Truzz首先将一个字节与验证检查相关的可能性度量为一个标量,即适应度,并进一步为字节分配一个突变概率。

3.1.1 Fitness for Bytes 字节的适应度

为了衡量一个字节与验证检查相关的可能性,Truzz首先对每个字节进行突变,然后根据路径转换中的差异计算适应度。适应度计算公式:

f(i,m)代表了第i个字节到i+m字节的适应度,特别的,对于种子S,我们把种子s的第i到i+m字节进行突变得到了种子S' ,假设种子S的执行路径为P_{S},种子S'的执行路径为P_{S^{'}} (注意,在此上下文中,路径由一组边组成)。在方程中,P_{S}\capP_{S^{'}}表示种子S和种子S'执行路径的交集,并且 \left | * \right | 代表的是集合 * 中的边数,例如 | P_{S}\capP_{S^{'}} | 的含义就是执行路径 P_{S} 和P_{S^{'}} 的交集中的边的数量。

回想一下,如果一个输入执行了一个包含error-handling code的执行路径,那么它的长度(即边的数量)可能比其他通过验证检查的种子执行的路径短得多。在上面公式中 \frac{\left | P_{S} \right |-\left | P_{S^{'}} \right |}{\left | P_{S} \right |} 表示了种子S' 的执行路径相比于种子S的缩短了多少。此外,\frac{\left | P_{S} \cap P_{S^{'}} \right |}{\left | P_{S} \right |} 度量了执行路径 P_{S}P_{S^{'}} 的交集,即在路径转换中改变的边。我们之所以不单单考虑路径的长度,是因为当路径的长度之差和路径的交集都很小的时候,两条路径会相差很大,但长度相近。在这种(罕见的)情况下,被突变的字节更有可能与验证检查相关,适应度应该很大(而如果只考虑路径的长度,长度相差很小,得到的适合度很小,与预期不符)。因此上面的式子测量了路径转换中关于长度和单个边的变化的差异。

这里我们以下图为例来计算适应度f(i,m). 假设有一个有效的种子S通过验证检查 和 Ⅱ,并且运行到了functional code 处,探索了P_{S} = 20 + 60 + 40 = 120 条边. 如果我们突变该种子的第a个字节得到一个输入,种子S' , 且该输入通过了验证检查但是未通过验证检查,在运行完error-handling code 后执行退出,则该输入探索了P_{S^{'}} = 20 + 10 =30条边. 基于上面的方程式,字节a的适应度 f(a,0)为:

 f(a,0) = \frac{1}{2} \times \left [ \frac{120-30}{120} + \left ( 1 - \frac{20}{120} \right ) \right ] \approx 0.79

 如果我们对种子S的第b到b+c字节进行突变,得到的输入运行到了functional code 处,则该输入探索了 P = 20 + 60 + 20 =100 条边 .所以第b到b+c字节的适应度f(b,c):

f(b,c) = \frac{1}{2} \times \left [ \frac{120-100}{120} + \left ( 1 - \frac{80}{120} \right ) \right ] = 0.25

因此通过对比我们可以发现f(a,0)> f(b,c) , 所以Truzz就会推断相比与第b到b+c字节,第a个字节有更大的适应度,更有可能与验证检查中的约束变量相关.

3.1.2 Dichotomy for Byte Analysis 字节分析的二分法

逐字节计算适应度是耗时的,特别是当输入包含大量字节时. 幸运的是,程序中的验证检查(validation check)很少,也就是说,只有一小部分输入字节是与验证检查相关。大部分的输入字节流入功能代码区域。考虑到这一点,我们利用二分法来提高计算效率。如果当前段中的字节发生突变,导致适应度低于阈值或段的大小足够小,则将该段中的所有字节设置为相同的适应度;否则,Truzz继续二分法,直到识别出与验证相关的字节。

如算法Ⅰ所示,Truzz首先创建一个数组 F 来表示每个字节的适应度值,并将种子一分为二,分为左右半边,初始化一个集合D (第2-4行) ,初始化的 D = ( 0, size/2), (size/2 + 1, size) ) 。分别是种子左右半边的字节区间。

然后开始二分法,在循环中,如果D非空,Truzz从D中获得一个由开始索引和结束索引表示的区间(b,e),根据区间对种子S的(b,e)范围字节进行突变得到种子S' ,根据被突变后的输入S' 去修改位图 得到 B' ,根据位图 B' 得到对应的执行路径P_{S^{'}} (第5-9行)。

对于路径P_{S}P_{S^{'}},根据上面的方程式Ⅰ,Truzz计算当前间隔内字节(b,e)的适应度(第10行)。如果计算的fitness(适应度)小于预定义的阈值或当前间隔的长度小于预定义的最小长度则将间隔中的所有字节都赋值为fitness(第11-12行)。否则,Truzz将继续二分法,并将创建的新区间添加到集合D中(第14-15行)。考虑到在每轮二分法中,Truzz最多能够在一个变异输入中处理一半的字节,因此对字节的适应值分配是一种高效的方式。

Input: 种子S, 种子的执行路径P_S, 间隔的最小长度l, 适应度的阈值t

Variables: 一个代表代码覆盖率的位图B, 区间的起始索引b, 区间的结束索引e, 一组(b,e)间隔集合D, 适应度f

Output:F,一个保存了每个字节适应度的数组

-------------------------------------------------------------------------------

如在下图中,目标程序的种子是03AC。在字节分析阶段,Truzz对前两个字节03进行突变,并生成输入3AAC。因为突变前的03AC和突变后的3AAC执行执行路径没有改变,所以Truzz将前两个字节的适应度分配为0,这肯定小于阈值。相反,当突变另外两个字节AC时,执行路径明显缩短,适应度值为0.83,大于阈值,从而继续二分。然后,Truzz继续将字节AC分成两部分A和C。最后,Truzz推断最后一个字节C与验证检查相关。基于二分法,Truzz只需要进行大约2\times log_{2^{}}\left ( N \right )次突变,其中N是输入种子S的长度。请注意,较大的适应度阈值 和 较大的区间大小阈值将减少计算成本,但会导致粗粒度的结果(导致我们分析过程不够细致,比较粗糙)。

3.1.3 Probability for Bytes Mutation 字节变异的概率

为了保护与验证检查(validation check)相关的字节,根据适应度,Truzz去决定在生成新输入时是否可以突变字节。为每个字节分配突变概率的目的是避免以高概率去突变与验证相关的字节。Truzz根据下面公式分配突变概率:

P(i,m)=max\left ( 1-f(i,m),L_{P} \right )

其中 f(i,m) 是第i字节到 i+m 字节的适应度。字节的适应度越大,则与验证检查相关的概率越大,从而突变的可能性就越小,因此与验证相关的字节就可以免受突变的影响。请注意,我们还设置了概率下界L_{P} ,以确保模糊测试仍然会生成验证检查失败的输入,并覆盖error-handling code代码区域,而不是完全忽略它

3.2 Seed Prioritization 种子优先级

大多数CGFs是按顺序选择要突变的种子,即根据添加到种子库中的种子的顺序来选择种子。然而,种子与它们探索未发现代码区域的潜力不同。由于时间预算有限,该方案可能会错过发现更多代码覆盖率的机会。为了提高代码发现的效率,Truzz以启发式的方式确定每个种子发现更多代码覆盖率的潜力。直观地说,如果新发现的路径包含更多的新边,该路径很可能会探索一个含有更多未发现代码行的代码区域也就是说,一条包含更多新边的新路径,有更高的机会拥有更多未发现块的后代(如图1中的新路径ACFJLN拥有未发现块I、K、M、O、P)。因此,Truzz根据新发现的执行路径中新边的数量对每个种子进行排名。新边的数量越多,选择相应种子的优先级越高。注意,Truzz会在模糊测试过程中更新每个种子的排名。如果一颗种子在经过多次模糊处理后只发现了很少的一些新的边缘,Truzz就会降低该种子的排名。

如下图算法Ⅱ所示,Truzz首先执行一次演练,为初始种子分配排名(第1-8行)。位图 B_{overall} 跟踪所有历史边缘覆盖,并在开始(第1行)初始化设置为空。对于所有的初始种子,Truzz对每个种子执行测试,并将执行路径记录为位图B,通过比较位图B和B_{overall},如果发现了新的覆盖范围,则将它们添加到集合M中(第2-6行)【算法第4行含义:对于种子s的执行路径位图B,除去{\color{Red} B_{overall}\bigcap B}部分,就是代表了执行种子s所发现的新路径对应的位图,我们将发现的新边数量记为n,并记录到M中】。除了记录种子,集合M还包括种子s发现的新边的数量。然后,Truzz将当前位图B合并到整体位图B_{overall} 中(第7行)。对种子语料库中每个种子都执行完一次演练后,根据发现新边的数量对种子进行排序,根据种子的排名Truzz从种子语料库(第10行)中选择一个顶级种子在将能量(即突变的数量)分配给选定的种子(第12行)之后,Truzz根据字节分析阶段计算的适应度对种子进行突变,并生成一个新的输入(第13行)在使用新的输入i执行程序之后,fuzzing检查其对应的位图 B' 是否至少有一个以前从未覆盖过的新边(第14-15行)。如果有,Truzz计算输入i覆盖的新边的数量 n' ,并保留新的输入i作为新的种子,并将 (i,n' )的信息并入M (第16-17行)。然后,能量减少1,输入i的执行位图 B' 被合并到整体位图B_{overall}中(第18-19行)。直到为种子s分配的能量耗尽,根据突变种子s发现的新边缘的数量,记为n_{all},初始种子s的发现的新边数量n将被更新为n_{all},Truzz根据各个种子找到的新边的数量对种子语料库中的种子进行排序(第22行)。

Input: Seed Corpus 种子语料库 S, energy 突变的数量

Variables:新边的数量 n, 通过突变一颗种子发现的新边缘的数量 n_all, 一组(seed, # new_edges)元组 M,生成的输入 i,跟踪代码覆盖率的位图 B

3.3 Application 应用

该方法也适用于其他cgf。在模糊测试选择种子和选择字节时,这两个阶段都有改进。种子语料库根据种子排名进行排序,以便模糊测试人员从排序序列的顶部选择种子。大多数CGFs随机选择要修改的字节,或者选择与特定路径约束相关的字节。在其他CGFs选择一个字节后,我们的Truzz将根据字节的概率分布接受或拒绝选择。为了获得概率分布,Truzz在其他模糊器具有确定性阶段时,将我们的字节分析集成到确定性阶段。该阶段根据确定的变异策略去逐字节的变异种子。如果模糊器不执行确定性阶段,则Truzz会进行预变异以获得概率。

4. Evaluation 

在本节中,我们运行实验来验证Truzz的性能,并回答以下研究问题:

  1. RQ1:字节分析(Byte Analysis)在引导更多生成的输入流入功能代码区域方面有多有效?

  2. RQ2:在多大程度上可以避免突变与验证相关的字节,种子优先级如何提高模糊测试的效率?

  3. RQ3:被提议的框架可以(唯一地)发现多少bug ?

4.1 Experimental Setup 实验装置

为了评估我们的Truzz,我们用6个最先进的模糊器集成了Truzz,并将其性能与相应的普通模糊器进行了比较。具体来说,我们在8个目标程序上运行每个模糊器24小时,然后重复实验5次。我们所有的测量都是在Ubuntu 18.04、Intel(R) Xeon(R) Gold 6230R CPU和4个NVIDIA GeForce RTX 2080 Ti gpu上执行的。

Benchmark Fuzzers. 如前所述,我们选择6个模糊器来评估我们的方法。选择NEUZZ和GreyOne是因为它们推断字节约束关系来解析约束。我们选择AFL是因为它是一个通用的CGF。我们也选择AFL的一些扩展。AFLFast通过制定路径转换改善AFL。MOPT优化了AFL的突变算子调度。FuzzFactory将覆盖引导的模糊化推广到领域特定的测试目标。

  • NEUZZ 利用神经网络建模输入字节和分支行为之间的关系。为了将字节分析应用于NEUZZ, Truzz (NEUZZ)在采用NEUZZ提出的突变策略之前执行预突变。
  • GreyOneFTI 利用轻量级和可靠的模糊驱动污染推断(FTI)来推断输入字节和路径约束变量之间的关系。我们将模糊器命名为GreyOneFTI,因为GreyOne不发布它的代码,我们尽最大努力实现它的FTI核心。Truzz (GreyOneFTI)通过保护与外部条件相关的字节来改进GreyOneFTI。
  • AFL,the American Fuzzy Lop,是一种应用广泛的CGF,在实际应用中发现了许多bug。Truzz(AFL)在其确定阶段推断与验证相关的字节,并在其havoc阶段根据字节概率分布选择字节进行突变。
  • AFLFast 是在AFL的基础上开发的,通过制定路径转换来优化功率调度。因此,AFLFast更倾向于将更多的计算资源分配到执行频率较低的路径上。与Truzz (AFL)类似,Truzz (AFLFast)应用字节概率来选择字节。
  • MOPT 采用粒子群优化算法(PSO)实现变异算子调度。Truzz (MOPT)在所有类型的模糊模式中应用字节分析,以保护与验证检查相关的字节。我们将参数-L设置为60。MOPT提供两种起搏器模糊模式,我们选择MOPT- afl -tmp进行实验。
  • FuzzFactory 保存中间输入,如果它们在特定于领域的状态下取得进展。Truzz (FuzzFactory)的实现与Truzz (AFL)的实现类似。

Target Programs. 我们在8个不同的实际程序上评估了Truzz(如表1所示),这些程序是我们从NEUZZ和MOPT的评估中采用的。

nm列出了目标文件中的符号。Objdump显示目标文件的信息。readelf显示关于ELF格式目标文件的信息。Size列出了每个二进制文件的节大小。从目标文件中剥离丢弃所有符号。djpeg是一个广泛用于处理JPEG图像文件的工具。tiff2bw将彩色TIFF图像转换为灰度图像。tiffsplit将多图像TIFF分割为单图像TIFF文件。

Initial Seeds. 为了使比较公平,我们在每个目标应用程序上运行每个模糊器,使用从目标程序的测试套件和公共种子语料库(NEUZZ和MOPT)收集的相同初始种子。NEUZZ不同于其他模糊器,因为它需要初始数据集。根据NEUZZ中描述的设置,我们跑了一个小时的AFL来收集最初的训练集(X,Y),其中X是输入字节的集合,Y是相应的边覆盖位图。8个初始训练集的平均规模为1005个。

4.2 RQ1: Effectiveness of Byte Analysis 字节分析的有效性

正如方法中所描述的,Truzz使用字节分析来识别与验证检查相关的字节。然后,我们将比其他字节更少地去更改与验证相关的字节。因此,Truzz严重依赖于字节分析的准确性。因此,本小节将分析Truzz的字节分析的准确性。我们手动分析了实验中使用的五个程序,以标记这些程序中的所有错误处理路径(error-handling path),包括nm、objdump、readelf、size和strip。在分析方法上,我们采用FIFUZZ所使用的手动分析方法。我们扫描每个错误函数的定义,并检查它是否可以通过返回错误代码或空指针触发错误。每次生成并执行变异的输入时,我们都要确定输入是否符合错误处理路径,并在模糊测试中统计错误处理路径的数量。如果一个输入通过了所有的验证检查,并且没有触发任何错误处理代码,那么它就被认为是“有效的”。

为了避免种子优先级的影响,我们在本实验中只使用字节分析实现Truzz。最后,以NEUZZ和Truzz(NEUZZ)为例说明了字节分析的准确性。在NEUZZ的突变策略中,它随机选择500条边,并对每条边取两个种子的导数。然后将推导得到的梯度信息记录在梯度文件中。最后,NEUZZ将基于这个渐变文件进行突变。为了保证实验的公平性,随机选取1000个种子,两个模糊器使用相同的模型。NEUZZ和Truzz(NEUZZ)都基于相同的渐变文件对输入进行突变,而且它们具有相同的突变数量。我们计算有效输入在它们生成的突变输入中的比例,如表2所示。Truzz(NEUZZ)生成的有效样本平均比NEUZZ多16.14%。特别是对于nm和objdump, Truzz(NEUZZ)生成的有效样本比NEUZZ多20%以上。

  1. Truzz (NEUZZ): 使用NEUZZ作为模糊器集成Truzz框架的实现。

  2. Valid:表示输入通过所有验证检查。

  3. Invalid:表示输入流入错误处理代码。

回答RQ1。在相同的实验条件下,Truzz比vanilla模糊器能产生更多的有效样本。

4.3 RQ2: Code Coverage Discovery 代码覆盖率的发现

只有在探索相关的代码区域时,才会触发错误。因此,代码覆盖率是覆盖引导模糊器的一个重要指标。我们在8个程序上运行两个版本的模糊器,持续24小时(重复5次),并利用AFL的AFL -showmap工具计算代码覆盖率。然后我们将平均性能与边缘覆盖率进行比较。我们首先用两个版本的模糊器评估边缘覆盖的增长趋势。如图所示。除tiff2bw和tiffsplit外,在所有目标程序上,配备truz的模糊器都有稳定且较强的增长趋势。注意,对于NEUZZ,由于涉及1000个初始种子,因此起始点的边缘覆盖率要比只有一个初始种子的其他模糊器高得多。

 具体来说,配备truzz的模糊器实现了更好的新边缘覆盖率对于大多数程序来说都比普通版本更好,如表3所示。平均而言,配备truzz的模糊器发现的新边缘覆盖比普通模糊器多24.75%。例如,Truzz (AFL)在6个目标程序中发现的边缘比AFL多20%。但是,由于AFL和AFLFast没有推断出字节约束关系,在某些情况下代码覆盖率下降的原因还不清楚。为了揭示可能的根本原因,我们在??节提供了一个案例研究。在今后的工作中查明根本原因将是有趣的。

我们使用VarghaDelaney A Measurement 进一步评估了Truzz的性能,其中A^12∈[0,1]表示种群1优于种群2的随机优越性的度量。如果两个种群相等,那么A^12 = 0.5。A^12大于0.5表示总体1随机大于总体2,反之亦然。例如,当

  1. A^12≥0.71时,配备truzz的模糊器与普通模糊器的差异确定为大;

  2. A^_12≥0.64时,配备truzz的模糊器与普通模糊器的差异确定为相等;

  3. A^_12≥0.56时,配备truzz的模糊器与普通模糊器的差异确定为小;

具体来说,测试了普通模糊器和配备truzz的模糊器在24小时后从五轮实验中收集的新边数量和路径覆盖率。如表5所示,A^12≥0.71的结果占比为81.25%。特别是70.83%的结果达到了最高得分A^12 = 1.00。由此得出结论,普通模糊器与配备truzz的模糊器在新边和路径覆盖率方面的差异在统计学上是很大的。

此外,我们在Truzz中分别进行了字节分析和种子优先级排序的实验,如表4所示。字节分析配置的模糊器(NEUZZ和GreyOne)和种子优先级配置的模糊器(所有模糊器)分别比普通模糊器多发现16.13±10.25%和28.68±25.85%的新边缘。配置了字节分析的AFL、AFLFast、MOPT和FuzzFactory的性能并没有太大的提高。我们认为其原因是这些模糊器随机选择突变位置,导致突变验证相关字节的选择概率较低。

基于推理的模糊器,如NEUZZ和Greyone将首先突变感兴趣的字节(例如与边相关的字节)。但是根据它们的推理策略,与验证相关的字节通常被认为是“有趣的”。如果在突变阶段没有保护与验证相关的字节,则突变的输入将流入错误处理代码。因此,字节分析更适合于基于推理的模糊器,即推断输入字节和路径约束之间的关系的模糊器。

回答RQ2。Truzz可以显著提高覆盖率发现(平均增加24.75%的边缘覆盖率)。在48对程序模糊器中的44对中,配备truzz的模糊器比普通模糊器实现了更多的代码覆盖率。

4.4 RQ3:Bug Discovery 发现Bug能力

我们比较了truzz模糊器和普通模糊器在现实世界中的发现bug数量。

我们使用GDB和afl-collect来分析模糊器发现的bug数量。首先,我们使用afl-collect去除无效的崩溃样本,实现崩溃样本去冗余。其次,我们使用GDB手动分析每个bug的程序逻辑,删除具有相同根源的bug表6显示了在8个实际应用程序中发现的惟一bug(在5次运行中累积)的数量。

总体而言,普通模糊器和配备truzz的模糊器分别在4个应用中发现了10个和13个漏洞在剩下的4个程序中,所有的模糊测试器都不能在24小时内识别出漏洞。特别的是,只有装备truzz的fuzzers才认为strip是脆弱的。对于tiff2bw,它通过结合红、绿、蓝通道的百分比将RGB或调色板颜色TIFF图像转换为灰度图像。在tiff2bw中发现了9个缺陷。在readelf的情况下,当它显示来自任何ELF格式的对象文件的信息时,我们发现了一个错误。Strip显示一个列表,显示所有可用的架构和对象格式,我们识别出3个错误。对于三个truzz - missing bug(两个是由AFL和MOPT发现的size,一个是由MOPT发现的tiff2bw),我们发现MOPT (size和tiff2bw)和AFL在size上的执行速度分别下降了10%和13.55%,这可能会导致bug的丢失。此外,模糊的随机性也可能对结果有影响。我们对每个fuzzer重复试验5次,但AFL或MOPT只在1次试验中发现truzz遗漏的bug。

回答RQ3。在bug发现方面,装备truzz的模糊器优于他们的普通模糊器,识别了8个目标程序中的13个错误,其中6个是普通模糊器无法识别的。

5 Discussion

程序的复杂性。我们的Truzz推断输入字节和验证检查之间的关系,但是推断可能会错误地将非验证检查识别为验证检查。也就是说,Truzz可以保护与非验证检查相关的字节。然而,我们的Truzz仍然允许fuzzing以低概率检查“较短”路径,即属于“较短”路径的代码区域仍然被检查。一个可能的改进是在 ‘普通模糊器策略’ 和 ‘用于选择字节的字节分析策略’ 之间进行最佳切换。对Truzz的另一个威胁是,一些较长的路径可能具有较少的后代。因此,一些种子的优先级可能被错误地分配。如果种子的突变不能识别出更多的新边,Truzz通过降低种子的排名来缓解这个问题。

执行速度的开销Truzz会降低执行速度,因为它导致fuzzing更关注功能代码的执行,这通常比错误处理代码花费更多的计算时间。使用Truzz的GreyOne和NEUZZ的Byte Analysis执行速度分别下降了23.55±30.86%和2.50±1.94%。配置Truzz的AFL和AFLFast的种子优先排序执行速度分别下降了12.22%和22.52%。具体而言,AFLFast在tiff2bw和tiffsplit上的执行速度显著下降(分别为59.74%和38.47%)。这可能是为什么Truzz(AFLFast)发现的新报道较少的原因。我们相信这样的权衡是值得的,因为专注于探索功能代码将导致更多的新代码覆盖和bug发现。

Truzz的局限性Truzz在处理以可能具有各种常量的输入字节为目标的场景时存在局限性。例如,如果switch-case分支的默认分支是一个error-handling code,那么我们的Truzz就不能区分不同的case。通过在式(2)中设置更高的L_p可以缓解这种情况。另一种缓解这种情况的方法是引入更多的种子,这可能会覆盖开关情况中的不同分支。我们的Truzz在为校验和推断字节时也有限制。与校验和相关的数据的轻微更改将使校验和检查失败,而Truzz将避免突变校验和相关的数据。这妨碍了对校验和的不同数据值的研究。作为未来的工作,研究如何保护一个连续字节块以及如何主动解决约束是很有趣的。

6 Conclusion 结论

推理方法可能包括属于验证检查的额外字节。这将降低模糊处理的性能,并将过多的计算资源分配给错误处理代码。我们通过推断与验证相关的字节并保护字节不被频繁突变来缓解这个问题。这种推断是基于观察到路径转换中的路径差异暗示了程序的性质。因此,大多数生成的输入将检查功能代码,而不是error-handling code。这种解决方案增加了在功能代码中识别更多代码覆盖率和错误的可能性。我们基于我们的想法设计和实现了Truzz,它增加了生成有效输入的可能性,即只流入函数代码的输入。我们对8个开源程序的评估显示,配备truz的模糊器明显优于它们的普通版本。我们希望本文所开发的优化方法能够推动覆盖导向灰盒模糊域的发展。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值