论文阅读2:PATA: Fuzzing with Path Aware Taint Analysis 基于路径感知污点分析的模糊测试

清华和南大合作的一篇fuzzing结合污点分析的论文, 发在2022年S&P 2022上
论文链接 https://www.computer.org/csdl/proceedings-article/sp/2022/131600a154/1wKCe9rJFfi
github地址 https://github.com/PATA-FUZZ/PATA

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

前置知识:LHS 与 RHS:

        LHS 和 RHS 的含义是“赋值操作的左侧或右侧” ,并不一定意味着就是  “= 赋值操作符的左侧或右侧”。说白了就是变量出现在复制操作的左边是进行LHS查询,出现在右边就是进行RHS查询。

console.log(a)

这段代码中,a就是进行了RHS查询,因为我们并没有对a进行赋值操作,而是直接引用了a,我们需要查找并拿到a的值才能传递给console.log

如果a=2,这里对a的引用则是LHs引用。

function foo(a) {
    var b = a;
    return a + b;
}
var c = foo(2);

对于上述代码段,有三处LHS查询,有四处RHS查询:

  1. LHS: c=  、  a=2(这一个LHS其实是一个隐式查询) 、b=
  2. RHS:foo(2  、 = a  、 return a;   、 return b;

Abstract

模糊测试中的污点分析通过推断影响模糊测试的输入字节,来辅助模糊测试人员去求解复杂的约束条件。而在现实世界的程序中,在执行路径中经常会有循环,在这些循环中的约束可以被多次访问和记录。传统的污点分析技术难以区分同一约束的多次出现。本文提出了PATA,一个实现路径感知污点分析的模糊器,即对于循环当中,去基于执行路径信息来区分同一变量的多次出现。PATA使用以下步骤进行操作。首先,PATA识别约束中使用的变量(一个条件约束可能由多个约束变量构成),并构造代表变量序列(RVS),由所有代表约束变量及其值的出现情况组成。接下来,PATA对输入进行扰动,将其RVS与原始输入的RVS进行匹配,并寻找值的变化以识别RVS中每个条目的影响输入字节。最后,PATA通过改变相应的输入字节来求解给定路径上的约束。

Introduction

污点分析通过识别影响给定约束的输入字节来提高模糊器的变异质量。这使得模糊测试器可以专注于这些关键字节,而不是笼统地接触所有字节。因此,模糊测试器可以更高效地探索程序逻辑。

目前有两种主流的污点分析技术:即传播和推断。基于传播的污点分析使用不同的标签对输入的每个字节进行污点处理,然后在程序执行过程中使用传播规则对这些标签进行传播。基于推理的污点分析在程序执行过程中反复扰动输入字节,收集变量的值。如果一个变量的值改变了,那么在被干扰的字节和这个变量之间存在数据依赖关系。通过污点分析,最先进的模糊测试器可以显著提高性能并检测出许多错误。

污染超标和污染不足都会对分析的正确性造成严重影响,进而影响模糊测试的性能。最突出的原因之一是路径感知(pathunawareness):循环和多个函数调用点可能会导致给定的约束被访问多次,并受到单个执行路径中不同输入字节的影响(可以理解为每个循环中与约束变量有关的字节可能不同,如下代码片段,我们的约束变量a,在for循环的每一轮中都不同)

str_a = "ABCDEFG";
char a;
for(i=0; i < strlen(str_a); i++)
{
    a = str_a[i];
    if(a == 'F')
    {
        ...
    }
    ...
}

(1)基于传播的污点分析在没有路径感知的情况下存在过度污染的问题。它无法区分不同的情况,也无法确定何时清除冗余标签。因此当同一个约束被多次访问时,其标签继续累积,导致过度污染。在最坏的情况下,所有输入字节都被标记为一个约束的关键字节,这对突变没有任何帮助。(例如上面代码例子,如果按照寻常的污染分析,会认为str_a中的所有字节都与约束变量a有关,但实际上"A"只与第一个循环中的a有关,"B"只与第二个循环中的a有关...)(2)如果没有路径感知,基于推理的污点分析会受到污染不足的影响。首先,这种方法使用的字节级变异可能会改变执行路径,导致访问约束的次数不同,或者完全跳过它。它缺乏路径感知能力,可能会导致错误或不完整的结果。其次,即使路径在突变中保持不变,路径不感知推理技术也只能捕获每个约束的值一次;因此,影响其他情况的字节会在结果中丢失。

本文提出了一种基于路径感知的污点分析方法。我们的方法通过以下步骤实现:

首先,它将沿着 执行路径 表示程序状态的约束变量 收集到代表性变量序列(RVS)中;这里的挑战是在污点分析中合理地跟踪有代表性的变量。因为分析是基于变量出现的,最高保真度的方法是在执行过程中记录所有变量的状态。但是记录它们的成本太高,因此必须使用抽样。因此,在不影响分析精度的前提下,必须采用一种合理的策略来跟踪所有变量的一个子集,即代表变量(所有约束变量的不重复版?)

然后,它为每个变量的出现识别相应的关键字节。在推理过程中,模糊器对输入的每个字节进行扰动,并监视变量发生的值变化以识别关键字节。但是该路径可能会偏离原始路径。例如,约束变量可能完全消失,也可能仍然出现,但出现次数不同。这里的挑战是,当执行路径改变时,如何正确匹配变量的出现。因此,需要一种匹配算法来正确匹配变化路径之间的变量出现。

最后,利用路径感知的污点分析结果指导模糊器的变异过程。这里的挑战是有效地利用分析结果来探索原始路径上的其他分支。在执行过程中,路径感知污染推断的结果由关键字节和每个变量出现的值组成。如果结果被充分利用,模糊器的突变有效性可以提高,使其能够解决魔术字节检查和其他复杂的约束。因此,需要一种变异策略来充分利用这些结果。

为了应对这些挑战,本文提出PATA,一个实现上述过程的模糊测试器:

1. PATA通过搜索将约束范围缩小到对约束影响较大的变量,通过回溯增加对输入字节的敏感性,沿路径收集约束变量。

2. 然后,它从路径中提取给定变量的子序列,匹配变量并比较变量扰动前后的值,从而识别关键字节。

3. 最后,PATA通过使用面向路径的突变对关键字节进行突变,以有效地利用分析结果。

PATA尝试在给定的执行路径中探索每个未覆盖的分支。根据变量的值和特征,在关键字节上选择合适的突变方法来绕过约束。

Motivating Example

图1显示了一个简单PNG文件的前两个块:图像头块IHDR;以及图像数据块IDAT。清单1是从libpng中提取的。它使用循环来确定块的类型。具体来说,libpng首先获取块长度和块名(例如“IHDR”)。

然后,它将块名称与预定义值png_IDAT进行顺序比较,直到找到匹配的类型,并使用相应的逻辑处理它们。设v1和v2表示第7行和第13行上的变量。对于样例输入,如图2所示,其路径为“v1→v2→v1→v2”。注意,这两个约束变量都被访问了两次。由于无法区分变量的不同出现情况,基于传播的污点分析将会过度污染,而基于推理的污点分析将会不足污染。

图1:一个示例PNG文件。它显示了前两个必要的块:图像头块IHDR;以及图像数据块IDAT。

清单1:从libpng提取的代码片段。这个例子说明了路径感知在污染分析中的必要性。当for循环被多次执行时,约束变量v1和v2对不同迭代的输入字节有不同的影响。路径无关的污染分析方法不能感知此信息,从而使模糊器获得有关约束的不准确信息。

图2:当输入扰动后执行路径发生变化时,PATA利用匹配算法来确定扰动后哪个约束变量的出现与原路径中的约束变量的出现相匹配。配对的双用’  √ ‘ 标记。

Propagation-Based Path-Unaware Taint Analysis.基于传播的污染分析(无路径感知)。这种方法用标签污染输入字节,并在程序执行期间传播它们。路径不感知导致它过度污染约束的关键字节。具体来说,按照执行路径和传播规则,在处理第一个块时,0x0c-0x0f(“IHDR”)处的字节标签传播到v1。但是在解析第二个块时,0x25-0x28(“IDAT”)的字节标签也传播到v1。因为第一次出现的标签没有被清除,所以v1拥有所有两个块名称的标签。换句话说,0x0c-0x0f和0x25-0x28处的字节都将被视为其关键字节。但是,0x0c-0x0f处的字节只影响v1的第一次出现,0x25-0x28处的字节只影响第二次出现。在更常见的情况下,更多连续的字节被认为是关键字节。结果使得模糊器很难选择合适的字节进行突变。

Inference-Based Path-Unaware Taint Analysis.基于推理的污染分析(无路径感知)。传统的方法是通过改变每个字节并检查值是否改变来推断出污染信息。路径意识导致污染不足。关键问题是约束变量只记录一个值。推断样本输入关键字节的过程如下步骤所示:

  1. 记录值:假设我们记录每个变量出现的最新值,那么在执行样本输入后,左侧的v1和v2的值是“IDAT”和“IDAT”。

    (执行过程:① v1: chunk_name = IHDR;  v2: chunk_name = IHDR;

                          ② v1: chunk_name = IDAT ;  v2: chunk_name = IDAT;                                                                                                                                                                                               且在①中v2处,正确的chunk_name = IHDR使得满足条件,确保了在②中v1中不会报错chunk_error )
  2. 扰动字节并记录新值:当图1中0x0c处的字节从“I”更改为“J”时,根据清单1,路径将更改为“v1→v2→v1”。原因是修改的块名“JHDR”无效,在解析有效的块名“IDAT”后,触发第9行错误(“IDAT前缺少IHDR”)。在执行变异后的输入后,左边的v1和v2的值变成了“IDAT”和“JHDR”。

    (执行过程:① v1: chunk_name = JHDR; v2: chunk_name = JHDR;

                       ② v1: chunk_name = IDAT;

    由于①中v2处错误的chunk_name,使得v1条件内部的代码没有执行,从而导致在②中v1处,条件内部的执行不满足,就会触发错误chunk_error。)

  3. 比较值:因为v1的值没有变化,而v2的值发生了变化,所以在分析时,会认为0x0c处的字节对v1不重要,但对v2很重要。但是,这个字节对v1和v2都很重要。更具体地说,在路径“v1→v2→v1→v2”中,该字节对v1和v2的第一次出现都很重要,但对它们的第二次出现则不重要。在更常见的情况下,丢失关键字节会造成更严重的后果。更重要的是,区分同一变量在不同情况下的关键字节对于模糊测试者探索更多状态非常重要。

Basic Idea of PATA.PATA的基本思想。PATA执行路径感知污染分析,更准确地定位关键字节。

分析过程包括以下步骤:

  1. 收集执行路径的RVS(代表性变量序列)。PATA收集每个变量的出现情况,并将它们的值沿路径记录到RVS中。具体来说,对于执行路径[v1, v2, v1, v2],它们各自的左侧值(left-hand side values, LHS)分别是 'IHDR'、'IHDR'、'IDAT'和'IDAT'。

    【left-hand side values 左侧值是什么? LHS value, 可以理解为操作符左侧变量的值?】

    代表变量序列(Representative Variables Sequence, RVS),由所有代表约束变量及其值的出现情况组成。

  2. 扰动输入字节并记录这些输入下的rvs。如图2所示,当图1中0x0c处的字节从“I”更改为“J”时,新的出现序列为[v1, v2, v1],左侧值(LHS)为“JHDR”、“JHDR”和“IDAT”。

  3. 匹配两个rvs之间的变量出现情况。如图2所示,匹配变量的前三次出现,即v1、v2和v1。在原来的RVS中最后出现的v2不会出现在新的RVS中。

  4. 比较匹配结果之间的值。第一次出现的v1和v2都改变了,而第二次出现的v1没有改变。

因此,PATA认为0x0c处的字节对于v1和v2的第一次出现是至关重要的。这个字节不会影响第二次出现v1,因为值" IDAT "没有改变。继续这个过程,PATA可以得到序列(RVS)中 ”所有出现的 约束条件 每次出现时“ 的关键字节(即RVS中所有出现的约束变量,在每次出现的时候对应的关键字节有可能不同)。这些结果对于模糊测试是有效的,因为模糊测试器可以准确地改变关键字节,从而探索路径上任何相邻的它认为有趣的基本块。

图2:当输入扰动后执行路径发生变化时,PATA利用匹配算法来确定扰动后哪个约束变量的出现与原路径中的约束变量的出现相匹配。配对的双用’  √ ‘ 标记。

PATA Design

图3给出了PATA的总体设计,包括三个步骤:

1. Constraint Variable Collection     约束变量集合
2. Critical Byte Identification            关键字节识别
3. Path-Oriented Mutation               Path-Oriented突变

本节中的以下文本将介绍每个步骤的详细信息。

标图3:PATA的设计 
  1. 在步骤1中,PATA通过识别具有代表性的约束变量,插装程序,以收集路径并构造具有代表性的变量序列(RVS)来收集约束变量。每个变量的相关特征也被提取出来。

  2. 在步骤2中,PATA通过字节级突变和发生匹配来识别关键字节。在字节级变异中,它收集由不同运行中出现的变量组成的RVSs。PATA将扰动输入的RVSs与原始输入的RVSs进行匹配,以识别每个变量出现的相应关键字节。

  3. 在步骤3中,PATA执行面向路径的变异。在RVS的每一项上,利用每一次出现的关键字节,结合数值和细节特征,选择合适的变异方法来绕过约束。最后,将变异后的种子传递给常规的模糊测试,以探索程序状态并发现新的bug。

A.  Constraint Variable Collection  约束变量集合

路径感知的污点分析设计中的第一个挑战是合理地追踪具有代表性的变量。形式上,约束变量被定义为一个元组(V, P),其中V = {V1,…, Vn}是约束中使用的一组程序变量(可以理解为一个约束条件由若干个程序变量和运算符组成,例如: a>b),P是约束中使用的谓词(运算符?)。

例如,约束 a > 5 包含一个约束变量,其中V = {a, 5},且 P = ‘ > ’ (大于号)。为了解决这一挑战,如图3所示,PATA识别代表性约束变量,并将它们的每次出现的值记录到代表性变量序列(RVS)中,该序列由在执行路径中访问的所有有代表性的变量组成。为了收集代表性变量,PATA首先收集约束中使用的所有程序变量;对于找到的每个变量,PATA通过 在需要时 回溯到直接受输入字节影响的源变量 来提高输入突变的敏感性。除了识别之外,PATA还通过提取变量特征为输入突变提供更多信息。

1)Identify Representative Variables:

