也搞安全一段时间了,现在对过去看得东西做个总结。
先写一个关于栈溢出的东西。
首先,先了解一下什么是栈溢出。
程序在运行过程中的栈是由系统创建和维护的,同时也支持程序内的函数调用功能。在进行函数调用的时候,程序会将返回地址压入栈中,而执行完被调用函数代码之后,则会通过ret指令从栈中弹出压入的返回地址,然后将返回地址压入EIP指令寄存器中,从而程序继续执行。
但就是这种将控制程序流程的敏感数据与程序变量同时保存在同一段内存的冯诺依曼体系,必然会给传冲去溢出攻击带来本质上的可能性。
栈溢出发生在程序向位于栈中的内存地址写数据时,当写入的数据长度超过栈分配给传冲去的空间时,就会造成栈溢出。从栈溢出的原理出发,攻击者可以找到如下几种方式来利用这种类型的漏洞:
1. 覆盖缓冲区附近程序的变量,改变程序的执行流程和结果,从而达到攻击者的目的。
2. 覆盖战中保存的函数的返回地址,修改为攻击者指定的地址,当程序返回时,程序将跳转到攻击者指定的地址,理想情况下可以执行任意代码。
3. 覆盖某个函数指针或者程序异常处理结构,只要溢出后目标函数或者异常处理例程被执行,同样可以让程序流跳转到任意地址。
说了这么多,就是栈溢出的一些概念。
下面说说在C下的函数调用过程(其他语言大同小异),来理解一下栈溢出为什么会产生。参数入栈顺序如参考文献[1]。我们使用_stdcall模式,即从右至左顺序压入函数参数。
代码清单1:
#include <string.h>
void fun1(char * tmp)
{
char c[8];
strcpy(c,tmp);
}
int main()
{
_asm
{
nop;
nop;
nop;
nop;
nop;
}
char array0[]="fffffff";
char array1[]="ffffffff"; //f=0x66
char array2[]="ffffffffgggg";
char array3[]="ffffffffgggghhhh";
fun1(array0);
return 1;
}
如代码清单1所示,我们定义了4个字符数组array0-array3,定义了函数fun1,fun1的功能是是将传递进来的字符串拷贝到它的局部变量数组c中。
下面就对照两张图来理解一下函数的整个调用过程。
按照图上我们说一下上面程序fun1函数时怎么入栈的,当我们执行到
char array3[]="ffffffffgggghhhh";
后,下面的操作就该进行fun1的调用了
1.首先是fun1函数参数入栈
如图:
我们的参数array0保存在EDX中,所以是EDX入栈。
2.系统首先将当前主函数的指令序列所在的地址0x0040110F入栈,记录下来,以便在fun1函数执行结束后能后回到正确的main中的指令序列。
如图
3.当返回地址入栈后,下面该做的就是将主函数的栈底ebp入栈,以便从子函数返回后能够得到main函数的整个帧栈的大小。
帧栈:每个函数的每次调用,都有它自己独立的一个栈帧,这个栈帧中维持着所需要的各种信息。寄存器ebp指向当前的栈帧的底部(高地址),寄存器esp指向当前的栈帧的顶部(低地址)(栈是从高地址向低地址延伸的)。
如图:
4.当ebp也入栈了,就代表着main函数的状态已经保存好了,可以开始构造fun1函数的帧栈了,首先是fun1函数帧栈的栈底
如图
5.栈底构造好了,那么该构造栈顶了
6.现在fun1函数的帧栈已经确定,可以开始执行函数内容了
7.函数内容执行完后,开始出栈,首先,是先恢复父函数的帧栈,那么首先要将原来函数上移的ESP恢复到原来没有上移的位置,也就是父函数EBP所在的位置
如图
8.当ESP指向到父函数所在位置的时刻,就可以将父函数的栈底出栈了,此时,就确定了原来父函数的帧栈。
如图
9.父函数的帧栈恢复好了,则调用RET来返回原来的指令流所在位置。
如图:
上面就是一个函数执行的全过程,当然不只只是这么简单,一般情况下,函数在执行前还要保存必要的寄存器值,这里就不叙述了,需要了解的可以去网上查一查。然而函数在栈中这样的调用过程,导致了栈溢出漏洞。
我们把上面的代码稍微修改一下,改为:
char array0[]="fffffff";
char array1[]="ffffffff"; //f=0x66
char array2[]="ffffffffgggg";
char array3[]="ffffffffgggghhhh";
fun1(array1);
也就是把本来fun1函数的参数从array0换成array1,运行看一下,结果是:
为什么呢,我们来到栈空间看一下,栈空间里是这样的:
左图是一个正确的堆栈,右图是一个错误的堆栈,原因在图中已经标注出来,多输入的一位字符覆盖了main函数的栈底指针ebp的值,造成了堆栈的不平衡,所以程序最后会报错。
然而,这可以引起我们的思考,既然这样,如我我们覆盖了这个ebp值在下面的一个返回地址,那是不是程序就会跳转到我们指定的地方执行,答案是确定的。常规的栈溢出也是这样做的,覆盖函数返回地址,到达我们精心构造的shellcode,然后执行我们需要的代码,亦或者是通过覆盖其他值,已达到我们跳过某些判断的目的,因为大多数判断,比如
If(flag==1)
可能flag也只是栈中的一个值,而我们只需要覆盖了这个值,就可以控制程序的执行流程,达到我们的目的。
今天先写这么多,回头继续。
Refer:
1. http://www.cnblogs.com/forlina/archive/2011/09/12/2174164.html
2. http://baike.baidu.com/link?url=s3wzc88x5mh08IPswD_tYY2-IZ3FaHwJykY1zLov9E8xPE4mzYfqdCmHn1axEsdnsgiF_9c5uXNHrZOjiRo0Ia
3. 《0day安全:软件漏洞分析技术》
4. 《Metasploit渗透测试魔鬼训练营》
5. 《软件调试》
6. 《缓冲区溢出攻击—检测、剖析与预防》
这里排版不是很好,需要更好的可以看:
http://blog.codingdogs.com/?p=95
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法