class Rex::Proto::SMB::Client

Constants

CONST

Some short-hand class aliases

CRYPT
EVADE
NTLM_CONST
NTLM_CRYPT
NTLM_UTILS
UTILS
XCEPT

Attributes

auth_user[RW]
auth_user_id[RW]
challenge_key[RW]

public read methods

default_domain[RW]
default_name[RW]
dialect[RW]

public read methods

dns_domain_name[RW]
dns_host_name[RW]
encrypt_passwords[RW]

public read/write methods

evasion_opts[RW]

public read/write methods

extended_security[RW]

public read/write methods

last_file_id[RW]
last_search_id[RW]
last_tree_id[RW]
multiplex_id[RW]
native_lm[RW]

public read/write methods

native_os[RW]

public read/write methods

peer_native_lm[RW]

public read methods

peer_native_os[RW]

public read methods

process_id[RW]
read_timeout[RW]

public read/write methods

require_signing[RW]
security_mode[RW]
send_lm[RW]
send_ntlm[RW]
sequence_counter[RW]
server_guid[RW]
session_id[RW]

public read methods

signing_key[RW]
socket[RW]
spnopt[RW]
system_time[RW]
system_zone[RW]
use_lanman_key[RW]
use_ntlmv2[RW]
usentlm2_session[RW]
verify_signature[RW]

Public Class Methods

new(socket) click to toggle source
# File lib/rex/proto/smb/client.rb, line 28
def initialize(socket)
  self.socket = socket
  self.native_os = 'Windows 2000 2195'
  self.native_lm = 'Windows 2000 5.0'
  self.encrypt_passwords = true
  self.extended_security = false
  self.multiplex_id = rand(0xffff)
  self.process_id = rand(0xffff)
  self.read_timeout = 10
  self.evasion_opts = {

    # Padding is performed between packet headers and data
    'pad_data' => EVADE::EVASION_NONE,

    # File path padding is performed on all open/create calls
    'pad_file' => EVADE::EVASION_NONE,

    # Modify the \PIPE\ string in trans_named_pipe calls
    'obscure_trans_pipe' => EVADE::EVASION_NONE,
  }

  self.verify_signature = false
  self.use_ntlmv2 = false
  self.usentlm2_session = true
  self.send_lm = true
  self.use_lanman_key = false
  self.send_ntlm  = true

  # Signing
  self.sequence_counter = 0
  self.signing_key      = ''
  self.require_signing  = false

  #Misc
  self.spnopt = {}

end

Public Instance Methods

close(file_id = self.last_file_id, tree_id = self.last_tree_id, do_recv = true) click to toggle source

Closes an open file handle

# File lib/rex/proto/smb/client.rb, line 1268
def close(file_id = self.last_file_id, tree_id = self.last_tree_id, do_recv = true)

  pkt = CONST::SMB_CLOSE_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_CLOSE
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['TreeID'] = tree_id
  pkt['Payload']['SMB'].v['WordCount'] = 3

  pkt['Payload'].v['FileID'] = file_id
  pkt['Payload'].v['LastWrite'] = -1

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_CLOSE)

  return ack
end
create(filename, disposition = 1, impersonation = 2, do_recv = true) click to toggle source

Creates a file or opens an existing pipe

# File lib/rex/proto/smb/client.rb, line 1159
def create(filename, disposition = 1, impersonation = 2, do_recv = true)

  pkt = CONST::SMB_CREATE_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_CREATE_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 24

  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['FileNameLen'] = filename.length
  pkt['Payload'].v['CreateFlags'] = 0x16
  pkt['Payload'].v['AccessMask'] = 0x02000000 # Maximum Allowed
  pkt['Payload'].v['ShareAccess'] = 7
  pkt['Payload'].v['CreateOptions'] = 0
  pkt['Payload'].v['Impersonation'] = impersonation
  pkt['Payload'].v['Disposition'] = disposition
  pkt['Payload'].v['Payload'] = filename + "\x00"

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_NT_CREATE_ANDX)

  # Save off the FileID
  if (ack['Payload'].v['FileID'] > 0)
    self.last_file_id = ack['Payload'].v['FileID']
  end

  return ack
end
create_directory(name) click to toggle source

Creates a new directory on the mounted tree

# File lib/rex/proto/smb/client.rb, line 2030
def create_directory(name)
  files = { }
  parm = [0].pack('V') + name + "\x00"
  resp = trans2(CONST::TRANS2_CREATE_DIRECTORY, parm, '')
end
create_pipe(filename, disposition = 1, impersonation = 2) click to toggle source

Returns a SMB_CREATE_RES response for a given named pipe

# File lib/rex/proto/smb/client.rb, line 1154
def create_pipe(filename, disposition = 1, impersonation = 2)
  self.create(filename)
end
delete(filename, tree_id = self.last_tree_id, do_recv = true) click to toggle source

Deletes a file from a share

# File lib/rex/proto/smb/client.rb, line 1200
def delete(filename, tree_id = self.last_tree_id, do_recv = true)

  pkt = CONST::SMB_DELETE_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_DELETE
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['TreeID'] = tree_id
  pkt['Payload']['SMB'].v['WordCount'] = 1

  pkt['Payload'].v['SearchAttributes'] = 0x06
  pkt['Payload'].v['BufferFormat'] = 4
  pkt['Payload'].v['Payload'] = filename + "\x00"

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_DELETE)

  return ack
end
find_first(path) click to toggle source

Enumerates a specific path on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1873
def find_first(path)
  files = { }
  parm = [
    26,  # Search for ALL files
    20,  # Maximum search count
    6,   # Resume and Close on End of Search
    260, # Level of interest
    0,   # Storage type is zero
  ].pack('vvvvV') + path + "\x00"

  resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '')
  search_next = 0

  # Loop until we run out of results
  loop do
    pcnt = resp['Payload'].v['ParamCount']
    dcnt = resp['Payload'].v['DataCount']
    poff = resp['Payload'].v['ParamOffset']
    doff = resp['Payload'].v['DataOffset']

    # Get the raw packet bytes
    resp_rpkt = resp.to_s

    # Remove the NetBIOS header
    resp_rpkt.slice!(0, 4)

    resp_parm = resp_rpkt[poff, pcnt]
    resp_data = resp_rpkt[doff, dcnt]

    if search_next == 0
      # search id, search count, end of search, error offset, last name offset
      sid, scnt, eos, eoff, loff = resp_parm.unpack('v5')
    else
      # FIND_NEXT doesn't return a SID
      scnt, eos, eoff, loff = resp_parm.unpack('v4')
    end

    didx = 0
    while (didx < resp_data.length)
      info_buff = resp_data[didx, 70]
      break if info_buff.length != 70

      info = info_buff.unpack(
        'V'+  # Next Entry Offset
        'V'+  # File Index
        'VV'+ # Time Create
        'VV'+ # Time Last Access
        'VV'+ # Time Last Write
        'VV'+ # Time Change
        'VV'+ # End of File
        'VV'+ # Allocation Size
        'V'+  # File Attributes
        'V'+  # File Name Length
        'V'+  # Extended Attr List Length
        'C'+  # Short File Name Length
        'C'   # Reserved
      )

      name = resp_data[didx + 70 + 24, info[15]]

      # Verify that the filename was actually present
      break unless name

      # Key the file list minus any trailing nulls
      files[name.sub(/\x00+$/n, '')] =
      {
        'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D',
        'attr' => info[14],
        'info' => info
      }

      break if info[0] == 0
      didx += info[0]
    end

    last_search_id = sid
    last_offset    = loff
    last_filename  = name

    # Exit the search if we reached the end of our results
    break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0)

    # If we aren't at the end of the search, run find_next
    resp = find_next(last_search_id, last_offset, last_filename)

    # Flip bit so response params will parse correctly
    search_next = 1 
  end

  files