Identify Representative Variables识别代表性变量:约束变量由可以直接或间接影响约束的变量组成。为了实现路径感知,理想的策略是记录所有遇到的约束变量及其值的所有出现情况。但是把它们都记录下来是不现实的,也是没有必要的。为了缩小观察值变化的范围,PATA在程序中搜索所有的约束,以提取对它们有直接影响的变量。然而,为了通过突变和监视变量值来推断关键字节,变量的值应该对输入敏感。换句话说,当输入值发生突变时,变量的值也应该改变。

我们将这些变量表示为代表变量。需要特别注意 ‘逻辑操作 和 字节数组比较’ 形式的约束条件。

Logical Operations逻辑操作:约束可以采用逻辑操作的形式。换句话说,约束可以由几个 ‘基本的比较’ 的连接或分离组成。由于约束依赖于其他变量,所以该约束它的值很难改变。即使我们观察到变量的值发生了变化,我们也很难改变有影响的字节,因为这样的约束变量提供的特征很少。对于这些 由几个 ‘基本的比较’ 的连接或分离组成的 约束,我们需要回溯它们的来源来识别它们的因变量,并获得丰富的约束特征来辅助突变。(‘与’操作还好,比较容易改变逻辑运算的值,‘或’操作就很难改变)

(可以理解为:一个约束由好几个基本判断条件(逻辑操作)结合而成,可能涉及到了好几个变量,我们需要追溯找出构成该约束中的相关变量)

