什么是栈迁移
所谓栈迁移,一般而言,是在栈溢出量很小(一般只能溢出到返回地址处),无法打一个完整的ROP链的情况下,利用两次leave ret,将栈帧位置改变到我可控的地址上,在这个地址上我预先已经写好了一段payload,于是在栈迁移过后程序就会继续执行我预先在别处布置好的payload,那么接下来我们就好好地来解释一下栈迁移的原理是什么
栈迁移的原理
首先,我们编写一个demo函数,来展示一个程序栈帧的建立与消亡过成
void demo()
{
char buf[0x20];
return 0;
}
int main()
{
demo();
return 0;
}
IDA打开看一下

进入demo的指令是call指令

这条指令相当于干了两件事:把当前指令(call)的下一条指令(mov)的地址压栈,跳转到demo

进入demo函数后,我们重点关注push rbp;mov rbp,rsp;sub rsp 30h这三条指令,在这里我讲详细讲解栈帧的构建与消亡过程
执行call指令后

执行push rbp后

此时rsp会向下移动,同时将当前的rbp压栈
执行mov rbp,rsp后

此时rbp,rsp的值相同
执行sub rsp,30h之后

从而为buf开辟了函数栈帧
在函数消亡时,会执行leave;ret;而leave指令,就是我们今天的主角,这条指令相当于两条指令:mov rsp,rbp;pop rbp
那我们来看一下正常函数调用结束的流程:
执行mov rsp,rbp之后

执行pop rbp之后

执行完ret之后

由此我们实现了栈帧的消亡
好了,现在如果程序存在一个栈溢出,但是溢出量很少,只能够溢出到ret的位置,那么此时我们该如何下手呢?于是,栈迁移应运而生
假设我们预先在一个可写地址(比如bss段或者栈上)构造好了rop链,那么我们可以如何让程序执行到我们的rop呢?

这里我们跳过函数栈帧的构建过程,直接来看栈溢出后程序将要返回的情况

我们利用栈溢出,把原先的old rbp伪造成了fake rbp(0x602020),返回地址伪造成了leave ret的地址(注意,栈帧我画的是上面是高地址,下面是低地址,而bss上面是低地址,下面是高地址)
那么在程序返回时,执行第一个leave(程序原本的leave),会变成如下状态
执行第一个leave的mov rsp,rbp

执行完第一个leave的pop rbp

由于返回地址也被填成了leave ret,所以接下来会执行第二个leave
执行第二个leave的mov rsp,rbp

执行第二个leave的pop rbp

此时rbp就变成0了,当然具体变成什么要看你0x602020位置的填充是多少
于是执行完这个leave之后,程序接着会执行ret指令,从而顺利的调用到rop链
以上就是栈迁移的全部原理
666,强强强