栈帧开辟原理(详解)
将此程序(hello.c)编译后调试
1 |
|
main汇编
fun汇编
汇编指令
push rbp 栈顶rsp-=8 并将
rbp的地址
传给rspsub rsp 8
mov rsp rbp
减完再赋值
pop rbp 将
栈顶rsp的值
赋给 rbp 并栈顶rsp+=8mov rbp rsp
add rsp 8
赋值后再加
call 0x400526 < fun> 将 push 0x400550(call指令下一条指令的地址) 并将0x400526 < fun>赋值给rip
push 0x400550
mov rip 0x400526
leave 结果:rsp指向rbp+8 , rbp指向rbp 的内容,
move rsp rbp (
rbp的地址
赋值给rsp的地址,即rsp指向rbp
)pop rbp (
rbp指向rsp的内容(rbp的内容)
,rsp+=8)
ret rip指向rsp的内容,rsp+=8
pop rip
begin
编译后调试,在main函数下断点
可以发现,调用fun函数前
MOV edi 0x4005e4
将 0xa(10) 传给edi
<rdi传递第一个参数>
call(进入函数)
call指令调用地址进入fun函数,并将返回地址压入栈中
call fun 之前 rip指向 main+19
call fun之后 rip指向<fun>
且call指令将main+24压入栈中,便于之后接着执行main
开辟栈帧
进入fan函数之后,要开辟栈帧
push rbp
mov rbp, rsp
> 一般情况下,会sub rsp n
(增加栈帧空间)
先将rbp的地址
压入栈中 方便返回时恢复main函数的栈帧(rbp)
再将rbp指向rsp
,将栈帧放在返回地址 main+24 的上面
栈帧开辟完成后
rbp存着上层函数的栈帧(rbp)
rbp+8存着返回地址
上层函数将执行的代码地址
返回上层函数
- 一般函数结束
leave
ret
leave
先
move rsp rbp
将栈减小,使rsp指向rbp
,再
pop rbp
使rsp 指向 rbp+8(保存返回地址的地方)
,rbp指向rbp保存的内容
ret
pop rip
rip指向rsp的内容,rsp+=8
- 但是本测试,栈帧为0 ,即
rsp与rbp指向同一地址
所以不需要用到 leave
可以直接
pop rbp
ret
最终使rip指向
main+24
(先前call指令 压入栈中的地址)
rsp指向fun栈帧rbp+0x10
rbp指向
原本main函数的rbp
,恢复了上层函数的代码执行和栈帧
之后再leave ret
会使rip指向rbp+8
所以如果我们更改
fun函数的rbp内容为shelladdr-8
,会使main函数的rbp地址为shelladdr-8
紧接着在main函数返回时会执行
main函数返回地址(rbp+8)
–>即shellcode .且之后rsp指向
shelladdr+8
rbp指向
shelladdr-8
的内容