Class: RubySMB::GenericPacket

Inherits:
BinData::Record
  • Object
show all
Defined in:
lib/ruby_smb/generic_packet.rb

Overview

Parent class for all SMB Packets.

Direct Known Subclasses

SMB1::Packet::CloseRequest, SMB1::Packet::CloseResponse, SMB1::Packet::EchoRequest, SMB1::Packet::EchoResponse, SMB1::Packet::EmptyPacket, SMB1::Packet::LogoffRequest, SMB1::Packet::LogoffResponse, SMB1::Packet::NegotiateRequest, SMB1::Packet::NegotiateResponse, SMB1::Packet::NegotiateResponseExtended, SMB1::Packet::NtCreateAndxRequest, SMB1::Packet::NtCreateAndxResponse, SMB1::Packet::NtTrans::CreateRequest, SMB1::Packet::NtTrans::CreateResponse, SMB1::Packet::NtTrans::Request, SMB1::Packet::NtTrans::Response, SMB1::Packet::ReadAndxRequest, SMB1::Packet::ReadAndxResponse, SMB1::Packet::SessionSetupLegacyRequest, SMB1::Packet::SessionSetupLegacyResponse, SMB1::Packet::SessionSetupRequest, SMB1::Packet::SessionSetupResponse, SMB1::Packet::Trans2::FindFirst2Request, SMB1::Packet::Trans2::FindFirst2Response, SMB1::Packet::Trans2::FindNext2Request, SMB1::Packet::Trans2::FindNext2Response, SMB1::Packet::Trans2::Open2Request, SMB1::Packet::Trans2::Open2Response, SMB1::Packet::Trans2::QueryFileInformationRequest, SMB1::Packet::Trans2::QueryFileInformationResponse, SMB1::Packet::Trans2::QueryFsInformationRequest, SMB1::Packet::Trans2::QueryFsInformationResponse, SMB1::Packet::Trans2::QueryPathInformationRequest, SMB1::Packet::Trans2::QueryPathInformationResponse, SMB1::Packet::Trans2::Request, SMB1::Packet::Trans2::RequestSecondary, SMB1::Packet::Trans2::Response, SMB1::Packet::Trans2::SetFileInformationRequest, SMB1::Packet::Trans2::SetFileInformationResponse, SMB1::Packet::Trans::PeekNmpipeResponse, SMB1::Packet::Trans::Request, SMB1::Packet::Trans::Response, SMB1::Packet::Trans::TransactNmpipeRequest, SMB1::Packet::Trans::TransactNmpipeResponse, SMB1::Packet::TreeConnectRequest, SMB1::Packet::TreeConnectResponse, SMB1::Packet::TreeDisconnectRequest, SMB1::Packet::TreeDisconnectResponse, SMB1::Packet::WriteAndxRequest, SMB1::Packet::WriteAndxResponse, SMB2::Packet::CloseRequest, SMB2::Packet::CloseResponse, SMB2::Packet::CreateRequest, SMB2::Packet::CreateResponse, SMB2::Packet::EchoRequest, SMB2::Packet::EchoResponse, SMB2::Packet::ErrorPacket, SMB2::Packet::IoctlRequest, SMB2::Packet::IoctlResponse, SMB2::Packet::LogoffRequest, SMB2::Packet::LogoffResponse, SMB2::Packet::NegotiateRequest, SMB2::Packet::NegotiateResponse, SMB2::Packet::QueryDirectoryRequest, SMB2::Packet::QueryDirectoryResponse, SMB2::Packet::QueryInfoRequest, SMB2::Packet::QueryInfoResponse, SMB2::Packet::ReadRequest, SMB2::Packet::ReadResponse, SMB2::Packet::SessionSetupRequest, SMB2::Packet::SessionSetupResponse, SMB2::Packet::SetInfoRequest, SMB2::Packet::SetInfoResponse, SMB2::Packet::TreeConnectRequest, SMB2::Packet::TreeConnectResponse, SMB2::Packet::TreeDisconnectRequest, SMB2::Packet::TreeDisconnectResponse, SMB2::Packet::WriteRequest, SMB2::Packet::WriteResponse

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.describeString

Outputs a nicely formatted string representation of the Packet's structure.

Returns:

  • (String)

    formatted string representation of the packet structure



8
9
10
11
12
13
14
# File 'lib/ruby_smb/generic_packet.rb', line 8

def self.describe
  description = ''
  fields_hashed.each do |field|
    description << format_field(field)
  end
  description
end

.fields_hashedArray<Hash>

Returns an array of hashes representing the fields for this record.

Returns:

  • (Array<Hash>)

    the array of hash representations of the record's fields



124
125
126
# File 'lib/ruby_smb/generic_packet.rb', line 124

def self.fields_hashed
  walk_fields(fields)
end

.format_field(field, depth = 0) ⇒ String

Takes a hash representation of a field and spits out a formatted string representation.

Parameters:

  • field (Hash)

    the hash representing the field

  • depth (Fixnum) (defaults to: 0)

    the recursive depth level to track indentation

Returns:

  • (String)

    the formatted string representation of the field



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/ruby_smb/generic_packet.rb', line 134

