第七章 漏洞挖掘基础
方法概述
漏洞挖掘方法分类
漏洞挖掘主要分为两种方法,一种是静态分析技术,一种是动态分析技术。
符号执行
符号执行:使用符号值替代具体值,模拟程序的执行。
三个关键点:变量符号化、程序执行模拟和约束求解
变量符号化是指用一个符号值表示程序中的变量,所有与被符号化的变量相关的变量取值都会用符号值或符号值的表达式表示。
程序执行模拟最重要的是运算语句和分支语句的模拟:
- 对于运算语句,由于符号执行使用符号值替代具体值,所以无法直接计算得到一个明确的结果,需要使用符号表达式的方式表示变量的值。
- 对于分支语句,每当遇到分支语句,原先的一条路径就会分裂成多条路径,符号执行会记录每条分支路径的约束条件。最终,通过采用合适的路径遍历方法,符号执行可以收集到所有执行路径的约束条件表达式。
约束求解主要负责路径可达性进行判定及测试输入生成的工作。对一条路径的约束表达式,可以采用约束求解器进行求解:
- 如有解,该路径是可达的,可以得到到达该路径的输入;
- 如无解,该路径是不可达的,也无法生成到达该路径的输入。
符号执行的优缺点:优点是代价小,效率高;但是我们的可能的路径会随着程序规模的增长而呈指数级增长,所以可能有时候会难以分析
符号执行的用处:广泛用于软件测试和漏洞挖掘中,通过创建高覆盖率的测试用例,通过约束求解的方法来求解是否存在满足漏洞分析的规则的值
举一个例子:
1 | int a[10]; |
这一段,我们就可以通过符号执行的方式去挖掘漏洞,我们可以发现两个条件判定语句中,存在着对10这个元素的漏判,所以我们输入10就可以成功引发漏洞了
污点分析
通过标记程序中的数据(外部输入数据或者内部数据)为污点,跟踪程序处理污点数据的内部流程,进而帮助人们进行深入的程序分析和理解
我们首先需要确定污点源,然后需要标记和分析污点。
这样我们就可以发现程序的内在逻辑与漏洞了
污点分析核心三要素:
- 污点源:是污点分析的目标来源(Source点),通常表示来自程序外部的不可信数据,包括硬盘文件内容、网络数据包等。
- 传播规则:是污点分析的计算依据,通常包括污点扩散规则和清除规则,其中普通赋值语句、计算语句可使用扩散规则,而常值赋值语句则需要利用清除规则进行计算。
- 污点检测:是污点分析的功能体现,其通常在程序执行过程中的敏感位置(Sink点)进行污点判定,而敏感位置主要包括程序跳转以及系统函数调用等。
优缺点:
适用于由输入参数引发漏洞的检测,比如SQL注入漏洞等。
污点分析技术具有较高的分析准确率,然而针对大规模代码的分析,由于路径数量较多,因此其分析的性能会受到较大的影响。
词法分析
通过对代码进行基于文本或字符标识的匹配分析对比,以查找符合特定特征和词法规则的危险函数、API或简单语句组合。就是说,将代码文本和归纳好的缺陷模式进行匹配,然后去发现漏洞。
优缺点:优点是算法较简单,性能高;缺点是只能进行表面的词法检测,在深度的检测中会出现大量漏报和误报,对于高危漏洞无法进行很好的检测
具体内容见PPT,有两个实验,需要看一下
数据流分析
是一种用来获取相关数据沿着程序执行路径流动的信息分析技术,分析对象是程序执行路径上的数据流动或可能的取值,分为过程内分析和过程间分析
过程内分析只针对程序中函数内的代码进行分析,又分为:
- 流不敏感分析(flow insensitive):按代码行号从上而下进行分析;
- 流敏感分析(flow sensitive):首先产生程序控制流图(Control Flow Graph,CFG),再按照CFG的拓扑排序正向或逆向分析;
- 路径敏感分析(path sensitive):不仅考虑到语句先后顺序,还会考虑语句可达性,即会沿实际可执行到路径进行分析。
过程间分析则考虑函数之间的数据流,即需要跟踪分析目标数据在函数之间的传递过程。
- 上下文不敏感分析:忽略调用位置和函数参数取值等函数调用的相关信息。
- 上下文敏感分析:对不同调用位置调用的同一函数加以区分。
程序代码模型:数据流分析使用的,主要包括程序代码中间的表示,以及一些关键的数据结构。我们引入抽象语法树,其描述了程序的过程内代码的控制流结构。
三地址码。三地址码(Three address code,TAC)是一种中间语言,由一组类似于汇编语言的指令组成,每个指令具有不多于三个的运算分量。每个运算分量都像是一个寄存器。
常见的三地址码:
- x = y op z :表示 y 和 z 经过 op 指示的计算将结果存入 x
- x = op y :表示 y 经过操作 op 的计算将结果存入 x
- x = y :表示赋值操作
- goto L :表示无条件跳转
- if x goto L :表示条件跳转
- x = y[i] :表示数组赋值操作
- x = &y 、 x = *y :表示对地址的操作
- param x1, param x2, call p:表示过程调用 p(x1, x2)
还有控制流图,通常是指用于描述程序过程内的控制流的有向图。控制流由节点和有向边组成。节点可以是单条语句或程序代码段。有向边表示节点之间存在潜在的控制流路径。
上面是一些常见的控制流路径
调用图。调用图(Call Graph,CG)是描述程序中过程之间的调用和被调用关系的有向图,满足如下原则:对程序中的每个过程都有一个节点;对每个调用点都有一个节点;如果调用点c调用了过程p,就存在一条从c的节点到p的节点的边。
基于数据流的漏洞分析:
- 首先,进行代码建模,将代码构造为抽象语法树或程序控制流图;
- 然后,追踪获取变量的变化信息,根据漏洞分析规则检测安全缺陷和漏洞。
对于逻辑复杂的程序代码,数据流复杂,所以准确率较低,误报率较高
举个例子:
1 | int contrived(int *p, int *w, int x) { |
我们根据数据流分析来检测指针变量
因此我们发现,只有1256这一条路径是安全的,12346这一条存在着指针的漏洞。
模糊测试
是一种自动化或半自动化的安全漏洞检测技术,通过向目标软件输入大量的畸形数据并监测目标系统的异常来发现潜在的软件漏洞。
模糊测试属于黑盒测试的一种,它是一种有效的动态漏洞分析技术,黑客和安全技术人员使用该项技术已经发现了大量的未公开漏洞。
它的缺点是畸形数据的生成具有随机性,而随机性造成代码覆盖不充分导致了测试数据覆盖率不高。
分类:
- 基于生成的模糊测试:它是指依据特定的文件格式或者协议规范组合生成测试用例,该方法的关键点在于既要遵守被测程序的输入数据的规范要求,又要能变异出区别于正常的数据
- 基于变异的模糊测试:它是指在原有合法的测试用例基础上,通过变异策略生成新的测试用例。变异策略可以是随机变异策略、边界值变异策略、位变异策略等等,但前提条件是给定的初始测试用例是合法的输入。
步骤:
- 确定测试对象和输入数据:对模糊测试来说首要的问题是确定可能的输入数据,畸形输入数据的枚举对模糊测试至关重要。
- 生成模糊测试数据:一旦确定了输入数据,接着就可以生成模糊测试用的畸形数据。根据目标程序及输入数据格式的不同,可相应选择不同的测试数据生成算法。
- 检测模糊测试数据:检测模糊测试数据的过程首先要启动目标程序,然后把生成的测试数据输入到应用程序中进行处理。
- 监测程序异常:实时监测目标程序的运行,就能追踪到引发目标程序异常的源测试数据。
- 确定可利用性:还需要进一步确定所发现的异常情况是否能被进一步利用。
该方法的测试具有一定的随机性,不是所有的错误都能被检测出来
我们为了解决模糊测试的随机性,我们往里面引入了基于符号执行、污点传播分析等可进行程序理解的方法,在实现程序理解的基础上,有针对性的设计测试数据的生成,从而实现了比传统的随机模糊测试更高的效率,这种结合了程序理解和模糊测试的方法,称为智能模糊测试(smart Fuzzing)技术。
智能模糊测试的步骤:
- 反汇编:智能模糊测试的前提,是对可执行代码进行输入数据、控制流、执行路径之间相关关系的分析。为此,首先对可执行代码进行反汇编得到汇编代码,在汇编代码的基础上才能进行上述分析。
- 中间语言转换:需要将汇编代码转换成中间语言,由于中间语言易于理解,所以为可执行代码的分析提供了一种有效的手段。
- 采用智能技术分析输入数据和执行路径的关系:通过符号执行和约束求解技术、污点传播分析、执行路径遍历等技术手段,检测出可能产生漏洞的程序执行路径集合和输入数据集合。
- 利用分析获得的输入数据集合,对执行路径集合进行测试:采用上述智能技术获得的输入数据集合进行安全检测,使后续的安全测试检测出安全缺陷和漏洞的机率大大增加。
AFL模糊测试框架
AFL是一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。
AFL工作流程:
- 从源码编译程序时进行插桩,以记录代码覆盖率;
- 选择一些输入文件作为初始测试集加入输入队列;
- 将队列中的文件按策略进行“突变”;
- 如果经过变异文件更新了覆盖范围,则保留在队列中;
- 循环进行,期间触发了crash(异常结果)的文件会被记录下来。