This commit adds support for implementing the SMBFileServer Module

within Rex, allowing exploit modules to create a payload to be sent
to an SMBFileServer instance. This can be useful in cases where
you would find DLL injection in an system which will read files
over a UNC share, or other instances where a payload can be delivered
over SMB.

This code borrows heavily from the ms13_071_theme module written
by Juan Vazquez, however I have performed a fair amount of protocol
analysis and debugging to provide support for delivering an arbitrary
MSF payload over UNC.
The main differences being the presence of functions to support:
 -SMB CMD Trans Query Path Info (Basic and Standard)
 - SMB CMD Trans Query File Info (Standard and Internal)

This code can be considered "alpha", as I have only implemented support
for the SMB functions discovered during development of an exploit of an
arbitrary DLL injection into a server performing a "LoadLibraryA" call.*
However, this provides a basis upon which additional SMB functions can
be implemented to extend delivery of payloads over SMB.

A separate commit will expose the SMBFileServer Module within
./lib/msf/core/exploit/smb.rb

* This exploit will be committed separately once a fix has been confirmed
by the vendor.
bug/bundler_fix
Matthew Hall 2014-03-07 15:00:45 +00:00
parent ebee365fce
commit d380435113
2 changed files with 800 additions and 0 deletions

View File

@ -5,4 +5,5 @@ require 'rex/proto/smb/evasions'
require 'rex/proto/smb/crypt'
require 'rex/proto/smb/utils'
require 'rex/proto/smb/client'
require 'rex/proto/smb/server'
require 'rex/proto/smb/simpleclient'

799
lib/rex/proto/smb/server.rb Normal file
View File

