栈是一种基础数据结构,具有后进先出的特点,主要操作分别是压栈(PUSH)和弹栈(POP)两个。
二进制程序在运行的时候使用了这个结构,常见的局部变量,函数调用的时候的返回地址,甚至是寄存器会存储在这上面,当产生栈溢出漏洞的时候,会对整个程序执行造成巨大的影响。
一些前置知识:
call
这个asm会做两个行为:
ret
这个asm会做一个行为:
将栈顶地址pop给eip
leave
这个asm会做两个行为:
mov rbp, rsp
pop rbp
在x86下,也就是32位程序下:
会将函数调用的参数从右往左压入栈中
x86下C语言分为了三种调用方式:
IDA里是可以看到函数调用的方式的,在函数声明处
C语言默认调用CDECL
特点:
例如:
push 2
push 1
call function
add esp, 8 ; 注意, 这里调用者再恢复堆栈
STDCALL,这个是C++的标准调用方式
特点:
调用函数:可以看到这里没有add esp, 8
这个汇编,这里堆栈平衡由函数自己清理
push 2 ; 第二个参数入栈
push 1 ; 第一个参数入栈
call function ; 调用函数, 注意此时自动把 cs:eip 入栈
函数:
push ebp ; 保存 ebp 寄存器, 该寄存器将用来保存堆栈的栈顶指针, 可以在函数退出时恢复
mov ebp, esp ; 保存栈顶指针
mov eax, [ebp + 8H] ; 堆栈中 ebp 指向位置之前依次保存有 ebp, cs:eip, a, b, ebp + 8 指向 a
add eax, [ebp + 0CH] ; 堆栈中 ebp + 12 处保存了 b
mov esp, ebp ; 恢复 esp
pop ebp
ret 8
FASTCALL,编译器指定的快速调用方式
特点:
这里只需要它的特点就是会通过两个寄存器来传递一点参数就行了
在x64模式下,也就是64位程序:
函数参数按照RDI
/RSI
/RDX
/RCX
/R8
/R9
中,其它多余的参数保存在栈上