清单2:一个变量识别的例子。约束是一种逻辑操作,其值难以更改,且特征较少。

例如,清单2中的代码片段是从libpng中的png_read_info函数提取的。第2行的约束检查颜色是否为某种类型,并验证chunk PLTE是否缺失。这个约束是一个逻辑操作,这个逻辑操作的结果是一个布尔变量,只有两个可能的值。如果我们直接监控这个变量,就很难观察到值的变化,从而无法有效地精确到定位关键字节(也就是说我们对种子不同位置不断突变,反复输入,发现想要使得该约束的操作结果发生变化很难,因为可能涉及到了很多字节)解决方案是回溯其源变量。该变量有两个源变量,第一个是与颜色类型有关的变量,第二个与块解析器的状态有关。这两个变量都更直接地受到输入字节的影响。例如,当我们将图1中的示例文件作为输入时,IHDR块中的字节0x19决定了颜色类型。通过改变输入中的每个字节,我们可以很容易地找到造成相关变量值的变化的关键字节并定位相应的关键字节。如果源变量仍然是逻辑操作,我们需要递归地回溯它们的源。 

Byte Array Comparisons字节数组的比较程序经常使用函数来比较两个字节数组的内容。与逻辑运算类似,其比较结果难以改变。即使该值在输入扰动下发生变化,直接记录其结果也会丢失突变所必需的内容(因为逻辑操作的结果只有true和false,单单根据这个结果无法得到模糊测试辅助突变所需的信息)                                                                                                                      