end
find_next(sid, resume_key, last_filename) click to toggle source

Supplements find_first if file/dir count exceeds max search count

# File lib/rex/proto/smb/client.rb, line 1966
def find_next(sid, resume_key, last_filename)

  parm = [
    sid,                # Search ID
    20,                 # Maximum search count (Size of 20 keeps response to 1 packet)
    260,                # Level of interest
    resume_key,         # Resume key from previous (Last name offset)
    6,                  # Close search if end of search
  ].pack('vvvVv') + 
  last_filename.to_s +  # Last filename returned from find_first or find_next
  "\x00"                # Terminate the file name

  # Returns the FIND_NEXT2 response packet for parsing by the find_first function
  trans2(CONST::TRANS2_FIND_NEXT2, parm, '')
end
negotiate(smb_extended_security=true, do_recv = true) click to toggle source

Negotiate a SMB dialect

# File lib/rex/proto/smb/client.rb, line 521
def negotiate(smb_extended_security=true, do_recv = true)

  dialects = ['LANMAN1.0', 'LM1.2X002' ]

  if (self.encrypt_passwords)
    dialects.push('NT LANMAN 1.0', 'NT LM 0.12')
  end

  data = dialects.collect { |dialect| "\x02" + dialect + "\x00" }.join('')

  pkt = CONST::SMB_NEG_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NEGOTIATE
  pkt['Payload']['SMB'].v['Flags1'] = 0x18

  if(smb_extended_security)
    pkt['Payload']['SMB'].v['Flags2'] = 0x2801
  else
    pkt['Payload']['SMB'].v['Flags2'] = 0xc001
  end

  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s, EVADE::EVASION_NONE)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_NEGOTIATE)

  idx = ack['Payload'].v['Dialect']

  # Check for failed dialect selection
  if (idx < 0 or idx >= dialects.length)
    return nil
  end

  # Set the selected dialect
  self.dialect = dialects[idx]

  # Does the server support extended security negotiation?
  if (ack['Payload'].v['Capabilities'] & 0x80000000 != 0)
    self.extended_security = true
  end

  # Set the security mode
  self.security_mode = ack['Payload'].v['SecurityMode']

  #set require_signing
  if (ack['Payload'].v['SecurityMode'] & 0x08 != 0)
    self.require_signing      = true
  end

  # Set the challenge key
  if (ack['Payload'].v['EncryptionKey'] != nil)
    self.challenge_key = ack['Payload'].v['EncryptionKey']
  else
    # Handle Windows NT 4.0 responses
    if (ack['Payload'].v['KeyLength'] > 0)
      self.challenge_key = ack['Payload'].v['Payload'][0, ack['Payload'].v['KeyLength']]
    end
  end

  # Set the session identifier
  if (ack['Payload'].v['SessionKey'] != nil)
    self.session_id = ack['Payload'].v['SessionKey']
  end

  # Extract the payload (GUID/SecurityBlob)
  buf = ack['Payload'].v['Payload'] || ''

  # Set the server GUID
  if (self.extended_security and buf.length >= 16)
    self.server_guid = buf[0,16]
  end

  # Set the server SecurityBlob
  if (self.extended_security and buf.length > 16)
    # buf[16, buf.length - 16]
  end

  # The number of 100-nanosecond intervals that have elapsed since January 1, 1601, in
  # Coordinated Universal Time (UTC) format.
  # We convert it to a friendly Time object here
  self.system_time = UTILS.time_smb_to_unix(ack['Payload'].v['SystemTimeHigh'],ack['Payload'].v['SystemTimeLow'])
  self.system_time = ::Time.at( self.system_time )

  # A signed 16-bit signed integer that represents the server's time zone, in minutes,
  # from UTC. The time zone of the server MUST be expressed in minutes, plus or minus,
  # from UTC.
  # NOTE: althought the spec says +/- it doesn't say that it should be inverted :-/
  system_zone = ack['Payload'].v['ServerTimeZone']
  # Convert the ServerTimeZone to _seconds_ and back into a signed integer :-/
  if (system_zone & 0x8000) == 0x8000
    system_zone = (( (~system_zone)  & 0x0FFF ) + 1 )
  else
    system_zone *= -1
  end
  self.system_zone = system_zone * 60

  return ack
end
nttrans(subcommand, param = '', body = '', setup_count = 0, setup_data = '', do_recv = true) click to toggle source

Perform a nttransaction request using the specified subcommand, parameters, and data

# File lib/rex/proto/smb/client.rb, line 1672
def nttrans(subcommand, param = '', body = '', setup_count = 0, setup_data = '', do_recv = true)

  data = param + body

  pkt = CONST::SMB_NTTRANS_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  base_offset = pkt.to_s.length + (setup_count * 2) - 4
  param_offset = base_offset
  data_offset = param_offset + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 19 + setup_count

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCountMax'] = 1024
  pkt['Payload'].v['DataCountMax'] = 65000
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset
  pkt['Payload'].v['SetupCount'] = setup_count
  pkt['Payload'].v['SetupData'] = setup_data
  pkt['Payload'].v['Subcommand'] = subcommand

  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT)
  return ack
end
nttrans_secondary(param = '', body = '', do_recv = true) click to toggle source

Perform a nttransaction request using the specified subcommand, parameters, and data

