update 5.4

This commit is contained in:
firmianay 2018-04-02 22:52:43 +08:00
parent ca272b2f3c
commit 46deb0748b
6 changed files with 131 additions and 1 deletions

View File

@ -2,6 +2,7 @@
- [基本原理](#基本原理)
- [方法实现](#方法实现)
- [实例分析](#实例分析)
## 基本原理
@ -68,9 +69,138 @@ strcpy(x, y);
## 方法实现
#### 程序代码模型
数据流分析使用的程序代码模型主要包括程序代码的中间表示以及一些关键的数据结构,利用程序代码的中间表示可以对程序语句的指令语义进行分析。
#### 代码建模
**抽象语法树AST**是程序抽象语法结构的树状表现形式,其每个内部节点代表一个运算符,该节点的子节点代表这个运算符的运算分量。通过描述控制转移语句的语法结构,抽象语法树在一定程度上也描述了程序的过程内代码的控制流结构。
举个例子,辗转相除法的算法描述和抽象语法树如下:
```
while b ≠ 0
if a > b
a := a b
else
b := b a
return a
```
![](../pic/5.4_ast.png)
**三地址码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)
举个例子:
```
for (i = 0; i < 10; ++i) {
b[i] = i*i;
}
```
```
t1 := 0 ; initialize i
L1: if t1 >= 10 goto L2 ; conditional jump
t2 := t1 * t1 ; square of i
t3 := t1 * 4 ; word-align address
t4 := b + t3 ; address to store i*i
*t4 := t2 ; store through pointer
t1 := t1 + 1 ; increase i
goto L1 ; repeat loop
L2:
```
**静态单赋值形式SSA**是一种程序语句或者指令的表示形式,在这种表示形式中,所有的赋值都是针对具有不同名字的变量,也就是说,如果某个变量在不同的程序点被赋值,那么在这些程序点上,该变量在静态单赋值形式的表示中应该使用不同的名字。在使用下标的赋值表示中,变量的名字用于区分程序中的不同的变量,下标用于区分不同程序点上变量的赋值情况。另外,如果在一个程序中,同一个变量可能在两个不同的控制流路径中被赋值,并且在路径交汇后,该变量被使用,那么就需要一种被称为 Φ 函数的的表示规则将变量的赋值合并起来。
看下面这个例子:
![](../pic/5.4_ssa1.png)
![](../pic/5.4_ssa2.png)
通过 Φ 函数在最后一个区块的起始产生一个新的定义 y3这样程序就会根据具体的运行路径来选择是 y1 还是 y2而在最后一个区块中仅需要使用 y3即可得到正确的数值。
静态单赋值形式对于数据流分析的意义在于,可以简单而直接地发现变量的赋值和使用情况,以此分析数据的流向并发现程序不安全的行为。
**控制流图CFG**通常是指用于描述程序过程内的控制流的有向图。控制流由节点和有向边组成。典型的节点是基本块BB即程序语句的线性序列。有向边表示节点之间存在潜在的控制流路径通常都带有属性如if语句的true分支和false分支
看几个例子:
![](../pic/5.4_cfg.png)
- (a):一个 if-then-else 语句
- (b):一个 while 循环
- (c)有两个出口的自然环路natural loop例如一个有 if 语句的 while 循环,非结构化但可以简化
- (d):有两个入口的循环,例如 goto 到一个 while 或者 for 循环里,不可简化
**调用图CG**是描述程序中过程之间的调用和被调用关系的有向图。控制图是一个节点和边的集合,并满足如下原则:
- 对程序中的每个过程都有一个节点
- 对每个调用点都有一个节点
- 如果调用点 c 调用了过程 p就存在一条从 c 的节点到 p 的节点的边
#### 程序建模
程序建模包括代码解析和辅助分析两个部分。其中代码解析过程是指词法分析、语法分析、中间代码生成以及过程内的控制流分析等基础的分析过程。辅助分析主要包括控制流分析等为数据流分析提供支持的分析过程。
在代码解析过程中,词法分析读入源程序输出词素序列,每个词素对应一个词法单元,语法分析使用词法单元的第一个分量来创建抽象语法树,中间代码生成过程将抽象语法树转化为三地址码,而三地址码常表示为静态单赋值形式。编译器在源代码编译过程中得到的中间表示及其他的数据结构可以很好地为检测程序漏洞的数据流分析所用。因此,一些分析系统将相应的代码编译器实现的某些过程或者代码解析组件作为分析系统的前端,利用这些过程或者组件获得所需的数据结构,完成堆程序源代码的解析。然而,对于解释型语言或者脚本语言编写的程序,程序代码直接被解释器解释执行,没有相应的编译器实现对程序代码的基本解析。这时,我们需要在分析系统中设计完成代码解析的各个部分。而对于某些语言如 Java其编译后得到的中间程序被相应的虚拟机执行。这时分析系统可以分析这个中间程序即 .class 文件。
在辅助分析过程中,通过过程内的程序流分析可以构建过程的控制流图。如果分析系统选择使用抽象语法树作为中间表示,可以尝试在抽象语法树中增加控制流的边。如果选择三地址码作为中间表示,则需要分析其中的控制转移语句构建控制流图,具体过程如下:首先逐句分析程序指令或者指令,识别其中的控制转移语句,将一段代码划分为一个个基本块,然后根据控制转移语句指明的跳转到的代码的位置,将基本块连接起来。
对于调用图的构建,如果是直接过程调用的程序,每个调用的目标都可以静态确定,则调用图中的每个调用点恰好有一条边指向一个调用过程。但如果程序使用了过程参数或者函数指针,则通过静态分析只能得到近似的估计。对于面向对象程序设计语言来说,间接调用才是常用的方式。此时需要分析调用点调用所接收对象的类型来确定调用的是哪个方法。
#### 漏洞分析规则
程序漏洞通常和程序中变量的状态或者变量的取值相关。状态自动机可以描述和程序变量状态相关的漏洞分析规则,自动机的状态和变量相应的状态对应。和变量取值相关的检测规则通常包含和程序语句或者指令相关的对变量取值的记录规则以及在特定情况下变量取值需要满足的约束。
一个描述指针变量使用的有限状态自动机的例子:
![](../pic/5.4_state.png)
一个用于检测缓冲区溢出漏洞的分析变量取值的规则如下:
```c
char a[10]; // len(a) = 10;
[...]
strcpy(dest, src); // len(dest) > len(src);
```
#### 静态漏洞分析
数据流分析检测漏洞是利用分析规则按照一定的顺序分析代码中间表示的过程。
**过程内分析**。对于抽象语法树的分析,可以按照程序执行语句的过程从右向左、自底向上地进行分析。对于三地址码的分析,则可以直接识别其操作以及操作相关的变量。
通常,赋值语句、控制转移语句和过程调用语句是数据流分析最关心的三类语句。通过赋值语句,程序变量的状态或者取值常常会改变,在分析过程中,可以根据分析规则将所关心的变量的状态或取值记录下来。在分析的同时还需要考虑到控制转移语句中的路径条件。对过程调用语句的分析需要根据函数或方法的名字识别其语义或者根据调用图进行过程间的分析。通过将代码语义和漏洞分析规则联系起来,就能确定在分析中需要记录怎样的信息。
过程内分析的另一个关键是确定分析语句的顺序,即利用程序语句的存储位置或者根据控制流图依次确定待分析的每一条程序语句。在流不敏感分析中,常常使用线性扫描的方式依次分析每一条中间表示形式的语句;流敏感的分析或路径敏感的分析,则根据控制流图进行分析。对控制流图的遍历主要是深度优先和广度优先两种方式。在遍历并分析程序的过程中,需要在基本块上保存一定的部分分析结果,以为之后的分析所用。基本块的摘要通常包括前置条件和后置条件两部分,前置条件记录对基本块分析前已有的相关分析结果;后置条件是分析基本块后得到的结果。在分析基本块之前,首先使用该节点的前向节点的后置条件,计算该基本块的前置条件,如果该基本块已经被分析过,则将已有的前置条件和计算得到的前置条件相比较,如果相同,则不需要再对该块进行分析。
**过程间分析**。一个简单的思路是:如果在分析某段程序中遇到过程调用语句,就分析其调用过程的内部的代码,完成分析之后再回到原来的程序段继续分析。另一种思路是借鉴基本块的分析,给过程设置上摘要,也包含前置条件和后置条件。
#### 指向分析
指向分析points-to analysis用于回答变量指向哪些被分配空间的对象这样的问题。通过对待分析的程序使用指向分析可以大致确定变量指向哪些对象进而构建相对准确的调用图。指向分析常常需要虚拟一个存储空间用于记录被分配空间的对象。
例如下面这段 Java 代码:
```
obj = new Type(); // 在虚拟存储空间记录一个对象 o同时记录变量 obj 指向对象 o
obj1 = obj2; // 如果 obj2 指向对象 o2则记录 obj1 指向对象 o2
obj1.field = obj2; // 记录 obj1 的实例域 field 指向 obj2 指向的对象
obj2 = obj1.field; // 记录 obj2 指向对象 obj1 的实例域 field
```
对于 C 语言的指向分析就相对复杂一些,因为 C 语言可以使用指针变量。指针变量存储的是某个对象在存储空间中的地址,所以在指向分析中,通常还要加入地址这样的信息,主要有下面四种形式的赋值语句:
```
p = q;
p = &q;
p = *q;
*p = q;
```
通过指向分析,可以得到变量指向的被分配空间的对象集合。根据集合中对象的类型以及程序中类的层次结构,可以大致确定某个调用点调用的方法是哪些类中声明的方法。指向分析的结果中如果两个变量指向的对象的集合是相同的,则可以确定它们互为别名。
## 实例分析

BIN
pic/5.4_ast.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
pic/5.4_cfg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
pic/5.4_ssa1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

BIN
pic/5.4_ssa2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
pic/5.4_state.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB