diff --git a/SUMMARY.md b/SUMMARY.md index f34cd29..35b54f0 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -113,7 +113,7 @@ GitHub 地址:https://github.com/firmianay/CTF-All-In-One * [5.4 数据流分析](doc/5.4_dataflow_analysis.md) * [5.4.1 Soot](doc/5.4.1_soot.md) * [5.5 污点分析](doc/5.5_taint_analysis.md) - * [5.5.1 动态污点分析](doc/5.5.1_dyn_taint_analysis.md) + * [5.5.1 TaintCheck](doc/5.5.1_taintcheck.md) * [5.6 LLVM](doc/5.6_llvm.md) * [5.6.1 Clang](doc/5.6.1_clang.md) * [5.7 程序切片](doc/5.7_slicing.md) diff --git a/doc/4.3_gcc_arg.md b/doc/4.3_gcc_arg.md index d9c8266..00c564b 100644 --- a/doc/4.3_gcc_arg.md +++ b/doc/4.3_gcc_arg.md @@ -149,7 +149,7 @@ void main() { ``` 通过设置参数 `-lmcheck` 来链接 mcheck 函数: ``` -$ gcc -lmcheck mcheck.c +$ gcc -lmcheck t_mcheck.c $ ./a.out About to free About to free a second time @@ -179,6 +179,50 @@ Aborted (core dumped) ``` 具体参考 `man 3 mcheck` 和 `man 3 mallopt`。 +glibc 还提供了 `mtrace()` 和 `muntrace()` 函数分别在程序中打开和关闭对内存分配调用进行跟踪的功能。这些函数需要与环境变量 `MALLOC_TRACE` 配合使用,该变量定义了写入跟踪信息的文件名。在被调用时,`mtrace()` 会检查是否定义了该文件,又是否可以读写该文件。如果一切正常,那么会在文件里跟踪和记录所有对 malloc 系列函数的调用。由于生成的文件不易于理解,还提供了脚本(`mtrace`)用于分析文件,并生成易于理解的汇总报告。 + +将上面的例子修改一下: +```c +#include +#include +#include + +void main() { + char *p; + + mtrace(); + + calloc(16, 16); + fprintf(stderr, "calloc some chunks that will not be freed\n"); + + p = malloc(1000); + fprintf(stderr, "About to free\n"); + free(p); + fprintf(stderr, "About to free a second time\n"); + free(p); + fprintf(stderr, "Finish\n"); + + muntrace(); +} +``` +``` +$ gcc t_mtrace.c +$ export MALLOC_TRACE=/tmp/t +$ ./a.out +calloc some chunks that will not be freed +About to free +About to free a second time +Finish +$ mtrace /tmp/t +- 0x000055e427cde7b0 Free 5 was never alloc'd 0x55e425da287c + +Memory not freed: +----------------- + Address Size Caller +0x000055e427cde6a0 0x100 at 0x55e425da27f6 +``` +于是 double-free 和内存泄漏被检测出来了。 + ## 参考资料 - [GCC online documentation](https://gcc.gnu.org/onlinedocs/) diff --git a/doc/5.5.1_dyn_taint_analysis.md b/doc/5.5.1_dyn_taint_analysis.md deleted file mode 100644 index 0e0c918..0000000 --- a/doc/5.5.1_dyn_taint_analysis.md +++ /dev/null @@ -1,125 +0,0 @@ -# 5.5.1 动态污点分析 - -- [基本原理](#基本原理) -- [方法实现](#方法实现) -- [实例分析](#实例分析) - - -## 基本原理 -动态污点分析是在程序运行的基础上,对数据流或控制流进行监控,从而实现对数据在内存中的显式传播、数据误用等进行跟踪和检测。动态污点分析与静态污点分析的唯一区别在于静态污点分析技术在检测时并不真正运行程序,而是通过模拟程序的执行过程来传播污点标记,而动态污点分析技术需要运行程序,同时实时传播并检测污点标记。 - -动态污点分析技术可分为三个部分: -- 污点数据标记:程序攻击面是程序接受输入数据的接口集,一般由程序入口点和外部函数调用组成。在污点分析中,来自外部的输入数据会被标记为污点数据。根据输入数据来源的不同,可分为三类:网络输入、文件输入和输入设备输入。 -- 污点动态跟踪:在污点数据标记的基础上,对进程进行指令粒度的动态跟踪分析,分析每一条指令的效果,直至覆盖整个程序的运行过程,跟踪数据流的传播。 - - 动态污点跟踪通常基于以下三种机制 - - 动态代码插桩:可以跟踪单个进程的污点数据流动,通过在被分析程序中插入分析代码,跟踪污点信息流在进程中的流动方向。 - - 全系统模拟:利用全系统模拟技术,分析模拟系统中每条指令的污点信息扩散路径,可以跟踪污点数据在操作系统内的流动。 - - 虚拟机监视器:通过在虚拟机监视器中增加分析污点信息流的功能,跟踪污点数据在整个客户机中各个虚拟机之间的流动。 - - 污点动态跟踪通常需要影子内存(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 -... -0x0804862c <+86>: lea eax,[ebp-0x2a] -0x0804862f <+89>: push eax -0x08048630 <+90>: call 0x8048566 -``` -程序调用 strncpy 函数的二进制代码如下: -``` -0x080485a1 <+59>: push DWORD PTR [ebp-0x2c] -0x080485a4 <+62>: call 0x8048420 -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 -``` - -首先,在扫描该程序的二进制代码时,能够扫描到 `call `,该函数会读入外部输入,即程序的攻击面。确定了攻击面后,我们将分析污染源数据并进行标记,即将 `[ebp-0x2a]` 数组(即源程序中的source)标记为污点数据。程序继续执行,该污染标记会随着该值的传播而一直传递。在进入 `fun()` 函数时,该污染标记通过形参实参的映射传递到参数 `str` 上。然后运行到 Sink 点函数 `strncpy()`。该函数的第二个参数即 `str` 和 第三个参数 `strlen(str)` 都是污点数据。最后在执行 `strncpy()` 函数时,若设定了相应的漏洞规则(目标数组小于源数组),则漏洞规则将被触发,检测出缓冲区溢出漏洞。 diff --git a/doc/5.5.1_taintcheck.md b/doc/5.5.1_taintcheck.md new file mode 100644 index 0000000..46b04e5 --- /dev/null +++ b/doc/5.5.1_taintcheck.md @@ -0,0 +1 @@ +# 5.5.1 TaintCheck diff --git a/doc/5.5_taint_analysis.md b/doc/5.5_taint_analysis.md index 918107b..02d869b 100644 --- a/doc/5.5_taint_analysis.md +++ b/doc/5.5_taint_analysis.md @@ -1,11 +1,17 @@ # 5.5 污点分析 -- [基本原理](#基本原理) -- [方法实现](#方法实现) -- [实例分析](#实例分析) +- [污点分析](#污点分析) + - [基本原理](#基本原理) + - [方法实现](#方法实现) + - [实例分析](#实例分析) +- [动态污点分析](#动态污点分析) + - [基本原理](#基本原理) + - [方法实现](#方法实现) + - [实例分析](#实例分析) -## 基本原理 +## 污点分析 +### 基本原理 污点分析是一种跟踪并分析污点信息在程序中流动的技术。在漏洞分析中,使用污点分析技术将所感兴趣的数据(通常来自程序的外部输入)标记为污点数据,然后通过跟踪和污点数据相关的信息的流向,可以知道它们是否会影响某些关键的程序操作,进而挖掘程序漏洞。即将程序是否存在某种漏洞的问题转化为污点信息是否会被 Sink 点上的操作所使用的问题。 污点分析常常包括以下几个部分: @@ -56,7 +62,7 @@ if (rs.next()) - 基于依赖关系的污点分析。考虑隐式信息流,在分析过程中,根据程序中的语句或者指令之间的依赖关系,检查 Sink 点处敏感操作是否依赖于 Source 点处接收污点信息的操作。 -## 方法实现 +### 方法实现 静态污点分析系统首先对程序代码进行解析,获得程序代码的中间表示,然后在中间表示的基础上对程序代码进行控制流分析等辅助分析,以获得需要的控制流图、调用图等。在辅助分析的过程中,系统可以利用污点分析规则在中间表示上识别程序中的 Source 点和 Sink 点。最后检测系统根据污点分析规则,利用静态污点分析检查程序是否存在污点类型的漏洞。 #### 基于数据流的污点分析 @@ -86,7 +92,7 @@ if (rs.next()) 分析程序依赖关系的过程可以看做是构建程序依赖图的过程。程序依赖图是一个有向图。它的节点是程序语句,它的有向边表示程序语句之间的依赖关系。程序依赖图的有向边常常包括数据依赖边和控制依赖边。在构建有一定规模的程序的依赖图时,需要按需地构建程序依赖关系,并且优先考虑和污点信息相关的程序代码。 -## 实例分析 +### 实例分析 在使用污点分析方法检测程序漏洞时,污点数据相关的程序漏洞是主要关注对象,如 SQL 注入漏洞、命令注入漏洞和跨站脚本漏洞等。 下面是一个存在 SQL 注入漏洞 ASP 程序的例子: @@ -127,3 +133,124 @@ sql1 = a & c - 调用 Request.QueryString("foo") 的返回结果 sql2 是污染的。 - 函数 MySqlStuff 被调用,它的参数 sql1,sql2 都是污染的。分了分析函数的处理过程,根据第 6 行函数的声明,标记其参数 cmd1,cmd2 是污染的。 - 第 10 行是程序的 Sink 点,函数 conn.Execute 执行 SQL 操作,其参数 cmd2 是污染的,进而发现污染数据从 Source 点传播到 Sink 点。因此,认为程序存在 SQL 注入漏洞 + + +## 动态污点分析 +### 基本原理 +动态污点分析是在程序运行的基础上,对数据流或控制流进行监控,从而实现对数据在内存中的显式传播、数据误用等进行跟踪和检测。动态污点分析与静态污点分析的唯一区别在于静态污点分析技术在检测时并不真正运行程序,而是通过模拟程序的执行过程来传播污点标记,而动态污点分析技术需要运行程序,同时实时传播并检测污点标记。 + +动态污点分析技术可分为三个部分: +- 污点数据标记:程序攻击面是程序接受输入数据的接口集,一般由程序入口点和外部函数调用组成。在污点分析中,来自外部的输入数据会被标记为污点数据。根据输入数据来源的不同,可分为三类:网络输入、文件输入和输入设备输入。 +- 污点动态跟踪:在污点数据标记的基础上,对进程进行指令粒度的动态跟踪分析,分析每一条指令的效果,直至覆盖整个程序的运行过程,跟踪数据流的传播。 + - 动态污点跟踪通常基于以下三种机制 + - 动态代码插桩:可以跟踪单个进程的污点数据流动,通过在被分析程序中插入分析代码,跟踪污点信息流在进程中的流动方向。 + - 全系统模拟:利用全系统模拟技术,分析模拟系统中每条指令的污点信息扩散路径,可以跟踪污点数据在操作系统内的流动。 + - 虚拟机监视器:通过在虚拟机监视器中增加分析污点信息流的功能,跟踪污点数据在整个客户机中各个虚拟机之间的流动。 + - 污点动态跟踪通常需要影子内存(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 +... +0x0804862c <+86>: lea eax,[ebp-0x2a] +0x0804862f <+89>: push eax +0x08048630 <+90>: call 0x8048566 +``` +程序调用 strncpy 函数的二进制代码如下: +``` +0x080485a1 <+59>: push DWORD PTR [ebp-0x2c] +0x080485a4 <+62>: call 0x8048420 +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 +``` + +首先,在扫描该程序的二进制代码时,能够扫描到 `call `,该函数会读入外部输入,即程序的攻击面。确定了攻击面后,我们将分析污染源数据并进行标记,即将 `[ebp-0x2a]` 数组(即源程序中的source)标记为污点数据。程序继续执行,该污染标记会随着该值的传播而一直传递。在进入 `fun()` 函数时,该污染标记通过形参实参的映射传递到参数 `str` 上。然后运行到 Sink 点函数 `strncpy()`。该函数的第二个参数即 `str` 和 第三个参数 `strlen(str)` 都是污点数据。最后在执行 `strncpy()` 函数时,若设定了相应的漏洞规则(目标数组小于源数组),则漏洞规则将被触发,检测出缓冲区溢出漏洞。 diff --git a/doc/5.9_pattern_based_analysis.md b/doc/5.9_pattern_based_analysis.md index fe0abbf..53da200 100644 --- a/doc/5.9_pattern_based_analysis.md +++ b/doc/5.9_pattern_based_analysis.md @@ -2,11 +2,78 @@ - [基本原理](#基本原理) - [方法实现](#方法实现) -- [实例分析](#实例分析) ## 基本原理 +基于模式的漏洞分析能够比较精确地通过形式化描述证明软件系统的执行,并能够以自动机的形式化语言对软件程序进行形式化建模,从而合理地描述模式中各个模块的不同属性和属性之间的依赖关系,方便分析人员对软件系统的检测和分析。 + +在对软件程序进行模式分析之前,需要进行不同漏洞模式的构建,以待后续进行基于模式的匹配分析。根据不同漏洞模式触发原理和触发机制,分析各个软件模块的不同属性和依赖关系,从中抽象出漏洞触发的核心条件,并建立基于形式化语言或描述性语言的漏洞模式。漏洞模式建立后,下一步将针对二进制抽象进行基于漏洞模式的分析检测,首先将程序反汇编,并将反汇编代码转化为中间表示。针对二进制程序的中间表示进一步分析出其相关属性信息描述,并针对其属性信息进行模式匹配和检测分析。 + ## 方法实现 +#### 反汇编分析 +利用反汇编技术可以将二进制代码转化为可理解程度更高的汇编级代码。 -## 实例分析 +- 基本算法 + - 确定进行反汇编的代码区域,将指令和数据进行区分十分重要 + - 确定指令的起始地址后,下一步就是读取该地址所包含的值,并执行一次表查找,将二进制操作码的置与它的汇编语言助记符对应起来 + - 获取指令及其操作数后,需要对它的汇编语言等价形式进行转化,并将其在反汇编代码中输出 + - 输出一条指令后,继续反汇编下一条指令,并重复上面的步骤,直到完成所有指令的反汇编 +- 线性扫描策略:该算法假设一条指令结束的地方就是另一条指令开始的地方,因此确定起始地址最为困难,其采用的方法是,假设程序中标注为代码的节所包含的全部是机器语言指令。反汇编从代码段的第一个字节开始,以线性模式扫描整个代码段,逐条反汇编每条指令,直到完成整个代码段。 +- 基于控制流的递归扫描策略:为了避免把数据误认为指令,递归扫描算法重视控制流对反汇编过程的影响,控制流根据某一条指令是否被另一条指令引用来决定是否对其进行反汇编。 + +将程序反汇编后,可以得到许多程序分析的重要信息: +- 反汇编文本:包括汇编指令信息以及控制流信息等 +- 函数信息:包括函数入口地址、长度、参数、导入导出表等 +- 交叉引用:包括代码交叉引用和数据交叉引用 + +反汇编的不足: +- 区分数据和代码十分困难 +- 静态反汇编不能得到动态信息 +- 指令长度是可变的,导致难以确定指令的结束位置 + +#### 逆向中间表示 +- 逆向中间表示的设计原则: + - 使用精简指令集,能够极大地减少汇编语言的指令数目,而且每条指令都采用标准字长,能够简化分析过程 + - 使用足够多的寄存器数量,以保证中间语言能够满足不同处理器架构的需求 + - 使用尽量简单的寻址方式:立即数寻址、寄存器寻址、直接寻址、寄存器间接寻址 + - 使用统一的操作数格式,将隐式操作数转换为在中间语言指令中的显示操作数 + +目前常用的中间表示有:REIL、VEX、Vine 等。 + +#### 漏洞模式建模和检测 +缓冲区溢出类漏洞模式: +- 不安全函数调用模式。不安全函数主要包括一些没有判断输入长度的内存和字符串操作函数,如 strcpy,其原型是 `char *strcpy(char *dest, const char *src);`,为其建立漏洞模式首先需要获取目标地址缓冲区大小和源数据缓冲区大小,如果源缓冲区大于目的缓冲区,则存在溢出。 + 1. 根据定义的不安全函数库,搜索定位程序中调用不安全函数的位置 + 2. 针对不同的不安全函数,定位源缓冲区和目的缓冲区,并通过回溯程序,确定源缓冲区和目的缓冲区的大小和位置关系以及源缓冲区数据是否可控 + 3. 根据定义的基于不安全函数的缓冲区溢出模式,判断是否会发生缓冲区溢出漏洞 +- 循环写内存模式。如果一个程序的写缓冲区操作发生在循环中,且循环次数是用户可控的,就可能发生溢出,如: + ```c + taint_data = fread(); + buffer[256]; + taint_size = len(taint_data); + index = 0; + while (index < taint_size) { + buffer[index] = taint_data[index]; + index++; + } // 如果 taint_size > buffer_size,则会发生溢出 + ``` + 1. 定位程序中的循环写内存操作的位置 + 2. 通过回溯程序,做三方面的判断,即判断循环控制变量是否可控和程序对循环变量的验证是否完备、判断目的缓冲区是否位于关键的内存区域、判断源缓冲区的数据来源是否可控 + 3. 根据回溯程序的结果,给出检测结果、即循环控制变量可控且验证不完备且目的缓冲区位于关键内存区域,即存在缓冲区溢出漏洞 + +整数溢出类漏洞模式: +- 整型运算以及赋值操作的抽象表示。 + - `Operation(addr) = {(opcode, result, loperand, roperand)}` + - Operation(addr) 表示地址为 addr 的算术运算;result 表示运算结果的类型,opcode 表示运算名称,loperand 和 roperand 分别表示运算的左右操作数 + - `Assignment(addr) = {(destination, source-value)}` + - Assignment(addr) 表示地址为 addr 的赋值操作,destination 表示目的操作数类型,source-value 表示源操作数的数值 +- 整数溢出漏洞建模。可对整型运算和赋值操作进行约束限制,以检测其是否构成整数溢出漏洞。 + 1. 根据污点传播等方法,映射可控的输入数据在程序中的处理过程,在此基础上,定位与整数操作相关联的输入数据,并分析程序在对可控输入数据进行运算和赋值操作前,是否对其进行了完备验证 + 2. 根据定义的漏洞模式,分别判断是否匹配,匹配过程要根据实际的二进制程序对可控整数的使用,来判断可控整数是否会影响内存分配类的关键操作 + 3. 根据漏洞模式匹配情况和溢出造成的危险操作,得到最终结果 + +内存地址对象破坏性调用漏洞模式:如 use-after-free。 +1. 需要分析函数的功能,检测是否存在内存地址释放型函数以及内存地址调用型函数 +2. 检测函数调用的顺序是否正常 +3. 检测函数调用过程中,是否针对特定对象发生内存地址破坏性调用的异常情况,如果存在,则说明存在漏洞 diff --git a/doc/5_advanced.md b/doc/5_advanced.md index 67d6072..55c5538 100644 --- a/doc/5_advanced.md +++ b/doc/5_advanced.md @@ -16,7 +16,7 @@ * [5.4 数据流分析](5.4_dataflow_analysis.md) * [5.4.1 Soot](5.4.1_soot.md) * [5.5 污点分析](5.5_taint_analysis.md) - * [5.5.1 动态污点分析](5.5.1_dyn_taint_analysis.md) + * [5.5.1 TaintCheck](5.5.1_taintcheck.md) * [5.6 LLVM](5.6_llvm.md) * [5.6.1 Clang](5.6.1_clang.md) * [5.7 程序切片](5.7_slicing.md) diff --git a/src/others/4.3_gcc_arg/t_mcheck.c b/src/others/4.3_gcc_arg/t_mcheck.c new file mode 100644 index 0000000..1b29b2e --- /dev/null +++ b/src/others/4.3_gcc_arg/t_mcheck.c @@ -0,0 +1,12 @@ +#include +#include + +void main() { + char *p; + p = malloc(1000); + fprintf(stderr, "About to free\n"); + free(p); + fprintf(stderr, "About to free a second time\n"); + free(p); + fprintf(stderr, "Finish\n"); +} diff --git a/src/others/4.3_gcc_arg/t_mtrace.c b/src/others/4.3_gcc_arg/t_mtrace.c new file mode 100644 index 0000000..428756c --- /dev/null +++ b/src/others/4.3_gcc_arg/t_mtrace.c @@ -0,0 +1,21 @@ +#include +#include +#include + +void main() { + char *p; + + mtrace(); + + calloc(16, 16); + fprintf(stderr, "calloc some chunks that will not be freed\n"); + + p = malloc(1000); + fprintf(stderr, "About to free\n"); + free(p); + fprintf(stderr, "About to free a second time\n"); + free(p); + fprintf(stderr, "Finish\n"); + + muntrace(); +}