为了克服这种结构,我们应该检查变量是否来自字节数组比较,并记录比较后的内容。为了同时支持逻辑操作和字节数组比较,采用算法1识别源约束变量。

算法1将程序的源代码作为输入,并输出一个唯一代表变量的向量。

1. 首先扫描目标代码src中的所有约束集合C;(行1)

2. 将C中的约束无重复地添加到V中。(行2-4)

3. 接下来,检查V中的每个记录v(因为我们记录来的是约束,有的约束并不是一个单单的约束变量,有可能由若干约束变量组成,或逻辑操作,或字节数组比较)若v不是一个简单的约束变量:

   a. 如果检查到v,它是一个逻辑操作,我们用它的所有操作数源(逻辑操作的操作对象?)去替换它。

   b. 如果约束v使用 ‘字节数组比较函数’ 的结果,我们将它替换为 字节数组比较变量 来记录它的参数。

   算法1不断持续这个过程,直到V不再发生变化为止(即所有的约束都完成了“追溯”,V中的所有都是单个最简的约束变量)

Algorithm 1

 2)Extract Features :                                                     

特征提取:在识别约束变量的同时,提取约束变量的详细特征。变量特征由操作数特征(操作数数据类型和长度)、模式特征(约束类别,例如:Cmp)和块特征(约束的依赖基本块)。这些特征为路径导向的突变提供了更多信息。

Operand features 操作数的特征

操作数特征描述了操作数数据类型及其位长。数据类型决定了编译器或解释器应该如何处理相应的变量。我们使用三种类型来涵盖实际程序中所有可能的情况,即整数类型、浮点类型和字节数组类型。这使得模糊器可以在突变种子时推断出精确的运行时值。相比之下,许多其他工作忽略了数据类型,总是将操作数解释为整数,这在种子变异期间引入了不准确性。除了数据类型之外,操作数位长是另一个重要的特性。位长减小了模糊器在求解一些复杂约束条件时的解空间。对于模糊器来说,确定关键字节与约束变量之间的关系(如直接复制)也很有用,即约束值与关键字节之间存在一一对应的映射关系。

Pattern features 模式的特征

模式特征描述了约束变量的类别和它的特定特征。约束变量具有下列三种模式之一,即cmp、switch和call。

1. cmp:这是最常见的模式,通常有两个操作数(lhs和rhs)比较。它的具体特性包括两个元素:谓词(操作符?)和是否有常量操作数谓词描述如何比较操作数。记录一个cmp是否为常量值,可以让模糊器高效地绕过魔法字节比较约束。如果其中有一个操作数是常量,我们总是把它设置为rhs(即放在比较运算符的右侧)。
2. switch:该模式将一个表达式与若干特定情况相匹配。如果匹配了其中一个案例,则控制流被转移到该案例的对应目的地(即来到相应的case语句中);否则,控制流将转移到默认目标(按照顺序从上往下一个一个比较case的值)。switch比较中的每个case总是一个常量,因此我们直接记录它的数据类型和所有case值作为它的特定特征
3. call:这种模式表示用来比较两个字符串或字节数组内容的程序调用函数(如C语言中的strcmp)。对于这个类别,我们记录实际的函数名称作为它的特定特征。

Block features 块特征

块特征记录约束变量所属的基本块信息。在给定块特征的情况下,我们可以找到约束的后继块,并根据其成功块的覆盖率确定其变异优先级。在进行变量识别的同时,提取块特征以保持对应关系。

B. Critical Byte Identification 关键字节识别

第二个挑战是当执行路径在输入扰动期间发生改变时,如何正确匹配变量的出现。为了应对这一挑战,PATA通过按字节级突变定位关键字节。如图3所示。在字节级突变中,PATA收集各运行期间由变量出现次数组成的RVSs。PATA将扰动输入的RVSs与原始输入的RVSs进行匹配,以识别每个变量出现时对应的关键字节。

关键字节表示在约束变量的给定出现中影响变量值的字节。PATA稍微改变输入的每个字节以获得一个新的RVS并监视变量值的变化。这些变化可能导致路径偏离原始路径、相同的约束变量具有不同的访问次数或被完全跳过。直接丢弃这些字节将导致精度损失。这导致了推断关键字节的困难之处。一个约束变量可能在rvs序列中出现多次。如果我们只记录约束变量一次出现的值,我们将丢失其他出现的值的变化。但是,如果我们监视一个约束变量的每次出现,那么当执行路径改变时,一个变量可能出现在两个rvs序列的不同位置。更复杂的是,单字节突变有时会导致变量出现的次数增加、减少或减少到零。约束变量不匹配导致关键字节的错误推断。

