更改函数的返回地址
这是网络安全老师布置的实验,觉得是大学以来做过的最有意思的一个实验。
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的部分截图: