程序的运行需要数据,而数据就存放在内存中。首选的存放地址肯定是寄存器中(运行速度快),但是寄存器也就几个,数据很多,所以就把数据存放在了堆栈中。
要想精确的访问到存储的数据,就要一个固定的内存地址,数据会从固定的内存地址开始依次排列。函数内使用的局部变量都是临时存储的,如果每次调用都要往内存中存数据并且不去删除,就会造成很大的浪费。解决方法就是给他一块临时的空间,用完之后就覆盖掉,就开辟了堆栈。
程序在读写数据的时候是通过地址查找的,如果函数调用之前与调用之后的堆栈不同,就会导致找不到数据或者数据错误。所以要保持栈的大小,使ESP始终指向栈顶
!
也就是说在函数运行之前,函数所用到的所有数据都要入栈,这是需要先将esp的值赋给ebp
,使其作为下一个开辟栈的临时栈底,函数内部程序执行完成后(ret之前
),esp恢复到入栈前的状态,最后将ebp移除栈
。这就是一个简单的栈平衡,ebp在程序的运行过程中有着特定保存数据基址的特性,根据这个特性,ebp
一般不会被更改。
ROP
链过长时很可能栈空间不够,并且ebp
之前的空间其实只是填充一些没什么用的数据,所以需要一个新的地址空间来存放当前的payload 当前的栈空间不足所以我们可以通过劫持当前的esp(rsp)
,使其指向另外的地址,作为伪造栈的栈顶。然后在找gadgets。
和基本的pop ; ret
类似,我们可以利用leveal ;ret
实现栈的迁移。
level ret //拆解
mov ebp,esp //将esp的地址赋给ebp,做为新的栈顶
pop ebp //还原成之前的栈底地址
pop eip // eip指向ret
在存在栈溢出的程序中,只要我们能控制到栈中的ebp
,我们就可以通过两次leave劫持栈。
当第一次执行leave gadgets
这个gadgets里的mov ebp,esp
时我们看到esp
的值变成了old ebp
。
执行了 pop ebp
之后
第一次leave gadgets
执行完之后
第一次leave; ret,new esp
是为了栈劫持的目标地址。执行到retn
时,esp
还在原来的栈上,ebp
已经指向了新的栈顶.
针对于为什么减去4
还可以这样理解在进入一个函数的时候,程序会进行push eip+4;push ebp;mov ebp,esp
的操作来避免执行完函数后堆栈不平衡以及找不到之前的入口地址。但是我们执行了两次pop ebp
这时候程序内就多了一个 pop ebp
所以会使esp-4
,将ebp
覆盖为想要调整的位置-4.
https://oneda1sy.gitee.io/2020/02/24/stack-balance/ https://zhuanlan.zhihu.com/p/75000638