OS:Ubuntu 18.04
编译器:gcc 7.5.0
调试器:gdb 8.1.1
test.c源代码:
#include <stdio.h> void func(int a, int b) { int sum; int sub; sum = a + b; sub = a - b; } int main() { int a = 0; int b = 0; char c = ‘A‘; func(a, b); return 0; }
使用gcc编译(编译选项忽视即可),gdb调试
gcc test.c -ggdb -m32 -g -std=c99 -D_GNU_SOURCE -fno-stack-protector -mpreferred-stack-boundary=2 -Wno-format-security -o test
gdb test
1、push指令:push source 相当于 add esp, 4 mov [esp], source(我在相当长的时间里搞不清到底是先加4还是先传值)
2、调用函数的过程
1)调用方
a. 参数压栈,传参结束,这时esp指向栈顶(最左边的参数)
b. call指令:将call后面的call_ip写入eip,将call的下一条指令地址压栈,这时esp指向这个返回地址
2)被调用方
a. push ebp:将ebp压栈,这时,esp指向存储ebp的栈内地址
b. mov ebp, esp:将esp赋值给ebp,这样ebp的值就是存储着原来的ebp的栈内地址,ebp指向原来的ebp
c. 将ebp作为栈底,esp作为栈顶,两者中间形成了子函数的栈空间
3、函数返回的过程
1)被调用方
a. leave指令:相当于mov esp, ebp pop ebp,与调用方开始的两条指令刚好相反,作用有两个:还原调用子函数前的ebp,将esp指向返回地址
b. ret指令:相当于pop eip,将esp指向的地址的内容赋值给eip,这样esp也就刚好指向传参时最左边的参数
c. 如果有返回值,会存放在eax当中
2)调用方
a. 将调用前压入的参数全部出栈,彻底恢复调用函数之前的栈,一般是sub esp, num
b. 如果有返回值,从eax中取
4、压入的ebp的作用
压入栈中的ebp代表着调用方的栈底,这与调用方的局部变量的寻址直接相关,当函数调用结束后,调用方还需要借助出栈的原ebp值来确定局部变量的位置。如果原ebp的值被改变,那么子函数调用结束后,虽然会继续执行正确的指令,但其中涉及的局部变量都会错位,进而出现错误。
5、压入的返回地址的作用
这很好理解了,就是函数返回后要执行的那条指令的地址,修改了这个返回地址,相当于修改了接下来要执行的指令,获得了程序控制权。
搞清楚了调用函数的过程,也就可以理解缓冲区溢出的原理了,其主要就是通过修改原ebp和返回地址的值来获得程序控制权
原文:https://www.cnblogs.com/lylhome/p/14730119.html