metasploit-framework/lib/packetfu/tcp.rb

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