# File lib/rex/proto/smb/client.rb, line 1717
def nttrans_secondary(param = '', body = '', do_recv = true)

  data = param + body

  pkt = CONST::SMB_NTTRANS_SECONDARY_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  base_offset = pkt.to_s.length - 4
  param_offset = base_offset
  data_offset = param_offset + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_NT_TRANSACT_SECONDARY
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 18

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset

  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_NT_TRANSACT_SECONDARY)
  return ack
end
open(filename, mode = 0x12, access = 0x42, do_recv = true) click to toggle source

Opens an existing file or creates a new one

# File lib/rex/proto/smb/client.rb, line 1231
def open(filename, mode = 0x12, access = 0x42, do_recv = true)

  pkt = CONST::SMB_OPEN_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_OPEN_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 15

  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['Access'] = access
  pkt['Payload'].v['SearchAttributes'] = 0x06
  pkt['Payload'].v['OpenFunction'] = mode
  pkt['Payload'].v['Payload'] = filename + "\x00"

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_OPEN_ANDX)

  # Save off the FileID
  if (ack['Payload'].v['FileID'] > 0)
    self.last_file_id = ack['Payload'].v['FileID']
  end

  return ack
end
queryfs(level) click to toggle source
# File lib/rex/proto/smb/client.rb, line 1756
def queryfs(level)
  parm = [level].pack('v')

  begin
    resp = trans2(CONST::TRANS2_QUERY_FS_INFO, parm, '')

    pcnt = resp['Payload'].v['ParamCount']
    dcnt = resp['Payload'].v['DataCount']
    poff = resp['Payload'].v['ParamOffset']
    doff = resp['Payload'].v['DataOffset']

    # Get the raw packet bytes
    resp_rpkt = resp.to_s

    # Remove the NetBIOS header
    resp_rpkt.slice!(0, 4)

    resp_parm = resp_rpkt[poff, pcnt]
    resp_data = resp_rpkt[doff, dcnt]
    return resp_data

  rescue ::Exception
    raise $!
  end
end
queryfs_fs_attribute() click to toggle source

Obtains file system attribute information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1862
def queryfs_fs_attribute
  data = queryfs(CONST::SMB_QUERY_FS_ATTRIBUTE_INFO)
  vals = data.unpack('VVVA*')
  return {
    'fs_attributes' => vals[0],
    'max_file_name' => vals[1],
    'fs_name'       => vals[3][0, vals[2]].gsub("\x00", '')
  }
end
queryfs_fs_device() click to toggle source

Obtains file system device information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1852
def queryfs_fs_device
  data = queryfs(CONST::SMB_QUERY_FS_DEVICE_INFO)
  vals = data.unpack('VV')
  return {
    'device_type'   => vals[0],
    'device_chars'  => vals[1],
  }
end
queryfs_fs_size() click to toggle source

Obtains file system size information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1840
def queryfs_fs_size
  data = queryfs(CONST::SMB_QUERY_FS_SIZE_INFO)
  vals = data.unpack('VVVVVV')
  return {
    'total_alloc_units' => (vals[1] << 32) + vals[0],
    'total_free_units'  => (vals[3] << 32) + vals[2],
    'sectors_per_unit'  => vals[4],
    'bytes_per_sector'  => vals[5]
  }
end
queryfs_fs_volume() click to toggle source

Obtains file system volume information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1829
def queryfs_fs_volume
  data = queryfs(CONST::SMB_QUERY_FS_VOLUME_INFO)
  vals = data.unpack('VVVVCCA*')
  return {
    'create_time' => (vals[1] << 32) + vals[0],
    'serial'      => vals[2],
    'label'       => vals[6][0,vals[3]].gsub("\x00", '')
  }
end
queryfs_info_allocation() click to toggle source

Obtains allocation information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1809
def queryfs_info_allocation
  data = queryfs(CONST::SMB_INFO_ALLOCATION)
  head = %w{fs_id sectors_per_unit unit_total units_available bytes_per_sector}
  vals = data.unpack('VVVVv')
  info = { }
  head.each_index {|i| info[head[i]]=vals[i]}
  return info
end
queryfs_info_volume() click to toggle source

Obtains volume information on the mounted tree

# File lib/rex/proto/smb/client.rb, line 1819
def queryfs_info_volume
  data = queryfs(CONST::SMB_INFO_VOLUME)
  vals = data.unpack('VCA*')
  return {
    'serial' => vals[0],
    'label'  => vals[2][0,vals[1]].gsub("\x00", '')
  }
end
read(file_id = self.last_file_id, offset = 0, data_length = 64000, do_recv = true) click to toggle source

Reads data from an open file handle

# File lib/rex/proto/smb/client.rb, line 1339
def read(file_id = self.last_file_id, offset = 0, data_length = 64000, do_recv = true)

  pkt = CONST::SMB_READ_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_READ_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 10

  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['FileID'] = file_id
  pkt['Payload'].v['Offset'] = offset
  # pkt['Payload'].v['MaxCountHigh'] = (data_length / 65536).to_i
  pkt['Payload'].v['MaxCountLow'] = (data_length % 65536).to_i
  pkt['Payload'].v['MinCount'] = data_length
  pkt['Payload'].v['Reserved2'] = -1

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_READ_ANDX, true)

  err = ack['Payload']['SMB'].v['ErrorClass']

  # Catch some non-fatal error codes
  if (err != 0 && err != CONST::SMB_ERROR_BUFFER_OVERFLOW)
    failure = XCEPT::ErrorCode.new
    failure.word_count = ack['Payload']['SMB'].v['WordCount']
    failure.command = ack['Payload']['SMB'].v['Command']
    failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
    raise failure
  end

  return ack
end
session_request(name = '*SMBSERVER', do_recv = true) click to toggle source

Request a SMB session over NetBIOS

# File lib/rex/proto/smb/client.rb, line 492
def session_request(name = '*SMBSERVER', do_recv = true)

  name ||= '*SMBSERVER'

  data = ''
  data << "\x20" + UTILS.nbname_encode(name) + "\x00"
  data << "\x20" + CONST::NETBIOS_REDIR      + "\x00"

  pkt = CONST::NBRAW_PKT.make_struct
  pkt.v['Type'] = 0x81
  pkt['Payload'].v['Payload'] = data

  # Most SMB implementations can't handle this being fragmented
  ret = self.smb_send(pkt.to_s, EVADE::EVASION_NONE)
  return ret if not do_recv

  res = self.smb_recv

  ack = CONST::NBRAW_PKT.make_struct
  ack.from_s(res)

  if (ack.v['Type'] != 130)
    raise XCEPT::NetbiosSessionFailed
  end

  return ack
end
session_setup(*args) click to toggle source

Authenticate and establish a session

# File lib/rex/proto/smb/client.rb, line 625
def session_setup(*args)

  if (self.dialect =~ /^(NT LANMAN 1.0|NT LM 0.12)$/)

    if (self.challenge_key)
      return self.session_setup_no_ntlmssp(*args)
    end

    if ( self.extended_security )
      return self.session_setup_with_ntlmssp(*args)
    end

  end

  return self.session_setup_clear(*args)
end
session_setup_clear(user = '', pass = '', domain = '', do_recv = true) click to toggle source

Authenticate using clear-text passwords

# File lib/rex/proto/smb/client.rb, line 643
def session_setup_clear(user = '', pass = '', domain = '', do_recv = true)

  data = [ pass, user, domain, self.native_os, self.native_lm ].collect{ |a| a + "\x00" }.join('');

  pkt = CONST::SMB_SETUP_LANMAN_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 10
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['PasswordLen'] = pass.length + 1
  pkt['Payload'].v['Capabilities'] = 64
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

  if (ack['Payload'].v['Action'] != 1 and user.length > 0)
    self.auth_user = user
  end

  self.auth_user_id = ack['Payload']['SMB'].v['UserID']

  info = ack['Payload'].v['Payload'].split(/\x00/n)
  self.peer_native_os = info[0]
  self.peer_native_lm = info[1]
  self.default_domain = info[2]

  return ack
end
session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true) click to toggle source

Authenticate without NTLMSSP

# File lib/rex/proto/smb/client.rb, line 690
def session_setup_no_ntlmssp(user = '', pass = '', domain = '', do_recv = true)

  # Requires a challenge key to have been seen during negotiation
  raise XCEPT::NTLM1MissingChallenge if not self.challenge_key

  #
  # We can not yet handle signing in this situation
  # But instead of throwing an exception,we will disable signing, continue and hope for the best.
  #

  #raise XCEPT::SigningError if self.require_signing
  self.require_signing = false if self.require_signing


  if NTLM_UTILS.is_pass_ntlm_hash?(pass)
    arglm = {
      :lm_hash => [ pass.upcase()[0,32] ].pack('H32'),
      :challenge =>  self.challenge_key
    }
    hash_lm = NTLM_CRYPT::lm_response(arglm)

    argntlm = {
      :ntlm_hash =>  [ pass.upcase()[33,65] ].pack('H32'),
      :challenge =>  self.challenge_key
    }
    hash_nt = NTLM_CRYPT::ntlm_response(argntlm)
  else
    hash_lm = pass.length > 0 ? NTLM_CRYPT.lanman_des(pass, self.challenge_key) : ''
    hash_nt = pass.length > 0 ? NTLM_CRYPT.ntlm_md4(pass, self.challenge_key)   : ''
  end

  data = ''
  data << hash_lm
  data << hash_nt
  data << user + "\x00"
  data << domain + "\x00"
  data << self.native_os + "\x00"
  data << self.native_lm + "\x00"

  pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  pkt['Payload']['SMB'].v['Flags2'] = 0x2001
  pkt['Payload']['SMB'].v['WordCount'] = 13
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
  pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
  pkt['Payload'].v['Capabilities'] = 64
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

  if (ack['Payload'].v['Action'] != 1 and user.length > 0)
    self.auth_user = user
  end

  self.auth_user_id = ack['Payload']['SMB'].v['UserID']

  info = ack['Payload'].v['Payload'].split(/\x00/n)

  self.peer_native_os = info[0]
  self.peer_native_lm = info[1]
  self.default_domain = info[2]

  return ack
end
session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt, do_recv = true) click to toggle source

Authenticate without ntlmssp with a precomputed hash pair

# File lib/rex/proto/smb/client.rb, line 768
def session_setup_no_ntlmssp_prehash(user, domain, hash_lm, hash_nt, do_recv = true)

  data = ''
  data << hash_lm
  data << hash_nt
  data << user + "\x00"
  data << domain + "\x00"
  data << self.native_os + "\x00"
  data << self.native_lm + "\x00"

  pkt = CONST::SMB_SETUP_NTLMV1_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  pkt['Payload']['SMB'].v['Flags2'] = 0x2001
  pkt['Payload']['SMB'].v['WordCount'] = 13
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['PasswordLenLM'] = hash_lm.length
  pkt['Payload'].v['PasswordLenNT'] = hash_nt.length
  pkt['Payload'].v['Capabilities'] = 64
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX)

  if (ack['Payload'].v['Action'] != 1 and user.length > 0)
    self.auth_user = user
  end

  self.auth_user_id = ack['Payload']['SMB'].v['UserID']

  info = ack['Payload'].v['Payload'].split(/\x00/n)

  self.peer_native_os = info[0]
  self.peer_native_lm = info[1]
  self.default_domain = info[2]

  return ack
end
session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do_recv = true) click to toggle source

Authenticate using extended security negotiation