我们以第二节中从libpng中提取的示例来演示路径匹配中的此类问题。对于PNG文件中的每个块,代码片段将其名称与预定义值进行顺序比较,直到找到匹配的类型,并使用相应的逻辑处理它们。假设我们使用图1中的输入样本,将偏移量0x0c处的字节从I扰动到J。图2给出了扰动前后的执行路径。路径发生了变化是因为libpng不允许IDAT块出现在IHDR块之前(我们通过代码片段可以发现如果IDAT块在前,就会触发v1中的错误检查代码),修改后的输入在处理IDAT块时触发了相应的错误检查逻辑。尽管路径改变了,我们应该匹配变量的共同出现,例如v1在两个序列中的第一个出现。

图1:一个示例PNG文件。它显示了前两个必要的块:图像头块IHDR;以及图像数据块IDAT。
图2:当输入扰动后执行路径发生变化时,PATA利用匹配算法来确定扰动后哪个约束变量的出现与原路径中的约束变量的出现相匹配。配对的双用’  √ ‘ 标记。

本文提出算法2来解决这个问题。对于输入中的每个偏移量,我们使用各种扰动方法对其进行修改,包括位翻转、加1或减1,以及用一些有趣的值替换它。这样做的目的是对字节进行少量修改,以尽可能地维护执行路径,但同时也改变了依赖字节的约束变量的值。然而,有些变量可能是不稳定的,即它们可能对相同的输入在不同的运行中获得不同的值。算法会跳过它们,以避免过度污染。

通过监视变量值的变化,可以识别出相关的关键字节。然而路径可能在扰动下发生变化。为了在路径发生变化时有效地匹配变量,引入了一种称为Variable Occurrence Subsequence(变量出现子序列, vseq)的数据结构。它是一个map映射,键(key)是变量标识符,值(value)是路径中的出现顺序(注:该map不是算法2输出的hashmap)。该算法考虑输入的公共前缀中被匹配的出现次数,并比较它们的值。如果匹配的变量之间的值发生了变化,则会推断发生变化的字节是关键的。

我们用第二节中的例子来演示上述步骤。在用偏移量0x0c干扰字节之后,PATA将RVS中每个约束变量的出现次数提取到一个子序列中。对于v1,子序列左侧的原始值为[" IHDR ", " IDAT "],而新的子序列为[" JHDR ", " IDAT "]。按照图2所示的算法,v1的两个出现都在改变的路径之间匹配。通过比较匹配的值,我们推断0x0c处的字节对于第一次出现的v1是至关重要的。这个字节对于第二次出现并不重要,因为没有改变值" IDAT "。在此过程中,PATA可以得到RVS中所有访问过的约束变量的每次出现的关键字节。这些结果对于模糊测试非常有用,因为模糊测试器可以准确地改变关键字节,以探索执行路径上每个条件事件的其他分支。

Algorithm 2

Input:种子s;源种子执行路径path;不稳定的变量标识符列表:unstable

Output:Hashmap

算法理解:

我们给定一个初始种子s,且其执行路径为path;我们通过种子s的执行路径path获取其路径上面的代表性变量序列,即RVS(path),并通过getSeqForEachVar来获取vseq(每个变量的出现序列对应的出现值)

对种子s的每个位置offset分别使用每一种突变方式,得到种子s',与其执行路径path',以及对应的vseq'

对于初始种子s对应的path的vseq,检查每个变量v是否为不稳定变量(unstable),如果v不稳定,则略过该变量;如果v稳定,我们分别获取种子s突变前后,执行路径上该变量v的出现序列occurSeq和occurSeq',如果种子突变前后执行路径发生了变化,则变量v的出现次数也可能发生变化,所以我们取突变前后较小的那个变量v出现次数作为我们的min_len,并开始一一匹配的检查occurSeq[i] 与 occurSeq'[i] 是否相同(其中 0 <= i < min_len ),如果这些匹配中有一次不相同,就证明我们对种子s的offset这个位置的突变,会对变量v造成影响,我们就将该位置字节视为变量v在第i次出现的关键字节。(所以变量v第i次出现时所对应的关键字节个数也许有很多)

C. Path-Oriented Mutation 基于路径的突变

第三个挑战是有效利用分析结果,这涉及到路径感知突变。如图3所示,PATA采用面向路径的突变方法来利用分析结果。对于每个种子,我们可以得到由约束变量及其各自的值组成的RVS。在序列的每个条目上,我们的目标是改变输入中的某些字节,以探索未发现的程序状态。利用序列中约束变量每次出现时对应的关键字节,结合出现值和细节特征,PATA能够执行比随机模糊测试更精确的变异操作。

我们以第二节中的例子来说明突变如何利用路径意识发挥巨大作用。样本输入的出现序列为[v1, v2, v1‘, v2’],对应的块序列为[B4, B2, B5, B1, B3]。路径感知的污点分析发现了四个约束变量出现的关键字节,分别是0x0c-0x0f、0x0c-0x0f、0x25-0x28和0x25-0x28。沿着序列中出现的变量,面向路径的变异执行以下步骤:

  1. 对于v1,将0x0c-0x0f的值由“IHDR”改为“IDAT”,以探索新的路径[B5, B6];

  2. 对于v2,将0x0c-0x0f的值从“IHDR”更改为另一个值,如“AAAA”,以探索新的路径[B4, B3, B5, B6];

  3. 对于v1‘ ,它将0x25 - 0x28的值从“IDAT”更改为另一个值,如“AAAA”,以覆盖新的路径[B4, B2, B4, B3];

  4. 对于v2’,将0x25-0x28的值从“IDAT”更改为“IHDR”,以执行路径[B4, B2, B4, B2]。

