lib/msf/core/exploit/remote/smb/client.rb
# -*- coding: binary -*-
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
# These constants are unused here, but may be used in some code that
# includes this. Local definitions should be preferred.
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', ''], fallbacks: ['USERNAME']),
OptString.new('SMBPass', [ false, 'The password for the specified username', ''], fallbacks: ['PASSWORD']),
OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication', '.'], fallbacks: ['DOMAIN']),
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']),
OptString.new(
'SMB::ProtocolVersion',
[
true,
'One or a list of coma-separated SMB protocol versions to '\
'negotiate (e.g. "1" or "1,2" or "2,3,1")', '1,2,3'
],
regex: '^[123](?:,[123])*$'
),
OptBool.new(
'SMB::AlwaysEncrypt',
[
true,
'Enforces encryption even if the server does not require it (SMB3.x only). '\
'Note that when it is set to false, the SMB client will still '\
'encrypt the communication if the server requires it',
true
]
)
], 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, versions: [], backend: nil)
if versions.nil? || versions.empty?
versions = datastore['SMB::ProtocolVersion'].split(',').map(&:strip).reject(&:blank?).map(&:to_i)
# if the user explicitly set the protocol version to 1, still use ruby_smb
backend ||= :ruby_smb if versions == [1]
end
disconnect() if global
s = super(global, {'SSL' => false})
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 = Rex::Proto::SMB::SimpleClient.new(s, direct, versions, always_encrypt: datastore['SMB::AlwaysEncrypt'], backend: backend)
# 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)
# Override the default RubySMB capabilities with Kerberos authentication
if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS
fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank?
fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank?
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes'])
fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::SMB.new(
host: datastore['DomainControllerRhost'].blank? ? nil : datastore['DomainControllerRhost'],
hostname: datastore['Smb::Rhostname'],
proxies: datastore['Proxies'],
realm: datastore['SMBDomain'],
username: datastore['SMBUser'],
password: datastore['SMBPass'],
framework: framework,
framework_module: self,
cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'],
ticket_storage: kerberos_ticket_storage,
offered_etypes: offered_etypes
)
simple_client.client.extend(Msf::Exploit::Remote::SMB::Client::KerberosAuthentication)
simple_client.client.kerberos_authenticator = kerberos_authenticator
end
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}
)
# XXX: Any reason to connect to the IPC$ share in this method?
simple_client.connect("\\\\#{datastore['RHOST']}\\IPC$")
end
# This method returns the native operating system of the peer
def smb_peer_os
unless self.simple.negotiated_smb_version == 1
print_warning("peer_native_os is only available with SMB1 (current version: SMB#{self.simple.negotiated_smb_version})")
end
self.simple.client.peer_native_os
end
# This method returns the native lanman version of the peer
def smb_peer_lm
unless self.simple.negotiated_smb_version == 1
print_warning("peer_native_lm is only available with SMB1 (current version: SMB#{self.simple.negotiated_smb_version})")
end
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, read: true, write: false)
self.simple.open(path, perm, datastore['SMB::ChunkSize'], read: read, write: write)
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, 'o')
rescue RubySMB::Error::UnexpectedStatusCode => e
found = false
rescue Rex::Proto::SMB::Exceptions::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
# native_lm/native_os is only available with SMB1
vprint_status('Force SMB1 since SMB fingerprint needs native_lm/native_os information')
connect(versions: [1])
# 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 => e
dlog("Error with SMB fingerprint: #{e.message}")
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, RubySMB::Error::RubySMBError => 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
# Retrieve a list of all shares using any available method
def smb_netshareenumall
begin
return smb_srvsvc_netshareenumall
rescue Rex::Proto::SMB::Exceptions::ErrorCode, RubySMB::Error::RubySMBError => e
vprint_error("Warning: NetShareEnumAll failed via Server Service: #{e}")
return [] if self.simple.client.is_a?(RubySMB::Client)
vprint_error("Falling back to LANMAN")
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, RubySMB::Error::RubySMBError => 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 = smb_lookup_share_type(types[t])
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
# XXX: #trans is not supported by RubySMB
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