# File lib/rex/proto/smb/client.rb, line 816
def session_setup_with_ntlmssp(user = '', pass = '', domain = '', name = nil, do_recv = true)

  ntlm_options = {
      :signing                => self.require_signing,
      :usentlm2_session       => self.usentlm2_session,
      :use_ntlmv2             => self.use_ntlmv2,
      :send_lm                => self.send_lm,
      :send_ntlm              => self.send_ntlm,
      :use_lanman_key         => self.use_lanman_key
      }

  ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options)

  if (name == nil)
    name = Rex::Text.rand_text_alphanumeric(16)
  end

  blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name, ntlmssp_flags)

  native_data = ''
  native_data << self.native_os + "\x00"
  native_data << self.native_lm + "\x00"

  pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end
  pkt['Payload']['SMB'].v['WordCount'] = 12
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['SecurityBlobLen'] = blob.length
  pkt['Payload'].v['Capabilities'] = 0x800000d4
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = blob + native_data

  ret = self.smb_send(pkt.to_s)

  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)


  # The server doesn't know about NTLM_NEGOTIATE
  if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
    return session_setup_no_ntlmssp(user, pass, domain)
  end

  # Make sure the error code tells us to continue processing
  if (ack['Payload']['SMB'].v['ErrorClass'] != 0xc0000016)
    failure = XCEPT::ErrorCode.new
    failure.word_count = ack['Payload']['SMB'].v['WordCount']
    failure.command = ack['Payload']['SMB'].v['Command']
    failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
    raise failure
  end

  # Extract the SecurityBlob from the response
  data = ack['Payload'].v['Payload']
  blob = data.slice!(0, ack['Payload'].v['SecurityBlobLen'])

  # Extract the native lanman and os strings
  info = data.split(/\x00/n)
  self.peer_native_os = info[0]
  self.peer_native_lm = info[1]

  # Save the temporary UserID for use in the next request
  temp_user_id = ack['Payload']['SMB'].v['UserID']

  # Get default data
  blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(blob)
  self.challenge_key = blob_data[:challenge_key]
  server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error
  #netbios name
  self.default_name =  blob_data[:default_name] || ''
  #netbios domain
  self.default_domain = blob_data[:default_domain] || ''
  #dns name
  self.dns_host_name =  blob_data[:dns_host_name] || ''
  #dns domain
  self.dns_domain_name =  blob_data[:dns_domain_name] || ''
  #Client time
  chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || ''


  resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, self.challenge_key, domain,
                      default_name, default_domain, dns_host_name,
                      dns_domain_name, chall_MsvAvTimestamp ,
                      self.spnopt, ntlm_options)
  enc_session_key = ''
  self.sequence_counter = 0

  if self.require_signing
    self.signing_key, enc_session_key, ntlmssp_flags = NTLM_UTILS.create_session_key(ntlmssp_flags, server_ntlmssp_flags, user, pass, domain,
                    self.challenge_key, client_challenge, ntlm_cli_challenge,
                    ntlm_options)
  end

  # Create the security blob data
  blob = NTLM_UTILS.make_ntlmssp_secblob_auth(domain, name, user, resp_lm, resp_ntlm, enc_session_key, ntlmssp_flags)

  pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end
  pkt['Payload']['SMB'].v['WordCount'] = 12
  pkt['Payload']['SMB'].v['UserID'] = temp_user_id
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['Capabilities'] = 0x8000d05c
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['SecurityBlobLen'] = blob.length
  pkt['Payload'].v['Payload'] = blob + native_data

  # NOTE: if do_recv is set to false, we cant reach here...
  self.smb_send(pkt.to_s)

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)

  # Make sure that authentication succeeded
  if (ack['Payload']['SMB'].v['ErrorClass'] != 0)

    if (user.length == 0)
      # Ensure that signing is disabled when we hit this corner case
      self.require_signing = false

      # Fall back to the non-ntlmssp authentication method
      return self.session_setup_no_ntlmssp(user, pass, domain)
    end

    failure = XCEPT::ErrorCode.new
    failure.word_count = ack['Payload']['SMB'].v['WordCount']
    failure.command = ack['Payload']['SMB'].v['Command']
    failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
    raise failure
  end

  self.auth_user_id = ack['Payload']['SMB'].v['UserID']

  if (ack['Payload'].v['Action'] != 1 and user.length > 0)
    self.auth_user = user
  end

  return ack
end
session_setup_with_ntlmssp_blob(blob = '', do_recv = true, userid = 0) click to toggle source

An exploit helper function for sending arbitrary SPNEGO blobs

# File lib/rex/proto/smb/client.rb, line 983
def session_setup_with_ntlmssp_blob(blob = '', do_recv = true, userid = 0)
  native_data = ''
  native_data << self.native_os + "\x00"
  native_data << self.native_lm + "\x00"

  pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  pkt['Payload']['SMB'].v['Flags2'] = 0x2801
  pkt['Payload']['SMB'].v['WordCount'] = 12
  pkt['Payload']['SMB'].v['UserID'] = userid
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['SecurityBlobLen'] = blob.length
  pkt['Payload'].v['Capabilities'] = 0x8000d05c
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = blob + native_data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, false)
end
session_setup_with_ntlmssp_temp(domain = '', name = nil, do_recv = true) click to toggle source

Authenticate using extended security negotiation (NTLMSSP), but stop half-way, using the temporary ID

# File lib/rex/proto/smb/client.rb, line 1013
def session_setup_with_ntlmssp_temp(domain = '', name = nil, do_recv = true)

  if (name == nil)
    name = Rex::Text.rand_text_alphanumeric(16)
  end

  blob = NTLM_UTILS.make_ntlmssp_secblob_init(domain, name)

  native_data = ''
  native_data << self.native_os + "\x00"
  native_data << self.native_lm + "\x00"

  pkt = CONST::SMB_SETUP_NTLMV2_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_SESSION_SETUP_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  pkt['Payload']['SMB'].v['Flags2'] = 0x2801
  pkt['Payload']['SMB'].v['WordCount'] = 12
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['MaxBuff'] = 0xffdf
  pkt['Payload'].v['MaxMPX'] = 2
  pkt['Payload'].v['VCNum'] = 1
  pkt['Payload'].v['SecurityBlobLen'] = blob.length
  pkt['Payload'].v['Capabilities'] = 0x8000d05c
  pkt['Payload'].v['SessionKey'] = self.session_id
  pkt['Payload'].v['Payload'] = blob + native_data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_SESSION_SETUP_ANDX, true)

  # The server doesn't know about NTLM_NEGOTIATE, try ntlmv1
  if (ack['Payload']['SMB'].v['ErrorClass'] == 0x00020002)
    return session_setup_no_ntlmssp(user, pass, domain)
  end

  # Make sure the error code tells us to continue processing
  if (ack['Payload']['SMB'].v['ErrorClass'] != 0xc0000016)
    failure = XCEPT::ErrorCode.new
    failure.word_count = ack['Payload']['SMB'].v['WordCount']
    failure.command = ack['Payload']['SMB'].v['Command']
    failure.error_code = ack['Payload']['SMB'].v['ErrorClass']
    raise failure
  end

  # Extract the SecurityBlob from the response
  data = ack['Payload'].v['Payload']
  blob = data.slice!(0, ack['Payload'].v['SecurityBlobLen'])

  # Extract the native lanman and os strings
  info = data.split(/\x00/n)
  self.peer_native_os = info[0]
  self.peer_native_lm = info[1]

  # Save the temporary UserID for use in the next request
  self.auth_user_id = ack['Payload']['SMB'].v['UserID']

  # Extract the NTLM challenge key the lazy way
  cidx = blob.index("NTLMSSP\x00\x02\x00\x00\x00")

  if (cidx == -1)
    raise XCEPT::NTLM2MissingChallenge
  end

  # Store the challenge key
  self.challenge_key = blob[cidx + 24, 8]

  return ack
end
smb_defaults(packet) click to toggle source

Set the SMB parameters to some reasonable defaults

# File lib/rex/proto/smb/client.rb, line 145
def smb_defaults(packet)
  packet.v['MultiplexID'] = self.multiplex_id.to_i
  packet.v['TreeID'] = self.last_tree_id.to_i
  packet.v['UserID'] = self.auth_user_id.to_i
  packet.v['ProcessID'] = self.process_id.to_i
end
smb_parse_close(pkt, data) click to toggle source

Process incoming SMB_COM_CLOSE packets

