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::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::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



118
119
120
# File 'lib/ruby_smb/generic_packet.rb', line 118

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



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/ruby_smb/generic_packet.rb', line 128

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

.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
# File 'lib/ruby_smb/generic_packet.rb', line 42

def self.read(val)
  begin
    super(val)
  rescue IOError => e
    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



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/ruby_smb/generic_packet.rb', line 151

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



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/ruby_smb/generic_packet.rb', line 101

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



71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/ruby_smb/generic_packet.rb', line 71

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



90
91
92
93
94
95
96
97
98
99
# File 'lib/ruby_smb/generic_packet.rb', line 90

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