what you don't know can hurt you
Home Files News &[SERVICES_TAB]About Contact Add New

MS17-010 SMB Remote Code Execution Detection

MS17-010 SMB Remote Code Execution Detection
Posted Sep 1, 2024
Authored by Luke Jennings, Sean Dillon | Site metasploit.com

Uses information disclosure to determine if MS17-010 has been patched or not. Specifically, it connects to the IPC$ tree and attempts a transaction on FID 0. 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 Metasploit module does not require valid SMB credentials in default server configurations. It can log on as the user "\" and connect to IPC$.

tags | exploit, shellcode, info disclosure
advisories | CVE-2017-0143, CVE-2017-0144, CVE-2017-0145, CVE-2017-0146, CVE-2017-0147, CVE-2017-0148
SHA-256 | 7da47a7e8285d0a6b8ee0d6e5384264f78b38a3863420fbdc47ecf044ace7dde

MS17-010 SMB Remote Code Execution Detection

Change Mirror Download
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Auxiliary
include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::Remote::SMB::Client
include Msf::Exploit::Remote::SMB::Client::Authenticated
include Msf::Exploit::Remote::SMB::Client::PipeAuditor

include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report

def initialize(info = {})
super(update_info(info,
'Name' => 'MS17-010 SMB RCE Detection',
'Description' => %q{
Uses information disclosure to determine if MS17-010 has been patched or not.
Specifically, it connects to the IPC$ tree and attempts a transaction on FID 0.
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>', # @zerosum0x0
'Luke Jennings' # DoublePulsar detection Python code
],
'References' =>
[
[ 'CVE', '2017-0143'],
[ 'CVE', '2017-0144'],
[ 'CVE', '2017-0145'],
[ 'CVE', '2017-0146'],
[ '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://web.archive.org/web/20170513050203/https://technet.microsoft.com/en-us/library/security/ms17-010.aspx']
],
'License' => MSF_LICENSE,
'Notes' =>
{
'AKA' => [
'DOUBLEPULSAR',
'ETERNALBLUE'
]
}
))

register_options(
[
OptBool.new('CHECK_DOPU', [false, 'Check for DOUBLEPULSAR on vulnerable hosts', true]),
OptBool.new('CHECK_ARCH', [false, 'Check for architecture on vulnerable hosts', true]),
OptBool.new('CHECK_PIPE', [false, 'Check for named pipe on vulnerable hosts', false])
])
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 ? 'x86 (32-bit)' : 'x64 (64-bit)'
end

def run_host(ip)
checkcode = Exploit::CheckCode::Unknown
details = {}

begin
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")

os = simple.client.peer_native_os.dup
details[:os] = os.dup
if status == 'STATUS_INSUFF_SERVER_RESOURCES'
if datastore['CHECK_ARCH']
case dcerpc_getarch
when ARCH_X86
os << ' x86 (32-bit)'
details[:arch] = ARCH_X86
when ARCH_X64
os << ' x64 (64-bit)'
details[:arch] = ARCH_X64
end
end

print_good("Host is likely VULNERABLE to MS17-010! - #{os}")

checkcode = Exploit::CheckCode::Vulnerable(details: details)

report_vuln(
host: ip,
port: rport, # A service is necessary for the analyze command
name: self.name,
refs: self.references,
info: "STATUS_INSUFF_SERVER_RESOURCES for FID 0 against IPC$ - #{os}"
)

# vulnerable to MS17-010, check for DoublePulsar infection
if datastore['CHECK_DOPU']
code, signature1, signature2 = do_smb_doublepulsar_probe(tree_id)

if code == 0x51
xor_key = calculate_doublepulsar_xor_key(signature1).to_s(16).upcase
arch = calculate_doublepulsar_arch(signature2)
print_warning("Host is likely INFECTED with DoublePulsar! - Arch: #{arch}, XOR Key: 0x#{xor_key}")
report_vuln(
host: ip,
name: "MS17-010 DoublePulsar Infection",
refs: self.references,
info: "MultiPlexID += 0x10 on Trans2 request - Arch: #{arch}, XOR Key: 0x#{xor_key}"
)
end
end

if datastore['CHECK_PIPE']
pipe_name, _ = check_named_pipes(return_first: true)
if pipe_name
print_good("Named pipe found: #{pipe_name}")

