摘要
主要目标:在不用符号执行的情况下求解路径约束。
引入技术:可扩展的字节级污点跟踪,上下文敏感的分支计数,基于梯度下降的搜索,输入长度探测。
测试:LAVA-M,file,jhead,nm,objdump,size。
1 介绍
使用符号执行:klee,mayhem[5,8],许多约束无法求解:符号执行综述[6]
避免符号执行及其他大开销的程序分析技术:AFL[1],覆盖范围反馈。开销低,却导致很多变异出来的输入是无效的。
一些fuzzer给AFL添加策略,来解决"magic bytes"问题vuzzer, steelix[25,19],但是无法解决其他路径约束。
angora尝试在不使用符号执行的情况下求解路径约束,引入了以下技术:
- 上下文敏感的分支覆盖率。AFL使用上下文敏感的分支覆盖率来简化程序状态。sec 3.2
- 可扩展的字节级污点跟踪。大多数路径约束依赖于输入的少量字节,通过跟踪输入字节是如何流入每个分支约束的,angora可以针对性地变异这些字节。sec 3.3
- 基于梯度下降搜索。在针对一个路径约束变异出一个满足路径约束的输入时,采用梯度下降的策略。sec 3.4
- 类型推断。推断连续字节的变量类型使得梯度下降更有效率。sec 3.5
- 输入长度探测。探测什么样的输入长度会影响路径约束,在适当的时候增加输入长度。sec 3.6
经测试,angora表现出众:
2 背景:AFL
模糊测试是一种自动发现bug的测试技术。AFL是一个最先进的基于变异的灰盒fuzzer。它应用轻量的编译时插桩和遗传算法来自动发现可能发现目标程序新状态的测试用例。作为一个基于覆盖率的fuzzer,AFL产生能够进入各种不同分支的输入来触发bug。
2.1 分支覆盖率(略)
2.2 变异策略(略)
3 设计
3.1 概述
核心技术:
- 判断哪些输入字节流入对应的判断语句中,并针对性变异。sec 3.3
- 梯度下降求解约束。sec 3.4
- 合并连续字节为变量,推断类型。sec 3.5
- 输入长度探测。sec 3.6
Figure 1 显示了模糊测试条件语句的步骤。Figure 2中的程序实际演示了这些操作步骤。
- 字节级污点跟踪:当fuzz到第2行的条件判断时,使用字节级污点跟踪,angora认定字节1024到1031流入了这个表达式,所以它仅仅变异这些字节。
- 基于梯度下降的搜索算法:angora将第2行的判断语句视作f(x),分别找到f(x)>0和f(x’)≤0的x和x‘。
- 类型推断:在梯度下降过程中,angora分别计算f对x各分量的偏导数,因此必须确定各分量及其类型。
- 输入长度探测:如果输入长度达不到1032字节,main程序不会执行到foo函数。因此,angora捕获输入长度对执行分支的影响,从而探查恰当长度的输入。
3.2 上下文敏感的分支计数
上下文不敏感的AFL无法把握流经相同分支上下文不同的输入之间差异。
angora引入上下文敏感,采用xor应对递归带来的过多分支问题,使fuzz能发现7倍于原来的新分支。(能敏感地找出分支间的差别,而非覆盖了更多范围。)
3.3 字节级污点跟踪
污点跟踪代价高昂,但是我们的直觉是,大多数程序运行不必执行污点跟踪。一旦我们基于一个输入执行了污点跟踪,发现了某些偏移字节流入了某个条件语句,之后在fuzz中针对性变异这几个字节时就可以不必进行污点跟踪(这使得angora与AFL之类的fuzz有着相近的吞吐量)。
angora为每个变量赋予独特的污染标签,标识它在输入中的字节偏移。保存污染标签的数据结构将会极大影响内存开销。一个简单的想法是,对每个污染标签用一个位向量来表示,位向量中的第i个位表示输入中的第i个字节。但是,因为这个位向量是随着输入长度线性增长的,对于长度很长的输入,这是不可行的。悲剧的是,在寻找bug时,长输入是不可避免的。
为了减小污染标签的大小,我们可以在一个表中存储这个位向量,并用表的索引作为污染标签。只要表中项的数量的对数(以2为底log)远小于最长位向量(这是常态),就可以极大地减小污染标签的大小。sun:传播从传向量变成传索引
然而,这个数据结构页引发了新的挑战。污染标签必须支持以下操作:
- INSERT(b):插入一个位向量b,并返回它的标签。
- FIND(t):返回一个标签t对应的位向量。
- UNION(tx,ty):返回一个污染标签,能够代表标签tx和ty合并后的结果。
FIND代价很低,但是UNION代价高昂。它经历以下步骤。首先,它找到对应两个污染标签的位向量,并求它们的并,这一步开销不高。接下来,它查找table中是否已存在这个位向量,如果不存在则添加。但是如何高效搜索呢?线性的搜索代价太高。代替方案是,我们可以建立一个位向量的hash set,但是如果有很多这种情况,并且每个位向量都很长,计算hash code的时间就会很长,需要的空间也很大。因为UNION在污点跟踪中是一个常用操作,它必须高效。值得注意的是,我们不能使用UNION-FIND(减)数据结构,因为位向量并不是不相交的。例如,两个不同的位向量可能在相同位置有1。sun:另建一个table专门保存合并后的结构?不统一(多层变量传污染)
我们提出一个新的数据结构来解决这个问题。对于每个位向量,该数据结构使用无符号整数为它分配一个惟一的标签。当插入一个新的位向量时,该数据结构为它分配下一个无符号整数。
该数据结构包含两个组件:
- 将位向量映射到污染标签的二叉树sun:字典树。每个位向量b由第|b|层上的树节点vb表示,|b|是位向量的长度。vb保存b的标签。为了从root抵达vb,按位检查b0,b1……,如果bi是0,前往左孩子,否则前往右孩子。每个节点都包含一个指向其父节点的反向指针,允许我们从vb开始检索位向量。
- 将污染标签映射到位向量的查找表。标签是这个表的索引,对应的入口点是代表这个标签的位向量的树节点。
这个数据结构中,每个叶子节点都表示位向量,除叶子节点以外的节点都不表示位向量。然而,树中的许多节点可能是不必要的。我们在插入节点时使用如下方案简化:
-
1)去除位向量中后面所有的0。
-
2)根据位向量逐位遍历二叉树:
- 如果该位是0,访问左子树。
- 否则访问右子树。
如果孩子不存在,则创建它。
-
3)在访问的最后一个节点处保存位向量的标签。
3.4 基于梯度下降的搜索算法
我们把fuzz中的变异问题视作一个搜索问题,并应用机器学习中的搜索算法。我们使用梯度下降来具体实施,其它的搜索算法也能用。
此处,我们将执行一个分支的条件语句视作一个黑盒函数f(x)的约束,x是流入这个约束的输入值f。()函数也捕获了程序执行过程中对输入的运算。对f(x)的约束有三种1)f(x)<0;2)f(x)<=0;3)f(x)==0。TABLE 2 中可以看到,我们可以将所有的比较转换成这三种约束。
为了满足这些约束,我们需要求f(x)的最小值,这里使用梯度下降来达到这个目的。
3.5 类型推断
shape inference:哪些连续字节被视作一个整体,作为程序中一个独立的值。
type inference:这个值是什么类型。
帮助节约梯度下降的求解时间。
3.6 输入长度探测
在污点分析过程中给类read函数的返回值贴上一个特有的标签,如果类read函数的返回值被用于条件判断,且不满足条件,angora会增加输入的长度,直到read函数可以获得它需要的所有字节。