def self.format_field(field, depth = 0)
  name = field[:name].to_s
  if field[:class].ancestors.include? BinData::Record
    class_str = ''
    name.upcase!
  else
    class_str = field[:class].to_s.split('::').last
    class_str = "(#{class_str})"
    name.capitalize!
  end
  formatted_name = "\n" + ("\t" * depth) + name
  formatted_string = format '%-30s %-10s %s', formatted_name, class_str, field[:label]
  field[:fields].each do |sub_field|
    formatted_string << format_field(sub_field, (depth + 1))
  end
  formatted_string
end

.from_hex(val) ⇒ Object



73
74
75
# File 'lib/ruby_smb/generic_packet.rb', line 73

def self.from_hex(val)
  self.read(val.scan(/../).map { |x| x.hex.chr }.join)
end

.read(val) ⇒ Object

Overrides the class #read method with some automatic exception handling. If an EOFError is thrown, it will try to read the data into the protocol specific empty ErrorPacket so that the NTstatus code can be read. We re-raise the exception in the event that it is not an SMB1 or SMB2 packet, or if it is already an error packet.



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/ruby_smb/generic_packet.rb', line 42

def self.read(val)
  begin
    super(val)
  rescue IOError => e
    # $stderr.puts "#{e.class}: #{e.message}"
    # $stderr.puts e.backtrace.join("\n")
    case self.to_s
      when /EmptyPacket|ErrorPacket/
        raise RubySMB::Error::InvalidPacket, 'Not a valid SMB packet'
      when /SMB1/
        packet = RubySMB::SMB1::Packet::EmptyPacket.read(val)
      when /SMB2/
        begin
          packet = RubySMB::SMB2::Packet::ErrorPacket.read(val)
        rescue RubySMB::Error::InvalidPacket
          # Handle the case where an SMB2 error packet is expected, but the
          # server sent an SMB1 empty packet instead. This behavior has been
          # observed with older versions of Samba when something goes wrong
          # on the server side. We just want to give it a chance and try to
          # parse it as an SMB1 empty packet to keep information and avoid
          # failing as much as possible.
          packet = RubySMB::SMB1::Packet::EmptyPacket.read(val)
        end
      else
        raise RubySMB::Error::InvalidPacket, 'Not a valid SMB packet'
    end
    packet.original_command = self::COMMAND
    packet
  end
end

.walk_fields(fields) ⇒ Array<Hash>

Recursively walks through a field, building a hash representation of that field and all of it's sub-fields.

Parameters:

  • fields (Array<BinData::SanitizedField>)

    an array of fields to walk

Returns:

  • (Array<Hash>)

    an array of hashes representing the fields



157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ruby_smb/generic_packet.rb', line 157

def self.walk_fields(fields)
  field_hashes = []
  fields.each do |field|
    field_hash = {}
    field_hash[:name] = field.name
    prototype = field.prototype
    field_hash[:class] = prototype.instance_variable_get(:@obj_class)
    params = prototype.instance_variable_get(:@obj_params)
    field_hash[:label] = params[:label]
    field_hash[:value] = params[:value]
    sub_fields = params[:fields]
    field_hash[:fields] = if sub_fields.nil?
                            []
                          else
                            walk_fields(sub_fields)
                          end
    field_hashes << field_hash
  end
  field_hashes
end

Instance Method Details

#displayObject



16
17
18
19
20
21
22
# File 'lib/ruby_smb/generic_packet.rb', line 16

def display
  display_str = ''
  self.class.fields_hashed.each do |field|
    display_str << display_field(field)
  end
  display_str
end

#initialize_instanceObject



107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/ruby_smb/generic_packet.rb', line 107

def initialize_instance
  super

  unless [RubySMB::SMB1::Packet::EmptyPacket, RubySMB::SMB2::Packet::ErrorPacket].any? {|klass| self.is_a? klass}
    case packet_smb_version
    when 'SMB1'
      smb_header.command = self.class::COMMAND
    when 'SMB2'
      smb2_header.command = self.class::COMMAND
    end
  end
end

#packet_smb_versionObject



24
25
26
27
28
29
30
31
32
33
34
# File 'lib/ruby_smb/generic_packet.rb', line 24

def packet_smb_version
  class_name = self.class.to_s
  case class_name
  when /SMB1/
    'SMB1'
  when /SMB2/
    'SMB2'
  else
    ''
  end
end

#status_codeObject



77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/ruby_smb/generic_packet.rb', line 77

def status_code
  value = -1
  smb_version = packet_smb_version
  case smb_version
  when 'SMB1'
    value = smb_header.nt_status.value
  when 'SMB2'
    value = smb2_header.nt_status.value
  end
  status_code = WindowsError::NTStatus.find_by_retval(value).first
  if status_code.nil?
    status_code = WindowsError::ErrorCode.new("0x#{value.to_s(16)}", value, "Unknown 0x#{value.to_s(16)}")
  end
  status_code
end

#valid?TrueClass, FalseClass

Validates the packet protocol ID and the SMB command

Returns:

  • (TrueClass, FalseClass)

    true if the packet is valid, false otherwise



96
97
98
99
100
101
102
103
104
105
# File 'lib/ruby_smb/generic_packet.rb', line 96

def valid?
  case packet_smb_version
  when 'SMB1'
    return smb_header.protocol == RubySMB::SMB1::SMB_PROTOCOL_ID &&
      smb_header.command == self.class::COMMAND
  when 'SMB2'
    return smb2_header.protocol == RubySMB::SMB2::SMB2_PROTOCOL_ID &&
      smb2_header.command == self.class::COMMAND
  end
end