report_note(
host: ip,
port: rport,
proto: 'tcp',
sname: 'smb',
type: 'MS17-010 Named Pipe',
data: pipe_name
)
end
end
elsif status == "STATUS_ACCESS_DENIED" or status == "STATUS_INVALID_HANDLE"
# STATUS_ACCESS_DENIED (Windows 10) and STATUS_INVALID_HANDLE (others)
print_error("Host does NOT appear vulnerable.")
else
print_error("Unable to properly detect if host is vulnerable.")
end

unless (fp_match = Recog::Nizer.match('smb.native_os', simple.client.peer_native_os)).nil?
report_host(
host: rhost,
arch: details[:arch],
os_family: 'Windows',
os_flavor: fp_match['os.edition'],
os_name: fp_match['os.product']
)
end

rescue ::Interrupt
print_status("Exiting on interrupt.")
raise $!
rescue ::Rex::Proto::SMB::Exceptions::LoginError
print_error("An SMB Login Error occurred while connecting to the IPC$ tree.")
rescue ::Exception => e
print_error("#{e.class}: #{e.message}")
ensure
disconnect
end

checkcode
end

def do_smb_setup_tree(ipc_share)
connect(versions: [1])

# logon as user \
simple.login(datastore['SMBName'], datastore['SMBUser'], datastore['SMBPass'], datastore['SMBDomain'])

# connect to IPC$
simple.connect(ipc_share)

# 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'], pkt['SMB'].v['Signature2']
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)
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])

# convert error code to string
code = pkt['SMB'].v['ErrorClass']
smberr = Rex::Proto::SMB::Exceptions::ErrorCode.new

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)
# make a raw transaction packet
pkt = Rex::Proto::SMB::Constants::SMB_TRANS_PKT.make_struct
simple.client.smb_defaults(pkt['Payload']['SMB'])

# opcode 0x23 = PeekNamedPipe, fid = 0
setup = "\x23\x00\x00\x00"
setup_count = 2 # 2 words
trans = "\\PIPE\\\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_TRANSACTION
pkt['Payload']['SMB'].v['Flags1'] = 0x18
pkt['Payload']['SMB'].v['Flags2'] = 0x2801 # 0xc803 would unicode
pkt['Payload']['SMB'].v['TreeID'] = tree_id
pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count
pkt['Payload'].v['ParamCountMax'] = 0xffff
pkt['Payload'].v['DataCountMax'] = 0xffff
pkt['Payload'].v['ParamOffset'] = param_offset
pkt['Payload'].v['DataOffset'] = data_offset

# actual magic: PeekNamedPipe FID=0, \PIPE\
pkt['Payload'].v['SetupCount'] = setup_count
pkt['Payload'].v['SetupData'] = setup
pkt['Payload'].v['Payload'] = trans

pkt.to_s
end
end
Login or Register to add favorites

File Archive:

November 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Nov 1st
    30 Files
  • 2
    Nov 2nd
    0 Files
  • 3
    Nov 3rd
    0 Files
  • 4
    Nov 4th
    12 Files
  • 5
    Nov 5th
    44 Files
  • 6
    Nov 6th
    18 Files
  • 7
    Nov 7th
    9 Files
  • 8
    Nov 8th
    8 Files
  • 9
    Nov 9th
    3 Files
  • 10
    Nov 10th
    0 Files
  • 11
    Nov 11th
    14 Files
  • 12
    Nov 12th
    20 Files
  • 13
    Nov 13th
    63 Files
  • 14
    Nov 14th
    18 Files
  • 15
    Nov 15th
    8 Files
  • 16
    Nov 16th
    0 Files
  • 17
    Nov 17th
    0 Files
  • 18
    Nov 18th
    18 Files
  • 19
    Nov 19th
    7 Files
  • 20
    Nov 20th
    13 Files
  • 21
    Nov 21st
    6 Files
  • 22
    Nov 22nd
    48 Files
  • 23
    Nov 23rd
    0 Files
  • 24
    Nov 24th
    0 Files
  • 25
    Nov 25th
    60 Files
  • 26
    Nov 26th
    0 Files
  • 27
    Nov 27th
    0 Files
  • 28
    Nov 28th
    0 Files
  • 29
    Nov 29th
    0 Files
  • 30
    Nov 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2024 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close