这个过程依赖于路径感知的污点分析。因为不知道路径的污点分析将v1和v1‘视为相同的,所以它只会推断出0x25 - 0x28字节对于v1是关键的。通过突变它们,只能得到一条新路径:[B4, B2, B4, B3]。因此,我们可能会丢失在其他路径中涉及的bug。

 算法3给出了面向路径变异的整体过程。对于RVS上每出现一个约束变量,我们首先检查它是否有意义去求解。例如,如果该变量的所有后续基本块都已被覆盖,或者约束变量的值不稳定,我们认为它没有意义。我们的目标是解决事件的未探索分支。然后探讨约束变量是否与输入长度有关。如果是这样,我们将尝试增加或减少输入长度以满足约束。接下来,我们检查是否已成功定位到约束的关键字节。如果没有,我们跳过并继续处理下一个约束。使用给定约束变量的关键字节、值和特征,我们依次使用以下方法改变输入:直接复制探索、线性搜索和随机探索。变异的种子将被传递给一个传统的模糊测试器,用覆盖跟踪二进制执行,并检查它们是否触发了新的覆盖或表现出异常行为。如果任何突变的种子成功地覆盖了目标分支,那么我们将继续处理RVS中的下一个约束变量(?)。

 Input:初始种子s,种子s对应的执行路径path,存放变量对应的关键字节的hashmap:C,约束变量特征B,覆盖率跟踪程序P,值跟踪函数P’

 1)Length Exploration:长度探索:输入长度是模糊程序的一个重要参数。许多程序状态只有在输入长度满足某些预设条件时才会触发。然而许多传统的模糊测试器倾向于使用较短的输入来提高执行速度。此外,它们没有方法来识别输入长度和约束变量之间的关系。因此它们只能依靠随机方法来修改输入长度。PATA获取约束变量的详细特征,这允许模糊测试器探索与输入长度相关的约束。具体而言,与长度相关的约束变量具有较高的概率表现出模式cmp和switch。对于一个cmp约束变量,如果rhs(右侧)是一个常量(回调函数总是将常量值传递给rhs),我们检查lhs(左侧)的值是否等于输入长度。如果是这样,根据谓词(逻辑运算符)、当前长度和常量目标值,我们可以精确地追加或删除字节以达到预期长度。对于switch变量,我们检查它的运行时值是否等于长度。如果是,我们将其实例的每个值作为目标值,计算期望长度,并添加或删除字节。

2)Direct Copy Exploration:直接复制探索:约束变量和它的关键字节可能有许多复杂的关系,其中之一是“直接复制”,即约束变量直接就是输入字节的某一部分(即将输入字节某部分复制给变量)。Magic numbers和magic bytes是模糊测试的常见障碍,探索直接复制是满足它们的决定性因素。

Direct Copy Identification直接复制识别。识别过程包括以下步骤:

  1. 将操作数转换为字节序列(例如某变量len=strlen(a),我们可知变量len的值为字符串a的长度,假设字符串a长为5,则len=5,变量len的操作数即为5,而能对变量len产生影响的字节为a的全部字节)

  2. 将关键字节划分成几个连续的部分(关键字节,可以理解为能对当前变量产生影响的字节)

  3. 在这些部分中查找字节序列。第一步的主要问题是确定字节顺序,即多字节数值类型中字节的顺序。

直观地说,我们应该使用底层系统的字节顺序。然而,我们发现许多项目与系统字节顺序不同。因此,我们将操作数转换为小端字节序列和大端字节序列,以便接下来的处理。如果其中一个序列与输入字节匹配,则我们记录匹配的字节序。为了简单起见,我们还在大端序的前缀或小端序的后缀中填充额外的零字节。

字节的排列方式有两个通用规则:

  • 大端序(Big-Endian)将数据的低位字节存放在内存的高位地址,高位字节存放在低位地址。这种排列方式与数据用字节表示时的书写顺序一致,符合人类的阅读习惯。
  • 小端序(Little-Endian),将一个多位数的低位放在较小的地址处,高位放在较大的地址处,则称小端序。小端序与人类的阅读习惯相反,但更符合计算机读取内存的方式,因为CPU读取内存中的数据时,是从低地址向高地址方向进行读取的。

关键字节反映了某些输入字节对约束变量的影响。如果操作数与某些输入字节具有一对一的相关性,那么这些字节很可能从关键字节中复制到一个连续的段中。因此,在第二步中,我们根据连续性将相关的关键字节分成几个部分。最后一步,我们在每个部分中搜索小端序或大端序的操作数字节序列。如果找到一个这样的输入段,那么就确认了直接复制关系。如果它出现了几次,那么所有出现的情况都会被记录下来。

更具体地说,不同约束变量的识别过程存在一定的差异。对于cmp模式约束变量,它们通常有两个用于比较的操作数(lhs和rhs)和一个指定比较操作的谓词(比较操作符号)。约束特征表示约束是否为常量,如果是,回调函数将始终将常量放入rhs。在这种情况下,我们直接检查lhs。否则,我们应该同时检查lhs和rhs。对于switch模式约束,它们的运算对象运行时值控制 代码执行 转换到哪个分支。因为这些case值总是恒定的,我们只需要检查它的运算对象是否为直接复制。call调用模式约束有一些小的差异。它的运算对象是两个字节序列,因此我们跳过第1步。因为两个运算对象都可能是直接复制,所以需要对两个运算对象进行检查。

Expected Value Calculating:计算期望值:标识的直接复制意味着运算对象的来源。下一步是计算覆盖目标块的期望值。

