2018-03-24 18:51:04 +07:00
|
|
|
|
# 5.5.1 动态污点分析
|
2018-05-12 10:10:30 +07:00
|
|
|
|
|
|
|
|
|
- [基本原理](#基本原理)
|
|
|
|
|
- [方法实现](#方法实现)
|
|
|
|
|
- [实例分析](#实例分析)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 基本原理
|
|
|
|
|
动态污点分析是在程序运行的基础上,对数据流或控制流进行监控,从而实现对数据在内存中的显式传播、数据误用等进行跟踪和检测。动态污点分析与静态污点分析的唯一区别在于静态污点分析技术在检测时并不真正运行程序,而是通过模拟程序的执行过程来传播污点标记,而动态污点分析技术需要运行程序,同时实时传播并检测污点标记。
|
|
|
|
|
|
|
|
|
|
动态污点分析技术可分为三个部分:
|
|
|
|
|
- 污点数据标记:程序攻击面是程序接受输入数据的接口集,一般由程序入口点和外部函数调用组成。在污点分析中,来自外部的输入数据会被标记为污点数据。根据输入数据来源的不同,可分为三类:网络输入、文件输入和输入设备输入。
|
|
|
|
|
- 污点动态跟踪:在污点数据标记的基础上,对进程进行指令粒度的动态跟踪分析,分析每一条指令的效果,直至覆盖整个程序的运行过程,跟踪数据流的传播。
|
|
|
|
|
- 动态污点跟踪通常基于以下三种机制
|
|
|
|
|
- 动态代码插桩:可以跟踪单个进程的污点数据流动,通过在被分析程序中插入分析代码,跟踪污点信息流在进程中的流动方向。
|
|
|
|
|
- 全系统模拟:利用全系统模拟技术,分析模拟系统中每条指令的污点信息扩散路径,可以跟踪污点数据在操作系统内的流动。
|
|
|
|
|
- 虚拟机监视器:通过在虚拟机监视器中增加分析污点信息流的功能,跟踪污点数据在整个客户机中各个虚拟机之间的流动。
|
|
|
|
|
- 污点动态跟踪通常需要影子内存(shadow memory)来映射实际内存的污染情况,从而记录内存区域和寄存器是否是被污染的。对每条语句进行分析的过程中,污点跟踪攻击根据影子内存判断是否存在污点信息的传播,从而对污点信息进行传播并将传播结果保存于影子内存中,进而追踪污点数据的流向。
|
|
|
|
|
- 一般情况下,数据移动类和算数类指令都将造成显示的信息流传播。为了跟踪污点数据的显示传播,需要在每个数据移动指令和算数指令执行前做监控,当指令的结果被其中一个操作数污染后,把结果数据对应的影子内存设置为一个指针,指向源污染点操作数指向的数据结构。
|
|
|
|
|
- 污点误用检查:在正确标记污点数据并对污点数据的传播进行实时跟踪后,就需要对攻击做出正确的检测即检测污点数据是否有非法使用的情况。
|
|
|
|
|
|
|
|
|
|
动态污点分析的优缺点:
|
|
|
|
|
- 优点:误报率较低,检测结果的可信度较高。
|
|
|
|
|
- 缺点:
|
|
|
|
|
- 漏报率较高:由于程序动态运行时的代码覆盖率决定的。
|
|
|
|
|
- 平台相关性较高:特定的动态污点分析工具只能够解决在特定平台上运行的程序。
|
|
|
|
|
- 资源消耗大:包括空间上和时间上。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 方法实现
|
|
|
|
|
#### 污点数据标记
|
|
|
|
|
污点数据通常主要是指软件系统所接受的外部输入数据,在计算机中,这些数据可能以内存临时数据的形式存储,也可能以文件的形式存储。当程序需要使用这些数据时,一般通过函数或系统调用来进行数据访问和处理,因此只需要对这些关键函数进行监控,即可得到程序读取或输出了什么污点信息。另外对于网络输入,也需要对网络操作函数进行监控。
|
|
|
|
|
|
|
|
|
|
识别出污点数据后,需要对污点进行标记。污点生命周期是指在该生命周期的时间范围内,污点被定义为有效。污点生命周期开始于污点创建时刻,生成污点标记,结束于污点删除时刻,清除污点标记。
|
|
|
|
|
- 污点创建
|
|
|
|
|
- 将来自于非可靠来源的数据分配给某寄存器或内存操作数时
|
|
|
|
|
- 将已经标记为污点的数据通过运算分配给某寄存器或内存操作数时
|
|
|
|
|
- 污点删除
|
|
|
|
|
- 将非污点数据指派给存放污点的寄存器或内存操作数时
|
|
|
|
|
- 将污点数据指派给存放污点的寄存器或内存地址时,此时会删除原污点,并创建新污点
|
|
|
|
|
- 一些会清除污点痕迹的算数运算或逻辑运算操作时
|
|
|
|
|
|
|
|
|
|
#### 污点动态跟踪
|
|
|
|
|
当污点数据从一个位置传递到另一个位置时,则认为产生了污点传播。污点传播规则:
|
|
|
|
|
|
|
|
|
|
| 指令类型 | 传播规则 | 举例说明 |
|
|
|
|
|
| --- | --- | --- |
|
|
|
|
|
| 拷贝或移动指令 | T(a)<-T(b) | mov a, b |
|
|
|
|
|
| 算数运算指令 | T(a)<-T(b) | add a, b |
|
|
|
|
|
| 堆栈操作指令 | T(esp)<-T(a) | push a |
|
|
|
|
|
| 拷贝或移动类函数调用指令 | T(dst)<-T(src) | call memcpy |
|
|
|
|
|
| 清零指令 | T(a)<-false | xor a, a |
|
|
|
|
|
|
|
|
|
|
注:T(x) 的取值分为 true 和 false 两种,取值为 true 时表示 x 为污点,否则 x 不是污点。
|
|
|
|
|
|
|
|
|
|
对于污点信息流,通过污点跟踪和函数监控,已经能够进行污点信息流流动方向的分析。但由于缺少对象级的信息,仅靠指令级的信息流动并不能完全给出要分析的软件的确切行为。因此,需要在函数监控的基础上进行视图重建,如获取文件对象和套接字对象的详细信息,以方便进一步的分析工作。
|
|
|
|
|
|
|
|
|
|
根据漏洞分析的实际需求,污点分析应包括两方面的信息:
|
|
|
|
|
- 污点的传播关系,对于任一污点能够获知其传播情况。
|
|
|
|
|
- 对污点数据进行处理的所有指令信息,包括指令地址、操作码、操作数以及在污点处理过程中这些指令执行的先后顺序等。
|
|
|
|
|
|
|
|
|
|
污点动态跟踪的实现通常使用:
|
|
|
|
|
- 影子内存:真实内存中污点数据的镜像,用于存放程序执行的当前时刻所有的有效污点。
|
|
|
|
|
- 污点传播树:用于表示污点的传播关系。
|
|
|
|
|
- 污点处理指令链:用于按时间顺序存储与污点数据处理相关的所有指令。
|
|
|
|
|
|
|
|
|
|
当遇到会引起污点传播的指令时,首先对指令中的每个操作数都通过污点快速映射查找影子内存中是否存在与之对应的影子污点从而确定其是否为污点数据,然后根据污点传播规则得到该指令引起的污点传播结果,并将传播产生的新污点添加到影子内存和污点传播树中,同时将失效污点对应的影子污点删除。同时由于一条指令是否涉及污点数据的处理,需要在污点分析过程中动态确定,因此需要在污点处理指令链中记录污点数据的指令信息。
|
|
|
|
|
|
|
|
|
|
#### 污点误用检查
|
|
|
|
|
污点敏感点,即 Sink 点,是污点数据有可能被误用的指令或系统调用点,主要分为:
|
|
|
|
|
- 跳转地址:检查污点数据是否用于跳转对象,如返回地址、函数指针、函数指针偏移等。具体操作是在每个跳转类指令(如call、ret、jmp等)执行前进行监控分析,保证跳转对象不是污点数据所在的内存地址。
|
|
|
|
|
- 格式化字符串:检查污点数据是否用作printf系列函数的格式化字符串参数。
|
|
|
|
|
- 系统调用参数:检查特殊系统调用的特殊参数是否为污点数据。
|
|
|
|
|
- 标志位:跟踪标志位是否被感染,及被感染的标志位是否用于改变程序控制流。
|
|
|
|
|
- 地址:检查数据移动类指令的地址是否被感染。
|
|
|
|
|
|
|
|
|
|
在进行污点误用检查时,通常需要根据一些漏洞模式来进行检查,首先需要明确常见漏洞在二进制代码上的表现形式,然后将其提炼成漏洞模式,以更有效地指导自动化的安全分析。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 实例分析
|
|
|
|
|
下面我们来看一个使用动态污点分析的方法检测缓冲区溢出漏洞的例子。
|
|
|
|
|
```c
|
|
|
|
|
void fun(char *str)
|
|
|
|
|
{
|
|
|
|
|
char temp[15];
|
|
|
|
|
printf("in strncpy, source: %s\n", str);
|
|
|
|
|
strncpy(temp, str, strlen(str)); // Sink 点
|
|
|
|
|
}
|
|
|
|
|
int main(int argc, char *argv[])
|
|
|
|
|
{
|
|
|
|
|
char source[30];
|
|
|
|
|
gets(source); // Source 点
|
|
|
|
|
if (strlen(source) < 30)
|
|
|
|
|
fun(source);
|
|
|
|
|
else
|
|
|
|
|
printf("too long string, %s\n", source);
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
漏洞很明显, 调用 strncpy 函数存在缓冲区溢出。
|
|
|
|
|
|
|
|
|
|
程序接受外部输入字符串的二进制代码如下:
|
|
|
|
|
```
|
|
|
|
|
0x08048609 <+51>: lea eax,[ebp-0x2a]
|
|
|
|
|
0x0804860c <+54>: push eax
|
|
|
|
|
0x0804860d <+55>: call 0x8048400 <gets@plt>
|
|
|
|
|
...
|
|
|
|
|
0x0804862c <+86>: lea eax,[ebp-0x2a]
|
|
|
|
|
0x0804862f <+89>: push eax
|
|
|
|
|
0x08048630 <+90>: call 0x8048566 <fun>
|
|
|
|
|
```
|
|
|
|
|
程序调用 strncpy 函数的二进制代码如下:
|
|
|
|
|
```
|
|
|
|
|
0x080485a1 <+59>: push DWORD PTR [ebp-0x2c]
|
|
|
|
|
0x080485a4 <+62>: call 0x8048420 <strlen@plt>
|
|
|
|
|
0x080485a9 <+67>: add esp,0x10
|
|
|
|
|
0x080485ac <+70>: sub esp,0x4
|
|
|
|
|
0x080485af <+73>: push eax
|
|
|
|
|
0x080485b0 <+74>: push DWORD PTR [ebp-0x2c]
|
|
|
|
|
0x080485b3 <+77>: lea eax,[ebp-0x1b]
|
|
|
|
|
0x080485b6 <+80>: push eax
|
|
|
|
|
0x080485b7 <+81>: call 0x8048440 <strncpy@plt>
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
首先,在扫描该程序的二进制代码时,能够扫描到 `call <gets@plt>`,该函数会读入外部输入,即程序的攻击面。确定了攻击面后,我们将分析污染源数据并进行标记,即将 `[ebp-0x2a]` 数组(即源程序中的source)标记为污点数据。程序继续执行,该污染标记会随着该值的传播而一直传递。在进入 `fun()` 函数时,该污染标记通过形参实参的映射传递到参数 `str` 上。然后运行到 Sink 点函数 `strncpy()`。该函数的第二个参数即 `str` 和 第三个参数 `strlen(str)` 都是污点数据。最后在执行 `strncpy()` 函数时,若设定了相应的漏洞规则(目标数组小于源数组),则漏洞规则将被触发,检测出缓冲区溢出漏洞。
|