函数栈及缓冲区溢出攻击(一)

来源:互联网 发布:青少年违法犯罪数据 编辑:程序博客网 时间:2024/05/16 11:08

关于函数栈空间

栈是向下生长的,简单的说就是栈底地址大于栈顶地址。就x86体系的CPU而言,寄存器ebp指向的是栈底,而寄存器esp指向的是栈顶.

假设函数A调用函数B,我们称A函数为"调用者",B函数为“被调用者”则函数调用过程可以这么描述:

  (1)先将调用者(A)的堆栈的基址(ebp)入栈,以保存之前任务的信息。

  (2)然后将调用者(A)的栈顶指针(esp)的值赋给ebp,作为新的基址(即被调用者B的栈底)。

  (3)然后在这个基址(被调用者B的栈底)上开辟(一般用sub指令)相应的空间用作被调用者B的栈空间。

  (4) 函数B返回后,从当前栈帧的ebp即恢复为调用者A的栈顶(esp),然后调用者A再从恢复后的栈顶可弹出之前的ebp值。

在这里,我们写一个具体的例子,演示一下函数调用过程中,堆栈及寄存器ebp、esp的变化情况

void B(int a){    int b = a;}void A(){    int a[10] = {0};    B(a[1]);}void main(){    A();}

使用vc6.0编译得到如下汇编代码

函数A:

  0000055 push ebp  000018b ec mov ebp, esp  0000383 ec 68 sub esp, 104  0000653 push ebx  0000756 push esi  0000857 push edi  000098d 7d 98 lea edi, DWORD PTR [ebp-104]  0000cb9 1a 00 00 00 mov ecx, 26  00011b8 cc cc cc cc mov eax, -858993460  00016f3 ab rep stosd  00018c7 45 d8 00 0000 00 mov DWORD PTR _a$[ebp], 0  0001fb9 09 00 00 00 mov ecx, 9  0002433 c0 xor eax, eax  000268d 7d dc lea edi, DWORD PTR _a$[ebp+4]  00029f3 ab rep stosd  0002b8b 45 dc mov eax, DWORD PTR _a$[ebp+4]  0002e50 push eax  0002fe8 00 00 00 00 call ?B@@YAXH@Z  0003483 c4 04 add esp, 4  000375f pop edi  000385e pop esi  000395b pop ebx  0003a83 c4 68 add esp, 104  0003d3b ec cmp ebp, esp  0003fe8 00 00 00 00 call __chkesp  000448b e5 mov esp, ebp  000465d pop ebp  00047c3 ret 0

函数B:

  0000055 push ebp  000018b ec mov ebp, esp  0000383 ec 44 sub esp, 68  0000653 push ebx  0000756 push esi  0000857 push edi  000098d 7d bc lea edi, DWORD PTR [ebp-68]  0000cb9 11 00 00 00 mov ecx, 17  00011b8 cc cc cc cc mov eax, -858993460  00016f3 ab rep stosd  000188b 45 08 mov eax, DWORD PTR _a$[ebp]  0001b89 45 fc mov DWORD PTR _b$[ebp], eax  0001e5f pop edi  0001f5e pop esi  000205b pop ebx  000218b e5 mov esp, ebp  000235d pop ebp  00024c3 ret 0

由上面的汇编代码可以看出,函数A和函数B在调用之前都做了如下处理:

  0000055 push ebp  000018b ec mov ebp, esp
这两句代码分别对应函数调用过程的(1)、(2),即保存当前的栈底地址(edp),将当前的栈顶地址(esp)作为该函数的栈底。在保持完这些状态之后,开始开辟函数的栈空间,代码如下

  0000383 ec 44 sub esp, 68           ;开辟68字节的栈空间: 44H = 68D

而在函数执行完之后,又做了如下处理:

  000218b e5 mov esp, ebp  000235d pop ebp
这两句代码对应的是调用过程的(4),即还原调用函数前的栈状态。

单步跟踪运行,在调用B之前,查看此时的寄存器状态如下图:



 根据EBP的值,我们可看到此时函数栈的状态如下图:


单步执行 call @ILT+10(B)(0040100f) 后,堆栈状态如下:


我们注意到Call指令已经将函数B的返回地址(0x401094)压入了栈,在函数返回时,将该地址赋于寄存器EIP,使得程序能正确返回并继续往下执行。如果我们修改这个地址的值,那么在函数返回时,程序将跳到你指定的地址继续执行。缓冲区溢出攻击的原理就是如此,通过越界访问缓冲区以修改函数返回地址,从而获得程序控制权。

当程序进入函数B之后,寄存器的状态如下:


栈空间状态如下:


可以看出,函数B的栈底地址指向的值(4字节)是函数A的栈底地址,函数B的栈底地址指向的下一个值(4字节)是函数B的返回地址,我们只要能定位到这个返回地址并将之修改为我们自己的函数地址,那我们就掌握了程序的控制权。


在下一章,将给出一个例子,讨论如何准备一些特殊的数据,将这些数据传给没有做溢出检测的函数,从而获得程序的控制权。





原创粉丝点击