对于cmp模式,约束特征表示谓词(操作符)和操作数数据类型。我们可以结合lhs和rhs的运行时值来计算期望值。主要有三种情况:

  1. RHS是一个常量,LHS是某些输入字节的直接复制。在本例中,我们应该基于谓词(操作符)和操作数数据类型设置lhs。以清单3中的代码为例,假设lhs和rhs的运行时值分别为13和15。由约束特征的谓词“小于”可知(操作符为 ‘ < ’),由于lhs小于rhs,所以条件被当前值满足。因此我们需要计算lhs值等于或大于rhs。只需根据数据类型将常量(rhs)值加1作为我们的lhs的期望值。因此lhs的目标值为15或16。

    清单3 LHS直接使用输入的前8个字节
  2. RHS不是一个常量,LHS和RHS都是输入的直接复制。在这种情况下,我们将rhs视为常量,保持rhs的值,只改变lhs,就像前面的例子一样。

  3. RHS不是常量,两个操作数中只有一个是输入值的直接复制。在这种情况下,我们将没有从输入中复制的操作数视为常量,并像第一种情况那样更改另一个操作数。

对于switch模式,约束特征记录每种情况(case)的值。每一个case都是一个常量,其谓词(操作符)总是“相等”( ‘==’ 符号),因此期望值就是每一个case的值。

对于call调用模式,约束特征表示 ‘被调用的缓冲区比较函数’ 的名称。我们将这些函数分为两类:

  1. 比较两个缓冲区的内容是否相同,如“bcmp”。(bcmp() 比较内存(字符串)的前n个字节是否相等)

  2. 检查一个缓冲区的内容是否包含另一个缓冲区,如“strstr”。(strstr 函数查找一个字符串在另一个字符串中第一次出现的位置)

【缓冲区指的是暂存常量的变量】

无论在哪个类别中,当两个缓冲区的内容不同时,我们可以使用一个 ‘没有从输入直接复制的缓冲区的内容‘ 作为期望值。并致力于改变输入,使另一个缓冲区内容与其匹配,进而能进入该分支。

Critical Byte Patching:关键字节补丁:在计算出期望值之后,我们对关键字节进行修补以达到该期望值。根据预先确定的字节顺序,将期望值转换为字节序列,并将输入中相应的字节替换为所需的值。有时字节序列的长度可能比直接复制的字节更长,但我们仍然尝试替换它们以实现容错。

3)Linear Search 线性搜索:除了直接复制关系之外,其他输入到变量的关系更复杂。其中,一个常见且可解的关系是关键字节与约束变量之间的单调关系(单调增或单调减?)。在这种变异方法中,我们将约束视为一个函数,其参数是关键字节向量,值是gap。gap度量当前状态与满足约束的目标状态之间的差异对于这些可看作为单调函数的约束,线性搜索是一种实用的求解方法。虽然有些函数不是全局单调的,但它们可能表现出局部单调的行为。在这种情况下,线性搜索也可以解决它们。即使不能直接求解,线性搜索也有助于找到一个更接近目标值的临时种子。基于临时种子,PATA更有可能覆盖目标。由于在call调用模式约束变量中的缓冲区比较通常难以看作是单调函数,因此仅对cmp和switch模式约束采用线性搜索。

Algorithm 4

Input:初始种子s,关键字节 cbytes,变量运行时值 v,约束变量特征 B,覆盖率跟踪程序 P,值跟踪程序 P'

算法理解:

行1:对于种子s执行路径中的一个代表变量Var,我们获取其当前运行时值v,我们根据约束变量特征B计算gap。

行2-4:对于该变量的运行时值v当前所对应的所有关键字节cbytes,我们通过对其中之一某个关键字节分别选用三种不同的变化操作(增加、减少、不变),对每个操作结果都计算一个新的gap值,如果该操作的结果使gap变小,则我们就确定这个操作就是对该字节的操作(若不止一种操作会使gap变小,那么我们选择能够使gap最小的那个操作作为对当前字节的操作)。通过上述步骤确定所有关键字节的操作,并记录每个字节操作后的gap与原gap的差值作为该字节的差异(difference)(表示我们对变量v的该字节进行该操作,会使变量v时值与约束的目标状态之间的差异gap减小)。【注:如果某关键字节的操作为静止standstill,那么代表不对该字节做出操作,从而该字节的差异也为0】

行5:我们按照上一步计算的每个字节的差异值(difference)对字节进行排序,差异值越大的字节越优先被选择。

行6--:我们按照上面的排序选择最优先的字节b,并对该字节进行操作op(该操作是上面我们对每个字节所确定的操作),如果遍历到的当前字节b的操作op为静止standstill,那么我们就可以跳出循环,结束对该变量的关键字节的“操作”(因为我们对关键字节的操作顺序是按照差异值difference排序的,如果当前字节的操作为standstill,则其差异值difference就是0,那么由有序性可知,后面的所有字节操作都是standstill,对应的差异值即为0,即不需要对这些字节做出操作)。

对种子s的字节b做出其确定的op操作后我们得到一个新的种子,我们通过executeGetValue得到新种子执行所对应的变量Var所对应的时值v’,并根据v‘与约束变量特征B计算得到新的gap’。我们重复这个过程,直到我们的gap值不再继续缩小(即突变的结果与目标状态达到了距离最小),或者在我们的这个操作过程中,某次突变得到的种子成功解决了这个约束,则不再继续任何操作,下一步就是通过executeCheckCoverage函数检查突变后的种子s执行能不能对覆盖率产生影响。

对字节b做完上述操作后,我们将继续使用种子的当前状态,然后选择下一个关键字节重复上面的操作去进一步的突变种子。

