packetfu 1.0.0 → 1.0.2.pre
Sign up to get free protection for your applications and to get access to all the features.
- 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
|