# File lib/rex/proto/smb/client.rb, line 468
def smb_parse_close(pkt, data)

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_create(pkt, data) click to toggle source

Process incoming SMB_COM_NT_CREATE_ANDX packets

# File lib/rex/proto/smb/client.rb, line 357
def smb_parse_create(pkt, data)

  # Windows says 42, but Samba says 34, same structure :-/
  if (pkt['Payload']['SMB'].v['WordCount'] == 42)
    res = CONST::SMB_CREATE_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  if (pkt['Payload']['SMB'].v['WordCount'] == 34)
    res = CONST::SMB_CREATE_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_delete(pkt, data) click to toggle source

Process incoming SMB_COM_DELETE packets

# File lib/rex/proto/smb/client.rb, line 479
def smb_parse_delete(pkt, data)

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    res = CONST::SMB_DELETE_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_negotiate(pkt, data) click to toggle source

Process incoming SMB_COM_NEGOTIATE packets

# File lib/rex/proto/smb/client.rb, line 272
def smb_parse_negotiate(pkt, data)
  #Process NTLM negotiate responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 17)
    res = CONST::SMB_NEG_RES_NT_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process LANMAN negotiate responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 13)
    res = CONST::SMB_NEG_RES_LM_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process ERROR negotiate responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 1)
    res = CONST::SMB_NEG_RES_ERR_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_nttrans(pkt, data) click to toggle source

Process incoming SMB_COM_NT_TRANSACT packets

# File lib/rex/proto/smb/client.rb, line 398
def smb_parse_nttrans(pkt, data)

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  if (pkt['Payload']['SMB'].v['WordCount'] >= 18)
    res = CONST::SMB_NTTRANS_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_open(pkt, data) click to toggle source

Process incoming SMB_COM_OPEN_ANDX packets

# File lib/rex/proto/smb/client.rb, line 415
def smb_parse_open(pkt, data)
  # Process open responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 15)
    res = CONST::SMB_OPEN_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_read(pkt, data) click to toggle source

Process incoming SMB_COM_READ_ANDX packets

# File lib/rex/proto/smb/client.rb, line 450
def smb_parse_read(pkt, data)

  # Process read responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 12)
    res = CONST::SMB_READ_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_session_setup(pkt, data) click to toggle source

Process incoming SMB_COM_SESSION_SETUP_ANDX packets

# File lib/rex/proto/smb/client.rb, line 303
def smb_parse_session_setup(pkt, data)
  # Process NTLMSSP negotiate responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 4)
    res = CONST::SMB_SETUP_NTLMV2_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process LANMAN responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 3)
    res = CONST::SMB_SETUP_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_trans(pkt, data) click to toggle source

Process incoming SMB_COM_TRANSACTION packets

# File lib/rex/proto/smb/client.rb, line 381
def smb_parse_trans(pkt, data)

  if (pkt['Payload']['SMB'].v['WordCount'] == 10)
    res = CONST::SMB_TRANS_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_tree_connect(pkt, data) click to toggle source

Process incoming SMB_COM_TREE_CONNECT_ANDX packets

# File lib/rex/proto/smb/client.rb, line 327
def smb_parse_tree_connect(pkt, data)

  if (pkt['Payload']['SMB'].v['WordCount'] == 3)
    res = CONST::SMB_TREE_CONN_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_tree_disconnect(pkt, data) click to toggle source

Process incoming SMB_COM_TREE_DISCONNECT packets

# File lib/rex/proto/smb/client.rb, line 344
def smb_parse_tree_disconnect(pkt, data)

  # Process SMB responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    res = CONST::SMB_TREE_DISCONN_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  raise XCEPT::InvalidWordCount
end
smb_parse_write(pkt, data) click to toggle source

Process incoming SMB_COM_WRITE_ANDX packets

# File lib/rex/proto/smb/client.rb, line 432
def smb_parse_write(pkt, data)

  # Process write responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 6)
    res = CONST::SMB_WRITE_RES_PKT.make_struct
    res.from_s(data)
    return res
  end

  # Process SMB error responses
  if (pkt['Payload']['SMB'].v['WordCount'] == 0)
    return pkt
  end

  raise XCEPT::InvalidWordCount
end
smb_recv() click to toggle source

Read a SMB packet from the socket

# File lib/rex/proto/smb/client.rb, line 67
def smb_recv

  data = socket.timed_read(4, self.read_timeout)
  if (data.nil? or data.length < 4)
    raise XCEPT::NoReply
  end

  recv_len = data[2,2].unpack('n')[0]
  if (recv_len == 0)
    return data
  end

  recv_len += 4

  while (data.length != recv_len)
    buff = ''

    begin
      buff << self.socket.timed_read(recv_len - data.length, self.read_timeout)
    rescue Timeout::Error
    rescue
      raise XCEPT::ReadPacket
    end

    if (buff.nil? or buff.length == 0)
      raise XCEPT::ReadPacket
    end

    data << buff
  end

  #signing
  if self.require_signing && self.signing_key != ''
    if self.verify_signature
      raise XCEPT::IncorrectSigningError if not CRYPT::is_signature_correct?(self.signing_key,self.sequence_counter,data)
    end
    self.sequence_counter += 1
  end

  return data


end
smb_recv_and_cache() click to toggle source

Receive a full SMB reply and cache the parsed packet

# File lib/rex/proto/smb/client.rb, line 153
def smb_recv_and_cache
  @smb_recv_cache ||= []

  # This will throw an exception if it fails to read the whole packet
  data = self.smb_recv

  pkt = CONST::SMB_BASE_PKT.make_struct
  pkt.from_s(data)

  # Store the received packet into the cache
  @smb_recv_cache << [ pkt, data, Time.now ]
end
smb_recv_cache_find_match(expected_type) click to toggle source

Scan the packet receive cache for a matching response

# File lib/rex/proto/smb/client.rb, line 167
def smb_recv_cache_find_match(expected_type)
  
  clean = []
  found = nil

  @smb_recv_cache.each do |cent|
    pkt, data, tstamp = cent

    # Return matching packets and mark for removal
    if pkt['Payload']['SMB'].v['Command'] == expected_type
      found = [pkt,data]
      clean << cent
    end

    # Purge any packets older than 5 minutes
    if Time.now.to_i - tstamp.to_i > 300
      clean << cent
    end

    break if found
  end

  clean.each do |cent|
    @smb_recv_cache.delete(cent)
  end

  found
end
smb_recv_parse(expected_type, ignore_errors = false) click to toggle source

The main dispatcher for all incoming SMB packets

