函数调用栈分析

转载请注明出处:http://blog.youkuaiyun.com/wangxiaolong_china

 

关于堆栈空间利用最核心的一点就是:函数调用栈。而要深入理解函数调用栈,最重要的两点就是:栈的结构变化,ebp寄存器的作用。

首先要认识到这样两个事实:

1.      一个函数调用动作可分解为:零到多个push指令(用于参数入栈),一个call指令。call指令内部其实还暗含了一个将eip返回地址(即call指令下一条指令的地址)压栈的动作。

2.      几乎所有本地编译器都会在每个函数体之前插入类似的指令:push %ebp,mov %esp %ebp。

因此,在程序执行到一个函数的真正函数体的时候,已经有以下数据压入到堆栈中:零到多个参数,返回地址eip,ebp。

由此得到如下的栈结构(其中参数入栈顺序跟调用方式有关,这里以C语言默认的CDECL为例):

“push %ebp”“mov %esp %ebp”这两条指令实在是太有深意了:首先将ebp入栈,然后将栈顶指针esp赋值给ebp。“mov %esp %ebp”这条指令表面上看是用esp把ebp原来的值覆盖了,其实不然,因为在给ebp赋值之前,原ebp值已经被压栈(位于栈顶),esp赋值给ebp后,ebp恰好指向栈顶(即被压栈的原esp的位置)。

此时,ebp寄存器就处在一个非常重要的地位,该寄存器中存储着栈中的一个地址(原ebp入栈后的栈顶),以该地址为基准,向上(栈底方向)能获取返回地址,函数调用参数值;向下(栈顶方向)能获取函数局部变量值;而该地址处又存储着上一层函数调用时的ebp值!!一般而言,ss:[ebp+4]处为返回地址,ss:[ebp+8]处为第一个参数值(最后一个入栈的参数值,此处假设其占用4字节内存),ss:[ebp-4]处为第一个局部变量,ss:[ebp]处为上一层ebp值。

由于ebp中的地址总是“上一层函数调用时的ebp值”,而在每一层函数调用中,都能通过当时的ebp值“向上(栈底方向)能获取返回地址、函数调用参数,向下(栈顶方向)能获取函数局部变量值”。如此形成递归,直至到达栈底。这就是函数调用栈。由此看见,编译器对于ebp寄存器的使用实在是太精妙了。

此外,从当前ebp出发,逐层向上找到所有的ebp是非常容易的:

  1. <SPAN style="FONT-SIZE: 18px">unsigned int _ebp;  
  2. __asm _ebp, ebp;  
  3. while (not stack bottom)  
  4. {  
  5.     //...   
  6.     _ebp = *(unsigned int*)_ebp;  
  7. }  
  8. </SPAN>  
<span style="FONT-SIZE: 18px">unsigned int _ebp;
__asm _ebp, ebp;
while (not stack bottom)
{
    //...
    _ebp = *(unsigned int*)_ebp;
}
</span>

下面通过一个简单的C程序,简要的分析一下函数调用栈的变化情况。通过对具体C程序函数调用过程中堆栈空间变化的分析,加深对于函数调用栈的理解。

要分析的C程序源码如下:

  1. <SPAN style="FONT-SIZE: 18px">  stack.c  
  2. 1 #include <stdio.h>  
  3.   2   
  4.   3 void func1() {  
  5.   4         printf("in func1.\n");  
  6.   5 }  
  7.   6   
  8.   7 void func2() {  
  9.   8         printf("in func2.\n");  
  10.   9 }  
  11.  10   
  12.  11 void func3() {  
  13.  12         int a = 1;  
  14.  13         *(&a + 2) = (int)func1;  
  15.  14 }  
  16.  15   
  17.  16 int main(void) {  
  18.  17         int a_main = 1;  
  19.  18         *(&a_main - 3) = (int)func2;  
  20.  19         func3();  
  21.  20   
  22.  21         return 0;  
  23.  22 }  
  24. </SPAN>  
