\\MS17-010.py

This module requires Metasploit: http://metasploit.com/download

Current source: https://github.com/rapid7/metasploit-framework

auxiliary/scanner/smb/smb_ms_17_010

require ‘msf/core’

class MetasploitModule < Msf::Auxiliary

include Msf::Exploit::Remote::SMB::Client
include Msf::Exploit::Remote::SMB::Client::Authenticated

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.

    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 <[email protected]>' \],
  'References'     =>
    \[
      \[ 'CVE', '2017-0143'\],
      \[ 'CVE', '2017-0144'\],
      \[ 'CVE', '2017-0145'\],
      \[ 'CVE', '2017-0146'\],
      \[ 'CVE', '2017-0147'\],
      \[ 'CVE', '2017-0148'\],
      \[ 'MSB', 'MS17-010'\],
      \[ 'URL', 'https://technet.microsoft.com/en-us/library/security/ms17-010.aspx'\]
    \],
  'License'        => MSF\_LICENSE
))

end

def run_host(ip)
begin
status = do_smb_probe(ip)

  if status == "STATUS\_INSUFF\_SERVER\_RESOURCES"
    print\_warning("Host is likely VULNERABLE to MS17-010!")
    report\_vuln(
      host: ip,
      name: self.name,
      refs: self.references,
      info: 'STATUS\_INSUFF\_SERVER\_RESOURCES for FID 0 against IPC$'
    )
  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.")
  else
    print\_bad("Unable to properly detect if host is vulnerable.")
  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
  vprint\_error("#{e.class}: #{e.message}")
ensure
  disconnect
end

end

def do_smb_probe(ip)
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}")

# 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
status = smberr.get\_error(code)

print\_status("Received #{status} with FID = 0")
status

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