packetfu 1.0.0 → 1.0.2.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +1 -1
- data/README +3 -0
- data/examples/packetfu-shell.rb +5 -3
- data/lib/packetfu.rb +48 -63
- data/lib/packetfu/packet.rb +144 -206
- data/lib/packetfu/pcap.rb +5 -5
- data/lib/packetfu/{arp.rb → protos/arp.rb} +17 -2
- data/lib/packetfu/{eth.rb → protos/eth.rb} +15 -4
- data/lib/packetfu/protos/hsrp.rb +200 -0
- data/lib/packetfu/{icmp.rb → protos/icmp.rb} +20 -1
- data/lib/packetfu/{invalid.rb → protos/invalid.rb} +14 -0
- data/lib/packetfu/{ip.rb → protos/ip.rb} +29 -0
- data/lib/packetfu/{ipv6.rb → protos/ipv6.rb} +16 -1
- data/lib/packetfu/{tcp.rb → protos/tcp.rb} +26 -2
- data/lib/packetfu/{udp.rb → protos/udp.rb} +25 -1
- data/lib/packetfu/structfu.rb +10 -3
- data/lib/packetfu/version.rb +50 -0
- data/test/all_tests.rb +33 -30
- data/test/arp_test.pcap +0 -0
- data/test/eth_test.pcap +0 -0
- data/test/icmp_test.pcap +0 -0
- data/test/ip_test.pcap +0 -0
- data/test/packetfu_spec.rb +70 -0
- data/test/sample-ipv6.pcap +0 -0
- data/test/sample_hsrp_pcapr.cap +0 -0
- data/test/structfu_spec.rb +338 -0
- data/test/tcp_test.pcap +0 -0
- data/test/test_arp.rb +0 -0
- data/test/test_eth.rb +2 -1
- data/test/test_hsrp.rb +71 -0
- data/test/test_icmp.rb +0 -0
- data/test/test_ip6.rb +0 -0
- data/test/test_octets.rb +0 -0
- data/test/test_packet.rb +137 -3
- data/test/test_structfu.rb +0 -0
- data/test/test_tcp.rb +0 -0
- data/test/test_udp.rb +0 -0
- data/test/udp_test.pcap +0 -0
- metadata +52 -20
- data/TODO +0 -25
data/LICENSE
CHANGED
data/README
CHANGED
@@ -10,8 +10,11 @@ PacketFu is rdoc-compatable. In the same directory as this file, run "rdoc" by i
|
|
10
10
|
== Requirements
|
11
11
|
|
12
12
|
PcapRub:
|
13
|
+
|
13
14
|
$ svn co http://www.metasploit.com/svn/framework3/trunk/external/pcaprub
|
15
|
+
|
14
16
|
or
|
17
|
+
|
15
18
|
$ rvm gem install pcaprub
|
16
19
|
|
17
20
|
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.
|
data/examples/packetfu-shell.rb
CHANGED
@@ -43,6 +43,7 @@
|
|
43
43
|
# 72 20 62 69 74 73 2e r bits.
|
44
44
|
# => nil
|
45
45
|
|
46
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../lib/")
|
46
47
|
require 'examples'
|
47
48
|
require 'packetfu'
|
48
49
|
|
@@ -82,11 +83,12 @@ def packetfu_ascii_art
|
|
82
83
|
EOM
|
83
84
|
end
|
84
85
|
|
86
|
+
@pcaprub_loaded = PacketFu.pcaprub_loaded?
|
85
87
|
# Displays a helpful banner.
|
86
88
|
def banner
|
87
89
|
packetfu_ascii_art
|
88
90
|
puts ">>> PacketFu Shell #{PacketFu.version}."
|
89
|
-
if Process.euid.zero? &&
|
91
|
+
if Process.euid.zero? && @pcaprub_loaded
|
90
92
|
puts ">>> Use $packetfu_default.config for salient networking details."
|
91
93
|
print "IP: %-15s Mac: %s" % [$packetfu_default.ip_saddr, $packetfu_default.eth_saddr]
|
92
94
|
puts " Gateway: %s" % $packetfu_default.eth_daddr
|
@@ -103,9 +105,9 @@ end
|
|
103
105
|
|
104
106
|
# Silly wlan0 workaround
|
105
107
|
begin
|
106
|
-
$packetfu_default = PacketFu::Config.new(Utils.whoami?) if(
|
108
|
+
$packetfu_default = PacketFu::Config.new(Utils.whoami?) if(@pcaprub_loaded && Process.euid.zero?)
|
107
109
|
rescue RuntimeError
|
108
|
-
$packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(
|
110
|
+
$packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(@pcaprub_loaded && Process.euid.zero?)
|
109
111
|
end
|
110
112
|
|
111
113
|
banner
|
data/lib/packetfu.rb
CHANGED
@@ -4,8 +4,11 @@
|
|
4
4
|
# :include: ../INSTALL
|
5
5
|
# :include: ../LICENSE
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
cwd = File.expand_path(File.dirname(__FILE__))
|
8
|
+
|
9
|
+
$: << cwd
|
10
|
+
|
11
|
+
require File.join(cwd,"packetfu","structfu")
|
9
12
|
require "ipaddr"
|
10
13
|
require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
|
11
14
|
|
@@ -15,94 +18,76 @@ module PacketFu
|
|
15
18
|
@byte_order = :little
|
16
19
|
|
17
20
|
# Checks if pcaprub is loaded correctly.
|
18
|
-
|
19
|
-
|
21
|
+
@pcaprub_loaded = false
|
22
|
+
|
20
23
|
# PacketFu works best with Pcaprub version 0.8-dev (at least)
|
21
24
|
# The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
|
22
|
-
|
25
|
+
def self.pcaprub_platform_require
|
23
26
|
begin
|
24
27
|
require 'pcaprub'
|
25
28
|
rescue LoadError
|
26
29
|
return false
|
27
30
|
end
|
28
|
-
|
29
|
-
|
31
|
+
@pcaprub_loaded = true
|
32
|
+
end
|
30
33
|
|
31
34
|
pcaprub_platform_require
|
32
|
-
if
|
35
|
+
if @pcaprub_loaded
|
33
36
|
if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
|
34
|
-
|
37
|
+
@pcaprub_loaded = false # Don't bother with broken versions
|
35
38
|
raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
|
36
39
|
end
|
37
40
|
require "packetfu/capture"
|
38
41
|
require "packetfu/inject"
|
39
|
-
else
|
40
|
-
warn "Warning: Missing pcaprub, cannot load PacketFu::Capture or PacketFu::Inject"
|
41
42
|
end
|
42
43
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
require "packetfu/packet"
|
47
|
-
require "packetfu/invalid"
|
48
|
-
require "packetfu/eth"
|
49
|
-
require "packetfu/ip"
|
50
|
-
require "packetfu/arp"
|
51
|
-
require "packetfu/icmp"
|
52
|
-
require "packetfu/udp"
|
53
|
-
require "packetfu/tcp"
|
54
|
-
require "packetfu/ipv6" # This is pretty minimal.
|
55
|
-
require "packetfu/utils"
|
56
|
-
require "packetfu/config"
|
57
|
-
|
58
|
-
module PacketFu
|
59
|
-
|
60
|
-
VERSION = "1.0.0" # Version 1.0.0 was released July 31, 2010
|
44
|
+
def self.pcaprub_loaded?
|
45
|
+
@pcaprub_loaded
|
46
|
+
end
|
61
47
|
|
62
|
-
# Returns
|
63
|
-
|
64
|
-
|
65
|
-
PacketFu::VERSION
|
48
|
+
# Returns an array of classes defined in PacketFu
|
49
|
+
def self.classes
|
50
|
+
constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
|
66
51
|
end
|
67
52
|
|
68
|
-
|
69
|
-
|
70
|
-
if
|
71
|
-
|
72
|
-
bin_version = (bin_major.to_i << 16) + (bin_minor.to_i << 8) + bin_teeny.to_i
|
73
|
-
else
|
74
|
-
raise ArgumentError, "Compare version malformed. Should be \x22x.y.z\x22"
|
53
|
+
def self.add_packet_class(klass)
|
54
|
+
raise "Need a class" unless klass.kind_of? Class
|
55
|
+
if klass.name !~ /[A-Za-z0-9]Packet/
|
56
|
+
raise "Packet classes should be named 'ProtoPacket'"
|
75
57
|
end
|
58
|
+
@packet_classes ||= []
|
59
|
+
@packet_classes << klass
|
60
|
+
@packet_classes.sort! {|x,y| x.name <=> y.name}
|
76
61
|
end
|
77
62
|
|
78
|
-
|
79
|
-
|
80
|
-
#
|
81
|
-
# PacketFu.at_least? "0" # => true
|
82
|
-
# PacketFu.at_least? "0.2.9" # => true
|
83
|
-
# PacketFu.at_least? "0.3" # => true
|
84
|
-
# PacketFu.at_least? "1" # => true after 1.0's release
|
85
|
-
# PacketFu.at_least? "1.12" # => false
|
86
|
-
# PacketFu.at_least? "2" # => false
|
87
|
-
def self.at_least?(str)
|
88
|
-
this_version = binarize_version(self.version)
|
89
|
-
ask_version = binarize_version(str)
|
90
|
-
this_version >= ask_version
|
63
|
+
def self.packet_classes
|
64
|
+
@packet_classes || []
|
91
65
|
end
|
92
66
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
ask_version = binarize_version(str)
|
97
|
-
this_version < ask_version
|
67
|
+
def self.packet_prefixes
|
68
|
+
return [] unless @packet_classes
|
69
|
+
@packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
|
98
70
|
end
|
99
71
|
|
100
|
-
|
101
|
-
def self.newer_than?(str)
|
102
|
-
return false if str == self.version
|
103
|
-
!self.older_than?(str)
|
104
|
-
end
|
72
|
+
end
|
105
73
|
|
74
|
+
def require_protos(cwd)
|
75
|
+
protos_dir = File.join(cwd, "packetfu", "protos")
|
76
|
+
Dir.new(protos_dir).each do |fname|
|
77
|
+
next unless fname[/\.rb$/]
|
78
|
+
begin
|
79
|
+
require File.join(protos_dir,fname)
|
80
|
+
rescue
|
81
|
+
warn "Warning: Could not load `#{fname}'. Skipping."
|
82
|
+
end
|
83
|
+
end
|
106
84
|
end
|
107
85
|
|
86
|
+
require File.join(cwd,"packetfu","version")
|
87
|
+
require File.join(cwd,"packetfu","pcap")
|
88
|
+
require File.join(cwd,"packetfu","packet")
|
89
|
+
require_protos(cwd)
|
90
|
+
require File.join(cwd,"packetfu","utils")
|
91
|
+
require File.join(cwd,"packetfu","config")
|
92
|
+
|
108
93
|
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
data/lib/packetfu/packet.rb
CHANGED
@@ -1,88 +1,54 @@
|
|
1
1
|
module PacketFu
|
2
2
|
|
3
3
|
# Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
|
4
|
-
# other packets.
|
4
|
+
# other packets. It acts as both a singleton class, so things like
|
5
|
+
# Packet.parse can happen, and as an abstract class to provide
|
6
|
+
# subclasses some structure.
|
5
7
|
class Packet
|
8
|
+
|
6
9
|
attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
|
7
10
|
attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
|
8
11
|
attr_accessor :iface # Default inferface to send packets to
|
9
12
|
|
13
|
+
# Register subclasses in PacketFu.packet_class to do all kinds of neat things
|
14
|
+
# that obviates those long if/else trees for parsing. It's pretty sweet.
|
15
|
+
def self.inherited(subclass)
|
16
|
+
PacketFu.add_packet_class(subclass)
|
17
|
+
end
|
18
|
+
|
10
19
|
# Force strings into binary.
|
11
20
|
def self.force_binary(str)
|
12
21
|
str.force_encoding "binary" if str.respond_to? :force_encoding
|
13
22
|
end
|
14
23
|
|
15
24
|
# Parse() creates the correct packet type based on the data, and returns the apporpiate
|
16
|
-
# Packet subclass.
|
25
|
+
# Packet subclass object.
|
17
26
|
#
|
18
27
|
# There is an assumption here that all incoming packets are either EthPacket
|
19
|
-
# or InvalidPacket types.
|
28
|
+
# or InvalidPacket types. This will be addressed pretty soon.
|
29
|
+
#
|
30
|
+
# If application-layer parsing is /not/ desired, that should be indicated explicitly
|
31
|
+
# with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
|
20
32
|
#
|
21
|
-
#
|
22
|
-
def self.parse(packet,args={})
|
33
|
+
# It is no longer neccisary to manually add packet types here.
|
34
|
+
def self.parse(packet=nil,args={})
|
35
|
+
parse_app = true if(args[:parse_app].nil? or args[:parse_app])
|
23
36
|
force_binary(packet)
|
24
|
-
if
|
25
|
-
|
26
|
-
when "\x08\x00" # It's IP.
|
27
|
-
if 1.respond_to? :ord
|
28
|
-
ipv = packet[14,1][0].ord >> 4
|
29
|
-
else
|
30
|
-
ipv = packet[14,1][0] >> 4
|
31
|
-
end
|
32
|
-
case ipv # Check the IP version field.
|
33
|
-
when 4; # It's IPv4.
|
34
|
-
case packet[23,1] # Check the IP protocol field.
|
35
|
-
when "\x06"; p = TCPPacket.new # Returns a TCPPacket.
|
36
|
-
when "\x11"; p = UDPPacket.new # Returns a UDPPacket.
|
37
|
-
when "\x01"; p = ICMPPacket.new # Returns an ICMPPacket.
|
38
|
-
else; p = IPPacket.new # Returns an IPPacket since we can't tell the transport layer.
|
39
|
-
end
|
40
|
-
else; p = IPPacket.new # Returns an EthPacket since we don't know any other IP version.
|
41
|
-
end
|
42
|
-
when "\x08\x06" # It's arp
|
43
|
-
if packet.size >= 28 # Min size for complete arp
|
44
|
-
p = ARPPacket.new
|
45
|
-
else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny arps.
|
46
|
-
end
|
47
|
-
when "\x86\xdd" # It's IPv6
|
48
|
-
if packet.size >= 54 # Min size for a complete IPv6 packet.
|
49
|
-
p = IPv6Packet.new
|
50
|
-
else; p = EthPacket.new # Returns an EthPacket since we can't deal with tiny Ipv6.
|
51
|
-
end
|
52
|
-
else; p = EthPacket.new # Returns an EthPacket since we can't tell the network layer.
|
53
|
-
end
|
37
|
+
if parse_app
|
38
|
+
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
|
54
39
|
else
|
55
|
-
|
40
|
+
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
|
56
41
|
end
|
42
|
+
p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
|
57
43
|
parsed_packet = p.read(packet,args)
|
58
|
-
return parsed_packet
|
59
44
|
end
|
60
45
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
#way to know at a glance what packet types are supported.
|
66
|
-
def method_missing(sym, *args)
|
67
|
-
case sym.to_s
|
68
|
-
when /^invalid_/
|
69
|
-
@invalid_header.send(sym,*args)
|
70
|
-
when /^eth_/
|
71
|
-
@eth_header.send(sym,*args)
|
72
|
-
when /^arp_/
|
73
|
-
@arp_header.send(sym,*args)
|
74
|
-
when /^ip_/
|
75
|
-
@ip_header.send(sym,*args)
|
76
|
-
when /^icmp_/
|
77
|
-
@icmp_header.send(sym,*args)
|
78
|
-
when /^udp_/
|
79
|
-
@udp_header.send(sym,*args)
|
80
|
-
when /^tcp_/
|
81
|
-
@tcp_header.send(sym,*args)
|
82
|
-
when /^ipv6_/
|
83
|
-
@ipv6_header.send(sym,*args)
|
46
|
+
def handle_is_identity(ptype)
|
47
|
+
idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
|
48
|
+
if idx
|
49
|
+
self.kind_of? PacketFu.packet_classes[idx]
|
84
50
|
else
|
85
|
-
raise NoMethodError, "
|
51
|
+
raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
|
86
52
|
end
|
87
53
|
end
|
88
54
|
|
@@ -168,13 +134,6 @@ module PacketFu
|
|
168
134
|
# Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
|
169
135
|
# Note that read is a destructive process, so any existing data will be lost.
|
170
136
|
#
|
171
|
-
# TODO: This giant if tree is a mess, and worse, is decieving. You need to define
|
172
|
-
# actions both here and in parse(). All read() does is make a (good) guess as to
|
173
|
-
# what @headers to expect, and reads data to them.
|
174
|
-
#
|
175
|
-
# To take strings and turn them into packets without knowing ahead of time what kind of
|
176
|
-
# packet it is, use Packet.parse instead; parse() handles the figuring-out part.
|
177
|
-
#
|
178
137
|
# A note on the :strip => true argument: If :strip is set, defined lengths of data will
|
179
138
|
# be believed, and any trailers (such as frame check sequences) will be chopped off. This
|
180
139
|
# helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
|
@@ -188,123 +147,18 @@ module PacketFu
|
|
188
147
|
# correctly.
|
189
148
|
#
|
190
149
|
# So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
|
191
|
-
# this is a horrid
|
150
|
+
# this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
|
192
151
|
# should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
|
193
152
|
#
|
194
|
-
#
|
195
|
-
#
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
ipv = io[14].ord
|
204
|
-
else
|
205
|
-
ipv = io[14]
|
206
|
-
end
|
207
|
-
ip_hlen=(ipv & 0x0f) * 4
|
208
|
-
ip_ver=(ipv >> 4) # It's IPv4. Other versions, all bets are off!
|
209
|
-
if ip_ver == 4
|
210
|
-
ip_proto_num = io[23,1].unpack("C")[0]
|
211
|
-
@ip_header.read(io[14,ip_hlen])
|
212
|
-
if ip_proto_num == 0x06 # It's TCP.
|
213
|
-
tcp_len = io[16,2].unpack("n")[0] - 20
|
214
|
-
if args[:strip] # Drops trailers like frame check sequence (FCS). Often desired for cleaner packets.
|
215
|
-
tcp_all = io[ip_hlen+14,tcp_len] # Believe the tcp_len value; chop off anything that's not in range.
|
216
|
-
else
|
217
|
-
tcp_all = io[ip_hlen+14,0xffff] # Don't believe the tcp_len value; suck everything up.
|
218
|
-
end
|
219
|
-
tcp_hlen = ((tcp_all[12,1].unpack("C")[0]) >> 4) * 4
|
220
|
-
if tcp_hlen.to_i >= 20
|
221
|
-
@tcp_header.read(tcp_all)
|
222
|
-
@ip_header.body = @tcp_header
|
223
|
-
else # It's a TCP packet with an impossibly small hlen, so it can't be real TCP. Abort! Abort!
|
224
|
-
@ip_header.body = io[16,io.size-16]
|
225
|
-
end
|
226
|
-
elsif ip_proto_num == 0x11 # It's UDP.
|
227
|
-
udp_len = io[16,2].unpack("n")[0] - 20
|
228
|
-
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.
|
229
|
-
@udp_header.read(io[ip_hlen+14,udp_len])
|
230
|
-
else # ... Suck it all up. BTW, this will change the lengths if they are ever recalc'ed. Bummer.
|
231
|
-
@udp_header.read(io[ip_hlen+14,0xffff])
|
232
|
-
end
|
233
|
-
@ip_header.body = @udp_header
|
234
|
-
elsif ip_proto_num == 1 # It's ICMP
|
235
|
-
@icmp_header.read(io[ip_hlen+14,0xffff])
|
236
|
-
@ip_header.body = @icmp_header
|
237
|
-
else # It's an IP packet for a protocol we don't have a decoder for.
|
238
|
-
@ip_header.body = io[16,io.size-16]
|
239
|
-
end
|
240
|
-
else # It's not IPv4, so no idea what should come next. Just dump it all into an ip_header and ip payload.
|
241
|
-
@ip_header.read(io[14,ip_hlen])
|
242
|
-
@ip_header.body = io[16,io.size-16]
|
243
|
-
end
|
244
|
-
@eth_header.body = @ip_header
|
245
|
-
elsif eth_proto_num == 0x0806 # It's ARP
|
246
|
-
@arp_header.read(io[14,0xffff]) # You'll nearly have a trailer and you'll never know what size.
|
247
|
-
@eth_header.body=@arp_header
|
248
|
-
@eth_header.body
|
249
|
-
elsif eth_proto_num == 0x86dd # It's IPv6
|
250
|
-
@ipv6_header.read(io[14,0xffff])
|
251
|
-
@eth_header.body=@ipv6_header
|
252
|
-
else # It's an Ethernet packet for a protocol we don't have a decoder for
|
253
|
-
@eth_header.body = io[14,io.size-14]
|
254
|
-
end
|
255
|
-
if (args[:fix] || args[:recalc])
|
256
|
-
# Unfortunately, we cannot simply recalc with abandon, since
|
257
|
-
# we may have unaccounted trailers that will sneak into the checksum.
|
258
|
-
# The better way to handle this is to put trailers in their own
|
259
|
-
# StructFu field, but I'm not a-gonna right now. :/
|
260
|
-
ip_recalc(:ip_sum) if respond_to? :ip_header
|
261
|
-
recalc(:tcp) if respond_to? :tcp_header
|
262
|
-
recalc(:udp) if respond_to? :udp_header
|
263
|
-
end
|
264
|
-
else # You're not big enough for Ethernet.
|
265
|
-
@invalid_header.read(io)
|
266
|
-
end
|
267
|
-
# @headers[0]
|
268
|
-
self
|
269
|
-
rescue ::Exception => e
|
270
|
-
# remove last header
|
271
|
-
# nested_types = self.headers.collect {|header| header.class}
|
272
|
-
# nested_types.pop # whatever this packet type is, we weren't able to parse it
|
273
|
-
self.headers.pop
|
274
|
-
return_header_type = self.headers[self.headers.length-1].class.to_s
|
275
|
-
retklass = PacketFu::InvalidPacket
|
276
|
-
seekpos = 0
|
277
|
-
target_header = @invalid_header
|
278
|
-
case return_header_type.to_s
|
279
|
-
when "PacketFu::EthHeader"
|
280
|
-
retklass = PacketFu::EthPacket
|
281
|
-
seekpos = 0x0e
|
282
|
-
target_header = @eth_header
|
283
|
-
when "PacketFu::IPHeader"
|
284
|
-
retklass = PacketFu::IPPacket
|
285
|
-
seekpos = 0x0e + @ip_header.ip_hl * 4
|
286
|
-
target_header = @ip_header
|
287
|
-
when "PacketFu::TCPHeader"
|
288
|
-
retklass = PacketFu::TCPPacket
|
289
|
-
seekpos = 0x0e + @ip_header.ip_hl * 4 + @tcpheader.tcp_hlen
|
290
|
-
target_header = @tcp_header
|
291
|
-
when "PacketFu::UDPHeader"
|
292
|
-
retklass = PacketFu::UDPPacket
|
293
|
-
when "PacketFu::ARPHeader"
|
294
|
-
retklass = PacketFu::ARPPacket
|
295
|
-
when "PacketFu::ICMPHeader"
|
296
|
-
retklass = PacketFu::ICMPPacket
|
297
|
-
when "PacketFu::IPv6Header"
|
298
|
-
retklass = PacketFu::IPv6Packet
|
299
|
-
else
|
300
|
-
end
|
301
|
-
|
302
|
-
io = io[seekpos,io.length - seekpos]
|
303
|
-
target_header.body = io
|
304
|
-
p = retklass.new
|
305
|
-
p.headers = self.headers
|
306
|
-
p
|
307
|
-
raise e if $debug
|
153
|
+
# Finally, packet subclasses should take two arguments: the string that is the data
|
154
|
+
# to be transmuted into a packet, as well as args. This superclass method is merely
|
155
|
+
# concerned with handling args common to many packet formats (namely, fixing packets
|
156
|
+
# on the fly)
|
157
|
+
def read(args={})
|
158
|
+
if args[:fix] || args[:recalc]
|
159
|
+
ip_recalc(:ip_sum) if self.is_ip?
|
160
|
+
recalc(:tcp) if self.is_tcp?
|
161
|
+
recalc(:udp) if self.is_udp?
|
308
162
|
end
|
309
163
|
end
|
310
164
|
|
@@ -318,8 +172,60 @@ module PacketFu
|
|
318
172
|
peek_data.join
|
319
173
|
end
|
320
174
|
|
175
|
+
# Defines the layer this packet type lives at, based on the number of headers it
|
176
|
+
# requires. Note that this has little to do with the OSI model, since TCP/IP
|
177
|
+
# doesn't really have Session and Presentation layers.
|
178
|
+
#
|
179
|
+
# Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
|
180
|
+
# TCP, UDP, and other transport protocols are layer 3, and application
|
181
|
+
# protocols are at layer 4 or higher. InvalidPackets have an arbitrary
|
182
|
+
# layer 0 to distinguish them.
|
183
|
+
#
|
184
|
+
# Because these don't change much, it's cheaper just to case through them,
|
185
|
+
# and only resort to counting headers if we don't have a match -- this
|
186
|
+
# makes adding protocols somewhat easier, but of course you can just
|
187
|
+
# override this method over there, too. This is merely optimized
|
188
|
+
# for the most likely protocols you see on the Internet.
|
189
|
+
def self.layer
|
190
|
+
case self.name # Lol ran into case's fancy treatment of classes
|
191
|
+
when /InvalidPacket$/; 0
|
192
|
+
when /EthPacket$/; 1
|
193
|
+
when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
|
194
|
+
when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
|
195
|
+
when /HSRPPacket$/; 4
|
196
|
+
else; self.new.headers.size
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def layer
|
201
|
+
self.class.layer
|
202
|
+
end
|
203
|
+
|
204
|
+
def self.layer_symbol
|
205
|
+
case self.layer
|
206
|
+
when 0; :invalid
|
207
|
+
when 1; :link
|
208
|
+
when 2; :internet
|
209
|
+
when 3; :transport
|
210
|
+
else; :application
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def layer_symbol
|
215
|
+
self.class.layer_symbol
|
216
|
+
end
|
217
|
+
|
218
|
+
# Packet subclasses must override this, since the Packet superclass
|
219
|
+
# can't actually parse anything.
|
220
|
+
def self.can_parse?(str)
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
321
224
|
# Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
|
322
225
|
def hexify(str)
|
226
|
+
if str.respond_to? :force_encoding
|
227
|
+
str.force_encoding("ASCII-8BIT")
|
228
|
+
end
|
323
229
|
hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
|
324
230
|
chars = str.to_s.gsub(/[\x00-\x1f\x7f-\xff]/,'.')
|
325
231
|
chars_lines = chars.scan(/.{1,16}/)
|
@@ -375,6 +281,21 @@ module PacketFu
|
|
375
281
|
end
|
376
282
|
end
|
377
283
|
|
284
|
+
alias :orig_kind_of? :kind_of?
|
285
|
+
|
286
|
+
def kind_of?(klass)
|
287
|
+
return true if orig_kind_of? klass
|
288
|
+
packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
|
289
|
+
match = false
|
290
|
+
packet_types.each do |p|
|
291
|
+
if p.ancestors.include? klass
|
292
|
+
match = true
|
293
|
+
break
|
294
|
+
end
|
295
|
+
end
|
296
|
+
return match
|
297
|
+
end
|
298
|
+
|
378
299
|
# For packets, inspect is overloaded as inspect_hex(0).
|
379
300
|
# Not sure if this is a great idea yet, but it sure makes
|
380
301
|
# the irb output more sane.
|
@@ -407,29 +328,6 @@ module PacketFu
|
|
407
328
|
end
|
408
329
|
|
409
330
|
alias_method :protocol, :proto
|
410
|
-
|
411
|
-
# Returns true if this is an Invalid packet. Else, false.
|
412
|
-
def is_invalid? ; self.proto.include? "Invalid"; end
|
413
|
-
# Returns true if this is an Ethernet packet. Else, false.
|
414
|
-
def is_ethernet? ; self.proto.include? "Eth"; end
|
415
|
-
alias_method :is_eth?, :is_ethernet?
|
416
|
-
# Returns true if this is an IP packet. Else, false.
|
417
|
-
def is_ip? ; self.proto.include? "IP"; end
|
418
|
-
# Returns true if this is an TCP packet. Else, false.
|
419
|
-
def is_tcp? ; self.proto.include? "TCP"; end
|
420
|
-
# Returns true if this is an UDP packet. Else, false.
|
421
|
-
def is_udp? ; self.proto.include? "UDP"; end
|
422
|
-
# Returns true if this is an ARP packet. Else, false.
|
423
|
-
def is_arp? ; self.proto.include? "ARP"; end
|
424
|
-
# Returns true if this is an IPv6 packet. Else, false.
|
425
|
-
def is_ipv6? ; self.proto.include? "IPv6" ; end
|
426
|
-
# Returns true if this is an ICMP packet. Else, false.
|
427
|
-
def is_icmp? ; self.proto.include? "ICMP" ; end
|
428
|
-
# Returns true if this is an IPv6 packet. Else, false.
|
429
|
-
def is_ipv6? ; self.proto.include? "IPv6" ; end
|
430
|
-
# Returns true if the outermost layer has data. Else, false.
|
431
|
-
def has_data? ; self.payload.size.zero? ? false : true ; end
|
432
|
-
|
433
331
|
alias_method :length, :size
|
434
332
|
|
435
333
|
def initialize(args={})
|
@@ -445,6 +343,46 @@ module PacketFu
|
|
445
343
|
end
|
446
344
|
end
|
447
345
|
|
346
|
+
#method_missing() delegates protocol-specific field actions to the apporpraite
|
347
|
+
#class variable (which contains the associated packet type)
|
348
|
+
#This register-of-protocols style switch will work for the
|
349
|
+
#forseeable future (there aren't /that/ many packet types), and it's a handy
|
350
|
+
#way to know at a glance what packet types are supported.
|
351
|
+
def method_missing(sym, *args, &block)
|
352
|
+
case sym.to_s
|
353
|
+
when /^is_([a-zA-Z0-9]+)\?/
|
354
|
+
ptype = $1
|
355
|
+
if PacketFu.packet_prefixes.index(ptype)
|
356
|
+
self.send(:handle_is_identity, $1)
|
357
|
+
else
|
358
|
+
super
|
359
|
+
end
|
360
|
+
when /^([a-zA-Z0-9]+)_.+/
|
361
|
+
ptype = $1
|
362
|
+
if PacketFu.packet_prefixes.index(ptype)
|
363
|
+
self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
|
364
|
+
else
|
365
|
+
super
|
366
|
+
end
|
367
|
+
else
|
368
|
+
super
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def respond_to?(sym, include_private = false)
|
373
|
+
if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
|
374
|
+
self.instance_variable_get("@#{$1}_header").respond_to? sym
|
375
|
+
elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
|
376
|
+
if PacketFu.packet_prefixes.index($1)
|
377
|
+
true
|
378
|
+
else
|
379
|
+
super
|
380
|
+
end
|
381
|
+
else
|
382
|
+
super
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
448
386
|
end # class Packet
|
449
387
|
|
450
388
|
@@inspect_style = :pretty
|