<span style="FONT-SIZE: 18px">  stack.c
1 #include <stdio.h>
  2 
  3 void func1() {
  4         printf("in func1.\n");
  5 }
  6 
  7 void func2() {
  8         printf("in func2.\n");
  9 }
 10 
 11 void func3() {
 12         int a = 1;
 13         *(&a + 2) = (int)func1;
 14 }
 15 
 16 int main(void) {
 17         int a_main = 1;
 18         *(&a_main - 3) = (int)func2;
 19         func3();
 20 
 21         return 0;
 22 }
</span>

程序执行结果:

  1. <SPAN style="FONT-SIZE: 18px">root@linux:~/pentest# gcc -g -o stack stack.c  
  2. root@linux:~/pentest# ./stack  
  3. in func1.  
  4. in func2.  
  5. Segmentation fault  
  6. </SPAN>  
<span style="FONT-SIZE: 18px">root@linux:~/pentest# gcc -g -o stack stack.c
root@linux:~/pentest# ./stack
in func1.
in func2.
Segmentation fault
</span>

使用GDB反汇编stack程序:

  1. <SPAN style="FONT-SIZE: 18px">root@linux:~/pentest# gdb stack  
  2. GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2  
  3. Copyright (C) 2010 Free Software Foundation, Inc.  
  4. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>   
  5. This is free software: you are free to change and redistribute it.  
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"  
  7. and "show warranty" for details.  
  8. This GDB was configured as "i686-linux-gnu".  
  9. For bug reporting instructions, please see:  
  10. <http://www.gnu.org/software/gdb/bugs/>...   
  11. Reading symbols from /root/pentest/stack...done.  
  12. (gdb) disass main  
  13. Dump of assembler code for function main:  
  14.    0x080483f8 <+0>:   push   %ebp  
  15.    0x080483f9 <+1>:   mov    %esp,%ebp  
  16.    0x080483fb <+3>:   sub    {1}x10,%esp  
  17.    0x080483fe <+6>:   movl   {1}x1,-0x4(%ebp)  
  18.    0x08048405 <+13>:  lea    -0x4(%ebp),%eax  
  19.    0x08048408 <+16>:  sub    {1}xc,%eax  
  20.    0x0804840b <+19>:  mov    {1}x80483c8,%edx  
  21.    0x08048410 <+24>:  mov    %edx,(%eax)  
  22.    0x08048412 <+26>:  call   0x80483dc <func3>  
  23.    0x08048417 <+31>:  mov    {1}x0,%eax  
  24.    0x0804841c <+36>:  leave    
  25.    0x0804841d <+37>:  ret      
  26. End of assembler dump.  
  27. (gdb) disass func3   
  28. Dump of assembler code for function func3:  
  29.    0x080483dc <+0>:   push   %ebp  
  30.    0x080483dd <+1>:   mov    %esp,%ebp  
  31.    0x080483df <+3>:   sub    {1}x10,%esp  
  32.    0x080483e2 <+6>:   movl   {1}x1,-0x4(%ebp)  
  33.    0x080483e9 <+13>:  lea    -0x4(%ebp),%eax  
  34.    0x080483ec <+16>:  add    {1}x8,%eax  
  35.    0x080483ef <+19>:  mov    {1}x80483b4,%edx  
  36.    0x080483f4 <+24>:  mov    %edx,(%eax)  
  37.    0x080483f6 <+26>:  leave    
  38.    0x080483f7 <+27>:  ret      
  39. End of assembler dump.  
  40. (gdb) disass func2  
  41. Dump of assembler code for function func2:  
  42.    0x080483c8 <+0>:   push   %ebp  
  43.    0x080483c9 <+1>:   mov    %esp,%ebp  
  44.    0x080483cb <+3>:   sub    {1}x18,%esp  
  45.    0x080483ce <+6>:   movl   {1}x80484ea,(%esp)  
  46.    0x080483d5 <+13>:  call   0x80482f0 <puts@plt>  
  47.    0x080483da <+18>:  leave    
  48.    0x080483db <+19>:  ret      
  49. End of assembler dump.  
  50. (gdb) disass func1  
  51. Dump of assembler code for function func1:  
  52.    0x080483b4 <+0>:   push   %ebp  
  53.    0x080483b5 <+1>:   mov    %esp,%ebp  
  54.    0x080483b7 <+3>:   sub    {1}x18,%esp  
  55.    0x080483ba <+6>:   movl   {1}x80484e0,(%esp)  
  56.    0x080483c1 <+13>:  call   0x80482f0 <puts@plt>  
  57.    0x080483c6 <+18>:  leave    
  58.    0x080483c7 <+19>:  ret      
  59. End of assembler dump.  
  60. (gdb)  
  61. </SPAN>  
