841 lines
27 KiB
Ruby
841 lines
27 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'rex/proto/smb'
|
|
require 'rex/proto/ntlm'
|
|
require 'rex/proto/dcerpc'
|
|
require 'rex/encoder/ndr'
|
|
require 'recog'
|
|
|
|
module Msf
|
|
module Exploit::Remote::SMB
|
|
# This mixin provides utility methods for interacting with a SMB/CIFS service on
|
|
# a remote machine. These methods may generally be useful in the context of
|
|
# exploitation. This mixin extends the Tcp exploit mixin. Only one SMB
|
|
# service can be accessed at a time using this class.
|
|
module Client
|
|
|
|
include Msf::Exploit::Remote::Tcp
|
|
include Msf::Exploit::Remote::NTLM::Client
|
|
|
|
SIMPLE = Rex::Proto::SMB::SimpleClient
|
|
XCEPT = Rex::Proto::SMB::Exceptions
|
|
CONST = Rex::Proto::SMB::Constants
|
|
|
|
|
|
# Alias over the Rex DCERPC protocol modules
|
|
DCERPCPacket = Rex::Proto::DCERPC::Packet
|
|
DCERPCClient = Rex::Proto::DCERPC::Client
|
|
DCERPCResponse = Rex::Proto::DCERPC::Response
|
|
DCERPCUUID = Rex::Proto::DCERPC::UUID
|
|
NDR = Rex::Encoder::NDR
|
|
|
|
def initialize(info = {})
|
|
super
|
|
|
|
register_evasion_options(
|
|
[
|
|
OptBool.new('SMB::pipe_evasion', [ true, 'Enable segmented read/writes for SMB Pipes', false]),
|
|
OptInt.new('SMB::pipe_write_min_size', [ true, 'Minimum buffer size for pipe writes', 1]),
|
|
OptInt.new('SMB::pipe_write_max_size', [ true, 'Maximum buffer size for pipe writes', 1024]),
|
|
OptInt.new('SMB::pipe_read_min_size', [ true, 'Minimum buffer size for pipe reads', 1]),
|
|
OptInt.new('SMB::pipe_read_max_size', [ true, 'Maximum buffer size for pipe reads', 1024]),
|
|
OptInt.new('SMB::pad_data_level', [ true, 'Place extra padding between headers and data (level 0-3)', 0]),
|
|
OptInt.new('SMB::pad_file_level', [ true, 'Obscure path names used in open/create (level 0-3)', 0]),
|
|
OptInt.new('SMB::obscure_trans_pipe_level', [ true, 'Obscure PIPE string in TransNamedPipe (level 0-3)', 0]),
|
|
|
|
], Msf::Exploit::Remote::SMB::Client)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptBool.new('SMBDirect', [ false, 'The target port is a raw SMB service (not NetBIOS)', true ]),
|
|
OptString.new('SMBUser', [ false, 'The username to authenticate as', '']),
|
|
OptString.new('SMBPass', [ false, 'The password for the specified username', '']),
|
|
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.']),
|
|
OptString.new('SMBName', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER']),
|
|
OptBool.new('SMB::VerifySignature', [ true, "Enforces client-side verification of server response signatures", false]),
|
|
OptInt.new('SMB::ChunkSize', [ true, 'The chunk size for SMB segments, bigger values will increase speed but break NT 4.0 and SMB signing', 500]),
|
|
#
|
|
# Control the identified operating system of the client
|
|
#
|
|
OptString.new('SMB::Native_OS', [ true, 'The Native OS to send during authentication', 'Windows 2000 2195']),
|
|
OptString.new('SMB::Native_LM', [ true, 'The Native LM to send during authentication', 'Windows 2000 5.0']),
|
|
|
|
], Msf::Exploit::Remote::SMB::Client)
|
|
|
|
register_options(
|
|
[
|
|
Opt::RHOST,
|
|
OptPort.new('RPORT', [ true, 'The SMB service port', 445])
|
|
], Msf::Exploit::Remote::SMB::Client)
|
|
|
|
register_autofilter_ports([ 139, 445])
|
|
register_autofilter_services(%W{ netbios-ssn microsoft-ds })
|
|
end
|
|
|
|
# Override {Exploit::Remote::Tcp#connect} to setup an SMB connection
|
|
# and configure evasion options
|
|
#
|
|
# Also populates {#simple}.
|
|
#
|
|
# @param (see Exploit::Remote::Tcp#connect)
|
|
# @return (see Exploit::Remote::Tcp#connect)
|
|
def connect(global=true)
|
|
|
|
disconnect() if global
|
|
|
|
s = super(global)
|
|
self.sock = s if global
|
|
|
|
# Disable direct SMB when SMBDirect has not been set
|
|
# and the destination port is configured as 139
|
|
direct = smb_direct
|
|
if(datastore.default?('SMBDirect') and rport.to_i == 139)
|
|
direct = false
|
|
end
|
|
|
|
c = SIMPLE.new(s, direct)
|
|
|
|
# setup pipe evasion foo
|
|
if datastore['SMB::pipe_evasion']
|
|
# XXX - insert code to change the instance of the read/write functions to do segmentation
|
|
end
|
|
|
|
if (datastore['SMB::pad_data_level'])
|
|
c.client.evasion_opts['pad_data'] = datastore['SMB::pad_data_level']
|
|
end
|
|
|
|
if (datastore['SMB::pad_file_level'])
|
|
c.client.evasion_opts['pad_file'] = datastore['SMB::pad_file_level']
|
|
end
|
|
|
|
if (datastore['SMB::obscure_trans_pipe_level'])
|
|
c.client.evasion_opts['obscure_trans_pipe'] = datastore['SMB::obscure_trans_pipe_level']
|
|
end
|
|
|
|
self.simple = c if global
|
|
c
|
|
end
|
|
|
|
# Convert a standard ASCII string to 16-bit Unicode
|
|
def unicode(str)
|
|
Rex::Text.to_unicode(str)
|
|
end
|
|
|
|
# Establishes an SMB session over the default socket and connects to
|
|
# the IPC$ share.
|
|
#
|
|
# You should call {#connect} before calling this
|
|
#
|
|
# @param simple_client [Rex::Proto::SMB::SimpleClient] Optional SimpleClient instance to use
|
|
# @return [void]
|
|
def smb_login(simple_client = self.simple)
|
|
simple_client.login(
|
|
datastore['SMBName'],
|
|
datastore['SMBUser'],
|
|
datastore['SMBPass'],
|
|
datastore['SMBDomain'],
|
|
datastore['SMB::VerifySignature'],
|
|
datastore['NTLM::UseNTLMv2'],
|
|
datastore['NTLM::UseNTLM2_session'],
|
|
datastore['NTLM::SendLM'],
|
|
datastore['NTLM::UseLMKey'],
|
|
datastore['NTLM::SendNTLM'],
|
|
datastore['SMB::Native_OS'],
|
|
datastore['SMB::Native_LM'],
|
|
{:use_spn => datastore['NTLM::SendSPN'], :name => self.rhost}
|
|
)
|
|
simple_client.connect("\\\\#{datastore['RHOST']}\\IPC$")
|
|
end
|
|
|
|
|
|
# This method returns the native operating system of the peer
|
|
def smb_peer_os
|
|
self.simple.client.peer_native_os
|
|
end
|
|
|
|
# This method returns the native lanman version of the peer
|
|
def smb_peer_lm
|
|
self.simple.client.peer_native_lm
|
|
end
|
|
|
|
# This method opens a handle to an IPC pipe
|
|
def smb_create(pipe)
|
|
self.simple.create_pipe(pipe)
|
|
end
|
|
|
|
#the default chunk size of 48000 for OpenFile is not compatible when signing is enabled (and with some nt4 implementations)
|
|
#cause it looks like MS windows refuse to sign big packet and send STATUS_ACCESS_DENIED
|
|
#fd.chunk_size = 500 is better
|
|
def smb_open(path, perm)
|
|
self.simple.open(path, perm, datastore['SMB::ChunkSize'])
|
|
end
|
|
|
|
def smb_hostname
|
|
datastore['SMBName'] || '*SMBSERVER'
|
|
end
|
|
|
|
def smb_direct
|
|
datastore['SMBDirect']
|
|
end
|
|
|
|
def domain
|
|
datastore['SMBDomain']
|
|
end
|
|
|
|
def smbhost
|
|
if domain == "."
|
|
"#{rhost}:#{rport}"
|
|
else
|
|
"#{rhost}:#{rport}|#{domain}"
|
|
end
|
|
end
|
|
|
|
# If the username contains a / slash, then
|
|
# split it as a domain/username. NOTE: this
|
|
# is predicated on forward slashes, and not
|
|
# Microsoft's backwards slash convention.
|
|
def domain_username_split(user)
|
|
return user if(user.nil? || user.empty?)
|
|
if !user[/\//] # Only /, not \!
|
|
return [nil,user]
|
|
else
|
|
return user.split("/",2)
|
|
end
|
|
end
|
|
|
|
def splitname(uname)
|
|
if datastore["PRESERVE_DOMAINS"]
|
|
d,u = domain_username_split(uname)
|
|
return u
|
|
else
|
|
return uname
|
|
end
|
|
end
|
|
|
|
# Whether a remote file exists
|
|
#
|
|
# @param file [String] Path to a file to remove, relative to the
|
|
# most-recently connected share
|
|
# @raise [Rex::Proto::SMB::Exceptions::ErrorCode]
|
|
def smb_file_exist?(file)
|
|
begin
|
|
fd = simple.open(file, 'ro')
|
|
rescue XCEPT::ErrorCode => e
|
|
# If attempting to open the file results in a "*_NOT_FOUND" error,
|
|
# then we can be sure the file is not there.
|
|
#
|
|
# Copy-pasted from smb/exceptions.rb to avoid the gymnastics
|
|
# required to pull them out of a giant inverted hash
|
|
#
|
|
# 0xC0000034 => "STATUS_OBJECT_NAME_NOT_FOUND",
|
|
# 0xC000003A => "STATUS_OBJECT_PATH_NOT_FOUND",
|
|
# 0xC0000225 => "STATUS_NOT_FOUND",
|
|
error_is_not_found = [ 0xC0000034, 0xC000003A, 0xC0000225 ].include?(e.error_code)
|
|
# If the server returns some other error, then there was a
|
|
# permissions problem or some other difficulty that we can't
|
|
# really account for and hope the caller can deal with it.
|
|
raise e unless error_is_not_found
|
|
found = !error_is_not_found
|
|
else
|
|
# There was no exception, so we know the file is openable
|
|
fd.close
|
|
found = true
|
|
end
|
|
|
|
found
|
|
end
|
|
|
|
# Remove remote file
|
|
#
|
|
# @param file (see #smb_file_exist?)
|
|
# @return [void]
|
|
def smb_file_rm(file)
|
|
fd = smb_open(file, 'ro')
|
|
fd.delete
|
|
end
|
|
|
|
|
|
#
|
|
# Fingerprinting methods
|
|
#
|
|
|
|
|
|
# Calls the EnumPrinters() function of the spooler service
|
|
def smb_enumprinters(flags, name, level, blen)
|
|
stub =
|
|
NDR.long(flags) +
|
|
(name ? NDR.uwstring(name) : NDR.long(0)) +
|
|
NDR.long(level) +
|
|
NDR.long(rand(0xffffffff)+1)+
|
|
NDR.long(blen) +
|
|
"\x00" * blen +
|
|
NDR.long(blen)
|
|
|
|
handle = dcerpc_handle(
|
|
'12345678-1234-abcd-ef00-0123456789ab', '1.0',
|
|
'ncacn_np', ["\\SPOOLSS"]
|
|
)
|
|
|
|
begin
|
|
dcerpc_bind(handle)
|
|
dcerpc.call(0x00, stub)
|
|
return dcerpc.last_response.stub_data
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Exception => e
|
|
return nil
|
|
end
|
|
end
|
|
|
|
# This method dumps the print provider strings from the spooler
|
|
def smb_enumprintproviders
|
|
resp = smb_enumprinters(8, nil, 1, 0)
|
|
return nil if not resp
|
|
rptr, tmp, blen = resp.unpack("V*")
|
|
|
|
resp = smb_enumprinters(8, nil, 1, blen)
|
|
return nil if not resp
|
|
|
|
bcnt,pcnt,stat = resp[-12, 12].unpack("VVV")
|
|
return nil if stat != 0
|
|
return nil if pcnt == 0
|
|
return nil if bcnt > blen
|
|
return nil if pcnt < 3
|
|
|
|
#
|
|
# The correct way, which leads to invalid offsets :-(
|
|
#
|
|
#providers = []
|
|
#
|
|
#0.upto(pcnt-1) do |i|
|
|
# flags,desc_o,name_o,comm_o = resp[8 + (i*16), 16].unpack("VVVV")
|
|
#
|
|
# #desc = read_unicode(resp,8+desc_o).gsub("\x00", '')
|
|
# #name = read_unicode(resp,8+name_o).gsub("\x00", '')
|
|
# #comm = read_unicode(resp,8+comm_o).gsub("\x00", '')
|
|
# #providers << [flags,desc,name,comm]
|
|
#end
|
|
#
|
|
#providers
|
|
|
|
return resp
|
|
|
|
end
|
|
|
|
# This method performs an extensive set of fingerprinting operations
|
|
def smb_fingerprint
|
|
fprint = {}
|
|
|
|
# Connect to the server if needed
|
|
if not self.simple
|
|
connect()
|
|
# The login method can throw any number of exceptions, we don't
|
|
# care since we still get the native_lm/native_os.
|
|
begin
|
|
smb_login()
|
|
rescue ::Rex::Proto::SMB::Exceptions::NoReply,
|
|
::Rex::Proto::SMB::Exceptions::ErrorCode,
|
|
::Rex::Proto::SMB::Exceptions::LoginError
|
|
end
|
|
end
|
|
|
|
fprint['native_os'] = smb_peer_os()
|
|
fprint['native_lm'] = smb_peer_lm()
|
|
|
|
# Leverage Recog for SMB native OS fingerprinting
|
|
fp_match = Recog::Nizer.match('smb.native_os', fprint['native_os']) || { }
|
|
|
|
os = fp_match['os.product'] || 'Unknown'
|
|
sp = fp_match['os.version'] || ''
|
|
|
|
# Metasploit prefers 'Windows 2003' vs 'Windows Server 2003'
|
|
if os =~ /^Windows Server/
|
|
os = os.sub(/^Windows Server/, 'Windows')
|
|
end
|
|
|
|
if fp_match['os.edition']
|
|
fprint['edition'] = fp_match['os.edition']
|
|
end
|
|
|
|
if fp_match['os.build']
|
|
fprint['build'] = fp_match['os.build']
|
|
end
|
|
|
|
if sp == ''
|
|
sp = smb_fingerprint_windows_sp(os)
|
|
end
|
|
|
|
lang = smb_fingerprint_windows_lang
|
|
|
|
fprint['os'] = os
|
|
fprint['sp'] = sp
|
|
fprint['lang'] = lang
|
|
|
|
fprint
|
|
end
|
|
|
|
#
|
|
# Determine the service pack level of a Windows system via SMB probes
|
|
#
|
|
def smb_fingerprint_windows_sp(os)
|
|
sp = ''
|
|
|
|
if (os == 'Windows XP')
|
|
# SRVSVC was blocked in SP2
|
|
begin
|
|
smb_create("\\SRVSVC")
|
|
sp = 'Service Pack 0 / 1'
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
if (e.error_code == 0xc0000022)
|
|
sp = 'Service Pack 2+'
|
|
end
|
|
end
|
|
end
|
|
|
|
if (os == 'Windows 2000' and sp.length == 0)
|
|
# LLSRPC was blocked in a post-SP4 update
|
|
begin
|
|
smb_create("\\LLSRPC")
|
|
sp = 'Service Pack 0 - 4'
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
if (e.error_code == 0xc0000022)
|
|
sp = 'Service Pack 4 with MS05-010+'
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Perform granular XP SP checks if LSARPC is exposed
|
|
#
|
|
if (os == 'Windows XP')
|
|
|
|
#
|
|
# Service Pack 2 added a range(0,64000) to opnum 0x22 in SRVSVC
|
|
# Credit to spoonm for first use of unbounded [out] buffers
|
|
#
|
|
handle = dcerpc_handle(
|
|
'4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
|
|
'ncacn_np', ["\\BROWSER"]
|
|
)
|
|
|
|
begin
|
|
dcerpc_bind(handle)
|
|
|
|
stub =
|
|
NDR.uwstring(Rex::Text.rand_text_alpha(rand(10)+1)) +
|
|
NDR.wstring(Rex::Text.rand_text_alpha(rand(10)+1)) +
|
|
NDR.long(64001) +
|
|
NDR.long(0) +
|
|
NDR.long(0)
|
|
|
|
dcerpc.call(0x22, stub)
|
|
sp = "Service Pack 0 / 1"
|
|
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
|
|
rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
|
|
rescue ::Rex::Proto::DCERPC::Exceptions::Fault
|
|
sp = "Service Pack 2+"
|
|
rescue ::Exception
|
|
end
|
|
|
|
|
|
#
|
|
# Service Pack 3 fixed information leaks via [unique][out] pointers
|
|
# Call SRVSVC::NetRemoteTOD() to return [out] [ref] [unique]
|
|
# Credit:
|
|
# Pointer leak is well known, but Immunity also covered in a paper
|
|
# Silent fix of pointer leak in SP3 and detection method by Rhys Kidd
|
|
#
|
|
handle = dcerpc_handle(
|
|
'4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0',
|
|
'ncacn_np', ["\\BROWSER"]
|
|
)
|
|
|
|
begin
|
|
dcerpc_bind(handle)
|
|
|
|
stub = NDR.uwstring(Rex::Text.rand_text_alpha(rand(8)+1))
|
|
resp = dcerpc.call(0x1c, stub)
|
|
|
|
if(resp and resp[0,4] == "\x00\x00\x02\x00")
|
|
sp = "Service Pack 3"
|
|
else
|
|
if(resp and sp =~ /Service Pack 2\+/)
|
|
sp = "Service Pack 2"
|
|
end
|
|
end
|
|
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
|
|
rescue ::Rex::Proto::SMB::Exceptions::ReadPacket
|
|
rescue ::Exception
|
|
end
|
|
end
|
|
|
|
sp
|
|
end
|
|
|
|
|
|
#
|
|
# Determine the native language pack of a Windows system via SMB probes
|
|
#
|
|
def smb_fingerprint_windows_lang
|
|
|
|
#
|
|
# Remote language detection via Print Providers
|
|
# Credit: http://immunityinc.com/downloads/Remote_Language_Detection_in_Immunity_CANVAS.odt
|
|
#
|
|
|
|
lang = 'Unknown'
|
|
|
|
sigs =
|
|
{
|
|
'English' =>
|
|
[
|
|
Rex::Text.to_unicode('Windows NT Remote Printers'),
|
|
Rex::Text.to_unicode('LanMan Print Services')
|
|
],
|
|
'Spanish' =>
|
|
[
|
|
Rex::Text.to_unicode('Impresoras remotas Windows NT'),
|
|
Rex::Text.to_unicode('Impresoras remotas de Windows NT')
|
|
],
|
|
'Italian' =>
|
|
[
|
|
Rex::Text.to_unicode('Stampanti remote di Windows NT'),
|
|
Rex::Text.to_unicode('Servizi di stampa LanMan')
|
|
],
|
|
'French' =>
|
|
[
|
|
Rex::Text.to_unicode('Imprimantes distantes NT'),
|
|
Rex::Text.to_unicode('Imprimantes distantes pour Windows NT'),
|
|
Rex::Text.to_unicode("Services d'impression LanMan")
|
|
],
|
|
'German' =>
|
|
[
|
|
Rex::Text.to_unicode('Remotedrucker')
|
|
],
|
|
'Portuguese - Brazilian' =>
|
|
[
|
|
Rex::Text.to_unicode('Impr. remotas Windows NT'),
|
|
Rex::Text.to_unicode('Impressoras remotas do Windows NT')
|
|
],
|
|
'Portuguese' =>
|
|
[
|
|
Rex::Text.to_unicode('Imp. remotas do Windows NT')
|
|
],
|
|
'Hungarian' =>
|
|
[
|
|
Rex::Text.to_unicode("\x54\xe1\x76\x6f\x6c\x69\x20\x6e\x79\x6f\x6d\x74\x61\x74\xf3\x6b")
|
|
],
|
|
'Finnish' =>
|
|
[
|
|
Rex::Text.to_unicode("\x45\x74\xe4\x74\x75\x6c\x6f\x73\x74\x69\x6d\x65\x74")
|
|
],
|
|
'Dutch' =>
|
|
[
|
|
Rex::Text.to_unicode('Externe printers voor NT')
|
|
],
|
|
'Danish' =>
|
|
[
|
|
Rex::Text.to_unicode('Fjernprintere')
|
|
],
|
|
'Swedish' =>
|
|
[
|
|
Rex::Text.to_unicode("\x46\x6a\xe4\x72\x72\x73\x6b\x72\x69\x76\x61\x72\x65")
|
|
],
|
|
'Polish' =>
|
|
[
|
|
Rex::Text.to_unicode('Zdalne drukarki')
|
|
],
|
|
'Czech' =>
|
|
[
|
|
Rex::Text.to_unicode("\x56\x7a\x64\xe1\x6c\x65\x6e\xe9\x20\x74\x69\x73\x6b\xe1\x72\x6e\x79")
|
|
],
|
|
'Turkish' =>
|
|
[
|
|
"\x59\x00\x61\x00\x7a\x00\x31\x01\x63\x00\x31\x01\x6c\x00\x61\x00\x72\x00"
|
|
],
|
|
'Japanese' =>
|
|
[
|
|
"\xea\x30\xe2\x30\xfc\x30\xc8\x30\x20\x00\xd7\x30\xea\x30\xf3\x30\xbf\x30"
|
|
],
|
|
'Chinese - Traditional' =>
|
|
[
|
|
"\xdc\x8f\x0b\x7a\x53\x62\x70\x53\x3a\x67"
|
|
],
|
|
'Chinese - Traditional / Taiwan' =>
|
|
[
|
|
"\x60\x90\xef\x7a\x70\x53\x68\x88\x5f\x6a",
|
|
],
|
|
'Korean' =>
|
|
[
|
|
"\xd0\xc6\xa9\xac\x20\x00\x04\xd5\xb0\xb9\x30\xd1",
|
|
],
|
|
'Russian' =>
|
|
[
|
|
"\x1f\x04\x40\x04\x38\x04\x3d\x04\x42\x04\x35\x04\x40\x04\x4b\x04\x20\x00\x43\x04\x34\x04\x30\x04\x3b\x04\x35\x04\x3d\x04\x3d\x04\x3e\x04\x33\x04\x3e\x04\x20\x00\x34\x04\x3e\x04\x41\x04\x42\x04\x43\x04\x3f\x04\x30\x04",
|
|
],
|
|
|
|
}
|
|
|
|
begin
|
|
prov = smb_enumprintproviders()
|
|
if(prov)
|
|
sigs.each_key do |k|
|
|
sigs[k].each do |s|
|
|
if(prov.index(s))
|
|
lang = k
|
|
break
|
|
end
|
|
break if lang != 'Unknown'
|
|
end
|
|
break if lang != 'Unknown'
|
|
end
|
|
|
|
if(lang == 'Unknown')
|
|
|
|
@fpcache ||= {}
|
|
mhash = ::Digest::MD5.hexdigest(prov[4,prov.length-4])
|
|
|
|
if(not @fpcache[mhash])
|
|
|
|
buff = "\n"
|
|
buff << "*** NEW FINGERPRINT: PLEASE SEND TO [ msfdev[at]metasploit.com ]\n"
|
|
buff << " VERS: $Revision$\n"
|
|
buff << " HOST: #{rhost}\n"
|
|
buff << " OS: #{os}\n"
|
|
buff << " SP: #{sp}\n"
|
|
|
|
prov.unpack("H*")[0].scan(/.{64}|.*/).each do |line|
|
|
next if line.length == 0
|
|
buff << " FP: #{line}\n"
|
|
end
|
|
|
|
prov.split(/\x00\x00+/n).each do |line|
|
|
line.gsub!("\x00",'')
|
|
line.strip!
|
|
next if line.length < 6
|
|
|
|
buff << " TXT: #{line}\n"
|
|
end
|
|
|
|
buff << "*** END FINGERPRINT\n"
|
|
|
|
print_line(buff)
|
|
|
|
@fpcache[mhash] = true
|
|
end
|
|
|
|
end
|
|
end
|
|
rescue ::Interrupt
|
|
raise $!
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode
|
|
end
|
|
lang
|
|
end
|
|
|
|
# Map an integer share type to a human friendly descriptor
|
|
def smb_lookup_share_type(val)
|
|
[ 'DISK', 'PRINTER', 'DEVICE', 'IPC', 'SPECIAL', 'TEMPORARY' ][val]
|
|
end
|
|
|
|
# Retrieve detailed information about a specific share using any available method
|
|
def smb_netsharegetinfo(share)
|
|
smb_srvsvc_netsharegetinfo(share)
|
|
end
|
|
|
|
# Retrieve detailed share dinformation via the NetShareGetInfo function in the Server Service
|
|
def smb_srvsvc_netsharegetinfo(share)
|
|
shares = []
|
|
simple.connect("\\\\#{rhost}\\IPC$")
|
|
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
|
|
begin
|
|
dcerpc_bind(handle)
|
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
vprint_error(e.message)
|
|
return []
|
|
end
|
|
|
|
stubdata =
|
|
NDR.uwstring("\\\\#{rhost}") +
|
|
NDR.wstring(share) +
|
|
NDR.long(2)
|
|
|
|
response = dcerpc.call(0x10, stubdata)
|
|
|
|
if ! response
|
|
raise RuntimeError, "Invalid DCERPC response: <empty>"
|
|
end
|
|
|
|
head = response.slice!(0, 40)
|
|
if head.length != 40
|
|
raise RuntimeError, "Invalid DCERPC response: not enough data"
|
|
end
|
|
|
|
share_info = {
|
|
share_type: head[12, 4].unpack('V').first,
|
|
permissions: head[20, 4].unpack('V').first,
|
|
max_users: head[24, 4].unpack('V').first,
|
|
}
|
|
|
|
idx = 0
|
|
|
|
[:share, :comment, :path, :password].each do |field|
|
|
field_info = response[idx, 12].unpack("V*")
|
|
break if field_info.length == 0
|
|
idx += 12
|
|
|
|
field_text = response[idx, field_info.first * 2]
|
|
share_info[ field ] = field_text.gsub("\x00", '')
|
|
idx += (field_info.first * 2)
|
|
idx += (idx % 4)
|
|
end
|
|
|
|
share_info
|
|
end
|
|
|
|
# Retreive a list of all shares using any available method
|
|
def smb_netshareenumall
|
|
begin
|
|
return smb_srvsvc_netshareenumall
|
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
vprint_error("Warning: NetShareEnumAll failed via Server Service, falling back to LANMAN: #{e}")
|
|
fail_with(Failure::NoTarget, "No matching target")
|
|
return smb_lanman_netshareenumall
|
|
end
|
|
end
|
|
|
|
# Retrieve a list of shares via the NetShareEnumAll function in the Server Service
|
|
def smb_srvsvc_netshareenumall
|
|
shares = []
|
|
simple.connect("\\\\#{rhost}\\IPC$")
|
|
handle = dcerpc_handle('4b324fc8-1670-01d3-1278-5a47bf6ee188', '3.0', 'ncacn_np', ["\\srvsvc"])
|
|
begin
|
|
dcerpc_bind(handle)
|
|
rescue Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
vprint_error(e.message)
|
|
return []
|
|
end
|
|
|
|
stubdata =
|
|
NDR.uwstring("\\\\#{rhost}") +
|
|
NDR.long(1) #level
|
|
|
|
ref_id = stubdata[0,4].unpack("V")[0]
|
|
ctr = [1, ref_id + 4 , 0, 0].pack("VVVV")
|
|
|
|
stubdata << ctr
|
|
stubdata << NDR.align(ctr)
|
|
stubdata << ["FFFFFFFF"].pack("H*")
|
|
stubdata << [ref_id + 8, 0].pack("VV")
|
|
response = dcerpc.call(0x0f, stubdata)
|
|
res = response.dup
|
|
win_error = res.slice!(-4, 4).unpack("V")[0]
|
|
|
|
if win_error != 0
|
|
raise RuntimeError, "Invalid DCERPC response: win_error = #{win_error}"
|
|
end
|
|
|
|
# Remove unused data
|
|
res.slice!(0,12) # level, CTR header, Reference ID of CTR
|
|
share_count = res.slice!(0, 4).unpack("V")[0]
|
|
res.slice!(0,4) # Reference ID of CTR1
|
|
share_max_count = res.slice!(0, 4).unpack("V")[0]
|
|
|
|
if share_max_count != share_count
|
|
raise RuntimeError, "Invalid DCERPC response: count != count max (#{share_count}/#{share_max_count})"
|
|
end
|
|
|
|
# ReferenceID / Type / ReferenceID of Comment
|
|
types = res.slice!(0, share_count * 12).scan(/.{12}/n).map{|a| a[4,2].unpack("v")[0]}
|
|
|
|
share_count.times do |t|
|
|
length, offset, max_length = res.slice!(0, 12).unpack("VVV")
|
|
if offset != 0
|
|
raise RuntimeError, "Invalid DCERPC response: offset != 0 (#{offset})"
|
|
end
|
|
|
|
if length != max_length
|
|
raise RuntimeError, "Invalid DCERPC response: length !=max_length (#{length}/#{max_length})"
|
|
end
|
|
name = res.slice!(0, 2 * length).gsub('\x00','')
|
|
res.slice!(0,2) if length % 2 == 1 # pad
|
|
|
|
comment_length, comment_offset, comment_max_length = res.slice!(0, 12).unpack("VVV")
|
|
|
|
if comment_offset != 0
|
|
raise RuntimeError, "Invalid DCERPC response: comment_offset != 0 (#{comment_offset})"
|
|
end
|
|
|
|
if comment_length != comment_max_length
|
|
raise RuntimeError, "Invalid DCERPC response: comment_length != comment_max_length (#{comment_length}/#{comment_max_length})"
|
|
end
|
|
|
|
comment = res.slice!(0, 2 * comment_length)
|
|
|
|
res.slice!(0,2) if comment_length % 2 == 1 # pad
|
|
|
|
name = Rex::Text.to_ascii(name).gsub("\x00", "")
|
|
s_type = Rex::Text.to_ascii(smb_lookup_share_type(types[t])).gsub("\x00", "")
|
|
comment = Rex::Text.to_ascii(comment).gsub("\x00", "")
|
|
|
|
shares << [ name, s_type, comment ]
|
|
end
|
|
|
|
shares
|
|
end
|
|
|
|
# Retrieve a list of shares via the NetShareEnumAll function in the LANMAN service
|
|
# This method can only return shares with names 12 bytes or less
|
|
def smb_lanman_netshareenumall
|
|
shares = []
|
|
begin
|
|
res = self.simple.client.trans(
|
|
"\\PIPE\\LANMAN",
|
|
(
|
|
[0x00].pack('v') +
|
|
"WrLeh\x00" +
|
|
"B13BWz\x00" +
|
|
[0x01, 65406].pack("vv")
|
|
))
|
|
rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e
|
|
vprint_error("Could not enumerate shares via LANMAN")
|
|
return []
|
|
end
|
|
if res.nil?
|
|
vprint_error("Could not enumerate shares via LANMAN")
|
|
return []
|
|
end
|
|
|
|
lerror, lconv, lentries, lcount = res['Payload'].to_s[
|
|
res['Payload'].v['ParamOffset'],
|
|
res['Payload'].v['ParamCount']
|
|
].unpack("v4")
|
|
|
|
data = res['Payload'].to_s[
|
|
res['Payload'].v['DataOffset'],
|
|
res['Payload'].v['DataCount']
|
|
]
|
|
|
|
0.upto(lentries - 1) do |i|
|
|
sname,tmp = data[(i * 20) + 0, 14].split("\x00")
|
|
stype = data[(i * 20) + 14, 2].unpack('v')[0]
|
|
scoff = data[(i * 20) + 16, 2].unpack('v')[0]
|
|
scoff -= lconv if lconv != 0
|
|
scomm,tmp = data[scoff, data.length - scoff].split("\x00")
|
|
shares << [ sname, smb_lookup_share_type(stype), scomm]
|
|
end
|
|
|
|
shares
|
|
end
|
|
|
|
# @return [Rex::Proto::SMB::SimpleClient]
|
|
attr_accessor :simple
|
|
end
|
|
end
|
|
end
|