# File lib/rex/proto/smb/client.rb, line 197
def smb_recv_parse(expected_type, ignore_errors = false)

  pkt  = nil
  data = nil

  # This allows for some leeway when a previous response has not
  # been processed but a new request was sent. The old response
  # will eventually be timed out of the cache.
  1.upto(3) do |attempt|
    smb_recv_and_cache
    pkt,data = smb_recv_cache_find_match(expected_type)
    break if pkt
  end

  begin
    case pkt['Payload']['SMB'].v['Command']

      when CONST::SMB_COM_NEGOTIATE
        res = smb_parse_negotiate(pkt, data)

      when CONST::SMB_COM_SESSION_SETUP_ANDX
        res = smb_parse_session_setup(pkt, data)

      when CONST::SMB_COM_TREE_CONNECT_ANDX
        res = smb_parse_tree_connect(pkt, data)

      when CONST::SMB_COM_TREE_DISCONNECT
        res = smb_parse_tree_disconnect(pkt, data)

      when CONST::SMB_COM_NT_CREATE_ANDX
        res = smb_parse_create(pkt, data)

      when CONST::SMB_COM_TRANSACTION, CONST::SMB_COM_TRANSACTION2
        res = smb_parse_trans(pkt, data)

      when CONST::SMB_COM_NT_TRANSACT
        res = smb_parse_nttrans(pkt, data)

      when CONST::SMB_COM_NT_TRANSACT_SECONDARY
        res = smb_parse_nttrans(pkt, data)

      when CONST::SMB_COM_OPEN_ANDX
        res = smb_parse_open(pkt, data)

      when CONST::SMB_COM_WRITE_ANDX
        res = smb_parse_write(pkt, data)

      when CONST::SMB_COM_READ_ANDX
        res = smb_parse_read(pkt, data)

      when CONST::SMB_COM_CLOSE
        res = smb_parse_close(pkt, data)

      when CONST::SMB_COM_DELETE
        res = smb_parse_delete(pkt, data)

      else
        raise XCEPT::InvalidCommand
    end

    if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0)
      raise XCEPT::ErrorCode
    end

  rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode
      $!.word_count = pkt['Payload']['SMB'].v['WordCount']
      $!.command = pkt['Payload']['SMB'].v['Command']
      $!.error_code = pkt['Payload']['SMB'].v['ErrorClass']
      raise $!
  end

  return res
end
smb_send(data, evasion_level=0) click to toggle source

Send a SMB packet down the socket

# File lib/rex/proto/smb/client.rb, line 112
def smb_send(data, evasion_level=0)

  # evasion_level is ignored, since real evasion happens
  # in the actual socket layer

  size = 0
  wait = 0

  #signing
  if self.require_signing && self.signing_key != ''
    data = CRYPT::sign_smb_packet(self.signing_key, self.sequence_counter, data)
    self.sequence_counter += 1
  end

  begin
    # Just send the packet and return
    if (size == 0 or size >= data.length)
      return self.socket.put(data)
    end

    # Break the packet up into chunks and wait between them
    ret = 0
    while ( (chunk = data.slice!(0, size)).length > 0 )
      ret = self.socket.put(chunk)
      if (wait > 0)
        ::IO.select(nil, nil, nil, wait)
      end
    end
    return ret
  end
end
trans(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true) click to toggle source

Perform a transaction against a given pipe name

# File lib/rex/proto/smb/client.rb, line 1401
def trans(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

  # Null-terminate the pipe parameter if needed
  if (pipe[-1,1] != "\x00")
    pipe << "\x00"
  end

  pkt = CONST::SMB_TRANS_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
  mlen = 4200

  # Figure out how much space is taken up by our current arguments
  xlen =  pipe.length + param.length + body.length

  filler1 = ''
  filler2 = ''

  # Fill any available space depending on the evasion settings
  if (xlen < mlen)
    filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
  end

  # Squish the whole thing together
  data = pipe + filler1 + param + filler2 + body

  # Throw some form of a warning out?
  if (data.length > mlen)
    # XXX This call will more than likely fail :-(
  end

  # Calculate all of the offsets
  base_offset = pkt.to_s.length + (setup_count * 2) - 4
  param_offset = base_offset + pipe.length + filler1.length
  data_offset = param_offset + filler2.length + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCountMax'] = 1024
  pkt['Payload'].v['DataCountMax'] = 65000
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset
  pkt['Payload'].v['SetupCount'] = setup_count
  pkt['Payload'].v['SetupData'] = setup_data

  pkt['Payload'].v['Payload'] = data

  if no_response
    pkt['Payload'].v['Flags'] = 2
  end

  ret = self.smb_send(pkt.to_s)
  return ret if no_response or not do_recv

  self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
end
trans2(subcommand, param = '', body = '', do_recv = true) click to toggle source

Perform a transaction2 request using the specified subcommand, parameters, and data

# File lib/rex/proto/smb/client.rb, line 1623
def trans2(subcommand, param = '', body = '', do_recv = true)

  setup_count = 1
  setup_data = [subcommand].pack('v')

  data = param + body

  pkt = CONST::SMB_TRANS2_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  base_offset = pkt.to_s.length + (setup_count * 2) - 4
  param_offset = base_offset
  data_offset = param_offset + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION2
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCountMax'] = 1024
  pkt['Payload'].v['DataCountMax'] = 65000
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset
  pkt['Payload'].v['SetupCount'] = setup_count
  pkt['Payload'].v['SetupData'] = setup_data

  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_TRANSACTION2)

  return ack
end
trans_mailslot(name, data = '') click to toggle source

Perform a mailslot write over SMB Warning: This can kill srv.sys unless MS06-035 is applied

# File lib/rex/proto/smb/client.rb, line 1392
def trans_mailslot (name, data = '')
  # Setup data must be:
  #  Operation: 1 (write)
  #   Priority: 0
  #      Class: Reliable
  self.trans_maxzero(name, '', data, 3, [1, 0, 1].pack('vvv'), true )
end
trans_maxzero(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true) click to toggle source