<span style="FONT-SIZE: 18px">root@linux:~/pentest# gdb stack
GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/pentest/stack...done.
(gdb) disass main
Dump of assembler code for function main:
   0x080483f8 <+0>:	push   %ebp
   0x080483f9 <+1>:	mov    %esp,%ebp
   0x080483fb <+3>:	sub    {1}x10,%esp
   0x080483fe <+6>:	movl   {1}x1,-0x4(%ebp)
   0x08048405 <+13>:	lea    -0x4(%ebp),%eax
   0x08048408 <+16>:	sub    {1}xc,%eax
   0x0804840b <+19>:	mov    {1}x80483c8,%edx
   0x08048410 <+24>:	mov    %edx,(%eax)
   0x08048412 <+26>:	call   0x80483dc <func3>
   0x08048417 <+31>:	mov    {1}x0,%eax
   0x0804841c <+36>:	leave  
   0x0804841d <+37>:	ret    
End of assembler dump.
(gdb) disass func3 
Dump of assembler code for function func3:
   0x080483dc <+0>:	push   %ebp
   0x080483dd <+1>:	mov    %esp,%ebp
   0x080483df <+3>:	sub    {1}x10,%esp
   0x080483e2 <+6>:	movl   {1}x1,-0x4(%ebp)
   0x080483e9 <+13>:	lea    -0x4(%ebp),%eax
   0x080483ec <+16>:	add    {1}x8,%eax
   0x080483ef <+19>:	mov    {1}x80483b4,%edx
   0x080483f4 <+24>:	mov    %edx,(%eax)
   0x080483f6 <+26>:	leave  
   0x080483f7 <+27>:	ret    
End of assembler dump.
(gdb) disass func2
Dump of assembler code for function func2:
   0x080483c8 <+0>:	push   %ebp
   0x080483c9 <+1>:	mov    %esp,%ebp
   0x080483cb <+3>:	sub    {1}x18,%esp
   0x080483ce <+6>:	movl   {1}x80484ea,(%esp)
   0x080483d5 <+13>:	call   0x80482f0 <puts@plt>
   0x080483da <+18>:	leave  
   0x080483db <+19>:	ret    
End of assembler dump.
(gdb) disass func1
Dump of assembler code for function func1:
   0x080483b4 <+0>:	push   %ebp
   0x080483b5 <+1>:	mov    %esp,%ebp
   0x080483b7 <+3>:	sub    {1}x18,%esp
   0x080483ba <+6>:	movl   {1}x80484e0,(%esp)
   0x080483c1 <+13>:	call   0x80482f0 <puts@plt>
   0x080483c6 <+18>:	leave  
   0x080483c7 <+19>:	ret    
End of assembler dump.
(gdb)
</span>


 

下面将按照程序执行流程,分析函数调用栈的主要变化情况:

程序执行起点是main函数,其调用栈变化如下所示:

Main程序调用func3,故调用func3后,调用栈变化如下所示:

由于func1的地址覆写了eip_main函数调用返回地址,故func3执行结束后,将返回到func1并继续执行,程序调用栈变化如下所示:

程序从func1返回时,填入栈中的func2的地址将作为返回地址使用,即程序返回后将跳转到func2起始处执行,程序调用栈如下所示:

程序执行func2,在执行ret指令时,由于返回地址可能指向无效的段,从而导致程序执行结果出现Segmentationfault。

要使程序执行可以正常结束,而不会出现Segmentation fault,则需要main函数中18 *(&a_main- 3) = (int)func2;之后添加如下一行代码即可:

*(&a_main -2) = *(&a_main + 2);

通过该实验,对于函数调用栈在函数调用过程中的变化的理解进一步加深,有利于更好的理解栈溢出的原理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值