更改函数的返回地址

这是网络安全老师布置的实验,觉得是大学以来做过的最有意思的一个实验。

Task Description:

C语言编写程序,包含一个函数,改变函数的返回地址,使函数返回后跳转到某个指定的指令位置,而不是函数调用后紧跟的位置。

先上代码:

#include <stdio.h>

void foo(){
    int a, *p;
    p = (int*)((int)&a + 8);
    *p += 12;
}

int main(){
    foo();
    printf("First printf call\n");
    printf("Second printf call\n");
    return 0;
}

编译运行,结果输出

Second printf call.

并没有输出First printf call.达到预期效果。

 

原理并不复杂:在函数体中修改return地址,即找到return的地址的位置,然后修改它。

下面详细介绍下实验过程:

编译:
gcc main.c -g

反编译:

objdump a.out -d

得到部分汇编代码如下:

080483e4 <foo>:
 80483e4:    55                       push   %ebp
 80483e5:    89 e5                    mov    %esp,%ebp
 80483e7:    83 ec 10                 sub    $0x10,%esp
 80483ea:    8d 45 fc                 lea    -0x4(%ebp),%eax
 80483ed:    83 c0 08                 add    $0x8,%eax
 80483f0:    89 45 f8                 mov    %eax,-0x8(%ebp)
 80483f3:    8b 45 f8                 mov    -0x8(%ebp),%eax
 80483f6:    8b 00                    mov    (%eax),%eax
 80483f8:    8d 50 0c                 lea    0xc(%eax),%edx
 80483fb:    8b 45 f8                 mov    -0x8(%ebp),%eax
 80483fe:    89 10                    mov    %edx,(%eax)
 8048400:    c9                       leave  
 8048401:    c3                       ret    

08048402 <main>:
 8048402:    55                       push   %ebp
 8048403:    89 e5                    mov    %esp,%ebp
 8048405:    83 e4 f0                 and    $0xfffffff0,%esp
 8048408:    83 ec 10                 sub    $0x10,%esp
 804840b:    e8 d4 ff ff ff           call   80483e4 <foo>
 8048410:    c7 04 24 f0 84 04 08     movl   $0x80484f0,(%esp)
 8048417:    e8 fc fe ff ff           call   8048318 <puts@plt>
 804841c:    c7 04 24 02 85 04 08     movl   $0x8048502,(%esp)
 8048423:    e8 f0 fe ff ff           call   8048318 <puts@plt>
 8048428:    b8 00 00 00 00           mov    $0x0,%eax
 804842d:    c9                       leave  
 804842e:    c3                       ret    
 804842f:    90                       nop

 

从上述的汇编代码中,我们可以看到foo后面的指令地址是8048410,而进入调用printf("Second printf call“)的指令是地址804841c, 二者相差12,故我们应该将返回地址的值+12即可。

再说点函数调用的过程:

调用函数的过程是这样的:
调用者从右往左将参数入栈,再调用call 指令,call 指令完成事情:

1) 将返回地址入栈,2)修改eip寄存器,即计算机组成原理里面说的PC,使程序的指令指针指向被调用函数的起始指令地址。

接着就进入了被调用的函数的函数体中,C语言的函数调用习惯是:
pushl %ebp,

movl %esp %ebp

再对栈顶寄存器esp减去一个值,为函数中的局部变量预留空间。(因为栈的增长方向是从高地址往低地址增长的)

好的,介绍完毕。

 

从上述的汇编代码中:

80483ea: 8d 45 fc lea -0x4(%ebp),%eax

这一句汇编代码:把-4(%ebp)的地址赋值给%eax寄存器。对应的C语言的代码是:
p = &a;

所以我们便从中可以知道:foo()函数中的变量a是存储在-4(%ebp)位置的,即图中的Local Variable1的位置

如此看来,a的地址往上8个单位就是return的地址。
所以p = &a + 8就获得了return返回值跳转地址的地址。

对这个地址的内容进行修改:
*p += 12 // 之所以+12刚才已经分析出来了。

便完成了让函数调用结束后跳转到第二个printf函数调用的位置。

 

ps:

自己在做得时候,用gdb验证了一些知识,比较有意思:
如,查看%eip里面的值,发现的确是指向当前程序的指令的地址,就是PC的效果

在刚进入foo函数的时候,查看栈顶位置存储的值,发现是8048410,心中一块大石头落下来了,栈顶果然是return的指令地址。

gdb的部分截图:

posted on 2012-05-19 23:17  Jianfei Hu  阅读(3061)  评论(4编辑  收藏  举报