319 lines
9.0 KiB
Ruby
319 lines
9.0 KiB
Ruby
|
require 'ipaddr'
|
||
|
module PacketFu
|
||
|
|
||
|
# Octets implements the addressing scheme for IP.
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
# Int8 :o1
|
||
|
# Int8 :o2
|
||
|
# Int8 :o3
|
||
|
# Int8 :o4
|
||
|
class Octets < Struct.new(:o1, :o2, :o3, :o4)
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
Int8.new(args[:o1]),
|
||
|
Int8.new(args[:o2]),
|
||
|
Int8.new(args[:o3]),
|
||
|
Int8.new(args[:o4]))
|
||
|
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[:o1].read str[0,1]
|
||
|
self[:o2].read str[1,1]
|
||
|
self[:o3].read str[2,1]
|
||
|
self[:o4].read str[3,1]
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Returns an address in dotted-quad format.
|
||
|
def to_x
|
||
|
ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
|
||
|
IPAddr.new(ip_str).to_s
|
||
|
end
|
||
|
|
||
|
# Returns an address in numerical format.
|
||
|
def to_i
|
||
|
ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
|
||
|
IPAddr.new(ip_str).to_i
|
||
|
end
|
||
|
|
||
|
# Set the IP Address by reading a dotted-quad address.
|
||
|
def read_quad(str)
|
||
|
read([IPAddr.new(str).to_i].pack("N"))
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# IPHeader is a complete IP struct, used in IPPacket. Most traffic on most networks today is IP-based.
|
||
|
#
|
||
|
# For more on IP packets, see http://www.networksorcery.com/enp/protocol/ip.htm
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
# Fixnum (4 bits) :ip_v, Default: 4
|
||
|
# Fixnum (4 bits) :ip_hl, Default: 5
|
||
|
# Int8 :ip_tos, Default: 0 # TODO: Break out the bits
|
||
|
# Int16 :ip_len, Default: calculated
|
||
|
# Int16 :ip_id, Default: calculated # IRL, hardly random.
|
||
|
# Int16 :ip_frag, Default: 0 # TODO: Break out the bits
|
||
|
# Int8 :ip_ttl, Default: 0xff # Changes per flavor
|
||
|
# Int8 :ip_proto, Default: 0x01 # TCP: 0x06, UDP 0x11, ICMP 0x01
|
||
|
# Int16 :ip_sum, Default: calculated
|
||
|
# Octets :ip_src
|
||
|
# Octets :ip_dst
|
||
|
# String :body
|
||
|
#
|
||
|
# Note that IPPackets will always be somewhat incorrect upon initalization,
|
||
|
# and want an IPHeader#recalc() to become correct before a
|
||
|
# Packet#to_f or Packet#to_w.
|
||
|
class IPHeader < Struct.new(:ip_v, :ip_hl, :ip_tos, :ip_len,
|
||
|
:ip_id, :ip_frag, :ip_ttl, :ip_proto,
|
||
|
:ip_sum, :ip_src, :ip_dst, :body)
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
@random_id = rand(0xffff)
|
||
|
super(
|
||
|
(args[:ip_v] || 4),
|
||
|
(args[:ip_hl] || 5),
|
||
|
Int8.new(args[:ip_tos]),
|
||
|
Int16.new(args[:ip_len] || 20),
|
||
|
Int16.new(args[:ip_id] || ip_calc_id),
|
||
|
Int16.new(args[:ip_frag]),
|
||
|
Int8.new(args[:ip_ttl] || 32),
|
||
|
Int8.new(args[:ip_proto]),
|
||
|
Int16.new(args[:ip_sum] || ip_calc_sum),
|
||
|
Octets.new.read(args[:ip_src] || "\x00\x00\x00\x00"),
|
||
|
Octets.new.read(args[:ip_dst] || "\x00\x00\x00\x00"),
|
||
|
StructFu::String.new.read(args[:body])
|
||
|
)
|
||
|
end
|
||
|
|
||
|
# Returns the object in string form.
|
||
|
def to_s
|
||
|
byte_v_hl = [(self.ip_v << 4) + self.ip_hl].pack("C")
|
||
|
byte_v_hl + (self.to_a[2,10].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[:ip_v] = str[0,1].unpack("C").first >> 4
|
||
|
self[:ip_hl] = str[0,1].unpack("C").first.to_i & 0x0f
|
||
|
self[:ip_tos].read(str[1,1])
|
||
|
self[:ip_len].read(str[2,2])
|
||
|
self[:ip_id].read(str[4,2])
|
||
|
self[:ip_frag].read(str[6,2])
|
||
|
self[:ip_ttl].read(str[8,1])
|
||
|
self[:ip_proto].read(str[9,1])
|
||
|
self[:ip_sum].read(str[10,2])
|
||
|
self[:ip_src].read(str[12,4])
|
||
|
self[:ip_dst].read(str[16,4])
|
||
|
self[:body].read(str[20,str.size]) if str.size > 20
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Setter for the version.
|
||
|
def ip_v=(i); self[:ip_v] = i.to_i; end
|
||
|
# Getter for the version.
|
||
|
def ip_v; self[:ip_v].to_i; end
|
||
|
# Setter for the header length (divide by 4)
|
||
|
def ip_hl=(i); self[:ip_hl] = i.to_i; end
|
||
|
# Getter for the header length (multiply by 4)
|
||
|
def ip_hl; self[:ip_hl].to_i; end
|
||
|
# Setter for the differentiated services
|
||
|
def ip_tos=(i); typecast i; end
|
||
|
# Getter for the differentiated services
|
||
|
def ip_tos; self[:ip_tos].to_i; end
|
||
|
# Setter for total length.
|
||
|
def ip_len=(i); typecast i; end
|
||
|
# Getter for total length.
|
||
|
def ip_len; self[:ip_len].to_i; end
|
||
|
# Setter for the identication number.
|
||
|
def ip_id=(i); typecast i; end
|
||
|
# Getter for the identication number.
|
||
|
def ip_id; self[:ip_id].to_i; end
|
||
|
# Setter for the fragmentation ID.
|
||
|
def ip_frag=(i); typecast i; end
|
||
|
# Getter for the fragmentation ID.
|
||
|
def ip_frag; self[:ip_frag].to_i; end
|
||
|
# Setter for the time to live.
|
||
|
def ip_ttl=(i); typecast i; end
|
||
|
# Getter for the time to live.
|
||
|
def ip_ttl; self[:ip_ttl].to_i; end
|
||
|
# Setter for the protocol number.
|
||
|
def ip_proto=(i); typecast i; end
|
||
|
# Getter for the protocol number.
|
||
|
def ip_proto; self[:ip_proto].to_i; end
|
||
|
# Setter for the checksum.
|
||
|
def ip_sum=(i); typecast i; end
|
||
|
# Getter for the checksum.
|
||
|
def ip_sum; self[:ip_sum].to_i; end
|
||
|
# Setter for the source IP address.
|
||
|
def ip_src=(i); typecast i; end
|
||
|
# Getter for the source IP address.
|
||
|
def ip_src; self[:ip_src].to_i; end
|
||
|
# Setter for the destination IP address.
|
||
|
def ip_dst=(i); typecast i; end
|
||
|
# Getter for the destination IP address.
|
||
|
def ip_dst; self[:ip_dst].to_i; end
|
||
|
|
||
|
# Calulcate the true length of the packet.
|
||
|
def ip_calc_len
|
||
|
(ip_hl * 4) + body.to_s.length
|
||
|
end
|
||
|
|
||
|
# Calculate the true checksum of the packet.
|
||
|
# (Yes, this is the long way to do it, but it's e-z-2-read for mathtards like me.)
|
||
|
def ip_calc_sum
|
||
|
checksum = (((self.ip_v << 4) + self.ip_hl) << 8) + self.ip_tos
|
||
|
checksum += self.ip_len
|
||
|
checksum += self.ip_id
|
||
|
checksum += self.ip_frag
|
||
|
checksum += (self.ip_ttl << 8) + self.ip_proto
|
||
|
checksum += (self.ip_src >> 16)
|
||
|
checksum += (self.ip_src & 0xffff)
|
||
|
checksum += (self.ip_dst >> 16)
|
||
|
checksum += (self.ip_dst & 0xffff)
|
||
|
checksum = checksum % 0xffff
|
||
|
checksum = 0xffff - checksum
|
||
|
checksum == 0 ? 0xffff : checksum
|
||
|
end
|
||
|
|
||
|
# Retrieve the IP ID
|
||
|
def ip_calc_id
|
||
|
@random_id
|
||
|
end
|
||
|
|
||
|
# Sets a more readable IP address. If you wants to manipulate individual octets,
|
||
|
# (eg, for host scanning in one network), it would be better use ip_src.o1 through
|
||
|
# ip_src.o4 instead.
|
||
|
def ip_saddr=(addr)
|
||
|
self[:ip_src].read_quad(addr)
|
||
|
end
|
||
|
|
||
|
# Returns a more readable IP source address.
|
||
|
def ip_saddr
|
||
|
self[:ip_src].to_x
|
||
|
end
|
||
|
|
||
|
# Sets a more readable IP address.
|
||
|
def ip_daddr=(addr)
|
||
|
self[:ip_dst].read_quad(addr)
|
||
|
end
|
||
|
|
||
|
# Returns a more readable IP destination address.
|
||
|
def ip_daddr
|
||
|
self[:ip_dst].to_x
|
||
|
end
|
||
|
|
||
|
# Translate various formats of IPv4 Addresses to an array of digits.
|
||
|
def self.octet_array(addr)
|
||
|
if addr.class == String
|
||
|
oa = addr.split('.').collect {|x| x.to_i}
|
||
|
elsif addr.class == Fixnum
|
||
|
oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
|
||
|
elsif addr.class == Bignum
|
||
|
oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
|
||
|
elsif addr.class == Array
|
||
|
oa = addr
|
||
|
else
|
||
|
raise ArgumentError, "IP Address should be a dotted quad string, an array of ints, or a bignum"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Recalculate the calculated IP fields. Valid arguments are:
|
||
|
# :all
|
||
|
# :ip_len
|
||
|
# :ip_sum
|
||
|
# :ip_id
|
||
|
def ip_recalc(arg=:all)
|
||
|
case arg
|
||
|
when :ip_len
|
||
|
self.ip_len=ip_calc_len
|
||
|
when :ip_sum
|
||
|
self.ip_sum=ip_calc_sum
|
||
|
when :ip_id
|
||
|
@random_id = rand(0xffff)
|
||
|
when :all
|
||
|
self.ip_id= ip_calc_id
|
||
|
self.ip_len= ip_calc_len
|
||
|
self.ip_sum= ip_calc_sum
|
||
|
else
|
||
|
raise ArgumentError, "No such field `#{arg}'"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# IPPacket is used to construct IP packets. They contain an EthHeader, an IPHeader, and usually
|
||
|
# a transport-layer protocol such as UDPHeader, TCPHeader, or ICMPHeader.
|
||
|
#
|
||
|
# == Example
|
||
|
#
|
||
|
# require 'packetfu'
|
||
|
# ip_pkt = PacketFu::IPPacket.new
|
||
|
# ip_pkt.ip_saddr="10.20.30.40"
|
||
|
# ip_pkt.ip_daddr="192.168.1.1"
|
||
|
# ip_pkt.ip_proto=1
|
||
|
# ip_pkt.ip_ttl=64
|
||
|
# ip_pkt.ip_payload="\x00\x00\x12\x34\x00\x01\x00\x01"+
|
||
|
# "Lovingly hand-crafted echo responses delivered directly to your door."
|
||
|
# ip_pkt.recalc
|
||
|
# ip_pkt.to_f('/tmp/ip.pcap')
|
||
|
#
|
||
|
# == Parameters
|
||
|
#
|
||
|
# :eth
|
||
|
# A pre-generated EthHeader object.
|
||
|
# :ip
|
||
|
# A pre-generated IPHeader object.
|
||
|
# :flavor
|
||
|
# TODO: Sets the "flavor" of the IP packet. This might include known sets of IP options, and
|
||
|
# certainly known starting TTLs.
|
||
|
# :config
|
||
|
# A hash of return address details, often the output of Utils.whoami?
|
||
|
class IPPacket < Packet
|
||
|
|
||
|
attr_accessor :eth_header, :ip_header
|
||
|
|
||
|
# Creates a new IPPacket object.
|
||
|
def initialize(args={})
|
||
|
@eth_header = EthHeader.new(args).read(args[:eth])
|
||
|
@ip_header = IPHeader.new(args).read(args[:ip])
|
||
|
@eth_header.body=@ip_header
|
||
|
|
||
|
@headers = [@eth_header, @ip_header]
|
||
|
super
|
||
|
end
|
||
|
|
||
|
# Peek provides summary data on packet contents.
|
||
|
def peek(args={})
|
||
|
peek_data = ["I "]
|
||
|
peek_data << "%-5d" % to_s.size
|
||
|
peek_data << "%-21s" % "#{ip_saddr}"
|
||
|
peek_data << "->"
|
||
|
peek_data << "%21s" % "#{ip_daddr}"
|
||
|
peek_data << "%23s" % "I:"
|
||
|
peek_data << "%04x" % ip_id.to_i
|
||
|
peek_data.join
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|