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

关于函数栈空间

栈是向下生长的,简单的说就是栈底地址大于栈顶地址。就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:

  00000	55		 push	 ebp
  00001	8b ec		 mov	 ebp, esp
  00003	83 ec 68	 sub	 esp, 104
  00006	53		 push	 ebx
  00007	56		 push	 esi
  00008	57		 push	 edi
  00009	8d 7d 98	 lea	 edi, DWORD PTR [ebp-104]
  0000c	b9 1a 00 00 00	 mov	 ecx, 26
  00011	b8 cc cc cc cc	 mov	 eax, -858993460
  00016	f3 ab		 rep stosd
  00018	c7 45 d8 00 00
	00 00		 mov	 DWORD PTR _a$[ebp], 0
  0001f	b9 09 00 00 00	 mov	 ecx, 9
  00024	33 c0		 xor	 eax, eax
  00026	8d 7d dc	 lea	 edi, DWORD PTR _a$[ebp+4]
  00029	f3 ab		 rep stosd

  0002b	8b 45 dc	 mov	 eax, DWORD PTR _a$[ebp+4]
  0002e	50		 push	 eax
  0002f	e8 00 00 00 00	 call	 ?B@@YAXH@Z
  00034	83 c4 04	 add	 esp, 4
  00037	5f		 pop	 edi
  00038	5e		 pop	 esi
  00039	5b		 pop	 ebx
  0003a	83 c4 68	 add	 esp, 104
  0003d	3b ec		 cmp	 ebp, esp
  0003f	e8 00 00 00 00	 call	 __chkesp
  00044	8b e5		 mov	 esp, ebp
  00046	5d		 pop	 ebp
  00047	c3		 ret	 0

函数B:

  00000	55		 push	 ebp
  00001	8b ec		 mov	 ebp, esp
  00003	83 ec 44	 sub	 esp, 68
  00006	53		 push	 ebx
  00007	56		 push	 esi
  00008	57		 push	 edi
  00009	8d 7d bc	 lea	 edi, DWORD PTR [ebp-68]
  0000c	b9 11 00 00 00	 mov	 ecx, 17
  00011	b8 cc cc cc cc	 mov	 eax, -858993460
  00016	f3 ab		 rep stosd
  00018	8b 45 08	 mov	 eax, DWORD PTR _a$[ebp]
  0001b	89 45 fc	 mov	 DWORD PTR _b$[ebp], eax
  0001e	5f		 pop	 edi
  0001f	5e		 pop	 esi
  00020	5b		 pop	 ebx
  00021	8b e5		 mov	 esp, ebp
  00023	5d		 pop	 ebp
  00024	c3		 ret	 0

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

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

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

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

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

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



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


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


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

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


栈空间状态如下:


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


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





  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值