#!/usr/bin/env ruby # Copyright (C) 2007 Sylvain SARMEJEANNE # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; version 2. # This program is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # General Public License for more details. module Scruby require "rex/socket" # Trackin fields @@fields = {} def Scruby.fields @@fields end def Scruby.get_field(d) @@fields[d] end class Field attr_accessor :name attr_accessor :default_value attr_accessor :format # Constructor def initialize(name, default_value) @name = name @default_value = default_value @format = '' self.init() end # Field initialization. This function have to be redefined by subclasses. def init end # Retrieves the field value from a string. This may be redefined by subclasses. def dissect(layer, string) # Preparing the packet for building self.pre_build() part = string.unpack(self.format + 'a*') # Returning if nothing could be unpacked return '' if part[-2].nil? or part[-2] == '' # Updating the field value layer.instance_variable_set("@#{self.name}", self.from_net(part)) # 'remain' is the last element of the array (unpacking 'a*'), # with this command, part doesn't contain 'remain' anymore. remain = part.pop return remain end # Converts from network to internal encoding # e.g for IP.dst: number 2130706433 -> string "127.0.0.1" (2130706433 = 127*2^24 + 1*2^0) def from_net(value) return value[0] end # Converts from internal encoding to network # e.g. for IP.dst: string "127.0.0.1"-> number 2130706433 def to_net(value) return [value].pack(@format) end # Converts from human to internal encoding # e.g. allows TCP(:proto=>'ICMP') def from_human(value) return value end # Converts from internal encoding to human display # e.g. displays "0xDEADBEEF" for checksums def to_human(value) return value.to_s end # Same as to_human() but displays more information # e.g. "6 (TCP)" instead of "6" for IP protocol def to_human_complete(value) return value.to_s end # Returns yes if the field is to be added to the dissectors, e.g. depending # on the value of another field of the layer (see Dot11*) def is_applicable?(layer) return true end # Prepares the packet for building # e.g. for StrLenField, retrieves the right format size from the associated FieldLenField def pre_build end end # Shortcut mixins for reducing code size module FieldHumanHex def to_human(value) return sprintf('0x%x', value) end def to_human_complete(value) return sprintf('0x%x', value) end end # Shortcut mixins for reducing code size module FieldHumanHexEnum def to_human(value) return sprintf('0x%x', value) end def to_human_complete(value) # Checking if the value is in the enumeration keys if @enum.keys.include?(value) return sprintf('0x%x', value) + ' (' + @enum[value].to_s + ')' # Otherwise, just returning the value else return sprintf('0x%x', value) end end end # Shortcut mixins for signed conversion module SignedValue def utosc(val) (val > 0x7f) ? ((0x100 - val) * -1) : val end def utoss(val) (val > 0x7fff) ? ((0x10000 - val) * -1) : val end def utosl(val) (val > 0x7fffffff) ? ((0x100000000 - val) * -1) : val end def from_net(value) val = value[0] case @format when 'V','N' utosl(val) when 'v','n' utoss(val) when 'C' utosc(val) else raise "Unsupport format! #{@format}" end end end # Field for an enumeration. Don't use this one in your dissectors, # use the *EnumFields below instead. class EnumField string.length format = self.format + (@@bitsdone + @size).to_s + 'a*' part = string.unpack(format) # Returning if nothing could be unpacked return '' if part[-2].nil? or part[-2] == '' # Updating the field value layer.instance_variable_set("@#{self.name}", self.from_net(part)) # Adding the size of the field to the number of bits processed so far @@bitsdone += @size # If we have just built a byte or more, moving on to the next part of the string if @@bitsdone >= 8 nb_to_be_trimmed = @@bitsdone/8 # NB : @@bitsdone will not be always 0 after this (e.g. if bitsdone was not 0 mod 8) @@bitsdone -= nb_to_be_trimmed*8 # Getting rid of the nb_to_be_trimmed bytes that have just been processed return string[nb_to_be_trimmed..-1] # Otherwise, returning the whole string else return string end end def from_net(value) # Removing high-order bits bits = value[0] bits = bits.to_i(2) bits &= (1 << @size) - 1 return bits end def to_net(value) @@bitsdone ||= 0 # OR'ing this value the value the previous ones @@byte <<= @size @@byte |= value # Adding the size of the field to the number of bits processed so far @@bitsdone += @size to_be_returned = '' # If one or more bytes could have been processed if @@bitsdone >= 8 # Getting high-order bytes one by one in a begin...until loop begin @@bitsdone -= 8 new_byte = @@byte >> @@bitsdone to_be_returned += [new_byte].pack('C') # Removing high-order bits @@byte &= (1 << @@bitsdone) - 1 end until @@bitsdone < 8 end return to_be_returned end end # Field for one byte class ByteField 0 return value.to_s + ' (' + out + ')' end end # Field for one long (big endian/network order) class LongField