1062 lines
27 KiB
Ruby
1062 lines
27 KiB
Ruby
|
module PacketFu
|
||
|
|
||
|
# Implements the Explict Congestion Notification for TCPHeader.
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
#
|
||
|
# Fixnum (1 bit) :n
|
||
|
# Fixnum (1 bit) :c
|
||
|
# Fixnum (1 bit) :e
|
||
|
class TcpEcn < Struct.new(:n, :c, :e)
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
super(args[:n], args[:c], args[:e]) if args
|
||
|
end
|
||
|
|
||
|
# Returns the TcpEcn field as an integer... even though it's going
|
||
|
# to be split across a byte boundary.
|
||
|
def to_i
|
||
|
(n.to_i << 2) + (c.to_i << 1) + e.to_i
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil? || str.size < 2
|
||
|
if 1.respond_to? :ord
|
||
|
byte1 = str[0].ord
|
||
|
byte2 = str[1].ord
|
||
|
else
|
||
|
byte1 = str[0]
|
||
|
byte2 = str[1]
|
||
|
end
|
||
|
self[:n] = byte1 & 0b00000001 == 0b00000001 ? 1 : 0
|
||
|
self[:c] = byte2 & 0b10000000 == 0b10000000 ? 1 : 0
|
||
|
self[:e] = byte2 & 0b01000000 == 0b01000000 ? 1 : 0
|
||
|
self
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Implements the Header Length for TCPHeader.
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
# Fixnum (4 bits) :hlen
|
||
|
class TcpHlen < Struct.new(:hlen)
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
super(args[:hlen])
|
||
|
end
|
||
|
|
||
|
# Returns the TcpHlen field as an integer. Note these will become the high
|
||
|
# bits at the TCP header's offset, even though the lower 4 bits
|
||
|
# will be further chopped up.
|
||
|
def to_i
|
||
|
hlen.to_i & 0b1111
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil? || str.size.zero?
|
||
|
if 1.respond_to? :ord
|
||
|
self[:hlen] = (str[0].ord & 0b11110000) >> 4
|
||
|
else
|
||
|
self[:hlen] = (str[0] & 0b11110000) >> 4
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Returns the object in string form.
|
||
|
def to_s
|
||
|
[self.to_i].pack("C")
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Implements the Reserved bits for TCPHeader.
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
#
|
||
|
# Fixnum (1 bit) :r1
|
||
|
# Fixnum (1 bit) :r2
|
||
|
# Fixnum (1 bit) :r3
|
||
|
class TcpReserved < Struct.new(:r1, :r2, :r3)
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args[:r1] || 0,
|
||
|
args[:r2] || 0,
|
||
|
args[:r3] || 0) if args.kind_of? Hash
|
||
|
end
|
||
|
|
||
|
# Returns the Reserved field as an integer.
|
||
|
def to_i
|
||
|
(r1.to_i << 2) + (r2.to_i << 1) + r3.to_i
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil? || str.size.zero?
|
||
|
if 1.respond_to? :ord
|
||
|
byte = str[0].ord
|
||
|
else
|
||
|
byte = str[0]
|
||
|
end
|
||
|
self[:r1] = byte & 0b00000100 == 0b00000100 ? 1 : 0
|
||
|
self[:r2] = byte & 0b00000010 == 0b00000010 ? 1 : 0
|
||
|
self[:r3] = byte & 0b00000001 == 0b00000001 ? 1 : 0
|
||
|
self
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Implements flags for TCPHeader.
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
# Fixnum (1 bit) :urg
|
||
|
# Fixnum (1 bit) :ack
|
||
|
# Fixnum (1 bit) :psh
|
||
|
# Fixnum (1 bit) :rst
|
||
|
# Fixnum (1 bit) :syn
|
||
|
# Fixnum (1 bit) :fin
|
||
|
#
|
||
|
# Flags can typically be set by setting them either to 1 or 0, or to true or false.
|
||
|
class TcpFlags < Struct.new(:urg, :ack, :psh, :rst, :syn, :fin)
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
# This technique attemts to ensure that flags are always 0 (off)
|
||
|
# or 1 (on). Statements like nil and false shouldn't be lurking in here.
|
||
|
if args.nil? || args.size.zero?
|
||
|
super( 0, 0, 0, 0, 0, 0)
|
||
|
else
|
||
|
super(
|
||
|
(args[:urg] ? 1 : 0),
|
||
|
(args[:ack] ? 1 : 0),
|
||
|
(args[:psh] ? 1 : 0),
|
||
|
(args[:rst] ? 1 : 0),
|
||
|
(args[:syn] ? 1 : 0),
|
||
|
(args[:fin] ? 1 : 0)
|
||
|
)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Returns the TcpFlags as an integer.
|
||
|
# Also not a great candidate for to_s due to the short bitspace.
|
||
|
def to_i
|
||
|
(urg.to_i << 5) + (ack.to_i << 4) + (psh.to_i << 3) +
|
||
|
(rst.to_i << 2) + (syn.to_i << 1) + fin.to_i
|
||
|
end
|
||
|
|
||
|
# Helper to determine if this flag is a 1 or a 0.
|
||
|
def zero_or_one(i=0)
|
||
|
if i == 0 || i == false || i == nil
|
||
|
0
|
||
|
else
|
||
|
1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Setter for the Urgent flag.
|
||
|
def urg=(i); self[:urg] = zero_or_one(i); end
|
||
|
# Setter for the Acknowlege flag.
|
||
|
def ack=(i); self[:ack] = zero_or_one(i); end
|
||
|
# Setter for the Push flag.
|
||
|
def psh=(i); self[:psh] = zero_or_one(i); end
|
||
|
# Setter for the Reset flag.
|
||
|
def rst=(i); self[:rst] = zero_or_one(i); end
|
||
|
# Setter for the Synchronize flag.
|
||
|
def syn=(i); self[:syn] = zero_or_one(i); end
|
||
|
# Setter for the Finish flag.
|
||
|
def fin=(i); self[:fin] = zero_or_one(i); end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil?
|
||
|
if 1.respond_to? :ord
|
||
|
byte = str[0].ord
|
||
|
else
|
||
|
byte = str[0]
|
||
|
end
|
||
|
self[:urg] = byte & 0b00100000 == 0b00100000 ? 1 : 0
|
||
|
self[:ack] = byte & 0b00010000 == 0b00010000 ? 1 : 0
|
||
|
self[:psh] = byte & 0b00001000 == 0b00001000 ? 1 : 0
|
||
|
self[:rst] = byte & 0b00000100 == 0b00000100 ? 1 : 0
|
||
|
self[:syn] = byte & 0b00000010 == 0b00000010 ? 1 : 0
|
||
|
self[:fin] = byte & 0b00000001 == 0b00000001 ? 1 : 0
|
||
|
self
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
module PacketFu
|
||
|
|
||
|
# TcpOption is the base class for all TCP options. Note that TcpOption#len
|
||
|
# returns the size of the entire option, while TcpOption#optlen is the struct
|
||
|
# for the TCP Option Length field.
|
||
|
#
|
||
|
# Subclassed options should set the correct TcpOption#kind by redefining
|
||
|
# initialize. They should also deal with various value types there by setting
|
||
|
# them explicitly with an accompanying StructFu#typecast for the setter.
|
||
|
#
|
||
|
# By default, values are presumed to be strings, unless they are Numeric, in
|
||
|
# which case a guess is made to the width of the Numeric based on the given
|
||
|
# optlen.
|
||
|
#
|
||
|
# Note that normally, optlen is /not/ enforced for directly setting values,
|
||
|
# so the user is perfectly capable of setting incorrect lengths.
|
||
|
class TcpOption < Struct.new(:kind, :optlen, :value)
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
Int8.new(args[:kind]),
|
||
|
Int8.new(args[:optlen])
|
||
|
)
|
||
|
if args[:value].kind_of? Numeric
|
||
|
self[:value] = case args[:optlen]
|
||
|
when 3; Int8.new(args[:value])
|
||
|
when 4; Int16.new(args[:value])
|
||
|
when 6; Int32.new(args[:value])
|
||
|
else; StructFu::String.new.read(args[:value])
|
||
|
end
|
||
|
else
|
||
|
self[:value] = StructFu::String.new.read(args[:value])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Returns the object in string form.
|
||
|
def to_s
|
||
|
self[:kind].to_s +
|
||
|
(self[:optlen].value.nil? ? nil : self[:optlen]).to_s +
|
||
|
(self[:value].nil? ? nil : self[:value]).to_s
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil?
|
||
|
self[:kind].read(str[0,1])
|
||
|
if str[1,1]
|
||
|
self[:optlen].read(str[1,1])
|
||
|
if str[2,1] && optlen.value > 2
|
||
|
self[:value].read(str[2,optlen.value-2])
|
||
|
end
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# The default decode for an unknown option. Known options should redefine this.
|
||
|
def decode
|
||
|
unk = "unk-#{self.kind.to_i}"
|
||
|
(self[:optlen].to_i > 2 && self[:value].to_s.size > 1) ? [unk,self[:value]].join(":") : unk
|
||
|
end
|
||
|
|
||
|
# Setter for the "kind" byte of this option.
|
||
|
def kind=(i); typecast i; end
|
||
|
# Setter for the "option length" byte for this option.
|
||
|
def optlen=(i); typecast i; end
|
||
|
|
||
|
# Setter for the value of this option.
|
||
|
def value=(i)
|
||
|
if i.kind_of? Numeric
|
||
|
typecast i
|
||
|
elsif i.respond_to? :to_s
|
||
|
self[:value] = i
|
||
|
else
|
||
|
self[:value] = ''
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Generally, encoding a value is going to be just a read. Some
|
||
|
# options will treat things a little differently; TS for example,
|
||
|
# takes two values and concatenates them.
|
||
|
def encode(str)
|
||
|
self[:value] = self.class.new(:value => str).value
|
||
|
end
|
||
|
|
||
|
# Returns true if this option has an optlen. Some don't.
|
||
|
def has_optlen?
|
||
|
(kind.value && kind.value < 2) ? false : true
|
||
|
end
|
||
|
|
||
|
# Returns true if this option has a value. Some don't.
|
||
|
def has_value?
|
||
|
(value.respond_to? :to_s && value.to_s.size > 0) ? false : true
|
||
|
end
|
||
|
|
||
|
# End of Line option. Usually used to terminate a string of options.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option000.htm
|
||
|
class EOL < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 0)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
def decode
|
||
|
"EOL"
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# No Operation option. Usually used to pad out options to fit a 4-byte alignment.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option001.htm
|
||
|
class NOP < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 1)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
def decode
|
||
|
"NOP"
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Maximum Segment Size option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option002.htm
|
||
|
class MSS < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 2,
|
||
|
:optlen => 4
|
||
|
)
|
||
|
)
|
||
|
self[:value] = Int16.new(args[:value])
|
||
|
end
|
||
|
|
||
|
def value=(i); typecast i; end
|
||
|
|
||
|
# MSS options with lengths other than 4 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 4
|
||
|
"MSS:#{self[:value].to_i}"
|
||
|
else
|
||
|
"MSS-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Window Size option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option003.htm
|
||
|
class WS < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 3,
|
||
|
:optlen => 3
|
||
|
)
|
||
|
)
|
||
|
self[:value] = Int8.new(args[:value])
|
||
|
end
|
||
|
|
||
|
def value=(i); typecast i; end
|
||
|
|
||
|
# WS options with lengths other than 3 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 3
|
||
|
"WS:#{self[:value].to_i}"
|
||
|
else
|
||
|
"WS-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Selective Acknowlegment OK option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option004.htm
|
||
|
class SACKOK < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 4,
|
||
|
:optlen => 2)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
# SACKOK options with sizes other than 2 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 2
|
||
|
"SACKOK"
|
||
|
else
|
||
|
"SACKOK-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Selective Acknowledgement option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option004.htm
|
||
|
#
|
||
|
# Note that SACK always takes its optlen from the size of the string.
|
||
|
class SACK < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 5,
|
||
|
:optlen => ((args[:value] || "").size + 2)
|
||
|
)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
def optlen=(i); typecast i; end
|
||
|
|
||
|
def value=(i)
|
||
|
self[:optlen] = Int8.new(i.to_s.size + 2)
|
||
|
self[:value] = StructFu::String.new(i)
|
||
|
end
|
||
|
|
||
|
def decode
|
||
|
"SACK:#{self[:value]}"
|
||
|
end
|
||
|
|
||
|
def encode(str)
|
||
|
temp_obj = self.class.new(:value => str)
|
||
|
self[:value] = temp_obj.value
|
||
|
self[:optlen] = temp_obj.optlen.value
|
||
|
self
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Echo option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option006.htm
|
||
|
class ECHO < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 6,
|
||
|
:optlen => 6
|
||
|
)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
# ECHO options with lengths other than 6 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 6
|
||
|
"ECHO:#{self[:value]}"
|
||
|
else
|
||
|
"ECHO-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Echo Reply option.
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option007.htm
|
||
|
class ECHOREPLY < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 7,
|
||
|
:optlen => 6
|
||
|
)
|
||
|
)
|
||
|
end
|
||
|
|
||
|
# ECHOREPLY options with lengths other than 6 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 6
|
||
|
"ECHOREPLY:#{self[:value]}"
|
||
|
else
|
||
|
"ECHOREPLY-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# Timestamp option
|
||
|
#
|
||
|
# http://www.networksorcery.com/enp/protocol/tcp/option008.htm
|
||
|
class TS < TcpOption
|
||
|
def initialize(args={})
|
||
|
super(
|
||
|
args.merge(:kind => 8,
|
||
|
:optlen => 10
|
||
|
)
|
||
|
)
|
||
|
self[:value] = StructFu::String.new.read(args[:value] || "\x00" * 8)
|
||
|
end
|
||
|
|
||
|
# TS options with lengths other than 10 are malformed.
|
||
|
def decode
|
||
|
if self[:optlen].to_i == 10
|
||
|
val1,val2 = self[:value].unpack("NN")
|
||
|
"TS:#{val1};#{val2}"
|
||
|
else
|
||
|
"TS-bad:#{self[:value]}"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# TS options are in the format of "TS:[timestamp value];[timestamp secret]" Both
|
||
|
# should be written as decimal numbers.
|
||
|
def encode(str)
|
||
|
if str =~ /^([0-9]+);([0-9]+)$/
|
||
|
tsval,tsecr = str.split(";").map {|x| x.to_i}
|
||
|
if tsval <= 0xffffffff && tsecr <= 0xffffffff
|
||
|
self[:value] = StructFu::String.new([tsval,tsecr].pack("NN"))
|
||
|
else
|
||
|
self[:value] = StructFu::String.new(str)
|
||
|
end
|
||
|
else
|
||
|
self[:value] = StructFu::String.new(str)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
class TcpOptions < Array
|
||
|
|
||
|
include StructFu
|
||
|
|
||
|
# If args[:pad] is set, the options line is automatically padded out
|
||
|
# with NOPs.
|
||
|
def to_s(args={})
|
||
|
opts = self.map {|x| x.to_s}.join
|
||
|
if args[:pad]
|
||
|
unless (opts.size % 4).zero?
|
||
|
(4 - (opts.size % 4)).times { opts << "\x01" }
|
||
|
end
|
||
|
end
|
||
|
opts
|
||
|
end
|
||
|
|
||
|
def force_binary(str)
|
||
|
str.force_encoding "binary" if str.respond_to? :force_encoding
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
self.clear if self.size > 0
|
||
|
force_binary(str)
|
||
|
return self if(!str.respond_to? :to_s || str.nil?)
|
||
|
i = 0
|
||
|
while i < str.to_s.size
|
||
|
this_opt = case str[i,1].unpack("C").first
|
||
|
when 0; TcpOption::EOL.new
|
||
|
when 1; TcpOption::NOP.new
|
||
|
when 2; TcpOption::MSS.new
|
||
|
when 3; TcpOption::WS.new
|
||
|
when 4; TcpOption::SACKOK.new
|
||
|
when 5; TcpOption::SACK.new
|
||
|
when 6; TcpOption::ECHO.new
|
||
|
when 7; TcpOption::ECHOREPLY.new
|
||
|
when 8; TcpOption::TS.new
|
||
|
else; TcpOption.new
|
||
|
end
|
||
|
this_opt.read str[i,str.size]
|
||
|
unless this_opt.has_optlen?
|
||
|
this_opt.value = nil
|
||
|
this_opt.optlen = nil
|
||
|
end
|
||
|
self << this_opt
|
||
|
i += this_opt.sz
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Decode parses the TcpOptions object's member options, and produces a
|
||
|
# human-readable string by iterating over each element's decode() function.
|
||
|
# If TcpOptions elements were not initially created as TcpOptions, an
|
||
|
# attempt will be made to convert them.
|
||
|
#
|
||
|
# The output of decode is suitable as input for TcpOptions#encode.
|
||
|
def decode
|
||
|
decoded = self.map do |x|
|
||
|
if x.kind_of? TcpOption
|
||
|
x.decode
|
||
|
else
|
||
|
x = TcpOptions.new.read(x).decode
|
||
|
end
|
||
|
end
|
||
|
decoded.join(",")
|
||
|
end
|
||
|
|
||
|
# Encode takes a human-readable string and appends the corresponding
|
||
|
# binary options to the TcpOptions object. To completely replace the contents
|
||
|
# of the object, use TcpOptions#encode! instead.
|
||
|
#
|
||
|
# Options are comma-delimited, and are identical to the output of the
|
||
|
# TcpOptions#decode function. Note that the syntax can be unforgiving, so
|
||
|
# it may be easier to create the subclassed TcpOptions themselves directly,
|
||
|
# but this method can be less typing if you know what you're doing.
|
||
|
#
|
||
|
# Note that by using TcpOptions#encode, strings supplied as values which
|
||
|
# can be converted to numbers will be converted first.
|
||
|
#
|
||
|
# == Example
|
||
|
#
|
||
|
# t = TcpOptions.new
|
||
|
# t.encode("MS:1460,WS:6")
|
||
|
# t.to_s # => "\002\004\005\264\002\003\006"
|
||
|
# t.encode("NOP")
|
||
|
# t.to_s # => "\002\004\005\264\002\003\006\001"
|
||
|
def encode(str)
|
||
|
opts = str.split(/[\s]*,[\s]*/)
|
||
|
opts.each do |o|
|
||
|
kind,value = o.split(/[\s]*:[\s]*/)
|
||
|
klass = TcpOption.const_get(kind.upcase)
|
||
|
value = value.to_i if value =~ /^[0-9]+$/
|
||
|
this_opt = klass.new
|
||
|
this_opt.encode(value)
|
||
|
self << this_opt
|
||
|
end
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Like TcpOption#encode, except the entire contents are replaced.
|
||
|
def encode!(str)
|
||
|
self.clear if self.size > 0
|
||
|
encode(str)
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
module PacketFu
|
||
|
|
||
|
# TCPHeader is a complete TCP struct, used in TCPPacket. Most IP traffic is TCP-based, by
|
||
|
# volume.
|
||
|
#
|
||
|
# For more on TCP packets, see http://www.networksorcery.com/enp/protocol/tcp.htm
|
||
|
#
|
||
|
# ==== Header Definition
|
||
|
#
|
||
|
# Int16 :tcp_src Default: random
|
||
|
# Int16 :tcp_dst
|
||
|
# Int32 :tcp_seq Default: random
|
||
|
# Int32 :tcp_ack
|
||
|
# TcpHlen :tcp_hlen Default: 5 # Must recalc as options are set.
|
||
|
# TcpReserved :tcp_reserved Default: 0
|
||
|
# TcpEcn :tcp_ecn
|
||
|
# TcpFlags :tcp_flags
|
||
|
# Int16 :tcp_win, Default: 0 # WinXP's default syn packet
|
||
|
# Int16 :tcp_sum, Default: calculated # Must set this upon generation.
|
||
|
# Int16 :tcp_urg
|
||
|
# TcpOptions :tcp_opts
|
||
|
# String :body
|
||
|
#
|
||
|
# See also TcpHlen, TcpReserved, TcpEcn, TcpFlags, TcpOpts
|
||
|
class TCPHeader < Struct.new(:tcp_src, :tcp_dst,
|
||
|
:tcp_seq,
|
||
|
:tcp_ack,
|
||
|
:tcp_hlen, :tcp_reserved, :tcp_ecn, :tcp_flags, :tcp_win,
|
||
|
:tcp_sum, :tcp_urg,
|
||
|
:tcp_opts, :body)
|
||
|
include StructFu
|
||
|
|
||
|
def initialize(args={})
|
||
|
@random_seq = rand(0xffffffff)
|
||
|
@random_src = rand_port
|
||
|
super(
|
||
|
Int16.new(args[:tcp_src] || tcp_calc_src),
|
||
|
Int16.new(args[:tcp_dst]),
|
||
|
Int32.new(args[:tcp_seq] || tcp_calc_seq),
|
||
|
Int32.new(args[:tcp_ack]),
|
||
|
TcpHlen.new(:hlen => (args[:tcp_hlen] || 5)),
|
||
|
TcpReserved.new(args[:tcp_reserved] || 0),
|
||
|
TcpEcn.new(args[:tcp_ecn]),
|
||
|
TcpFlags.new(args[:tcp_flags]),
|
||
|
Int16.new(args[:tcp_win] || 0x4000),
|
||
|
Int16.new(args[:tcp_sum] || 0),
|
||
|
Int16.new(args[:tcp_urg]),
|
||
|
TcpOptions.new.read(args[:tcp_opts]),
|
||
|
StructFu::String.new.read(args[:body])
|
||
|
)
|
||
|
end
|
||
|
|
||
|
attr_accessor :flavor
|
||
|
|
||
|
# Helper function to create the string for Hlen, Reserved, ECN, and Flags.
|
||
|
def bits_to_s
|
||
|
bytes = []
|
||
|
bytes[0] = (self[:tcp_hlen].to_i << 4) +
|
||
|
(self[:tcp_reserved].to_i << 1) +
|
||
|
self[:tcp_ecn].n.to_i
|
||
|
bytes[1] = (self[:tcp_ecn].c.to_i << 7) +
|
||
|
(self[:tcp_ecn].e.to_i << 6) +
|
||
|
self[:tcp_flags].to_i
|
||
|
bytes.pack("CC")
|
||
|
end
|
||
|
|
||
|
# Returns the object in string form.
|
||
|
def to_s
|
||
|
hdr = self.to_a.map do |x|
|
||
|
if x.kind_of? TcpHlen
|
||
|
bits_to_s
|
||
|
elsif x.kind_of? TcpReserved
|
||
|
next
|
||
|
elsif x.kind_of? TcpEcn
|
||
|
next
|
||
|
elsif x.kind_of? TcpFlags
|
||
|
next
|
||
|
else
|
||
|
x.to_s
|
||
|
end
|
||
|
end
|
||
|
hdr.flatten.join
|
||
|
end
|
||
|
|
||
|
# Reads a string to populate the object.
|
||
|
def read(str)
|
||
|
force_binary(str)
|
||
|
return self if str.nil?
|
||
|
self[:tcp_src].read(str[0,2])
|
||
|
self[:tcp_dst].read(str[2,2])
|
||
|
self[:tcp_seq].read(str[4,4])
|
||
|
self[:tcp_ack].read(str[8,4])
|
||
|
self[:tcp_hlen].read(str[12,1])
|
||
|
self[:tcp_reserved].read(str[12,1])
|
||
|
self[:tcp_ecn].read(str[12,2])
|
||
|
self[:tcp_flags].read(str[13,1])
|
||
|
self[:tcp_win].read(str[14,2])
|
||
|
self[:tcp_sum].read(str[16,2])
|
||
|
self[:tcp_urg].read(str[18,2])
|
||
|
self[:tcp_opts].read(str[20,((self[:tcp_hlen].to_i * 4) - 20)])
|
||
|
self[:body].read(str[(self[:tcp_hlen].to_i * 4),str.size])
|
||
|
self
|
||
|
end
|
||
|
|
||
|
# Setter for the TCP source port.
|
||
|
def tcp_src=(i); typecast i; end
|
||
|
# Getter for the TCP source port.
|
||
|
def tcp_src; self[:tcp_src].to_i; end
|
||
|
# Setter for the TCP destination port.
|
||
|
def tcp_dst=(i); typecast i; end
|
||
|
# Getter for the TCP destination port.
|
||
|
def tcp_dst; self[:tcp_dst].to_i; end
|
||
|
# Setter for the TCP sequence number.
|
||
|
def tcp_seq=(i); typecast i; end
|
||
|
# Getter for the TCP sequence number.
|
||
|
def tcp_seq; self[:tcp_seq].to_i; end
|
||
|
# Setter for the TCP ackowlegement number.
|
||
|
def tcp_ack=(i); typecast i; end
|
||
|
# Getter for the TCP ackowlegement number.
|
||
|
def tcp_ack; self[:tcp_ack].to_i; end
|
||
|
# Setter for the TCP window size number.
|
||
|
def tcp_win=(i); typecast i; end
|
||
|
# Getter for the TCP window size number.
|
||
|
def tcp_win; self[:tcp_win].to_i; end
|
||
|
# Setter for the TCP checksum.
|
||
|
def tcp_sum=(i); typecast i; end
|
||
|
# Getter for the TCP checksum.
|
||
|
def tcp_sum; self[:tcp_sum].to_i; end
|
||
|
# Setter for the TCP urgent field.
|
||
|
def tcp_urg=(i); typecast i; end
|
||
|
# Getter for the TCP urgent field.
|
||
|
def tcp_urg; self[:tcp_urg].to_i; end
|
||
|
|
||
|
# Getter for the TCP Header Length value.
|
||
|
def tcp_hlen; self[:tcp_hlen].to_i; end
|
||
|
# Setter for the TCP Header Length value.
|
||
|
def tcp_hlen=(i)
|
||
|
if i.kind_of? PacketFu::TcpHlen
|
||
|
self[:tcp_hlen]=i
|
||
|
else
|
||
|
self[:tcp_hlen].read(i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Getter for the TCP Reserved field.
|
||
|
def tcp_reserved; self[:tcp_reserved].to_i; end
|
||
|
# Setter for the TCP Reserved field.
|
||
|
def tcp_reserved=(i)
|
||
|
if i.kind_of? PacketFu::TcpReserved
|
||
|
self[:tcp_reserved]=i
|
||
|
else
|
||
|
self[:tcp_reserved].read(i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Getter for the ECN bits.
|
||
|
def tcp_ecn; self[:tcp_ecn].to_i; end
|
||
|
# Setter for the ECN bits.
|
||
|
def tcp_ecn=(i)
|
||
|
if i.kind_of? PacketFu::TcpEcn
|
||
|
self[:tcp_ecn]=i
|
||
|
else
|
||
|
self[:tcp_ecn].read(i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Getter for TCP Options.
|
||
|
def tcp_opts; self[:tcp_opts].to_s; end
|
||
|
# Setter for TCP Options.
|
||
|
def tcp_opts=(i)
|
||
|
if i.kind_of? PacketFu::TcpOptions
|
||
|
self[:tcp_opts]=i
|
||
|
else
|
||
|
self[:tcp_opts].read(i)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Resets the sequence number to a new random number.
|
||
|
def tcp_calc_seq; @random_seq; end
|
||
|
# Resets the source port to a new random number.
|
||
|
def tcp_calc_src; @random_src; end
|
||
|
|
||
|
# Returns the actual length of the TCP options.
|
||
|
def tcp_opts_len
|
||
|
self[:tcp_opts].to_s.size
|
||
|
end
|
||
|
|
||
|
# Sets and returns the true length of the TCP Header.
|
||
|
# TODO: Think about making all the option stuff safer.
|
||
|
def tcp_calc_hlen
|
||
|
self[:tcp_hlen] = TcpHlen.new(:hlen => ((20 + tcp_opts_len) / 4))
|
||
|
end
|
||
|
|
||
|
# Generates a random high port. This is affected by packet flavor.
|
||
|
def rand_port
|
||
|
rand(0xffff - 1025) + 1025
|
||
|
end
|
||
|
|
||
|
# Gets a more readable option list.
|
||
|
def tcp_options
|
||
|
self[:tcp_opts].decode
|
||
|
end
|
||
|
|
||
|
# Sets a more readable option list.
|
||
|
def tcp_options=(arg)
|
||
|
self[:tcp_opts].encode arg
|
||
|
end
|
||
|
|
||
|
# Equivalent to tcp_src.
|
||
|
def tcp_sport
|
||
|
self.tcp_src.to_i
|
||
|
end
|
||
|
|
||
|
# Equivalent to tcp_src=.
|
||
|
def tcp_sport=(arg)
|
||
|
self.tcp_src=(arg)
|
||
|
end
|
||
|
|
||
|
# Equivalent to tcp_dst.
|
||
|
def tcp_dport
|
||
|
self.tcp_dst.to_i
|
||
|
end
|
||
|
|
||
|
# Equivalent to tcp_dst=.
|
||
|
def tcp_dport=(arg)
|
||
|
self.tcp_dst=(arg)
|
||
|
end
|
||
|
|
||
|
# Recalculates calculated fields for TCP (except checksum which is at the Packet level).
|
||
|
def tcp_recalc(arg=:all)
|
||
|
case arg
|
||
|
when :tcp_hlen
|
||
|
tcp_calc_hlen
|
||
|
when :tcp_src
|
||
|
@random_tcp_src = rand_port
|
||
|
when :tcp_sport
|
||
|
@random_tcp_src = rand_port
|
||
|
when :tcp_seq
|
||
|
@random_tcp_seq = rand(0xffffffff)
|
||
|
when :all
|
||
|
tcp_calc_hlen
|
||
|
@random_tcp_src = rand_port
|
||
|
@random_tcp_seq = rand(0xffffffff)
|
||
|
else
|
||
|
raise ArgumentError, "No such field `#{arg}'"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# TCPPacket is used to construct TCP packets. They contain an EthHeader, an IPHeader, and a TCPHeader.
|
||
|
#
|
||
|
# == Example
|
||
|
#
|
||
|
# tcp_pkt = PacketFu::TCPPacket.new
|
||
|
# tcp_pkt.tcp_flags.syn=1
|
||
|
# tcp_pkt.tcp_dst=80
|
||
|
# tcp_pkt.tcp_win=5840
|
||
|
# tcp_pkt.tcp_options="mss:1460,sack.ok,ts:#{rand(0xffffffff)};0,nop,ws:7"
|
||
|
#
|
||
|
# tcp_pkt.ip_saddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.')
|
||
|
# tcp_pkt.ip_daddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.')
|
||
|
#
|
||
|
# tcp_pkt.recalc
|
||
|
# tcp_pkt.to_f('/tmp/tcp.pcap')
|
||
|
#
|
||
|
# == Parameters
|
||
|
# :eth
|
||
|
# A pre-generated EthHeader object.
|
||
|
# :ip
|
||
|
# A pre-generated IPHeader object.
|
||
|
# :flavor
|
||
|
# TODO: Sets the "flavor" of the TCP packet. This will include TCP options and the initial window
|
||
|
# size, per stack. There is a lot of variety here, and it's one of the most useful methods to
|
||
|
# remotely fingerprint devices. :flavor will span both ip and tcp for consistency.
|
||
|
# :type
|
||
|
# TODO: Set up particular types of packets (syn, psh_ack, rst, etc). This can change the initial flavor.
|
||
|
# :config
|
||
|
# A hash of return address details, often the output of Utils.whoami?
|
||
|
class TCPPacket < Packet
|
||
|
|
||
|
attr_accessor :eth_header, :ip_header, :tcp_header, :headers
|
||
|
|
||
|
def initialize(args={})
|
||
|
@eth_header = (args[:eth] || EthHeader.new)
|
||
|
@ip_header = (args[:ip] || IPHeader.new)
|
||
|
@tcp_header = (args[:tcp] || TCPHeader.new)
|
||
|
@tcp_header.flavor = args[:flavor].to_s.downcase
|
||
|
|
||
|
@ip_header.body = @tcp_header
|
||
|
@eth_header.body = @ip_header
|
||
|
@headers = [@eth_header, @ip_header, @tcp_header]
|
||
|
|
||
|
@ip_header.ip_proto=0x06
|
||
|
super
|
||
|
if args[:flavor]
|
||
|
tcp_calc_flavor(@tcp_header.flavor)
|
||
|
else
|
||
|
tcp_calc_sum
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Sets the correct flavor for TCP Packets. Recognized flavors are:
|
||
|
# windows, linux, freebsd
|
||
|
def tcp_calc_flavor(str)
|
||
|
ts_val = Time.now.to_i + rand(0x4fffffff)
|
||
|
ts_sec = rand(0xffffff)
|
||
|
case @tcp_header.flavor = str.to_s.downcase
|
||
|
when "windows" # WinXP's default syn
|
||
|
@tcp_header.tcp_win = 0x4000
|
||
|
@tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK"
|
||
|
@tcp_header.tcp_src = rand(5000 - 1026) + 1026
|
||
|
@ip_header.ip_ttl = 64
|
||
|
when "linux" # Ubuntu Linux 2.6.24-19-generic default syn
|
||
|
@tcp_header.tcp_win = 5840
|
||
|
@tcp_header.tcp_options="MSS:1460,SACKOK,TS:#{ts_val};0,NOP,WS:7"
|
||
|
@tcp_header.tcp_src = rand(61_000 - 32_000) + 32_000
|
||
|
@ip_header.ip_ttl = 64
|
||
|
when "freebsd" # Freebsd
|
||
|
@tcp_header.tcp_win = 0xffff
|
||
|
@tcp_header.tcp_options="MSS:1460,NOP,WS:3,NOP,NOP,TS:#{ts_val};#{ts_sec},SACKOK,EOL,EOL"
|
||
|
@ip_header.ip_ttl = 64
|
||
|
else
|
||
|
@tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK"
|
||
|
end
|
||
|
tcp_calc_sum
|
||
|
end
|
||
|
|
||
|
# tcp_calc_sum() computes the TCP checksum, and is called upon intialization. It usually
|
||
|
# should be called just prior to dropping packets to a file or on the wire.
|
||
|
#--
|
||
|
# This is /not/ delegated down to @tcp_header since we need info
|
||
|
# from the IP header, too.
|
||
|
#++
|
||
|
def tcp_calc_sum
|
||
|
checksum = (ip_src.to_i >> 16)
|
||
|
checksum += (ip_src.to_i & 0xffff)
|
||
|
checksum += (ip_dst.to_i >> 16)
|
||
|
checksum += (ip_dst.to_i & 0xffff)
|
||
|
checksum += 0x06 # TCP Protocol.
|
||
|
checksum += (ip_len.to_i - ((ip_hl.to_i) * 4))
|
||
|
checksum += tcp_src
|
||
|
checksum += tcp_dst
|
||
|
checksum += (tcp_seq.to_i >> 16)
|
||
|
checksum += (tcp_seq.to_i & 0xffff)
|
||
|
checksum += (tcp_ack.to_i >> 16)
|
||
|
checksum += (tcp_ack.to_i & 0xffff)
|
||
|
checksum += ((tcp_hlen << 12) +
|
||
|
(tcp_reserved << 9) +
|
||
|
(tcp_ecn.to_i << 6) +
|
||
|
tcp_flags.to_i
|
||
|
)
|
||
|
checksum += tcp_win
|
||
|
checksum += tcp_urg
|
||
|
|
||
|
chk_tcp_opts = (tcp_opts.to_s.size % 2 == 0 ? tcp_opts.to_s : tcp_opts.to_s + "\x00")
|
||
|
chk_tcp_opts.unpack("n*").each {|x| checksum = checksum + x }
|
||
|
if (ip_len - ((ip_hl + tcp_hlen) * 4)) >= 0
|
||
|
real_tcp_payload = payload[0,( ip_len - ((ip_hl + tcp_hlen) * 4) )] # Can't forget those pesky FCSes!
|
||
|
else
|
||
|
real_tcp_payload = payload # Something's amiss here so don't bother figuring out where the real payload is.
|
||
|
end
|
||
|
chk_payload = (real_tcp_payload.size % 2 == 0 ? real_tcp_payload : real_tcp_payload + "\x00") # Null pad if it's odd.
|
||
|
chk_payload.unpack("n*").each {|x| checksum = checksum+x }
|
||
|
checksum = checksum % 0xffff
|
||
|
checksum = 0xffff - checksum
|
||
|
checksum == 0 ? 0xffff : checksum
|
||
|
@tcp_header.tcp_sum = checksum
|
||
|
end
|
||
|
|
||
|
# Recalculates various fields of the TCP packet.
|
||
|
#
|
||
|
# ==== Parameters
|
||
|
#
|
||
|
# :all
|
||
|
# Recomputes all calculated fields.
|
||
|
# :tcp_sum
|
||
|
# Recomputes the TCP checksum.
|
||
|
# :tcp_hlen
|
||
|
# Recomputes the TCP header length. Useful after options are added.
|
||
|
def tcp_recalc(arg=:all)
|
||
|
case arg
|
||
|
when :tcp_sum
|
||
|
tcp_calc_sum
|
||
|
when :tcp_hlen
|
||
|
@tcp_header.tcp_recalc :tcp_hlen
|
||
|
when :all
|
||
|
@tcp_header.tcp_recalc :all
|
||
|
tcp_calc_sum
|
||
|
else
|
||
|
raise ArgumentError, "No such field `#{arg}'"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# Peek provides summary data on packet contents.
|
||
|
def peek(args={})
|
||
|
peek_data = ["T "]
|
||
|
peek_data << "%-5d" % self.to_s.size
|
||
|
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.tcp_src}"
|
||
|
peek_data << "->"
|
||
|
peek_data << "%21s" % "#{self.ip_daddr}:#{self.tcp_dst}"
|
||
|
flags = ' ['
|
||
|
flags << (self.tcp_flags.urg.zero? ? "." : "U")
|
||
|
flags << (self.tcp_flags.ack.zero? ? "." : "A")
|
||
|
flags << (self.tcp_flags.psh.zero? ? "." : "P")
|
||
|
flags << (self.tcp_flags.rst.zero? ? "." : "R")
|
||
|
flags << (self.tcp_flags.syn.zero? ? "." : "S")
|
||
|
flags << (self.tcp_flags.fin.zero? ? "." : "F")
|
||
|
flags << '] '
|
||
|
peek_data << flags
|
||
|
peek_data << "S:"
|
||
|
peek_data << "%08x" % self.tcp_seq
|
||
|
peek_data << "|I:"
|
||
|
peek_data << "%04x" % self.ip_id
|
||
|
peek_data.join
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
end
|