赞
踩
方法:词法分析、数据流分析、控制流分析、模型检查、定理证明、符号执行、污点传播分析等。
不需要运行程序、分析效率高、资源消耗低
方法:模糊测试、动态污点分析、动态符号执行等
需要运行程序、准确率非常高、误报率很低
符号执行和污点分析两类技术都分别支持静态分析和动态分析
符号执行(Symbolic Execution)的基本思路是使用符号值替代具体值,模拟程序的执行。在模拟程序运行的过程中,符号执行引擎会收集程序中的语义信息,探索程序中的可达路径、分析程序中隐藏的错误。
动态符号执行结合了真实执行和传统符号执行技术的优点,在真实执行的过程中同时进行符号执行,可以在保证测试精度的前提下提升了执行效率。
符号执行三个关键点是变量符号化、程序执行模拟和约束求解。
变量符号化是指用一个符号值表示程序中的变量,所有与被符号化的变量相关的变量取值都会用符号值或符号值的表达式表示。
程序执行模拟最重要的是运算语句和分支语句的模拟:
约束求解主要负责路径可达性进行判定及测试输入生成的工作。对一条路径的约束表达式,可以采用约束求解器进行求解:
符号执行有代价小、效率高的优点,然而由于程序执行的可能路径随着程序规模的增大呈指数级增长,从而导致符号执行技术在分析输入和输出之间关系时,存在一个路径状态空间的路径爆炸问题。由于符号执行技术进行路径敏感的遍历式检测,当程序执行路径的数量超过约束求解工具的求解能力时,符号执行技术将难以分析。
符号执行已经广泛应用在软件测试、漏洞挖掘和软件破解等。
在软件测试中,符号执行可以获得程序执行路径的集合、路径的约束条件和输出的符号表达式,可以使用约束求解器求解出满足约束条件的各个路径的输入值,用于创建高覆盖率的测试用例。符号执行与模糊测试的结合也是当前流行的软件测试技术。
在漏洞挖掘中,通过符号执行技术可以获得漏洞监测点的变量符号表达式,结合路径约束条件、变量符号表达式和漏洞分析规则,可以通过约束求解的方法来求解是否存在满足或违反漏洞分析规则的值。
符号执行还可以用于搜索特定目标代码的到达路径,进而计算该路径的输入,用在面向特定任务(比如代码破解)的程序分析中。
int a[10];
scanf("%d", &i);
if (i > 0) {
if (i > 10)
i = i % 10;
a[i] = 1;
}
污点分析(Taint Analysis)通过标记程序中的数据(外部输入数据或者内部数据)为污点,跟踪程序处理污点数据的内部流程,进而帮助人们进行深入的程序分析和理解。
污点分析可以分为污点传播分析(静态分析)和动态污点分析(动态分析)。静态污点分析技术在检测时并不真正运行程序,而是通过模拟程序的执行过程来传播污点标记,而动态污点分析技术需要运行程序,同时实时传播并检测污点标记。
首先,确定污点源,即污点分析的目标来源。通常来讲,污点源表示了程序外部数据或者用户所关心的程序内部数据,是需要进行标记分析的输入数据。
然后,标记和分析污点。对污点源在内存中进行标记、计算涉及到污点的执行过程。
污点分析的核心是分析输入参数和执行路径之间的关系,它适用于由输入参数引发漏洞的检测,比如SQL注入漏洞等。
污点分析技术具有较高的分析准确率,然而针对大规模代码的分析,由于路径数量较多,因此其分析的性能会受到较大的影响。
词法分析通过对代码进行基于文本或字符标识的匹配分析对比,以查找符合特定特征和词法规则的危险函数、API或简单语句组合。
主要思想是将代码文本与归纳好的缺陷模式(比如边界条件检查)进行匹配,以此发现漏洞。
优点:算法简单,检测性能较高
缺点:只能进行表面的词法检测,不能进行语义方面的深层次分析,因此可以检测的安全缺陷和漏洞较少,会出现较高的漏报和误报,尤其对于高危漏洞无法进行有效检测。
核心思想:根据二进制可执行文件的格式特征,从二进制文件的头部、符号表以及调试信息中提取安全敏感信息(识别危险函数),来分析文件中是否存在安全缺陷。
对于findoverflow.exe
,是通过vc6代码生成的Release版本:
#include <stdio.h>
#include <string.h>
void makeoverflow(char *b){
char des[5];
strcpy(des,b);
}
void main(int argc,char *argv[]){
if(argc>1) {
if(strstr(argv[1],"overflow")!=0)
makeoverflow(argv[1]);
} else
printf("usage: findoverflow XXXXX\n");
}
通过该视图,可见,主要有一个main
函数,在该函数中可能有跳转,调用了sub_401000
函数、_strstr
函数和_printf
函数。此外,还定义了两个字符串常量,aUsageFindoverf
,在其上点右键->Text view,可以看到:
打开main函数汇编代码如下:
注意:通常在IDA的反汇编中,arg_x表示函数参数x的位置,var_8表示局部变量的位置;[]是内存寻址,[x+arg_x]通常表示的就是arg_x的地址值。
由release和debug生成的汇编代码是截然不同的,release版本非常简洁,执行效率优先,debug版本则严格按照语法结构,而且增加了很多方便调试的附加信息。
在主函数中,Printf函数无任何格式化参数存在,因此,敏感函数的可能在于sub_401000函数中,打开该函数的代码如图所示:
那么到底是否发生了溢出呢?
通过“sub esp 8”可以知道栈大小为8,因此,函数的局部变量var_8的大小最大就是8。这样的话,可以得到sub_401000函数的代码结构大致如下:
Sub_401000(arg_0)
{
Char var_8[8];
Strcpy(var_8, arg_0);
}
如果输入的字符串的长度大于8,就可能发生溢出了——需要验证。
为什么是8,而不是源代码里的5?
打开DOS对话框,运行示例程序,如果不给任何参数的话,会提示:usage: findoverflow XXXXX
如果输入参数,比如:findoverflow ssssssssss。却可以运行成功。
这是为什么呢?回顾逆向的反汇编代码,可以知道:
由于程序需要先判断是否包含子串overflow,因此,需要构造的输入需要满足这个条件。
输入:findoverflow overflow,此时出现缓冲区溢出的弹出窗口了。
基于此溢出漏洞,就可以进行漏洞的利用了。
Bugscam是一个IDA工具的idc脚本的轻量级的漏洞分析工具,通过检测栈溢出漏洞的strcpy
,sprintf
危险函数的位置,然后根据这些函数的参数,确定是否有缓冲区溢出漏洞。
下载网址:https://sourceforge.net/projects/bugscam/
run_analysis.idc
,运行即可,等待分析完毕,最后的分析报告结果保存在reports目录中的html文件中。#include <stdio.h>
#include <windows.h>
void vul(char*bu1){
char a[200];
lstrcpy(a,bu1);
printf("%s",a);
return; }
void main(){
char b[1024];
memset(b,'l',sizeof(b));
vul(b); }
其中,Severity是威胁等级,越高说明漏洞危险级别越高。本例的程序中,lstrcpyA函数存在溢出漏洞,地址401010处的代码可能将向目标203字节的区域写入1024字节的数据。
数据流分析是一种用来获取相关数据沿着程序执行路径流动的信息分析技术,分析对象是程序执行路径上的数据流动或可能的取值。
按照分析程序路径的深度,将数据流分析分为过程内分析和过程间分析。
过程内分析只针对程序中函数内的代码进行分析,又分为:
过程间分析则考虑函数之间的数据流,即需要跟踪分析目标数据在函数之间的传递过程。
数据流分析使用的程序代码模型主要包括程序代码的中间表示以及一些关键的数据结构,利用程序代码的中间表示可以对程序语句的指令语义进行分析。
抽象语法树。是程序抽象语法结构的树状表现形式,其每个内部节点代表一个运算符,该节点的子节点代表这个运算符的运算分量。通过描述控制转移语句的语法结构,抽象语法树在一定程度上也描述了程序的过程内代码的控制流结构。
举例,对于表达式“1+3*(4-1)+2”,其抽象语法树为:
三地址码。三地址码(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)
控制流图。控制流图(Control FLow Graph,CFG)通常是指用于描述程序过程内的控制流的有向图。控制流由节点和有向边组成。节点可以是单条语句或程序代码段。有向边表示节点之间存在潜在的控制流路径。
(a)有一个if-then-else语句;(b)有一个while循环;
© 有两个出口的自然环路;(d)有两个入口的循环。
调用图。调用图(Call Graph,CG)是描述程序中过程之间的调用和被调用关系的有向图,满足如下原则:对程序中的每个过程都有一个节点;对每个调用点都有一个节点;如果调用点c调用了过程p,就存在一条从c的节点到p的节点的边。
基于数据流的漏洞分析技术是通过分析软件代码中变量的取值变化和语句的执行情况,来分析数据处理逻辑和程序的控制流关系,从而分析软件代码的潜在安全缺陷。基于数据流的漏洞分析的一般流程为:
基于数据流的漏洞分析非常适合检查因控制流信息非法操作而导致的安全问题,如内存访问越界、常数传播等。由于对于逻辑复杂的软件代码,其数据流复杂,并呈现多样性的特点,因而检测的准确率较低,误报率较高。
int contrived(int *p, int *w, int x) { int *q; if (x) { kfree(w); // w free q = p; }else q=w; return *q; // p use after free } int contrived_caller(int *w, int x, int *p) { kfree(p); // p free [...] int r = contrived(p, w, x); [...] return *w; // w use after free }
在检测指针变量的错误使用时,我们关心的是变量的状态。左侧代码可能出现use-after-free
漏洞。
漏洞分析规则。下面是用于检测指针变量错误使用的检测规则:
v 被分配空间 ==> v.start
v.start: {kfree(v)} ==> v.free
v.free: {*v} ==> v.useAfterFree
v.free: {kfree(v)} ==> v.doubleFree
代码建模。这里我们采用路径敏感的数据流分析,控制流图如下
漏洞分析。分析过程从函数contrived_caller的入口点开始,可知调用函数contrived的时候p的状态为p.free。分析函数contrived中的两条路径:
在检测缓冲区溢出时,我们关心的是变量的取值,并在一些预定义的敏感操作所在的程序点上,对变量的取值进行检查。下面是一些记录变量的取值的规则。
char s[n]; // len(s) = n
strcpy(des, src); // len(des) > len(src)
strncpy(des, src, n); // len(des) > min(len(src), n)
s = "foo"; // len(s) = 4
strcat(s, suffix); // len(s) = len(s) + len(suffix) - 1
fgets(s, n, ...); // len(s) > n
它是指依据特定的文件格式或者协议规范组合生成测试用例,该方法的关键点在于既要遵守被测程序的输入数据的规范要求,又要能变异出区别于正常的数据。
它是指在原有合法的测试用例基础上,通过变异策略生成新的测试用例。变异策略可以是随机变异策略、边界值变异策略、位变异策略等等,但前提条件是给定的初始测试用例是合法的输入。
由于所有可被利用的漏洞都是由于应用程序接受了用户输入的数据造成的,并且在处理输入数据时没有首先过滤非法数据或者进行校验确认。对模糊测试来说首要的问题是确定可能的输入数据,畸形输入数据的枚举对模糊测试至关重要。
一旦确定了输入数据,接着就可以生成模糊测试用的畸形数据。根据目标程序及输入数据格式的不同,可相应选择不同的测试数据生成算法。
检测模糊测试数据的过程首先要启动目标程序,然后把生成的测试数据输入到应用程序中进行处理。
在模糊测试过程中,一个非常重要但却经常被忽视的步骤是对程序异常的监测。实时监测目标程序的运行,就能追踪到引发目标程序异常的源测试数据。
一旦监测到程序出现的异常,还需要进一步确定所发现的异常情况是否能被进一步利用。这个步骤不是模糊测试必需的步骤,只是检测这个异常对应的漏洞是否可以被利用。这个步骤一般由手工完成,需要分析人员具备深厚的漏洞挖掘和分析经验。
除了最后一步确定可利用性外,所有其它的四个阶段都是必须的。
尽管模糊测试对安全缺陷和漏洞的检测能力很强,但并不是说它对被测软件都能发现所有的错误,原因就是它测试样本的生成方式具有随机性。
智能模糊测试的前提,是对可执行代码进行输入数据、控制流、执行路径之间相关关系的分析。为此,首先对可执行代码进行反汇编得到汇编代码,在汇编代码的基础上才能进行上述分析。
从汇编代码中直接获取程序运行的内部信息,工作量较大,为此,需要将汇编代码转换成中间语言,由于中间语言易于理解,所以为可执行代码的分析提供了一种有效的手段。
这一步是智能模糊测试的关键,它通过符号执行和约束求解技术、污点传播分析、执行路径遍历等技术手段,检测出可能产生漏洞的程序执行路径集合和输入数据集合。例如,利用符号执行技术在符号执行过程中记录下输入数据的传播过程和传播后的表达形式,并通过约束求解得到在漏洞触发时执行的路径与原始输入数据之间的联系,从而得到触发执行路径异常的输入数据。
采用上述智能技术获得的输入数据集合进行安全检测,使后续的安全测试检测出安全缺陷和漏洞的机率大大增加。与传统的随机模糊测试技术相比,这些智能模糊测试技术的应用,由于了解了输入数据和执行路径之间的关系,因而生成的输入数据更有针对性,减少了大量无关测试数据的生成,提高了测试的效率。此外,在触发漏洞的同时,智能模糊测试技术包含了对漏洞成因的分析,极大减少了分析人员的工作量。
在于以尽可能小的代价找出程序中最有可能产生漏洞的执行路径集合,从而避免了盲目地对程序进行全路径覆盖测试,使得漏洞分析更有针对性。
智能模糊测试技术的提出,反映了软件安全性测试由模糊化测试向精确化测试转变的趋势。是典型的技术融合的漏洞挖掘测试方法。
用来实现Fuzzing测试的工具叫做Fuzzer。
成品的Fuzzer工具很多,许多是非常优秀的。Fuzzer根据测试类型可以分为很多类,常见的分类包括:文件型Fuzzer、网络型Fuzzer、接口型Fuzzer等。
下方工具可以生成多个文件测试用例,发现了Office2003的典型漏洞。
使用模糊测试工具在很多时候不能解决所有问题:
对于目标的可执行文件overflow.exe文件,是由如下程序生成的exe程序:
#include <stdio.h>
#include <string.h>
void overflow(char *b){
char des[50];
strcpy(des,b);
}
void main(int argc,char *argv[]){
if(argc>1) {
overflow(argv[1]);
} else
printf("usage: overflow XXXXX\n");
}
书写Fuzzer。在明确了输入的要求和暴力测试的循环条件后,可以写出如下的代码:
void main(int argc,char *argv[]){
char *testbuf=" "; char buf[1024];
memset(buf,0,1024);
if(argc>1) {
for(int i=20;i<50;i=i+2) {
testbuf=new char[i];
memset(testbuf,'c',i);
memcpy(buf,testbuf,i);
ShellExecute(NULL,"open",argv[1],buf,NULL,SW_NORMAL);
delete testbuf;}
}
else printf("Fuzzing X \n其中X为被测试目标程序所在路径");
}
通过一个for
循环(循环次数根据实际情况去设计)构造不同的字符串作为输入,通过“ShellExecute(NULL,"open",argv[1],buf,NULL,SW_NORMAL); “
来实现对目标程序的模糊测试。
上述Fuzzer的调用格式为:Fuzzing X 。X表示目标程序。
请完成上述实验并进行结果验证。
以一个白盒模糊测试为例。
新建文件夹demo,并创建实验的程序Test.c
,该代码编译后得到的程序如果被传入“deadbeef”则会终止,如果传入其他字符会原样输出。
使用afl的编译器编译,可以使模糊测试过程更加高效。
命令:afl-gcc -o test test.c
编译后会有插桩符号,使用下面的命令可以验证这一点。
命令:readelf -s ./test | grep afl
首先,创建两个文件夹in和out,分别存储模糊测试所需的输入和输出相关的内容。
命令:mkdir in out
然后,在输入文件夹中创建一个包含字符串“hello”的文件。
命令:echo hello> in/foo
foo就是我们的测试用例,里面包含初步字符串hello。AFL会通过这个语料进行变异,构造更多的测试用例。
运行如下命令,开始启动模糊测试(@@表示目标程序需要从文件读取输入):
命令:afl-fuzz -i in -o out -- ./test @@
观察fuzzing结果,如有crash,定位问题。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。