Land #8271, DOUBLEPULSAR detection for MS17-010

bug/bundler_fix
William Vu 2017-04-25 16:31:39 -05:00
commit 5476f6066c
No known key found for this signature in database
GPG Key ID: 68BD00CE25866743
1 changed files with 98 additions and 12 deletions

View File

@ -22,10 +22,17 @@ class MetasploitModule < Msf::Auxiliary
If the status returned is "STATUS_INSUFF_SERVER_RESOURCES", the machine does
not have the MS17-010 patch.
If the machine is missing the MS17-010 patch, the module will check for an
existing DoublePulsar (ring 0 shellcode/malware) infection.
This module does not require valid SMB credentials in default server
configurations. It can log on as the user "\" and connect to IPC$.
},
'Author' => [ 'Sean Dillon <sean.dillon@risksense.com>' ],
'Author' =>
[
'Sean Dillon <sean.dillon@risksense.com>', # @zerosum0x0
'Luke Jennings' # DoublePulsar detection Python code
],
'References' =>
[
[ 'CVE', '2017-0143'],
@ -35,27 +42,55 @@ class MetasploitModule < Msf::Auxiliary
[ 'CVE', '2017-0147'],
[ 'CVE', '2017-0148'],
[ 'MSB', 'MS17-010'],
[ 'URL', 'https://zerosum0x0.blogspot.com/2017/04/doublepulsar-initial-smb-backdoor-ring.html'],
[ 'URL', 'https://github.com/countercept/doublepulsar-detection-script'],
[ 'URL', 'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx']
],
'License' => MSF_LICENSE
))
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
def run_host(ip)
begin
status = do_smb_probe(ip)
ipc_share = "\\\\#{ip}\\IPC$"
tree_id = do_smb_setup_tree(ipc_share)
vprint_status("Connected to #{ipc_share} with TID = #{tree_id}")
status = do_smb_ms17_010_probe(tree_id)
vprint_status("Received #{status} with FID = 0")
if status == "STATUS_INSUFF_SERVER_RESOURCES"
print_warning("Host is likely VULNERABLE to MS17-010!")
print_good("Host is likely VULNERABLE to MS17-010! (#{simple.client.peer_native_os})")
report_vuln(
host: ip,
name: self.name,
refs: self.references,
info: 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$'
info: 'STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$ -- (#{simple.client.peer_native_os})'
)
# vulnerable to MS17-010, check for DoublePulsar infection
code, signature = do_smb_doublepulsar_probe(tree_id)
if code == 0x51
xor_key = calculate_doublepulsar_xor_key(signature).to_s(16).upcase
print_warning("Host is likely INFECTED with DoublePulsar! - XOR Key: #{xor_key}")
report_vuln(
host: ip,
name: "MS17-010 DoublePulsar Infection",
refs: self.references,
info: 'MultiPlexID += 0x10 on Trans2 request - XOR Key: #{xor_key}'
)
end
elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
# STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others)
print_good("Host does NOT appear vulnerable.")
print_bad("Host does NOT appear vulnerable.")
else
print_bad("Unable to properly detect if host is vulnerable.")
end
@ -72,19 +107,34 @@ class MetasploitModule < Msf::Auxiliary
end
end
def do_smb_probe(ip)
def do_smb_setup_tree(ipc_share)
connect
# logon as user \
simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])
# connect to IPC$
ipc_share = "\\\\#{ip}\\IPC$"
simple.connect(ipc_share)
tree_id = simple.shares[ipc_share]
print_status("Connected to #{ipc_share} with TID = #{tree_id}")
# return tree
return simple.shares[ipc_share]
end
def do_smb_doublepulsar_probe(tree_id)
# make doublepulsar knock
pkt = make_smb_trans2_doublepulsar(tree_id)
sock.put(pkt)
bytes = sock.get_once
# 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']
end
def do_smb_ms17_010_probe(tree_id)
# request transaction with fid = 0
pkt = make_smb_trans_ms17_010(tree_id)
sock.put(pkt)
@ -97,10 +147,46 @@ class MetasploitModule < Msf::Auxiliary
# convert error code to string
code = pkt['SMB'].v['ErrorClass']
smberr = Rex::Proto::SMB::Exceptions::ErrorCode.new
status = smberr.get_error(code)
print_status("Received #{status} with FID = 0")
status
return smberr.get_error(code)
end
def make_smb_trans2_doublepulsar(tree_id)
# make a raw transaction packet
# this one is a trans2 packet, the checker is trans
pkt = Rex::Proto::SMB::Constants::SMB_TRANS2_PKT.make_struct
simple.client.smb_defaults(pkt['Payload']['SMB'])
# opcode 0x0e = SESSION_SETUP
setup = "\x0e\x00\x00\x00"
setup_count = 1 # 1 word
trans = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
# calculate offsets to the SetupData payload
base_offset = pkt.to_s.length + (setup.length) - 4
param_offset = base_offset + trans.length
data_offset = param_offset # + 0
# packet baselines
pkt['Payload']['SMB'].v['Command'] = Rex::Proto::SMB::Constants::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x18
pkt['Payload']['SMB'].v['MultiplexID'] = 65
pkt['Payload']['SMB'].v['Flags2'] = 0xc007
pkt['Payload']['SMB'].v['TreeID'] = tree_id
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
pkt['Payload'].v['Timeout'] = 0x00a4d9a6
pkt['Payload'].v['ParamCountTotal'] = 12
pkt['Payload'].v['ParamCount'] = 12
pkt['Payload'].v['ParamCountMax'] = 1
pkt['Payload'].v['DataCountMax'] = 0
pkt['Payload'].v['ParamOffset'] = 66
pkt['Payload'].v['DataOffset'] = 78
pkt['Payload'].v['SetupCount'] = setup_count
pkt['Payload'].v['SetupData'] = setup
pkt['Payload'].v['Payload'] = trans
pkt.to_s
end
def make_smb_trans_ms17_010(tree_id)