Updating PacketFu to 1.0.2

git-svn-id: file:///home/svn/framework3/trunk@12689 4d416f70-5f16-0410-b530-b9f4589650da
unstable
Tod Beardsley 2011-05-23 14:04:38 +00:00
parent 28d5febfad
commit 1efb6a1ff2
22 changed files with 484 additions and 473 deletions

View File

@ -1,109 +1,4 @@
# :title: PacketFu Documentation
# :include: ../README
# :include: ../INSTALL
# :include: ../LICENSE
$: << File.expand_path(File.dirname(__FILE__))
require "packetfu/structfu"
require "ipaddr"
require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
module PacketFu
# Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
@byte_order = :little
# Checks if pcaprub is loaded correctly.
@@pcaprub_loaded = false
# PacketFu works best with Pcaprub version 0.8-dev (at least)
# The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
def self.pcaprub_platform_require
begin
require 'pcaprub'
rescue LoadError
return false
end
@@pcaprub_loaded = true
end
pcaprub_platform_require
if @@pcaprub_loaded
if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
@@pcaprub_loaded = false # Don't bother with broken versions
raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
end
require "packetfu/capture"
require "packetfu/inject"
end
end
require 'packetfu/packetfu'
require "packetfu/pcap"
require "packetfu/packet"
require "packetfu/invalid"
require "packetfu/eth"
require "packetfu/ip"
require "packetfu/arp"
require "packetfu/icmp"
require "packetfu/udp"
require "packetfu/hsrp" # Depends on UDP
require "packetfu/tcp"
require "packetfu/ipv6" # This is pretty minimal.
require "packetfu/utils"
require "packetfu/config"
module PacketFu
# Version 1.0.0 was released July 31, 2010
# Version 1.0.1 is unreleased.
VERSION = "1.0.1"
# Returns the current version of PacketFu. Incremented every once
# in a while, when I remember
def self.version
PacketFu::VERSION
end
# Returns the version in a binary format for easy comparisons.
def self.binarize_version(str)
if(str.respond_to?(:split) && str =~ /^[0-9]+(\.([0-9]+)(\.[0-9]+)?)?$/)
bin_major,bin_minor,bin_teeny = str.split(/\x2e/).map {|x| x.to_i}
bin_version = (bin_major.to_i << 16) + (bin_minor.to_i << 8) + bin_teeny.to_i
else
raise ArgumentError, "Compare version malformed. Should be \x22x.y.z\x22"
end
end
# Returns true if the version is equal to or greater than the compare version.
# If the current version of PacketFu is "0.3.1" for example:
#
# PacketFu.at_least? "0" # => true
# PacketFu.at_least? "0.2.9" # => true
# PacketFu.at_least? "0.3" # => true
# PacketFu.at_least? "1" # => true after 1.0's release
# PacketFu.at_least? "1.12" # => false
# PacketFu.at_least? "2" # => false
def self.at_least?(str)
this_version = binarize_version(self.version)
ask_version = binarize_version(str)
this_version >= ask_version
end
# Returns true if the current version is older than the compare version.
def self.older_than?(str)
this_version = binarize_version(self.version)
ask_version = binarize_version(str)
this_version < ask_version
end
# Returns true if the current version is newer than the compare version.
def self.newer_than?(str)
return false if str == self.version
!self.older_than?(str)
end
end
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby

View File

@ -1,40 +0,0 @@
== INSTALL
Installation is pretty straightforward -- it's a gem now!
$ rvm gem install packetfu
Not using rvm? For shame! Get it now, it will make your life 100x better.
$ links http://rvm.beginrescueend.com/
If you are installing from a source checkout, just run (as root / rvmsudo):
$ rvmsudo ./setup.rb
$ sudo ruby ./setup.rb # If not on rvm, and seriously what is wrong with you?
== Testing
The easiest way to test the installation is to run PacketFu via
irb, using the example shell in the "examples" directory:
% sudo irb -r packetfu-shell.rb
After the banner, you should see something like:
>>> Use $packetfu_default.config for salient networking details.
IP: 192.168.1.100 Mac: 00:1d:e0:54:2f:7e Gateway: 00:03:2f:32:a5:3c
Net: 192.168.1.0 Iface: wlan0
>>> Packet capturing/injecting enabled.
<><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><><>
If not, then Something Went Wrong. It's most likely that you have either
an older or broken version of pcaprub (try installing the version provided
with Metasploit), or you have a very, very old version of libpcap (version
0.9.4 is the oldest tested version, and there's really no reason to not
be at least on 1.0.0).
== Complaints
If things don't work out, please contact todb@planb-security.net, and I'll
try to get you all sorted out.

