栈平衡和栈迁移

栈平衡

为什么要使用堆栈

程序的运行需要数据,而数据就存放在内存中。首选的存放地址肯定是寄存器中(运行速度快),但是寄存器也就几个,数据很多,所以就把数据存放在了堆栈中。

要想精确的访问到存储的数据,就要一个固定的内存地址,数据会从固定的内存地址开始依次排列。函数内使用的局部变量都是临时存储的,如果每次调用都要往内存中存数据并且不去删除,就会造成很大的浪费。解决方法就是给他一块临时的空间,用完之后就覆盖掉,就开辟了堆栈。

代码分析:

堆栈示意图:

为什么要堆栈平衡

​ 程序在读写数据的时候是通过地址查找的,如果函数调用之前与调用之后的堆栈不同,就会导致找不到数据或者数据错误。所以要保持栈的大小,使ESP始终指向栈顶

总结

​ 也就是说在函数运行之前,函数所用到的所有数据都要入栈,这是需要先将esp的值赋给ebp,使其作为下一个开辟栈的临时栈底,函数内部程序执行完成后(ret之前),esp恢复到入栈前的状态,最后将ebp移除栈。这就是一个简单的栈平衡,ebp在程序的运行过程中有着特定保存数据基址的特性,根据这个特性,ebp一般不会被更改。

栈迁移

  • 当我们的ROP链过长时很可能栈空间不够,并且ebp之前的空间其实只是填充一些没什么用的数据,所以需要一个新的地址空间来存放当前的payload

概念

​ 当前的栈空间不足所以我们可以通过劫持当前的esp(rsp),使其指向另外的地址,作为伪造栈的栈顶。然后在找gadgets。

Gadgets

和基本的pop ; ret类似,我们可以利用leveal ;ret 实现栈的迁移。

1
2
3
4
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