Perform a transaction against a given pipe name Difference from trans: sets MaxParam/MaxData to zero This is required to trigger mailslot bug :-(

# File lib/rex/proto/smb/client.rb, line 1479
def trans_maxzero(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

  # Null-terminate the pipe parameter if needed
  if (pipe[-1] != 0)
    pipe << "\x00"
  end

  pkt = CONST::SMB_TRANS_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
  mlen = 4200

  # Figure out how much space is taken up by our current arguments
  xlen =  pipe.length + param.length + body.length

  filler1 = ''
  filler2 = ''

  # Fill any available space depending on the evasion settings
  if (xlen < mlen)
    filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
  end

  # Squish the whole thing together
  data = pipe + filler1 + param + filler2 + body

  # Throw some form of a warning out?
  if (data.length > mlen)
    # XXX This call will more than likely fail :-(
  end

  # Calculate all of the offsets
  base_offset = pkt.to_s.length + (setup_count * 2) - 4
  param_offset = base_offset + pipe.length + filler1.length
  data_offset = param_offset + filler2.length + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCountMax'] = 0
  pkt['Payload'].v['DataCountMax'] = 0
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset
  pkt['Payload'].v['SetupCount'] = setup_count
  pkt['Payload'].v['SetupData'] = setup_data

  pkt['Payload'].v['Payload'] = data

  if no_response
    pkt['Payload'].v['Flags'] = 2
  end

  ret = self.smb_send(pkt.to_s)
  return ret if no_response or not do_recv

  self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
end
trans_named_pipe(file_id, data = '', no_response = nil) click to toggle source

Perform a transaction against a named pipe

# File lib/rex/proto/smb/client.rb, line 1385
def trans_named_pipe(file_id, data = '', no_response = nil)
  pipe = EVADE.make_trans_named_pipe_name(evasion_opts['pad_file'])
  self.trans(pipe, '', data, 2, [0x26, file_id].pack('vv'), no_response)
end
trans_nonull(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true) click to toggle source

Perform a transaction against a given pipe name (no null terminator)

# File lib/rex/proto/smb/client.rb, line 1554
def trans_nonull(pipe, param = '', body = '', setup_count = 0, setup_data = '', no_response = false, do_recv = true)

  pkt = CONST::SMB_TRANS_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  # Packets larger than mlen will cause XP SP2 to disconnect us ;-(
  mlen = 4200

  # Figure out how much space is taken up by our current arguments
  xlen =  pipe.length + param.length + body.length

  filler1 = ''
  filler2 = ''

  # Fill any available space depending on the evasion settings
  if (xlen < mlen)
    filler1 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
    filler2 = EVADE.make_offset_filler(evasion_opts['pad_data'], (mlen-xlen)/2)
  end

  # Squish the whole thing together
  data = pipe + filler1 + param + filler2 + body

  # Throw some form of a warning out?
  if (data.length > mlen)
    # XXX This call will more than likely fail :-(
  end

  # Calculate all of the offsets
  base_offset = pkt.to_s.length + (setup_count * 2) - 4
  param_offset = base_offset + pipe.length + filler1.length
  data_offset = param_offset + filler2.length + param.length

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TRANSACTION
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 14 + setup_count

  pkt['Payload'].v['ParamCountTotal'] = param.length
  pkt['Payload'].v['DataCountTotal'] = body.length
  pkt['Payload'].v['ParamCountMax'] = 0
  pkt['Payload'].v['DataCountMax'] = 0
  pkt['Payload'].v['ParamCount'] = param.length
  pkt['Payload'].v['ParamOffset'] = param_offset
  pkt['Payload'].v['DataCount'] = body.length
  pkt['Payload'].v['DataOffset'] = data_offset
  pkt['Payload'].v['SetupCount'] = setup_count
  pkt['Payload'].v['SetupData'] = setup_data

  pkt['Payload'].v['Payload'] = data

  if no_response
    pkt['Payload'].v['Flags'] = 2
  end

  ret = self.smb_send(pkt.to_s)
  return ret if no_response or not do_recv

  self.smb_recv_parse(CONST::SMB_COM_TRANSACTION)
end
tree_connect(share = 'IPC$', pass = '', do_recv = true) click to toggle source

Connect to a specified share with an optional password

# File lib/rex/proto/smb/client.rb, line 1086
def tree_connect(share = 'IPC$', pass = '', do_recv = true)

  data = [ pass, share, '?????' ].collect{ |a| a + "\x00" }.join('');

  pkt = CONST::SMB_TREE_CONN_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])
  pkt['Payload']['SMB'].v['TreeID'] = 0

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_CONNECT_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 4
  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['PasswordLen'] = pass.length + 1
  pkt['Payload'].v['Capabilities'] = 64
  pkt['Payload'].v['Payload'] = data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_TREE_CONNECT_ANDX)

  self.last_tree_id = ack['Payload']['SMB'].v['TreeID']
  # why bother?
  # info = ack['Payload'].v['Payload'].split(/\x00/)

  return ack
end
tree_disconnect(tree_id = self.last_tree_id, do_recv = true) click to toggle source

Disconnect from the current tree

# File lib/rex/proto/smb/client.rb, line 1123
def tree_disconnect(tree_id = self.last_tree_id, do_recv = true)

  pkt = CONST::SMB_TREE_DISCONN_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_TREE_DISCONNECT
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2807
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 0
  pkt['Payload']['SMB'].v['TreeID'] = tree_id

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_TREE_DISCONNECT)

  if (tree_id == self.last_tree_id)
    self.last_tree_id = 0
  end

  return ack
end
write(file_id = self.last_file_id, offset = 0, data = '', do_recv = true) click to toggle source

Writes data to an open file handle

# File lib/rex/proto/smb/client.rb, line 1298
def write(file_id = self.last_file_id, offset = 0, data = '', do_recv = true)
  pkt = CONST::SMB_WRITE_PKT.make_struct
  self.smb_defaults(pkt['Payload']['SMB'])

  data_offset = pkt.to_s.length - 4

  filler = EVADE.make_offset_filler(evasion_opts['pad_data'], 4096 - data.length - data_offset)

  pkt['Payload']['SMB'].v['Command'] = CONST::SMB_COM_WRITE_ANDX
  pkt['Payload']['SMB'].v['Flags1'] = 0x18
  if self.require_signing
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] = 0x2805
  else
    #ascii
    pkt['Payload']['SMB'].v['Flags2'] =  0x2801
  end

  pkt['Payload']['SMB'].v['WordCount'] = 14

  pkt['Payload'].v['AndX'] = 255
  pkt['Payload'].v['FileID'] = file_id
  pkt['Payload'].v['Offset'] = offset
  pkt['Payload'].v['Reserved2'] = -1
  pkt['Payload'].v['WriteMode'] = 8
  pkt['Payload'].v['Remaining'] = data.length
  # pkt['Payload'].v['DataLenHigh'] = (data.length / 65536).to_i
  pkt['Payload'].v['DataLenLow'] = (data.length % 65536).to_i
  pkt['Payload'].v['DataOffset'] = data_offset + filler.length
  pkt['Payload'].v['Payload'] = filler + data

  ret = self.smb_send(pkt.to_s)
  return ret if not do_recv

  ack = self.smb_recv_parse(CONST::SMB_COM_WRITE_ANDX)

  return ack
end