metasploit-framework/lib/packetfu/icmp.rb

154 lines
4.2 KiB
Ruby
Raw Normal View History

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