View File

@ -1,28 +0,0 @@
== LICENSE
Copyright (c) 2008-2010, Tod Beardsley
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of Tod Beardsley nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY TOD BEARDSLEY ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL TOD BEARDSLEY BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,28 +0,0 @@
= PacketFu
A library for reading a writing packets to an interface or to a libpcap-formatted file.
It is maintained at http://code.google.com/p/packetfu
== Documentation
PacketFu is rdoc-compatable. In the same directory as this file, run "rdoc" by itself, and then view doc/index.html with your favored browser. Once that's done, navigate at the top, and read up on how to create a Packet or Capture from an interface with show_live or whatever.
== Requirements
PcapRub:
$ svn co http://www.metasploit.com/svn/framework3/trunk/external/pcaprub
or
$ rvm gem install pcaprub
Marshall Beddoe's PcapRub is required only for packet reading and writing from a network interfaces (which is a pretty big only). PcapRub itself relies on libpcap 0.9.8 or later for packet injection. PcapRub also requires root privilieges to access the interface directly.
== Examples
PacketFu ships with dozens and dozens of tests, built on Test::Unit. These should give good pointers on how you're expected to use it. See the /tests directory. Furthermore, PacketFu also ships with packetfu-shell.rb, which should be run via IRB (as root, if you intend to use your interfaces).
== Author
PacketFu is maintained primarily by Tod Beardsley <todb@planb-security.net>

93
lib/packetfu/packetfu.rb Normal file
View File

@ -0,0 +1,93 @@
# :title: PacketFu Documentation
# :include: ../README
# :include: ../INSTALL
# :include: ../LICENSE
cwd = File.expand_path(File.dirname(__FILE__))
$: << cwd
require File.join(cwd,"packetfu","structfu")
require "ipaddr"
require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
module PacketFu
# Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
@byte_order = :little
# Checks if pcaprub is loaded correctly.
@pcaprub_loaded = false
# PacketFu works best with Pcaprub version 0.8-dev (at least)
# The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
def self.pcaprub_platform_require
begin
require 'pcaprub'
rescue LoadError
return false
end
@pcaprub_loaded = true
end
pcaprub_platform_require
if @pcaprub_loaded
if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
@pcaprub_loaded = false # Don't bother with broken versions
raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
end
require "packetfu/capture"
require "packetfu/inject"
end
def self.pcaprub_loaded?
@pcaprub_loaded
end
# Returns an array of classes defined in PacketFu
def self.classes
constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
end
def self.add_packet_class(klass)
raise "Need a class" unless klass.kind_of? Class
if klass.name !~ /[A-Za-z0-9]Packet/
raise "Packet classes should be named 'ProtoPacket'"
end
@packet_classes ||= []
@packet_classes << klass
@packet_classes.sort! {|x,y| x.name <=> y.name}
end
def self.packet_classes
@packet_classes || []
end
def self.packet_prefixes
return [] unless @packet_classes
@packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
end
end
def require_protos(cwd)
protos_dir = File.join(cwd, "packetfu", "protos")
Dir.new(protos_dir).each do |fname|
next unless fname[/\.rb$/]
begin
require File.join(protos_dir,fname)
rescue
warn "Warning: Could not load `#{fname}'. Skipping."
end
end
end
require File.join(cwd,"packetfu","version")
require File.join(cwd,"packetfu","pcap")
require File.join(cwd,"packetfu","packet")
require_protos(cwd)
require File.join(cwd,"packetfu","utils")
require File.join(cwd,"packetfu","config")
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby

View File

