metasploit-framework/lib/msf/core/exploit/smb.rb

216 lines
6.1 KiB
Ruby

require 'rex/proto/smb'
require 'rex/proto/dcerpc'
module Msf
###
#
# 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 Exploit::Remote::SMB
include Exploit::Remote::Tcp
SIMPLE = Rex::Proto::SMB::SimpleClient
XCEPT = Rex::Proto::SMB::Exceptions
# 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::Proto::DCERPC::NDR
def initialize(info = {})
super
register_advanced_options(
[
OptBool.new('SMBPipeEvasion', [ true, 'Enable segmented read/writes for SMB Pipes', 'False']),
OptInt.new('SMBPipeWriteMinSize', [ true, 'Minimum buffer size for pipe writes', 1]),
OptInt.new('SMBPipeWriteMaxSize', [ true, 'Maximum buffer size for pipe writes', 1024]),
OptInt.new('SMBPipeReadMinSize', [ true, 'Minimum buffer size for pipe reads', 1]),
OptInt.new('SMBPipeReadMaxSize', [ true, 'Maximum buffer size for pipe reads', 1024]),
], Msf::Exploit::Remote::SMB)
register_options(
[
Opt::RHOST,
OptInt.new('RPORT', [ true, 'Set the SMB service port', 445]),
OptBool.new('SMBDirect', [ true, '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('SMBDOM', [ false, 'The Windows domain to use for authentication', 'WORKGROUP']),
OptString.new('SMBNAME', [ true, 'The NetBIOS hostname (required for port 139 connections)', '*SMBSERVER'])
], Msf::Exploit::Remote::SMB)
end
def connect()
disconnect()
super
self.simple = SIMPLE.new(self.sock, datastore['SMBDirect'])
# setup pipe evasion foo
if datastore['SMBPipeEvasion']
# XXX - insert code to change the instance of the read/write functions to do segmentation
end
# setup smb evasion foo XXX - should be broken out to seperate bits instead of by level
if (datastore['SMBEvasion'])
self.simple.client.evasion_level = datastore['SMBEvasion'].to_i
print_status("Using SMB evasion level #{self.simple.client.evasion_level}")
end
end
# Convert a standard ASCII string to 16-bit Unicode
def unicode (str)
Rex::Text.to_unicode(str)
end
# This method establishes a SMB session over the default socket
def smb_login
simple.login(
datastore['SMBNAME'],
datastore['SMBUSER'],
datastore['SMBPASS'],
datastore['SMBDOM']
)
simple.connect('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
def smb_dcerpc_bind (pipe, handle)
self.dcerpc_socket = pipe
self.dcerpc_bind(handle)
end
# This method calls a DCERPC procedure over a SMB pipe
def smb_dcerpc_call(fid, func, stub = '')
# Create the request packets
pkts = dcerpc_make_call(func, stub)
if (pkts == nil)
return
end
# Verify that the socket exists
if (sock == nil)
return
end
print_status("Sending " + pkts.size.to_s + " DCERPC fragments...")
pkts.each { |chunk|
smb_dcerpc_pipe_write(fid, chunk)
}
data = smb_dcerpc_pipe_read(fid)
return DCERPCResponse.new(data) if data.length > 0
end
# This method provides a mechanism for executing DCERPC transactions
# using READ/WRITE SMB commands (vs TransactNP)
def smb_dcerpc_pipe_writeread(fid, request)
smb_dcerpc_pipe_write(fid, request)
smb_dcerpc_pipe_read(fid)
end
# This method writes out a DCERPC transaction in random size
# blocks with random offsets (offsets are ignored by the server)
def smb_dcerpc_pipe_write(fid, request)
pipe_write_min = datastore['SMBPipeWriteMinSize']
pipe_write_max = datastore['SMBPipeWriteMaxSize']
if (pipe_write_min > pipe_write_max)
pipe_write_min = pipe_write_max
end
# Write the request out in random chunk sizes
while (request.length > 0)
wsize = rand(pipe_write_max - pipe_write_min) + pipe_write_min
fid.write( request.slice!(0, wsize), rand(1024)+1 )
end
end
# This method reads a DCERPC transaction response in random size
# blocks with random offsets (offsets are ignored by the server)
# XXX - If we read on a pipe with no data, our client code times out
# waiting for the response. A problem occurs when data becomes available
# at a later time - a read response is sent back from the server, which
# throws off the serialed command processsing code in the client. So, to
# avoid significant problems, never read on a pipe when you know there
# is no data left. This command will become obsolete once command queueing
# is implemented in the client.
def smb_dcerpc_pipe_read(fid)
pipe_read_min = datastore['SMBPipeReadMinSize']
pipe_read_max = datastore['SMBPipeReadMaxSize']
if (pipe_read_min > pipe_read_max)
pipe_read_min = pipe_read_max
end
data = ''
# Read the response back a few bytes a time
begin
rsize = nil
while(true)
bsize = rand(pipe_read_max - pipe_read_min) + pipe_read_min
t = (fid.read(bsize, rand(1024)+1))
break if t.length == 0
data << t
# If we have at least 10 bytes of data, check the DCERPC
# header and determine how many bytes are left to go.
# We do this to avoid a read on an empty pipe.
if (rsize.nil? and data.length >= 10)
r = DCERPCResponse.new(data.slice(0,10))
rsize = r.frag_len
end
# Quit reading once the full response is read
break if data and rsize and data.length >= rsize
end
rescue XCEPT::NoReply
end
return data
end
def smb_hostname
datastore['SMBNAME'] || '*SMBSERVER'
end
attr_accessor :simple
end
end