(之前写的没保存,这是第二遍写,我要杀人...)
# 栈的结构
# 栈迁移的应用场景
栈迁移主要是为了解决栈溢出空间大小不足的问题,当我们的 ROP 链过长时很可能栈空间不够,并且 ebp 之前的空间其实只是填充一些没什么用的数据,所以需要一个新的地址空间来存放当前的 payload
我们的栈
# 1. leave 和 ret 命令
leave:
mov esp , ebp
pop ebp #执行pop时,ebp出栈,并且将esp地址内的值放入ebp
这里要注意 mov esp,ebp
是将 ebp 的地址付给 esp,也就是说将 esp 从栈顶拉下来到 ebp 的位置, pop ebp
是将 esp 内部的值给 ebp
ret:
pop eip #将esp的值放入eip,让eip寄存器去执行,并且esp+4
# 2. 栈迁移
首先要利用栈迁移需要通过溢出来改写部分数据,将 ebp 的值改为伪造的栈的栈顶,ret 一般改到 read 处来改写伪造的栈的数据【使伪造的栈的栈顶( fake_esp
)存入 fake_ebp
的栈低】
栈迁移要使用两次 leave_ret 来转移栈
# 第一次 leave_ret
(来源于 ebp 后的 ret)
-
leave:
mov esp , ebp // 将 ebp 的地址赋给 esp,相当于将 esp 指向 ebp 相同的地方
pop ebp //将esp指向的地方的值给ebp,这里是值,值!(等于将fake_esp的地址给了ebp),然后esp+4
第一次写的时候有个疑问,第一次的 leave_ret 到底用的是谁的 leave_ret
,如果是 ebp 后的 ret 的,那么为什么后面还要在 ret 后加一个 leave_ret
,这样加上 read 里面的 leave_ret
相当与 3 个了;而如果用的是 read 内的 leave_ret
那不是会将 esp 拉到伪造的 ebp 处吗,一个 ret 的地址就执行了两个 leave_ret
?
我自己的理解:调用的 read 会在开辟一个新的栈帧,它的内部的 leave_ret
不会干扰外面栈的寄存器,所以等于使用的是 ebp 后面的 ret
2.ret
pop eip //将这里esp的值给eip,让eip去执行`read`,然后esp+4
# 第二次调用 leave_ret
(来源于 leave_ret)
【这里我们迁移过去要看看我们输入的值是在哪个地址,有时不是在迁移过去的起始处输入的】
-
leave
mov esp,ebp // 将 ebp 的地址给 esp,这次使 esp 指向 fake_esp
pop ebp // 将 esp 内的值给 ebp(fake_ebp 的地址给 ebp)
-
ret
pop eip // 将 system_plt 给 eip 让它去执行,这里就可以 getshell 了,然后 eip+4 ,【这里也意味着我们要将 system 放在 fake_ebp+4 的地方】
这里我们也可以看见直接用 ret 执行了命令,这相当于我们平常栈溢出利用的 ret, 通过这种方式我们可以在没有执行权限的地方来构造我们的 getshell 代码来执行 (但是不能执行 shellcode,执行方式不同)
这样我们就完成了栈迁移