@ -0,0 +1,799 @@
# -*- coding: binary -*-
require 'rex/socket'
require 'rex/proto/smb'
require 'rex/text'
require 'rex/logging'
require 'rex/struct2'
require 'rex/proto/smb/constants'
require 'rex/proto/smb/utils'
require 'rex/proto/dcerpc'
module Rex
module Proto
module SMB
###
#
# Runtime extension of the SMB clients that connect to the server.
#
###
module ServerClient
#
# Initialize a new request instance.
#
def init_cli(server)
self.server = server
end
#
# Transmits a response and adds the appropriate headers.
#
def send_response(response)
# Send it off.
put(response.to_s)
end
#
# The current request context.
#
attr_accessor :request
#
# A reference to the server the client is associated with.
#
attr_accessor :server
end
##
#
# SMB Server class
#
##
class Server
# Read Write
attr_accessor :listen_port, :listen_host, :context
attr_accessor :listener
attr_accessor :process_id, :name, :ip, :port, :data
attr_accessor :user_id, :tree_id, :multiplex_id
attr_accessor :debugging
# Read Only
attr_reader :hi, :lo
CONST = Rex::Proto::SMB::Constants
UTILS = Rex::Proto::SMB::Utils
#
# Setup State
#
def initialize(port, listen_host, context = {})
self.listen_host = listen_host
self.listen_port = port
self.context = context
self.listener = nil
self.multiplex_id = rand(0xffff)
self.process_id = rand(0xffff)
@state = {}
end
#
# Debug
#
def dprint(msg)
return if not self.debugging
$stdout.puts "#{msg}"
end
#
# SMB server.
#
def alias
super || "SMBServer"
end
#
# Listens on the defined port and host and starts monitoring for clients.
#
def start
params = {
'LocalHost' => self.listen_host,
'LocalPort' => self.listen_port,
'Context' => self.context,
}
self.listener = Rex::Socket::TcpServer.create( params )
# Register callbacks
self.listener.on_client_connect_proc = Proc.new { |client|
on_client_connect(client)
}
self.listener.on_client_data_proc = Proc.new { |client|
on_client_data(client)
}
self.listener.start
end
#
# Terminates the monitor thread and turns off the listener.
#
def stop
self.listener.stop
self.listener.close
end
#
# Waits for the SMB service to terminate
#
def wait
self.listener.wait if self.listener
end
#
# Register globals
#
def register(unc, contents, exe_file, hi, lo)
@unc = unc
@exe_file = exe_file
@hi = hi
@lo = lo
@exe = contents
@flags2 = 0xc807 # c801 or c001
end
protected
def on_client_connect(client)
dprint("New SMB connection from #{client.peerhost}:#{client.peerport}")
smb_conn(client)
end
def on_client_data(client)
dprint("New data from #{client.peerhost}:#{client.peerport}")
smb_recv(client)
true
end
def smb_conn(c)
@state[c] = {:name => "#{c.peerhost}:#{c.peerport}", :ip => c.peerhost, :port => c.peerport}
end
def smb_stop(c)
@state.delete(c)
end
def smb_recv(c)
smb = @state[c]
data = c.get_once
return if not data
smb[:data] = data
while(smb[:data].length > 0)
return if smb[:data].length < 4
plen = smb[:data][2,2].unpack('n')[0]
return if smb[:data].length < plen+4
buff = smb[:data].slice!(0, plen+4)
pkt_nbs = CONST::NBRAW_PKT.make_struct
pkt_nbs.from_s(buff)
dprint("NetBIOS request from #{smb[:name]} #{pkt_nbs.v['Type']} #{pkt_nbs.v['Flags']} #{buff.inspect}")
# Check for a NetBIOS name request
if (pkt_nbs.v['Type'] == 0x81)
# Accept any name they happen to send
host_dst = UTILS.nbname_decode(pkt_nbs.v['Payload'][1,32]).gsub(/[\x00\x20]+$/n, '')
host_src = UTILS.nbname_decode(pkt_nbs.v['Payload'][35,32]).gsub(/[\x00\x20]+$/n, '')
smb[:nbdst] = host_dst
smb[:nbsrc] = host_src
dprint("NetBIOS session request from #{smb[:name]} (asking for #{host_dst} from #{host_src})")
c.write("\x82\x00\x00\x00")
next
end
# Cast this to a generic SMB structure
pkt = CONST::SMB_BASE_PKT.make_struct
pkt.from_s(buff)
# Only response to requests, ignore server replies
if (pkt['Payload']['SMB'].v['Flags1'] & 128 != 0)
dprint("Ignoring server response from #{smb[:name]}")
next
end
cmd = pkt['Payload']['SMB'].v['Command']
begin
smb_cmd_dispatch(cmd, c, buff)
rescue ::Interrupt
raise $!
rescue ::Exception => e
dprint("Error processing request from #{smb[:name]} (#{cmd}): #{e.class} #{e} #{e.backtrace}")
next
end
end
end
def smb_set_defaults(c, pkt)
smb = @state[c]
pkt['Payload']['SMB'].v['ProcessID'] = self.process_id.to_i
pkt['Payload']['SMB'].v['UserID'] = self.user_id.to_i
pkt['Payload']['SMB'].v['TreeID'] = self.tree_id.to_i
pkt['Payload']['SMB'].v['MultiplexID'] = self.multiplex_id.to_i
end
def smb_error(cmd, c, errorclass, esn = false)
# 0xc0000022 = Deny
# 0xc000006D = Logon_Failure
# 0x00000000 = Ignore
pkt = CONST::SMB_BASE_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = cmd
pkt['Payload']['SMB'].v['Flags1'] = 0x88
if esn
pkt['Payload']['SMB'].v['Flags2'] = 0xc801
else
pkt['Payload']['SMB'].v['Flags2'] = 0xc001
end
pkt['Payload']['SMB'].v['ErrorClass'] = errorclass
c.put(pkt.to_s)
end
def smb_cmd_dispatch(cmd, c, buff)
smb = @state[c]
dprint("Received command " + cmd.to_s(16) + " from #{smb[:name]}")
pkt = CONST::SMB_BASE_PKT.make_struct
pkt.from_s(buff)
#Record the IDs
self.process_id = pkt['Payload']['SMB'].v['ProcessID']
self.user_id = pkt['Payload']['SMB'].v['UserID']
self.tree_id = pkt['Payload']['SMB'].v['TreeID']
self.multiplex_id = pkt['Payload']['SMB'].v['MultiplexID']
case cmd
when CONST::SMB_COM_NEGOTIATE
smb_cmd_negotiate(c, buff)
when CONST::SMB_COM_SESSION_SETUP_ANDX
wordcount = pkt['Payload']['SMB'].v['WordCount']
if wordcount == 0x0D # It's the case for Share Security Mode sessions
dprint("[smb_cmd_session_setup] wordcount is: " + wordcount.to_s)
smb_cmd_session_setup(c, buff)
#elsif wordcount == 0x0C # Also Share Security Mode sessions with NTLMSSP
# dprint("[smb_cmd_ntlmssp_session_setup] wordcount is: " + wordcount.to_s)
# smb_cmd_ntlmssp_session_setup(c, buff)
else
dprint("SMB Capture - #{smb[:ip]} Unknown SMB_COM_SESSION_SETUP_ANDX request type , ignoring... ")
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
when CONST::SMB_COM_TRANSACTION2
smb_cmd_trans(c, buff)
when CONST::SMB_COM_NT_CREATE_ANDX
smb_cmd_create(c, buff)
when CONST::SMB_COM_READ_ANDX
smb_cmd_read(c, buff)
when CONST::SMB_COM_CLOSE
smb_cmd_close(c, buff)
else
dprint("SMB Capture - Ignoring request from #{smb[:name]} - #{smb[:ip]} (#{cmd})")
smb_error(cmd, c, CONST::SMB_STATUS_SUCCESS)
end
end
def smb_cmd_negotiate(c, buff)
pkt = CONST::SMB_NEG_PKT.make_struct
pkt.from_s(buff)
dialects = pkt['Payload'].v['Payload'].gsub(/\x00/, '').split(/\x02/).grep(/^\w+/)
dialect = dialects.index("NT LM 0.12") || dialects.length-1
pkt = CONST::SMB_NEG_RES_NT_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 17
pkt['Payload'].v['Dialect'] = dialect
pkt['Payload'].v['SecurityMode'] = 2 # SHARE Security Mode
#pkt['Payload'].v['SecurityMode'] = 3 # USER Security Mode
pkt['Payload'].v['MaxMPX'] = 50
pkt['Payload'].v['MaxVCS'] = 1
pkt['Payload'].v['MaxBuff'] = 16644
pkt['Payload'].v['MaxRaw'] = 65536
pkt['Payload'].v['SystemTimeLow'] = @lo
pkt['Payload'].v['SystemTimeHigh'] = @hi
pkt['Payload'].v['ServerTimeZone'] = 0x0
pkt['Payload'].v['SessionKey'] = 0
#pkt['Payload'].v['Capabilities'] = 0x8080f3fd
pkt['Payload'].v['Capabilities'] = 0xd4 # XXX: Capabilities is 0x8080f3fd. XXX: Bug if we support capabilities including NTLMSSP
pkt['Payload'].v['KeyLength'] = 8
pkt['Payload'].v['Payload'] = Rex::Text.rand_text_hex(8)
c.put(pkt.to_s)
end
def smb_cmd_ntlmssp_session_setup(c, buff)
# TODO: Can't be arsed to implement ntlmssp yet
dprint("Broken here...")
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 4
pkt['Payload'].v['AndX'] = 0xff
pkt['Payload'].v['Reserved1'] = 00
pkt['Payload'].v['AndXOffset'] = 0
pkt['Payload'].v['Action'] = 0 # Not Logged in as GUEST
pkt['Payload'].v['Payload'] =
Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature
tree_connect_response = ""
tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount
tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand
tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved
tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset
tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support
tree_connect_response << [0xff].pack("C") # Perms
tree_connect_response << [0x01].pack("C")
tree_connect_response << [0x1f].pack("C")
tree_connect_response << [0xff].pack("C")
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount
tree_connect_response << "A:\x00" # Service
tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters
# Fix the Netbios Session Service Message Length
# to have into account the tree_connect_response,
# need to do this because there isn't support for
# AndX still
my_pkt = pkt.to_s + tree_connect_response
original_length = my_pkt[2, 2].unpack("n").first
original_length = original_length + tree_connect_response.length
my_pkt[2, 2] = [original_length].pack("n")
c.put(my_pkt)
end
def smb_cmd_session_setup(c, buff)
pkt = CONST::SMB_SETUP_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 3
pkt['Payload'].v['AndX'] = 0x75
pkt['Payload'].v['Reserved1'] = 00
pkt['Payload'].v['AndXOffset'] = 96
pkt['Payload'].v['Action'] = 0x1 # Logged in as Guest
pkt['Payload'].v['Payload'] =
Rex::Text.to_unicode("Unix", 'utf-16be') + "\x00\x00" + # Native OS # Samba signature
Rex::Text.to_unicode("Samba 3.4.7", 'utf-16be') + "\x00\x00" + # Native LAN Manager # Samba signature
Rex::Text.to_unicode("WORKGROUP", 'utf-16be') + "\x00\x00\x00" + # Primary DOMAIN # Samba signature
tree_connect_response = ""
tree_connect_response << [7].pack("C") # Tree Connect Response : WordCount
tree_connect_response << [0xff].pack("C") # Tree Connect Response : AndXCommand
tree_connect_response << [0].pack("C") # Tree Connect Response : Reserved
tree_connect_response << [0].pack("v") # Tree Connect Response : AndXOffset
tree_connect_response << [0x1].pack("v") # Tree Connect Response : Optional Support
tree_connect_response << [0xff].pack("C") # Perms
tree_connect_response << [0x01].pack("C")
tree_connect_response << [0x1f].pack("C")
tree_connect_response << [0xff].pack("C")
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [0].pack("v") # Tree Connect Response : Word Parameter
tree_connect_response << [13].pack("v") # Tree Connect Response : ByteCount
tree_connect_response << "A:\x00" # Service
tree_connect_response << "#{Rex::Text.to_unicode("NTFS")}\x00\x00" # Extra byte parameters
# Fix the Netbios Session Service Message Length
# to have into account the tree_connect_response,
# need to do this because there isn't support for
# AndX still
my_pkt = pkt.to_s + tree_connect_response
original_length = my_pkt[2, 2].unpack("n").first
original_length = original_length + tree_connect_response.length
my_pkt[2, 2] = [original_length].pack("n")
c.put(my_pkt)
end
def smb_cmd_create(c, buff)
pkt = CONST::SMB_CREATE_PKT.make_struct
pkt.from_s(buff)
# Tries to do CREATE and X
payload = pkt['Payload'].v['Payload'].gsub(/\x00/, '').gsub(/.*\\/, '\\')
file = Rex::Text.to_unicode(@exe_file)
length = pkt['Payload'].v['Payload'].length
dprint("[create_and_x] Payload is: " + payload)
dprint("[create_and_x] Payload length is: " + payload.length.to_s)
fname = @exe_file.length + 1 # Add the "\"
if length >= fname
# Asks for something other than a directory, like our exploit
dprint("[create_and_x] Sending file response: " + file + " with length: " + @exe.length.to_s)
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 42
pkt['Payload'].v['AndX'] = 0xff # no further commands
pkt['Payload'].v['OpLock'] = 0x3 # Grant Oplock on File
# No need to track fid here, we're just offering one file
pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0
pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened
pkt['Payload'].v['CreateTimeLow'] = @lo
pkt['Payload'].v['CreateTimeHigh'] = @hi
pkt['Payload'].v['AccessTimeLow'] = @lo
pkt['Payload'].v['AccessTimeHigh'] = @hi
pkt['Payload'].v['WriteTimeLow'] = @lo
pkt['Payload'].v['WriteTimeHigh'] = @hi
pkt['Payload'].v['ChangeTimeLow'] = @lo
pkt['Payload'].v['ChangeTimeHigh'] = @hi
pkt['Payload'].v['Attributes'] = 0x20 # Not an archive
pkt['Payload'].v['AllocLow'] = 1048576 # 1Mb
pkt['Payload'].v['AllocHigh'] = 0
pkt['Payload'].v['EOFLow'] = @exe.length
pkt['Payload'].v['EOFHigh'] = 0
pkt['Payload'].v['FileType'] = 0
pkt['Payload'].v['IPCState'] = 0x7
pkt['Payload'].v['IsDirectory'] = 0
elsif length < fname
# Asks for a directory
dprint("[create_and_x] Sending directory response")
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 42
pkt['Payload'].v['AndX'] = 0xff # no further commands
pkt['Payload'].v['OpLock'] = 0 # Deny OpLock on Directory
# No need to track fid here, we're just offering one file
pkt['Payload'].v['FileID'] = rand(0x7fff) + 1 # To avoid fid = 0
pkt['Payload'].v['Action'] = 0x1 # The file existed and was opened
pkt['Payload'].v['CreateTimeLow'] = @lo
pkt['Payload'].v['CreateTimeHigh'] = @hi
pkt['Payload'].v['AccessTimeLow'] = @lo
pkt['Payload'].v['AccessTimeHigh'] = @hi
pkt['Payload'].v['WriteTimeLow'] = @lo
pkt['Payload'].v['WriteTimeHigh'] = @hi
pkt['Payload'].v['ChangeTimeLow'] = @lo
pkt['Payload'].v['ChangeTimeHigh'] = @hi
pkt['Payload'].v['Attributes'] = 0x10 # Ordinary dir
pkt['Payload'].v['AllocLow'] = 0
pkt['Payload'].v['AllocHigh'] = 0
pkt['Payload'].v['EOFLow'] = 0
pkt['Payload'].v['EOFHigh'] = 0
pkt['Payload'].v['FileType'] = 0
pkt['Payload'].v['IPCState'] = 0x7
pkt['Payload'].v['IsDirectory'] = 1
end
if length >= 1
connect_response = ""
# GUID
connect_response << ([0].pack("C") * 16)
# File ID
connect_response << ([0].pack("C") * 8)
# Access Rights
connect_response << [0xff].pack("C")
connect_response << [0x01].pack("C")
connect_response << [0x1f].pack("C")
connect_response << [0].pack("C")
connect_response << ([0].pack("C") * 4) # Guest access
connect_response << ([0].pack("C") * 2) # Byte Count
my_pkt = pkt.to_s + connect_response
original_length = my_pkt[2, 2].unpack("n").first
original_length = original_length + connect_response.length
my_pkt[2, 2] = [original_length].pack("n")
c.put(my_pkt)
else
pkt = CONST::SMB_CREATE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
pkt['Payload']['SMB'].v['ErrorClass'] = 0xC0000034 # OBJECT_NAME_NOT_FOUND
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
c.put(pkt.to_s)
end
end
def smb_cmd_close(c, buff)
pkt = CONST::SMB_CLOSE_PKT.make_struct
pkt.from_s(buff)
pkt = CONST::SMB_CLOSE_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 0
c.put(pkt.to_s)
end
def smb_cmd_read(c, buff)
pkt = CONST::SMB_READ_PKT.make_struct
pkt.from_s(buff)
offset = pkt['Payload'].v['Offset']
length = pkt['Payload'].v['MaxCountLow']
pkt = CONST::SMB_READ_RES_PKT.make_struct
smb_set_defaults(c, pkt)
dprint("Sending File! Offset: " + offset.to_s + " Length: " + length.to_s)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 12
pkt['Payload'].v['AndX'] = 0xff # no more commands
pkt['Payload'].v['Remaining'] = 0xffff
pkt['Payload'].v['DataLenLow'] = length
pkt['Payload'].v['DataOffset'] = 59
pkt['Payload'].v['DataLenHigh'] = 0
pkt['Payload'].v['Reserved3'] = 0
pkt['Payload'].v['Reserved4'] = 0x0a
pkt['Payload'].v['ByteCount'] = length
pkt['Payload'].v['Payload'] = @exe[offset, length]
c.put(pkt.to_s)
end
def bin_to_hex(s)
s.unpack('H*').first
end
def hex_to_bin(s)
s.scan(/../).map { |x| x.hex }.pack('c*')
end
def smb_cmd_trans(c, buff)
# Client socket is c
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
sub_command = pkt['Payload'].v['SetupData'].unpack("v").first
dprint("Command is: " + sub_command.to_s)
ar = bin_to_hex(buff).to_s
mdc = ar[86..89]
loi = ar[144..147] # LOI is random I think, its MDC that counts?
case sub_command
when 0x24 # QUERY_FILE_INFO
dprint("[query_file_info_24]")
#smb_cmd_trans_query_file_info_standard(c, buff)
# path info works here
smb_cmd_trans_query_path_info_standard(c, buff)
when 0x7 # QUERY_FILE_INFO
dprint("[query_file_info_7]")
# path info works here
smb_cmd_trans_query_path_info_standard(c, buff)
when 0x5 # QUERY_PATH_INFO
dprint("[query_path_info]")
#dprint("LOI is: " + loi)
dprint("MDC is: " + mdc)
case mdc # MAX DATA COUNT
when '2800'
# Basic is 1004 (ec03) (MDC = 40 / 2800 hex)
dprint("[query_path_info_basic]")
smb_cmd_trans_query_path_info_basic(c, buff)
when '1800', '0201'
# Standard is 1005 (ed03) (MDC = 24 / 1800 hex) or 258 (0201)
dprint("[query_path_info_standard]")
smb_cmd_trans_query_path_info_standard(c, buff)
when '0800'
# Internal File info is 1006 (ee03) (MDC = 8 / 0800 hex)
dprint("[query_file_info_basic]")
smb_cmd_trans_query_file_info_standard(c, buff)
else
dprint("Unknown MDC - Sending to [query_path_info_standard]: " + mdc.to_s)
smb_cmd_trans_query_path_info_standard(c, buff)
end
when 0x1 # FIND_FIRST2
dprint("find_first2")
smb_cmd_trans_find_first2(c, buff)
else
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['ErrorClass'] = 0xc0000225 # NT_STATUS_NOT_FOUND
c.put(pkt.to_s)
end
end
# Internal
def smb_cmd_trans_query_file_info_standard(c, buff)
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '').strip
file = Rex::Text.to_unicode(@exe_file)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 2
pkt['Payload'].v['DataCountTotal'] = 8
pkt['Payload'].v['ParamCount'] = 2
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 8
pkt['Payload'].v['DataOffset'] = 60
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# QUERY_FILE Parameters
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Padding
# QUERY_FILE_INFO Data
"\x95\x1c\x02\x00\x00\x00\x00\x00"
# Index number (can be random?) Or Allocation Size?
my_pkt = pkt.to_s
original_length = my_pkt[2, 2].unpack("n").first
original_length = original_length + 8
my_pkt[2, 2] = [original_length].pack("n")
new_length = my_pkt[2, 2].unpack("n").first
#c.put(my_pkt)
c.put(pkt.to_s)
end
# Standard
def smb_cmd_trans_query_path_info_standard(c, buff)
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
payload = pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '').strip
file = Rex::Text.to_unicode(@exe_file)
length = pkt['Payload'].v['SetupData'].length
dprint("[query_info_standard] Payload length: " + length.to_s)
dprint("[query_info_standard] File name length: " + @exe_file.length.to_s)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
if length >= @exe_file.length
# Its asking for the file
attrib1 = "\x20\x00\x00\x00" # File attributes => file
attrib2 = "\x00" # IsFile
dprint("[query_info_standard] Sending file response: " + file + " with length: " + @exe.length.to_s)
else
# if QUERY_PATH_INFO_PARAMETERS doesn't include a file name,
# return a Directory answer
attrib1 = "\x10\x00\x00\x00" # File attributes => directory
attrib2 = "\x01" # IsDir
dprint("[query_info_standard] Sending directory response")
end
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 2
pkt['Payload'].v['DataCountTotal'] = 24
pkt['Payload'].v['ParamCount'] = 2
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 24
pkt['Payload'].v['DataOffset'] = 60
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# QUERY_PATH_INFO Parameters
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Padding
# QUERY_PATH_INFO Data
"\x00\x00\x10\x00\x00\x00\x00\x00" + # Allocation Size = 1048576 || 1Mb
[@exe.length].pack("V") + "\x00\x00\x00\x00" + # End Of File
"\x01\x00\x00\x00" + # Link Count
"\x00" + # Delete Pending
attrib2 +
"\x00\x00" # Unknown
c.put(pkt.to_s)
end
def smb_cmd_trans_query_path_info_basic(c, buff)
pkt = CONST::SMB_TRANS2_PKT.make_struct
pkt.from_s(buff)
payload = Rex::Text.to_unicode(pkt['Payload'].v['SetupData'].gsub(/\x00/, '').gsub(/.*\\/, '\\').strip)
file = Rex::Text.to_unicode(@exe_file)
dprint("[query_info_basic] Payload is: " + payload + " with length: " + @exe.length.to_s)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
if payload =~ /#{file}/i
attrib = "\x20\x00\x00\x00" # File attributes => file
dprint("[query_info_basic] Sending file response: " + file + " with length: " + @exe.length.to_s)
else
# if QUERY_PATH_INFO_PARAMETERS doesn't include a file name,
# return a Directory answer
attrib = "\x10\x00\x00\x00" # File attributes => directory
dprint("[query_info_basic] Sending directory response")
end
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 2
pkt['Payload'].v['DataCountTotal'] = 40
pkt['Payload'].v['ParamCount'] = 2
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 40
pkt['Payload'].v['DataOffset'] = 60
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# QUERY_PATH_INFO Parameters
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Padding
#QUERY_PATH_INFO Data
[@lo, @hi].pack("VV") + # Created
[@lo, @hi].pack("VV") + # Last Access
[@lo, @hi].pack("VV") + # Last Write
[@lo, @hi].pack("VV") + # Change
attrib +
"\x00\x00\x00\x00" # Unknown
c.put(pkt.to_s)
end
def smb_cmd_trans_find_first2(c, buff)
pkt = CONST::SMB_TRANS_RES_PKT.make_struct
smb_set_defaults(c, pkt)
file_name = Rex::Text.to_unicode(@exe_file)
dprint("Asking for " + file_name)
# For some reason filename is out by <x>4 (14/94?)
pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
pkt['Payload']['SMB'].v['Flags1'] = 0x88
pkt['Payload']['SMB'].v['Flags2'] = @flags2
pkt['Payload']['SMB'].v['WordCount'] = 10
pkt['Payload'].v['ParamCountTotal'] = 10
pkt['Payload'].v['DataCountTotal'] = 14 + file_name.length
pkt['Payload'].v['ParamCount'] = 10
pkt['Payload'].v['ParamOffset'] = 56
pkt['Payload'].v['DataCount'] = 14 + file_name.length
pkt['Payload'].v['DataOffset'] = 68
pkt['Payload'].v['Payload'] =
"\x00" + # Padding
# FIND_FIRST2 Parameters
"\xfd\xff" + # Search ID
"\x01\x00" + # Search count
"\x01\x00" + # End Of Search
"\x00\x00" + # EA Error Offset
"\x00\x00" + # Last Name Offset
"\x00\x00" + # Padding
#QUERY_PATH_INFO Data
[14 + file_name.length].pack("V") + # Next Entry Offset
"\x00\x00\x00\x00" + # File Index
[file_name.length].pack("V") + # File Name Len
file_name +
"\x00\x00" # Padding
c.put(pkt.to_s)
end
end # End Class
end # End SMB
end # End Proto
end # End Rex