@ -1,136 +1,54 @@
module PacketFu
# Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
# other packets.
# other packets. It acts as both a singleton class, so things like
# Packet.parse can happen, and as an abstract class to provide
# subclasses some structure.
class Packet
attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
attr_accessor :iface # Default inferface to send packets to
# Register subclasses in PacketFu.packet_class to do all kinds of neat things
# that obviates those long if/else trees for parsing. It's pretty sweet.
def self.inherited(subclass)
PacketFu.add_packet_class(subclass)
end
# Force strings into binary.
def self.force_binary(str)
str.force_encoding "binary" if str.respond_to? :force_encoding
end
# parse_app makes a valiant attempt at picking out particular applications (beyond
# the transport layer). As of right now, this only accounts for HSRP. I don't really
# intend to get very far with this because I need a better way to parse packets anyway --
# each packet type (including application layers) should be responsible for their own
# parsing rules. But, let's assume that'll never happen, so continue with this folly.
#
# This is an optional step, since it can lead to misidentified applications, depending
# on the strategy used to pick out app layers. For example, we really shouldn't have
# a rule that says that HTTP must be port 80, since it can easily be on 3128 or any
# other arbitrary port. However, we can say with certainty that HSRP must be dst port
# 1985, because that's what the RFC dictates.
def self.parse_app(parsed_packet,packet)
if parsed_packet.is_udp?
# Figure out UDP protocols (DNS, DHCP, etc)
# All HSRP is dst 224.0.0.2:1985 with a TTL of 1, so sayeth RFC 2281.
if(
parsed_packet.ip_ttl == 1 and
parsed_packet.ip_dst == 0xe0000002 and
parsed_packet.udp_dst == 1985
)
return HSRPPacket.new.read(packet)
else
return parsed_packet
end
elsif parsed_packet.is_tcp?
# Figure out TCP protocols (HTTP, SSH, etc)
return parsed_packet
else
# I don't know any others.
return parsed_packet
end
end
# Parse() creates the correct packet type based on the data, and returns the apporpiate
# Packet subclass.
# Packet subclass object.
#
# There is an assumption here that all incoming packets are either EthPacket
# or InvalidPacket types.
# or InvalidPacket types. This will be addressed pretty soon.
#
# If application-layer parsing is /not/ desired, that should be indicated explicitly
# with an argument of :parse_app => false.
# with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
#
# New packet types should get an entry here.
def self.parse(packet,args={})
# It is no longer neccisary to manually add packet types here.
def self.parse(packet=nil,args={})
parse_app = true if(args[:parse_app].nil? or args[:parse_app])
force_binary(packet)
if packet.size >= 14 # Min size for Ethernet. No check for max size, yet.
case packet[12,2] # Check the Eth protocol field.
when "\x08\x00" # It's IP.
if 1.respond_to? :ord
ipv = packet[14,1][0].ord >> 4
else
ipv = packet[14,1][0] >> 4
end
case ipv # Check the IP version field.
when 4; # It's IPv4.
case packet[23,1] # Check the IP protocol field.
when "\x06"; p = TCPPacket.new # Returns a TCPPacket.
when "\x11"; p = UDPPacket.new # Returns a UDPPacket.
when "\x01"; p = ICMPPacket.new # Returns an ICMPPacket.
else; p = IPPacket.new # Returns an IPPacket since we can't tell the transport layer.
end
else; p = IPPacket.new # Returns an IPPacket of this crazy IP version.
end
when "\x08\x06" # It's arp
if packet.size >= 28 # Min size for complete arp
p = ARPPacket.new
else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny arps.
end
when "\x86\xdd" # It's IPv6
if packet.size >= 54 # Min size for a complete IPv6 packet.
p = IPv6Packet.new
else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny Ipv6.
end
else; p = EthPacket.new # Returns an EthPacket since we can't tell the network layer.
end
if parse_app
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
else
p = InvalidPacket.new # Not the right size for Ethernet (jumbo frames are okay)
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
end
p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
parsed_packet = p.read(packet,args)
app_parsed_packet = parse_app ? parse_app(parsed_packet,packet) : nil
return app_parsed_packet || parsed_packet
end
#method_missing() delegates protocol-specific field actions to the apporpraite
#class variable (which contains the associated packet type)
#This register-of-protocols style switch will work for the
#forseeable future (there aren't /that/ many packet types), and it's a handy
#way to know at a glance what packet types are supported.
def method_missing(sym, *args, &block)
case sym.to_s
when /^invalid_/
@invalid_header.send(sym,*args)
when /^eth_/
@eth_header.send(sym,*args)
when /^arp_/
@arp_header.send(sym,*args)
when /^ip_/
@ip_header.send(sym,*args)
when /^icmp_/
@icmp_header.send(sym,*args)
when /^udp_/
@udp_header.send(sym,*args)
when /^hsrp_/
@hsrp_header.send(sym,*args)
when /^tcp_/
@tcp_header.send(sym,*args)
when /^ipv6_/
@ipv6_header.send(sym,*args)
def handle_is_identity(ptype)
idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
if idx
self.kind_of? PacketFu.packet_classes[idx]
else
raise NoMethodError, "Unknown method `#{sym}' for this packet object."
end
end
def respond_to?(sym, include_private = false)
if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
self.instance_variable_get("@#{$1}_header").respond_to? sym
else
super
raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
end
end
@ -216,13 +134,6 @@ module PacketFu
# Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
# Note that read is a destructive process, so any existing data will be lost.
#
# TODO: This giant if tree is a mess, and worse, is decieving. You need to define
# actions both here and in parse(). All read() does is make a (good) guess as to
# what @headers to expect, and reads data to them.
#
# To take strings and turn them into packets without knowing ahead of time what kind of
# packet it is, use Packet.parse instead; parse() handles the figuring-out part.
#
# A note on the :strip => true argument: If :strip is set, defined lengths of data will
# be believed, and any trailers (such as frame check sequences) will be chopped off. This
# helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
@ -236,123 +147,18 @@ module PacketFu
# correctly.
#
# So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
# this is a horrid XXX hack. Stripping is useful (and fun!), but the default behavior really
# this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
# should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
#
# Update: This scheme is so lame. Need to fix. Seriously.
# Update: still sucks. Really.
def read(io,args={})
begin
if io.size >= 14
@eth_header.read(io)
eth_proto_num = io[12,2].unpack("n")[0]
if eth_proto_num == 0x0800 # It's IP.
if 1.respond_to? :ord
ipv = io[14].ord
else
ipv = io[14]
end
ip_hlen=(ipv & 0x0f) * 4
ip_ver=(ipv >> 4) # It's IPv4. Other versions, all bets are off!
if ip_ver == 4
ip_proto_num = io[23,1].unpack("C")[0]
@ip_header.read(io[14,ip_hlen])
if ip_proto_num == 0x06 # It's TCP.
tcp_len = io[16,2].unpack("n")[0] - 20
if args[:strip] # Drops trailers like frame check sequence (FCS). Often desired for cleaner packets.
tcp_all = io[ip_hlen+14,tcp_len] # Believe the tcp_len value; chop off anything that's not in range.
else
tcp_all = io[ip_hlen+14,0xffff] # Don't believe the tcp_len value; suck everything up.
end
tcp_hlen = ((tcp_all[12,1].unpack("C")[0]) >> 4) * 4
if tcp_hlen.to_i >= 20
@tcp_header.read(tcp_all)
@ip_header.body = @tcp_header
else # It's a TCP packet with an impossibly small hlen, so it can't be real TCP. Abort! Abort!
@ip_header.body = io[16,io.size-16]
end
elsif ip_proto_num == 0x11 # It's UDP.
udp_len = io[16,2].unpack("n")[0] - 20
if args[:strip] # Same deal as with TCP. We might have stuff at the end of the packet that's not part of the payload.
@udp_header.read(io[ip_hlen+14,udp_len])
else # ... Suck it all up. BTW, this will change the lengths if they are ever recalc'ed. Bummer.
@udp_header.read(io[ip_hlen+14,0xffff])
end
@ip_header.body = @udp_header
elsif ip_proto_num == 1 # It's ICMP
@icmp_header.read(io[ip_hlen+14,0xffff])
@ip_header.body = @icmp_header
else # It's an IP packet for a protocol we don't have a decoder for.
@ip_header.body = io[16,io.size-16]
end
else # It's not IPv4, so no idea what should come next. Just dump it all into an ip_header and ip payload.
@ip_header.read(io[14,ip_hlen])
@ip_header.body = io[16,io.size-16]
end
@eth_header.body = @ip_header
elsif eth_proto_num == 0x0806 # It's ARP
@arp_header.read(io[14,0xffff]) # You'll nearly have a trailer and you'll never know what size.
@eth_header.body=@arp_header
@eth_header.body
elsif eth_proto_num == 0x86dd # It's IPv6
@ipv6_header.read(io[14,0xffff])
@eth_header.body=@ipv6_header
else # It's an Ethernet packet for a protocol we don't have a decoder for
@eth_header.body = io[14,io.size-14]
end
if (args[:fix] || args[:recalc])
# Unfortunately, we cannot simply recalc with abandon, since
# we may have unaccounted trailers that will sneak into the checksum.
# The better way to handle this is to put trailers in their own
# StructFu field, but I'm not a-gonna right now. :/
ip_recalc(:ip_sum) if respond_to? :ip_header
recalc(:tcp) if respond_to? :tcp_header
recalc(:udp) if respond_to? :udp_header
end
else # You're not big enough for Ethernet.
@invalid_header.read(io)
end
# @headers[0]
self
rescue ::Exception => e
# remove last header
# nested_types = self.headers.collect {|header| header.class}
# nested_types.pop # whatever this packet type is, we weren't able to parse it
self.headers.pop
return_header_type = self.headers[self.headers.length-1].class.to_s
retklass = PacketFu::InvalidPacket
seekpos = 0
target_header = @invalid_header
case return_header_type.to_s
when "PacketFu::EthHeader"
retklass = PacketFu::EthPacket
seekpos = 0x0e
target_header = @eth_header
when "PacketFu::IPHeader"
retklass = PacketFu::IPPacket
seekpos = 0x0e + @ip_header.ip_hl * 4
target_header = @ip_header
when "PacketFu::TCPHeader"
retklass = PacketFu::TCPPacket
seekpos = 0x0e + @ip_header.ip_hl * 4 + @tcpheader.tcp_hlen
target_header = @tcp_header
when "PacketFu::UDPHeader"
retklass = PacketFu::UDPPacket
when "PacketFu::ARPHeader"
retklass = PacketFu::ARPPacket
when "PacketFu::ICMPHeader"
retklass = PacketFu::ICMPPacket
when "PacketFu::IPv6Header"
retklass = PacketFu::IPv6Packet
else
end
io = io[seekpos,io.length - seekpos]
target_header.body = io
p = retklass.new
p.headers = self.headers
p
raise e if $debug
# Finally, packet subclasses should take two arguments: the string that is the data
# to be transmuted into a packet, as well as args. This superclass method is merely
# concerned with handling args common to many packet formats (namely, fixing packets
# on the fly)
def read(args={})
if args[:fix] || args[:recalc]
ip_recalc(:ip_sum) if self.is_ip?
recalc(:tcp) if self.is_tcp?
recalc(:udp) if self.is_udp?
end
end
@ -366,6 +172,55 @@ module PacketFu
peek_data.join
end
# Defines the layer this packet type lives at, based on the number of headers it
# requires. Note that this has little to do with the OSI model, since TCP/IP
# doesn't really have Session and Presentation layers.
#
# Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
# TCP, UDP, and other transport protocols are layer 3, and application
# protocols are at layer 4 or higher. InvalidPackets have an arbitrary
# layer 0 to distinguish them.
#
# Because these don't change much, it's cheaper just to case through them,
# and only resort to counting headers if we don't have a match -- this
# makes adding protocols somewhat easier, but of course you can just
# override this method over there, too. This is merely optimized
# for the most likely protocols you see on the Internet.
def self.layer
case self.name # Lol ran into case's fancy treatment of classes
when /InvalidPacket$/; 0
when /EthPacket$/; 1
when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
when /HSRPPacket$/; 4
else; self.new.headers.size
end
end
def layer
self.class.layer
end
def self.layer_symbol
case self.layer
when 0; :invalid
when 1; :link
when 2; :internet
when 3; :transport
else; :application
end
end
def layer_symbol
self.class.layer_symbol
end
# Packet subclasses must override this, since the Packet superclass
# can't actually parse anything.
def self.can_parse?(str)
false
end
# Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
def hexify(str)
if str.respond_to? :force_encoding
@ -426,6 +281,21 @@ module PacketFu
end
end
alias :orig_kind_of? :kind_of?
def kind_of?(klass)
return true if orig_kind_of? klass
packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
match = false
packet_types.each do |p|
if p.ancestors.include? klass
match = true
break
end
end
return match
end
# For packets, inspect is overloaded as inspect_hex(0).
# Not sure if this is a great idea yet, but it sure makes
# the irb output more sane.
@ -458,31 +328,6 @@ module PacketFu
end
alias_method :protocol, :proto
# Returns true if this is an Invalid packet. Else, false.
def is_invalid? ; self.proto.include? "Invalid"; end
# Returns true if this is an Ethernet packet. Else, false.
def is_ethernet? ; self.proto.include? "Eth"; end
alias_method :is_eth?, :is_ethernet?
# Returns true if this is an IP packet. Else, false.
def is_ip? ; self.proto.include? "IP"; end
# Returns true if this is an TCP packet. Else, false.
def is_tcp? ; self.proto.include? "TCP"; end
# Returns true if this is an UDP packet. Else, false.
def is_udp? ; self.proto.include? "UDP"; end
# Returns true if this is an HSRP packet. Else, false.
def is_hsrp? ; self.proto.include? "HSRP"; end
# Returns true if this is an ARP packet. Else, false.
def is_arp? ; self.proto.include? "ARP"; end
# Returns true if this is an IPv6 packet. Else, false.
def is_ipv6? ; self.proto.include? "IPv6" ; end
# Returns true if this is an ICMP packet. Else, false.
def is_icmp? ; self.proto.include? "ICMP" ; end
# Returns true if this is an IPv6 packet. Else, false.
def is_ipv6? ; self.proto.include? "IPv6" ; end
# Returns true if the outermost layer has data. Else, false.
def has_data? ; self.payload.size.zero? ? false : true ; end
alias_method :length, :size
def initialize(args={})
@ -498,6 +343,46 @@ module PacketFu
end
end
#method_missing() delegates protocol-specific field actions to the apporpraite
#class variable (which contains the associated packet type)
#This register-of-protocols style switch will work for the
#forseeable future (there aren't /that/ many packet types), and it's a handy
#way to know at a glance what packet types are supported.
def method_missing(sym, *args, &block)
case sym.to_s
when /^is_([a-zA-Z0-9]+)\?/
ptype = $1
if PacketFu.packet_prefixes.index(ptype)
self.send(:handle_is_identity, $1)
else
super
end
when /^([a-zA-Z0-9]+)_.+/
ptype = $1
if PacketFu.packet_prefixes.index(ptype)
self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
else
super
end
else
super
end
end
def respond_to?(sym, include_private = false)
if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
self.instance_variable_get("@#{$1}_header").respond_to? sym
elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
if PacketFu.packet_prefixes.index($1)
true
else
super
end
else
super
end
end
end # class Packet
@@inspect_style = :pretty

