module PacketFu # ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is typically used for network # administration and connectivity testing. # # For more on ICMP packets, see http://www.networksorcery.com/enp/protocol/icmp.htm # # ==== Header Definition # # Int8 :icmp_type # Type # Int8 :icmp_code # Code # Int16 :icmp_sum Default: calculated # Checksum # String :body class ICMPHeader < Struct.new(:icmp_type, :icmp_code, :icmp_sum, :body) include StructFu def initialize(args={}) super( Int8.new(args[:icmp_type]), Int8.new(args[:icmp_code]), Int16.new(args[:icmp_sum] || icmp_calc_sum), StructFu::String.new.read(args[:body]) ) end # Returns the object in string form. def to_s self.to_a.map {|x| x.to_s}.join end # Reads a string to populate the object. def read(str) force_binary(str) return self if str.nil? self[:icmp_type].read(str[0,1]) self[:icmp_code].read(str[1,1]) self[:icmp_sum].read(str[2,2]) self[:body].read(str[4,str.size]) self end # Setter for the type. def icmp_type=(i); typecast i; end # Getter for the type. def icmp_type; self[:icmp_type].to_i; end # Setter for the code. def icmp_code=(i); typecast i; end # Getter for the code. def icmp_code; self[:icmp_code].to_i; end # Setter for the checksum. Note, this is calculated automatically with # icmp_calc_sum. def icmp_sum=(i); typecast i; end # Getter for the checksum. def icmp_sum; self[:icmp_sum].to_i; end # Calculates and sets the checksum for the object. def icmp_calc_sum checksum = (icmp_type.to_i << 8) + icmp_code.to_i chk_body = (body.to_s.size % 2 == 0 ? body.to_s : body.to_s + "\x00") if 1.respond_to? :ord chk_body.scan(/../).map { |x| (x[0].ord << 8) + x[1].ord }.each { |y| checksum += y } else chk_body.scan(/../).map { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y } end checksum = checksum % 0xffff checksum = 0xffff - checksum checksum == 0 ? 0xffff : checksum end # Recalculates the calculatable fields for ICMP. def icmp_recalc(arg=:all) # How silly is this, you can't intern a symbol in ruby 1.8.7pl72? # I'm this close to monkey patching Symbol so you can force it... arg = arg.intern if arg.respond_to? :intern case arg when :icmp_sum self.icmp_sum=icmp_calc_sum when :all self.icmp_sum=icmp_calc_sum else raise ArgumentError, "No such field `#{arg}'" end end end # ICMPPacket is used to construct ICMP Packets. They contain an EthHeader, an IPHeader, and a ICMPHeader. # # == Example # # icmp_pkt.new # icmp_pkt.icmp_type = 8 # icmp_pkt.icmp_code = 0 # icmp_pkt.payload = "ABC, easy as 123. As simple as do-re-mi. ABC, 123, baby, you and me!" # # icmp_pkt.ip_saddr="1.2.3.4" # icmp_pkt.ip_daddr="5.6.7.8" # # icmp_pkt.recalc # icmp_pkt.to_f('/tmp/icmp.pcap') # # == Parameters # # :eth # A pre-generated EthHeader object. # :ip # A pre-generated IPHeader object. # :flavor # TODO: Sets the "flavor" of the ICMP packet. Pings, in particular, often betray their true # OS. # :config # A hash of return address details, often the output of Utils.whoami? class ICMPPacket < Packet attr_accessor :eth_header, :ip_header, :icmp_header def initialize(args={}) @eth_header = EthHeader.new(args).read(args[:eth]) @ip_header = IPHeader.new(args).read(args[:ip]) @ip_header.ip_proto = 1 @icmp_header = ICMPHeader.new(args).read(args[:icmp]) @ip_header.body = @icmp_header @eth_header.body = @ip_header @headers = [@eth_header, @ip_header, @icmp_header] super end # Peek provides summary data on packet contents. def peek(args={}) peek_data = ["C "] # I is taken by IP peek_data << "%-5d" % self.to_s.size type = case self.icmp_type.to_i when 8 "ping" when 0 "pong" else "%02x-%02x" % [self.icmp_type, self.icmp_code] end peek_data << "%-21s" % "#{self.ip_saddr}:#{type}" peek_data << "->" peek_data << "%21s" % "#{self.ip_daddr}" peek_data << "%23s" % "I:" peek_data << "%04x" % self.ip_id peek_data.join end end end