(之前写的没保存,这是第二遍写,我要杀人...)

# 栈的结构

# 栈迁移的应用场景

栈迁移主要是为了解决栈溢出空间大小不足的问题,当我们的 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)

  1. 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)

【这里我们迁移过去要看看我们输入的值是在哪个地址,有时不是在迁移过去的起始处输入的】

  1. leave

    mov esp,ebp // 将 ebp 的地址给 esp,这次使 esp 指向 fake_esp

    pop ebp // 将 esp 内的值给 ebp(fake_ebp 的地址给 ebp)

  2. ret

    pop eip // 将 system_plt 给 eip 让它去执行,这里就可以 getshell 了,然后 eip+4 ,【这里也意味着我们要将 system 放在 fake_ebp+4 的地方】

这里我们也可以看见直接用 ret 执行了命令,这相当于我们平常栈溢出利用的 ret, 通过这种方式我们可以在没有执行权限的地方来构造我们的 getshell 代码来执行 (但是不能执行 shellcode,执行方式不同)

这样我们就完成了栈迁移