算法4说明了线性搜索的过程。我们将每个关键字节视为一个自变量,依次线性增加或减少字节的值以找到合适的答案。整个过程包括以下步骤:

  1. 计算gap。gap度量当前约束变量运行时值和将解决约束的目标状态之间的差异量。对于cmp模式,通过计算lhs和rhs之差的绝对值来计算间gap。对于switch模式,我们将其视为多个恒定的cmp模式。通过获得控制值和case值之间的差的绝对值来计算gap。约束变量特征指出运行时的数据类型,因此我们可以获得精确的类型感知的差距值gap。

  2. 确定对字节的操作ops。每个字节可以有三种量的变化操作:增加、减少或静止。下一个任务是确定每个字节的移动操作。首先我们在字节中增加或减去一个小值(例如1)并计算新的gap。其次,我们比较差距来确定操作。确定对该字节的操作的最基本的规则是我们选择可以使gap小于原gap的操作。如果它们之间的gap都不大,我们将操作设置为静止。有时gap可能无效,例如,由于较早的条件语句引起的路径更改,变量会消失。在比较无效gap时,我们总是认为它比别人大。我们把操作前后gap的变化称为差值(difference)。应用于静止操作的突变差(difference)总是0。

  3. 按差异(difference)对关键字节排序。差异值(difference)较大的字节将被最先去操作,因为对这种字节的干扰对模糊测试可能更有意义。

  4. 移动和搜索。对于排序后的向量中的每一个字节,我们根据其所需的操作移动它,获取新值,并计算新的gap,直到gap不再减少。如果任何一个突变的种子解决了约束,那么我们就结束了这个过程。如果某个字节的操作是“静止”,那么所有后续的字节也都被认为是“静止”,因为它们是有序的,允许我们短路到循环结束。当整个过程结束后约束仍未解决时,我们仍然执行并保存最终的已突变输入,以供进一步突变。

4)Random Exploration 随机探索:如果前面所有的方法都不满足约束条件,我们仍然尽可能随机地改变关键字节。突变从线性搜索的最终突变输入开始。首先根据连续性将关键字节分成若干段;对于每个部分,我们随机选择几个要改变的字节、要突变的操作和要突变的轮数。这些操作包括位翻转、有趣的值替换和算术操作。突变可能仍然不能通过约束,但它的副作用可能帮助PATA找到更多未发现的基本块。

Ⅳ Implementation 实现

PATA采用算法1对变量进行识别。具体来说,PATA通过搜索和回溯所有“icmp”或“switch”指令来扫描约束。此外,我们总结了9个字节数组比较函数,即" bcmp "、" memcmp "、" memmem "、" strncmp "、" strncasecmp "、" strcmp "、" strcasecmp "、" strstr "和" strcasestr "。对于这些函数,PATA回溯并记录它们的参数作为具有call模式的约束变量。同时提取约束的块特征。

算法2实现关键字节定位。对于一个新的输入种子,它用位翻转、有趣值替换和算术操作扰乱每个字节。

算法3实现了面向路径的突变。对于每个输入种子,它沿着其路径进行分析,并获得对求解有意义的约束变量。然后用上述方法对约束变量的关键字节进行突变。

V. EVALUATION 评估

我们首先在两个广泛使用的基准上评估PATA:谷歌的模糊测试套件,它包含一系列现实世界的程序和LAVA-M,它由四个程序组成,从插入合成错误的binutils中提取。然后我们使用PA TA来测试从GitHub获得的更流行的开源项目。PATA发现了40个之前未知的bug。此外,我们还评估了路径感知的有效性及其开销,以全面了解PATA的改进。

1.实验设置

对比试验。在我们的评估中,PATA与AFL、MOPT、TortoiseFuzz、VUzzer、Angora、REDQUEEN和GREYONE进行了比较。AFL是一个经典的基于突变的灰盒模糊器,并有许多人在它的基础之上进行改进。MOPT和TortiseFuzz是最近提出的两个基于AFL的作品。比较VUzzer、Angora、REDQUEEN和GREYONE是因为它们是结合了污损分析和模糊分析的最相关的作品。由于REDQUEEN是针对OS内核的,为了公平起见,我们使用afl++的REDQUEEN模式。由于GREYONE的源代码不可用,我们在PATA的框架上重新实现了它的污染推断、字节优先级和一致性引导的演化。

Initial input seeds.对于谷歌的fuzzer-test套件和LAVA-M,所有的fuzzer都使用从数据集中收集的相同种子。当没有可用的种子时,将使用空种子作为备用选项。对于真实世界的项目,我们从互联网上随机选择一个有效的输入作为种子。

Performance metrics.性能指标。我们使用三个指标来评估fuzzer,即执行的路径数量、覆盖的基本块数量和触发的bug。由于不同的模糊器可能有不同的模糊状态表示,为了进行公平的比较,我们收集并运行了它们在评估过程中生成的种子,以收集路径(由AFL算法识别)和基本块(由LLVM工具识别)的数量,以统一这些表示。

另一个方面是触发的bug数量。区分唯一崩溃的方法在不同的模糊器之间也有所不同。为了进行公平和更好的比较,我们使用从这些崩溃中识别出的错误数量作为度量标准。对于LAVA-M,当错误被触发时,我们通过打印的ID识别唯一的错误。对于其他缺陷,当它与其他缺陷相比具有不同的调用堆栈时,我们将其定义为唯一的。我们分两步区分来自崩溃的bug。首先,我们收集触发的崩溃输入,通过回溯调用堆栈重新执行并过滤它们。为了提高准确性,我们进一步手动分析错误以消除重复条目。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值