C语言函数与栈、寄存器的关系
最近想学PWN,虽然这些是之前学过的,但是在做题的时候发现细节的重要性,就决定回顾一遍,这里写的主要也就是给自己记录一下,加深映像,同时加了一些自己的理解和补充
主要参考的文献:https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/stack_intro/
32位
上来肯定事先了解一下寄存器啦,如下图
这里就先说一下ESP和EBP好了,可以理解为前者是栈顶指针,后者是栈底指针,那么根据两个的偏移量就可以指定栈当中的内容了,栈刚开始理解为开辟的一个存放空间就好,但是该控件可以在用完后销毁,经常用来放函数中的局部变量,如下图所示
那么说到了局部变量,就正好把函数的实参形参与栈的分布结构,如下
实参N~1→主调函数返回地址→主调函数帧基指针EBP→被调函数局部变量1~N
也就是说C语言中函数的调用,实参是从右往左压入栈,而局部变量是由创建的顺序一次压入栈的
有一点要注意静态局部变量是在编译时就准备好的,和全局变量是一样的,为什么会展示出局部性,是因为给了他调用的次数(有时候是时间),它自身并不是在栈中新创建的,是提前就有的
EBP指针在当前函数运行过程中(未调用其他函数时)保持不变。在函数调用前,ESP指针指向栈顶地址,也是栈底地址。在函数完成现场保护之类的初始化工作后,ESP会始终指向当前函数栈帧的栈顶,此时,若当前函数又调用另一个函数,则会将此时的EBP视为旧EBP压栈,而与新调用函数有关的内容会从当前ESP所指向位置开始压栈。
然后是寄存器ebx、esi和edi为被调函数保存寄存器(callee-saved registers),即被调函数在覆盖这些寄存器的值时,必须先将寄存器原值压入栈中保存起来,并在函数返回前从栈中恢复其原值,因为主调函数可能也在使用这些寄存器,这里说了一堆反正就是调用函数时要小心就是了
我觉得极其关键的一个寄存器是eip,里面存放的是下一条要执行的命令的代码的地址(改变了它,就可以修改程序执行的流程了)
堆栈操作:
这里我主要看重C函数调用以及调用结束后的栈操作
具体解释:
调用函数是,先保存环境以便日后函数调用结束后,恢复环境,所以将当前比如主函数中的栈底指针压入栈,为什么不用保存栈顶指针ESP呢,显然程序执行的栈帧永远是最顶上的,那么栈顶指针永远在顶部,所以不用太在意啦;
起初ESP与EBP是相等的,但是函数难免要调用一些局部变量以及一些函数如printf等等,那么肯定要开辟一个空间,所以就把栈顶指针移位形成一个新栈帧;
销毁函数其实就是个你过程,将栈帧清除,返回原地址的过程
但其实C函数调用的压栈顺序根据不同方式是有差别的,具体如下表,一般性主要就是用前3个
最重要的区别就是栈回收时的操作,采取stdcall的函数栈的销毁工作是自己处理的;
而使用cdecl的函数栈的销毁是交给主函数来运作的,他的特性就是传参个数不确定,比如printf、scanf
具体如下图,请关注sub操作是在什么时候执行的
64位
列举一些重要的区别吧,我觉得最重要的一点就是对函数传参的顺序
64位函数的参数传递顺序和32位不同,传参顺序:rdi,rsi,rdx,rcx,r8,r9,栈