361 lines
11 KiB
Ruby
361 lines
11 KiB
Ruby
require 'packetfu/tcpopts'
|
|
module PacketFu
|
|
|
|
# Implements the Explict Congestion Notification for TCPHeader.
|
|
#
|
|
# ==== Header Definition
|
|
#
|
|
#
|
|
# bit1 :n
|
|
# bit1 :c
|
|
# bit1 :e
|
|
class TcpEcn < BinData::MultiValue
|
|
bit1 :n
|
|
bit1 :c
|
|
bit1 :e
|
|
|
|
# Returns the TcpEcn field as an integer.
|
|
def to_i
|
|
(n << 2) + (c << 1) + e
|
|
end
|
|
end
|
|
|
|
# Implements flags for TCPHeader.
|
|
#
|
|
# ==== Header Definition
|
|
#
|
|
# bit1 :urg
|
|
# bit1 :ack
|
|
# bit1 :psh
|
|
# bit1 :rst
|
|
# bit1 :syn
|
|
# bit1 :fin
|
|
class TcpFlags < BinData::MultiValue
|
|
bit1 :urg
|
|
bit1 :ack
|
|
bit1 :psh
|
|
bit1 :rst
|
|
bit1 :syn
|
|
bit1 :fin
|
|
|
|
# Returns the TcpFlags as an integer.
|
|
def to_i
|
|
(urg << 5) + (ack << 4) + (psh << 3) + (rst << 2) + (syn << 1) + fin
|
|
end
|
|
end
|
|
|
|
# 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
|
|
#
|
|
# uint16be :tcp_src, :initial_value => lambda {tcp_calc_src}
|
|
# uint16be :tcp_dst
|
|
# uint32be :tcp_seq, :initial_value => lambda {tcp_calc_seq}
|
|
# uint32be :tcp_ack
|
|
# bit4 :tcp_hlen, :initial_value => 5 # Must recalc as options are set.
|
|
# bit3 :tcp_reserved
|
|
# tcp_ecn :tcp_ecn
|
|
# tcp_flags :tcp_flags
|
|
# uint16be :tcp_win, :initial_value => 0x4000 # WinXP's default syn packet
|
|
# uint16be :tcp_sum, :initial_value => 0 # Must set this upon generation.
|
|
# uint16be :tcp_urg
|
|
# string :tcp_opts
|
|
# rest :body
|
|
#
|
|
# See also TcpEcn, TcpFlags, TcpOpts
|
|
class TCPHeader < BinData::MultiValue
|
|
|
|
uint16be :tcp_src, :initial_value => lambda {tcp_calc_src}
|
|
uint16be :tcp_dst
|
|
uint32be :tcp_seq, :initial_value => lambda {tcp_calc_seq}
|
|
uint32be :tcp_ack
|
|
bit4 :tcp_hlen, :initial_value => 5 # Must recalc as options are set.
|
|
bit3 :tcp_reserved
|
|
tcp_ecn :tcp_ecn
|
|
tcp_flags :tcp_flags
|
|
uint16be :tcp_win, :initial_value => 0x4000 # WinXP's default syn packet
|
|
uint16be :tcp_sum, :initial_value => 0 # Must set this upon generation.
|
|
uint16be :tcp_urg
|
|
string :tcp_opts
|
|
rest :body
|
|
|
|
# Create a new TCPHeader object, and intialize with a random sequence number.
|
|
def initialize(args={})
|
|
@random_seq = rand(0xffffffff)
|
|
@random_src = rand_port
|
|
super
|
|
end
|
|
|
|
attr_accessor :flavor
|
|
|
|
# tcp_calc_hlen adjusts the header length to account for tcp_opts. Note
|
|
# that if tcp_opts does not fall on a 32-bit boundry, tcp_calc_hlen will
|
|
# additionally pad the option string with nulls. Most stacks avoid this
|
|
# eventuality by padding with NOP options at OS-specific points in the
|
|
# option field. The practical effect of this is, you should tcp_calc_hlen
|
|
# only when all the options are already set; otherwise, additional options
|
|
# will be lost to the reciever as \x00 is an EOL option. Additionally,
|
|
# (and this is almost certainly a bug), there is no sanity checking to
|
|
# ensure the final tcp_opts value is 44 bytes or less (any more will bleed
|
|
# over into the tcp payload). You are forewarned!
|
|
#
|
|
# If you would like to craft specifically malformed packets with
|
|
# nonsense lengths of opts fields, you should avoid tcp_calc_hlen
|
|
# altogether, and simply set the values for tcp_hlen and tcp_opts manually.
|
|
def tcp_calc_hlen
|
|
pad = (self.tcp_opts.to_s.size % 4)
|
|
if (pad > 0)
|
|
self.tcp_opts += ("\x00" * pad)
|
|
end
|
|
self.tcp_hlen = ((20 + self.tcp_opts.to_s.size) / 4)
|
|
end
|
|
|
|
def tcp_calc_seq
|
|
@random_seq
|
|
end
|
|
|
|
def tcp_calc_src
|
|
@random_src
|
|
end
|
|
|
|
# Generates a random high port. This is affected by packet flavor.
|
|
def rand_port
|
|
rand(0xffff - 1025) + 1025
|
|
end
|
|
|
|
# Returns the actual length of the TCP options.
|
|
def tcp_opts_len
|
|
tcp_opts.to_s.size * 4
|
|
end
|
|
|
|
# Returns a more readable option list. Note, it can lack fidelity on bad option strings.
|
|
# For more on TCP options, see the TcpOpts class.
|
|
def tcp_options
|
|
TcpOpts.decode(self.tcp_opts)
|
|
end
|
|
|
|
# Allows a more writable version of TCP options.
|
|
# For more on TCP options, see the TcpOpts class.
|
|
def tcp_options=(arg)
|
|
self.tcp_opts=TcpOpts.encode(arg)
|
|
end
|
|
|
|
# Equivalent to tcp_src
|
|
def tcp_sport
|
|
self.tcp_src
|
|
end
|
|
|
|
# Equivalent to tcp_src=
|
|
def tcp_sport=(arg)
|
|
self.tcp_src=(arg)
|
|
end
|
|
|
|
# Equivalent to tcp_dst
|
|
def tcp_dport
|
|
self.tcp_dst
|
|
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 ethernet?; true; end
|
|
def ip?; true; end
|
|
def tcp?; true; end
|
|
|
|
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,SACK.OK"
|
|
@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,SACK.OK,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},SACK.OK,EOL,EOL"
|
|
@ip_header.ip_ttl = 64
|
|
else
|
|
@tcp_header.tcp_options="MSS:1460,NOP,NOP,SACK.OK"
|
|
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.scan(/[\x00-\xff]{2}/).collect { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y}
|
|
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.scan(/[\x00-\xff]{2}/).collect { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y}
|
|
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 # module PacketFu |