View File

@ -416,7 +416,7 @@ module PacketFu
# set_byte_order is pretty much totally deprecated.
def set_byte_order(byte_order)
PacketFu.instance_variable_set("@byte_order",byte_order)
PacketFu.instance_variable_set(:@byte_order,byte_order)
return true
end
@ -450,7 +450,7 @@ module PacketFu
arr = args[:arr] || args[:array] || []
ts = args[:ts] || args[:timestamp] || Time.now.to_i
ts_inc = args[:ts_inc] || args[:timestamp_increment]
pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get("@byte_order"),
pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
:arr => arr,
:ts => ts,
:ts_inc => ts_inc)
@ -468,7 +468,7 @@ module PacketFu
append = args[:append]
Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
pf = PcapFile.new
pf.array_to_file(:endian => PacketFu.instance_variable_get("@byte_order"),
pf.array_to_file(:endian => PacketFu.instance_variable_get(:@byte_order),
:arr => arr,
:ts => ts,
:ts_inc => ts_inc)

View File

@ -173,13 +173,29 @@ module PacketFu
attr_accessor :eth_header, :arp_header
def self.can_parse?(str)
return false unless EthPacket.can_parse? str
return false unless str.size >= 28
return false unless str[12,2] == "\x08\x06"
true
end
def read(str=nil,args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@arp_header.read(str[14,str.size])
@eth_header.body = @arp_header
super(args)
self
end
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@arp_header = ARPHeader.new(args).read(args[:arp])
@eth_header.eth_proto = "\x08\x06"
@eth_header.body=@arp_header
# Please send more flavors to todb-packetfu@planb-security.net.
# Please send more flavors to todb+packetfu@planb-security.net.
# Most of these initial fingerprints come from one (1) sample.
case (args[:flavor].nil?) ? :nil : args[:flavor].to_s.downcase.intern
when :windows; @arp_header.body = "\x00" * 64 # 64 bytes of padding
@ -197,7 +213,6 @@ module PacketFu
@headers = [@eth_header, @arp_header]
super
end
# Generates summary data for ARP packets.

View File

@ -245,10 +245,15 @@ module PacketFu
class EthPacket < Packet
attr_accessor :eth_header
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@headers = [@eth_header]
super
def self.can_parse?(str)
str.size >= 14
end
def read(str=nil,args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
super(args)
return self
end
# Does nothing, really, since there's no length or
@ -257,6 +262,12 @@ module PacketFu
@headers[0].inspect
end
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@headers = [@eth_header]
super
end
end
end

View File

@ -141,6 +141,33 @@ module PacketFu
attr_accessor :eth_header, :ip_header, :udp_header, :hsrp_header
def self.can_parse?(str)
return false unless str.size >= 54
return false unless EthPacket.can_parse? str
return false unless IPPacket.can_parse? str
return false unless UDPPacket.can_parse? str
temp_packet = UDPPacket.new
temp_packet.read(str)
if temp_packet.ip_ttl == 1 and [temp_packet.udp_sport,temp_packet.udp_dport] == [1985,1985]
return true
else
return false
end
end
def read(str=nil, args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ip_header.read(str[14,str.size])
@eth_header.body = @ip_header
@udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
@ip_header.body = @udp_header
@hsrp_header.read(str[14+(@ip_header.ip_hlen)+8,str.size])
@udp_header.body = @hsrp_header
super(args)
self
end
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@ip_header = IPHeader.new(args).read(args[:ip])
@ -150,7 +177,6 @@ module PacketFu
@udp_header.body = @hsrp_header
@ip_header.body = @udp_header
@eth_header.body = @ip_header
@headers = [@eth_header, @ip_header, @udp_header, @hsrp_header]
super
end

View File

@ -114,7 +114,26 @@ module PacketFu
class ICMPPacket < Packet
attr_accessor :eth_header, :ip_header, :icmp_header
def self.can_parse?(str)
return false unless str.size >= 54
return false unless EthPacket.can_parse? str
return false unless IPPacket.can_parse? str
return false unless str[23,1] == "\x01"
return true
end
def read(str=nil, args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ip_header.read(str[14,str.size])
@eth_header.body = @ip_header
@icmp_header.read(str[14+(@ip_header.ip_hlen),str.size])
@ip_header.body = @icmp_header
super(args)
self
end
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@ip_header = IPHeader.new(args).read(args[:ip])

View File

@ -30,6 +30,20 @@ module PacketFu
class InvalidPacket < Packet
attr_accessor :invalid_header
# Any packet is potentially an invalid packet
def self.can_parse?(str)
true
end
def self.layer
0
end
def read(str=nil,args={})
@invalid_header.read(str)
self
end
def initialize(args={})
@invalid_header = (args[:invalid] || InvalidHeader.new)
@headers = [@invalid_header]

View File

@ -175,6 +175,11 @@ module PacketFu
(ip_hl * 4) + body.to_s.length
end
# Return the claimed header length
def ip_hlen
(ip_hl * 4)
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
@ -289,6 +294,30 @@ module PacketFu
attr_accessor :eth_header, :ip_header
def self.can_parse?(str)
return false unless str.size >= 34
return false unless EthPacket.can_parse? str
if str[12,2] == "\x08\x00"
if 1.respond_to? :ord
ipv = str[14,1][0].ord >> 4
else
ipv = str[14,1][0] >> 4
end
return true if ipv == 4
else
return false
end
end
def read(str=nil, args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ip_header.read(str[14,str.size])
@eth_header.body = @ip_header
super(args)
self
end
# Creates a new IPPacket object.
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])

View File

@ -203,12 +203,27 @@ module PacketFu
attr_accessor :eth_header, :ipv6_header
def self.can_parse?(str)
return false unless EthPacket.can_parse? str
return false unless str.size >= 54
return false unless str[12,2] == "\x86\xdd"
true
end
def read(str=nil,args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ipv6_header.read(str[14,str.size])
@eth_header.body = @ipv6_header
super(args)
self
end
def initialize(args={})
@eth_header = (args[:eth] || EthHeader.new)
@ipv6_header = (args[:ipv6] || IPv6Header.new)
@eth_header.eth_proto = 0x86dd
@eth_header.body=@ipv6_header
@headers = [@eth_header, @ipv6_header]
super
end

View File

@ -920,8 +920,32 @@ module PacketFu
# A hash of return address details, often the output of Utils.whoami?
class TCPPacket < Packet
attr_accessor :eth_header, :ip_header, :tcp_header, :headers
attr_accessor :eth_header, :ip_header, :tcp_header
def self.can_parse?(str)
return false unless str.size >= 54
return false unless EthPacket.can_parse? str
return false unless IPPacket.can_parse? str
return false unless str[23,1] == "\x06"
return true
end
def read(str=nil, args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ip_header.read(str[14,str.size])
@eth_header.body = @ip_header
if args[:strip]
tcp_len = str[16,2].unpack("n")[0] - 20
@tcp_header.read(str[14+(@ip_header.ip_hlen),tcp_len])
else
@tcp_header.read(str[14+(@ip_header.ip_hlen),str.size])
end
@ip_header.body = @tcp_header
super(args)
self
end
def initialize(args={})
@eth_header = (args[:eth] || EthHeader.new)
@ip_header = (args[:ip] || IPHeader.new)

View File

@ -127,7 +127,31 @@ module PacketFu
class UDPPacket < Packet
attr_accessor :eth_header, :ip_header, :udp_header
def self.can_parse?(str)
return false unless str.size >= 54
return false unless EthPacket.can_parse? str
return false unless IPPacket.can_parse? str
return false unless str[23,1] == "\x11"
return true
end
def read(str=nil, args={})
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
@eth_header.read(str)
@ip_header.read(str[14,str.size])
@eth_header.body = @ip_header
if args[:strip]
udp_len = str[16,2].unpack("n")[0] - 20
@udp_header.read(str[14+(@ip_header.ip_hlen),udp_len])
else
@udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
end
@ip_header.body = @udp_header
super(args)
self
end
def initialize(args={})
@eth_header = EthHeader.new(args).read(args[:eth])
@ip_header = IPHeader.new(args).read(args[:ip])

View File

@ -2,6 +2,7 @@
# to create meaningful binary data.
module StructFu
# Normally, self.size and self.length will refer to the Struct
# size as an array. It's a hassle to redefine, so this introduces some
# shorthand to get at the size of the resultant string.
@ -106,6 +107,7 @@ module StructFu
# Returns a two byte value as a packed string.
def to_s
@packstr = (self.e == :big) ? "n" : "v"
[(self.v || self.d)].pack(@packstr)
end
@ -113,10 +115,12 @@ module StructFu
# Int16be is a two byte value in big-endian format.
class Int16be < Int16
undef :endian=
end
# Int16le is a two byte value in little-endian format.
class Int16le < Int16
undef :endian=
def initialize(v=nil, e=:little)
super(v,e)
@packstr = (self.e == :big) ? "n" : "v"
@ -132,6 +136,7 @@ module StructFu
# Returns a four byte value as a packed string.
def to_s
@packstr = (self.e == :big) ? "N" : "V"
[(self.v || self.d)].pack(@packstr)
end
@ -139,10 +144,12 @@ module StructFu
# Int32be is a four byte value in big-endian format.
class Int32be < Int32
undef :endian=
end
# Int32le is a four byte value in little-endian format.
class Int32le < Int32
undef :endian=
def initialize(v=nil, e=:little)
super(v,e)
end
@ -166,11 +173,11 @@ module StructFu
class IntString < Struct.new(:int, :string, :mode)
def initialize(string='',int=Int8,mode=nil)
unless int.respond_to?(:ancestors) && int.ancestors.include?(StructFu::Int)
raise StandardError, "Invalid length (#{int.inspect}) associated with this String."
else
if int < Int
super(int.new,string,mode)
calc
else
raise "IntStrings need a StructFu::Int for a length."
end
end

View File

@ -0,0 +1,50 @@
module PacketFu
# Version 1.0.0 was released July 31, 2010
# Version 1.0.1 is unreleased.
VERSION = "1.0.2"
def self.version
VERSION
end
# Returns the version in a binary format for easy comparisons.
def self.binarize_version(str)
if(str.respond_to?(:split) && str =~ /^[0-9]+(\.([0-9]+)(\.[0-9]+)?)?$/)
bin_major,bin_minor,bin_teeny = str.split(/\x2e/).map {|x| x.to_i}
bin_version = (bin_major.to_i << 16) + (bin_minor.to_i << 8) + bin_teeny.to_i
else
raise ArgumentError, "Compare version malformed. Should be \x22x.y.z\x22"
end
end
# Returns true if the version is equal to or greater than the compare version.
# If the current version of PacketFu is "0.3.1" for example:
#
# PacketFu.at_least? "0" # => true
# PacketFu.at_least? "0.2.9" # => true
# PacketFu.at_least? "0.3" # => true
# PacketFu.at_least? "1" # => true after 1.0's release
# PacketFu.at_least? "1.12" # => false
# PacketFu.at_least? "2" # => false
def self.at_least?(str)
this_version = binarize_version(self.version)
ask_version = binarize_version(str)
this_version >= ask_version
end
# Returns true if the current version is older than the compare version.
def self.older_than?(str)
return false if str == self.version
this_version = binarize_version(self.version)
ask_version = binarize_version(str)
this_version < ask_version
end
# Returns true if the current version is newer than the compare version.
def self.newer_than?(str)
return false if str == self.version
!self.older_than?(str)
end
end