栈迁移是一个强大的工具,指通过手段将栈转移到内存的其他可读可写区域,比如 bss 段,可以用于绕过栈溢出空间不足、shellcode转移到可执行空间等
栈迁移离不开两个重要的指令:leave 和 ret
其中 leave 可以视作以下两个指令的集合
mov rsp, rbp
pop rbp
而 ret 可以视作
pop rip
leave 指令的含义是将 rbp 指向的内存赋给 rsp ,然后将栈底弹出,此时 rsp 恰好指向返回地址,ret 又是弹出返回地址给 rip,rip 继续执行函数调用前的逻辑。这样一个函数的结束就正式完成了

有点小问题,箭头应该从栈底开始指,不是返回地址
不难发现,rbp 的去向完全取决于栈底指向的位置,而我们也确实可以通过覆盖栈底来控制 rbp 的去向
如果我们将栈底指向栈外的内存,rbp 自然也会跑到栈外去,但是我们的目标是将 rsp 也指向栈外。前面的 leave 指令含有 mov rsp, rbp ,那我们只要将返回地址覆写为 leave 指令,是不是就可以让 rsp 也指向栈外呢?
显然是可以的,此处假设 rbp 已经指向栈外,让我们来看再一次执行 leave 会发生什么

rsp 指向了栈外的内存!如果这一段内存被我们布置好了,比如最后 rsp 指向的其实是 system() 的地址,由于 leave 后面会跟着 ret ,rip 也就会去执行system() 函数
需要注意,我们通常最后将 rbp 指向 bss 段的中间部分,因为程序还是会像在栈上一样从高往低压栈,如果将 rbp 放在 bss 段开头,程序向下写数据时会超出 bss 段的区域,而新的区域如果没有读写权限,程序就会报错