packetfu 1.1.2 → 1.1.3
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/.gitignore +3 -0
- data/INSTALL.rdoc +40 -0
- data/LICENSE.txt +25 -0
- data/examples/100kpackets.rb +41 -0
- data/examples/ackscan.rb +38 -0
- data/examples/arp.rb +60 -0
- data/examples/arphood.rb +59 -0
- data/examples/dissect_thinger.rb +22 -0
- data/examples/ethernet.rb +10 -0
- data/examples/examples.rb +3 -0
- data/examples/ids.rb +4 -0
- data/examples/idsv2.rb +6 -0
- data/examples/new-simple-stats.rb +52 -0
- data/examples/oui.txt +84177 -0
- data/examples/packetfu-shell.rb +113 -0
- data/examples/simple-sniffer.rb +40 -0
- data/examples/simple-stats.rb +50 -0
- data/examples/slammer.rb +33 -0
- data/examples/uniqpcap.rb +15 -0
- data/lib/packetfu.rb +147 -0
- data/lib/packetfu/capture.rb +169 -0
- data/lib/packetfu/config.rb +58 -0
- data/lib/packetfu/inject.rb +65 -0
- data/lib/packetfu/packet.rb +533 -0
- data/lib/packetfu/pcap.rb +594 -0
- data/lib/packetfu/protos/arp.rb +268 -0
- data/lib/packetfu/protos/eth.rb +296 -0
- data/lib/packetfu/protos/hsrp.rb +206 -0
- data/lib/packetfu/protos/icmp.rb +179 -0
- data/lib/packetfu/protos/invalid.rb +55 -0
- data/lib/packetfu/protos/ip.rb +378 -0
- data/lib/packetfu/protos/ipv6.rb +250 -0
- data/lib/packetfu/protos/tcp.rb +1127 -0
- data/lib/packetfu/protos/udp.rb +240 -0
- data/lib/packetfu/structfu.rb +294 -0
- data/lib/packetfu/utils.rb +194 -0
- data/lib/packetfu/version.rb +50 -0
- data/packetfu.gemspec +21 -0
- data/setup.rb +1586 -0
- data/test/all_tests.rb +41 -0
- data/test/ethpacket_spec.rb +74 -0
- data/test/packet_spec.rb +73 -0
- data/test/packet_subclasses_spec.rb +13 -0
- data/test/packetfu_spec.rb +90 -0
- data/test/ptest.rb +16 -0
- data/test/sample-ipv6.pcap +0 -0
- data/test/sample.pcap +0 -0
- data/test/sample2.pcap +0 -0
- data/test/sample_hsrp_pcapr.cap +0 -0
- data/test/structfu_spec.rb +335 -0
- data/test/tcp_spec.rb +101 -0
- data/test/test_arp.rb +135 -0
- data/test/test_eth.rb +91 -0
- data/test/test_hsrp.rb +20 -0
- data/test/test_icmp.rb +54 -0
- data/test/test_inject.rb +31 -0
- data/test/test_invalid.rb +28 -0
- data/test/test_ip.rb +69 -0
- data/test/test_ip6.rb +68 -0
- data/test/test_octets.rb +37 -0
- data/test/test_packet.rb +174 -0
- data/test/test_pcap.rb +209 -0
- data/test/test_structfu.rb +112 -0
- data/test/test_tcp.rb +327 -0
- data/test/test_udp.rb +73 -0
- data/test/vlan-pcapr.cap +0 -0
- metadata +85 -6
@@ -0,0 +1,58 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# The Config class holds various bits of useful default information
|
4
|
+
# for packet creation. If initialized without arguments, @iface will be
|
5
|
+
# set to ENV['IFACE'] or Pcap.lookupdev (or lo), and the @pcapfile will
|
6
|
+
# be set to "/tmp/out.pcap" # (yes, it's Linux-biased, sorry, fixing
|
7
|
+
# this is a TODO.)
|
8
|
+
#
|
9
|
+
# Any number of instance variables can be passed in to the intialize function (as a
|
10
|
+
# hash), though only the expected network-related variables will be readable and
|
11
|
+
# writeable directly.
|
12
|
+
#
|
13
|
+
# == Examples
|
14
|
+
#
|
15
|
+
# PacketFu::Config.new(:ip_saddr => "1.2.3.4").ip_saddr #=> "1.2.3.4"
|
16
|
+
# PacketFu::Config.new(:foo=>"bar").foo #=> NomethodError: undefined method `foo'...
|
17
|
+
#
|
18
|
+
# The config() function, however, does provide access to custom variables:
|
19
|
+
#
|
20
|
+
# PacketFu::Config.new(:foo=>"bar").config[:foo] #=> "bar"
|
21
|
+
# obj = PacketFu::Config.new(:foo=>"bar")
|
22
|
+
# obj.config(:baz => "bat")
|
23
|
+
# obj.config #=> {:iface=>"eth0", :baz=>"bat", :pcapfile=>"/tmp/out.pcap", :foo=>"bar"}
|
24
|
+
class Config
|
25
|
+
attr_accessor :eth_saddr, # The discovered eth_saddr
|
26
|
+
:eth_daddr, # The discovered eth_daddr (ie, the gateway)
|
27
|
+
:eth_src, # The discovered eth_src in binary form.
|
28
|
+
:eth_dst, # The discovered eth_dst (gateway) in binary form.
|
29
|
+
:ip_saddr, # The discovered ip_saddr
|
30
|
+
:ip_src, # The discovered ip_src in binary form.
|
31
|
+
:iface, # The declared interface.
|
32
|
+
:pcapfile # A declared default file to write to.
|
33
|
+
|
34
|
+
def initialize(args={})
|
35
|
+
if Process.euid.zero?
|
36
|
+
@iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
|
37
|
+
end
|
38
|
+
@pcapfile = "/tmp/out.pcap"
|
39
|
+
args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) }
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns all instance variables as a hash (including custom variables set at initialization).
|
43
|
+
def config(arg=nil)
|
44
|
+
if arg
|
45
|
+
arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
|
46
|
+
else
|
47
|
+
config_hash = {}
|
48
|
+
self.instance_variables.each do |v|
|
49
|
+
key = v.to_s.gsub(/^@/,"").to_sym
|
50
|
+
config_hash[key] = self.instance_variable_get(v)
|
51
|
+
end
|
52
|
+
config_hash
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# The Inject class handles injecting arrays of binary data on the wire.
|
4
|
+
#
|
5
|
+
# To inject single packets, use PacketFu::Packet.to_w() instead.
|
6
|
+
class Inject
|
7
|
+
attr_accessor :array, :stream, :show_live # Leave these public and open.
|
8
|
+
attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
|
9
|
+
|
10
|
+
def initialize(args={})
|
11
|
+
@array = [] # Where the packet array goes.
|
12
|
+
@stream = [] # Where the stream goes.
|
13
|
+
@iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
|
14
|
+
@snaplen = args[:snaplen] || 0xffff
|
15
|
+
@promisc = args[:promisc] || false # Sensible for some Intel wifi cards
|
16
|
+
@timeout = args[:timeout] || 1
|
17
|
+
@show_live = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
# Takes an array, and injects them onto an interface. Note that
|
21
|
+
# complete packets (Ethernet headers on down) are expected.
|
22
|
+
#
|
23
|
+
# === Parameters
|
24
|
+
#
|
25
|
+
# :array || arr
|
26
|
+
# An array of binary data (usually packet.to_s style).
|
27
|
+
# :int || sleep
|
28
|
+
# Number of seconds to sleep between injections (in float format)
|
29
|
+
# :show_live || :live
|
30
|
+
# If true, puts data about what was injected to stdout.
|
31
|
+
#
|
32
|
+
# === Example
|
33
|
+
#
|
34
|
+
# inj = PacketFu::Inject.new
|
35
|
+
# inj.array_to_wire(:array => [pkt1, pkt2, pkt3], :sleep => 0.1)
|
36
|
+
#
|
37
|
+
def array_to_wire(args={})
|
38
|
+
pkt_array = args[:array] || args[:arr] || @array
|
39
|
+
interval = args[:int] || args[:sleep]
|
40
|
+
show_live = args[:show_live] || args[:live] || @show_live
|
41
|
+
|
42
|
+
@stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
|
43
|
+
pkt_count = 0
|
44
|
+
pkt_array.each do |pkt|
|
45
|
+
@stream.inject(pkt)
|
46
|
+
sleep interval if interval
|
47
|
+
pkt_count +=1
|
48
|
+
puts "Sent Packet \##{pkt_count} (#{pkt.size})" if show_live
|
49
|
+
end
|
50
|
+
# Return # of packets sent, array size, and array total size
|
51
|
+
[pkt_count, pkt_array.size, pkt_array.join.size]
|
52
|
+
end
|
53
|
+
|
54
|
+
# Equivalent to array_to_wire
|
55
|
+
def a2w(args={})
|
56
|
+
array_to_wire(args)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Equivalent to array_to_wire
|
60
|
+
def inject(args={})
|
61
|
+
array_to_wire(args)
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,533 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# Packet is the parent class of EthPacket, IPPacket, UDPPacket, TCPPacket, and all
|
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.
|
7
|
+
class Packet
|
8
|
+
|
9
|
+
attr_reader :flavor # Packet Headers are responsible for their own specific flavor methods.
|
10
|
+
attr_accessor :headers # All packets have a header collection, useful for determining protocol trees.
|
11
|
+
attr_accessor :iface # Default inferface to send packets to
|
12
|
+
attr_accessor :inspect_style # Default is :dissect, can also be :hex or :default
|
13
|
+
|
14
|
+
# Register subclasses in PacketFu.packet_class to do all kinds of neat things
|
15
|
+
# that obviates those long if/else trees for parsing. It's pretty sweet.
|
16
|
+
def self.inherited(subclass)
|
17
|
+
PacketFu.add_packet_class(subclass)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Force strings into binary.
|
21
|
+
def self.force_binary(str)
|
22
|
+
str.force_encoding "binary" if str.respond_to? :force_encoding
|
23
|
+
end
|
24
|
+
|
25
|
+
# Parse() creates the correct packet type based on the data, and returns the apporpiate
|
26
|
+
# Packet subclass object.
|
27
|
+
#
|
28
|
+
# There is an assumption here that all incoming packets are either EthPacket
|
29
|
+
# or InvalidPacket types. This will be addressed pretty soon.
|
30
|
+
#
|
31
|
+
# If application-layer parsing is /not/ desired, that should be indicated explicitly
|
32
|
+
# with an argument of :parse_app => false. Otherwise, app-layer parsing will happen.
|
33
|
+
#
|
34
|
+
# It is no longer neccisary to manually add packet types here.
|
35
|
+
def self.parse(packet=nil,args={})
|
36
|
+
parse_app = true if(args[:parse_app].nil? or args[:parse_app])
|
37
|
+
force_binary(packet)
|
38
|
+
if parse_app
|
39
|
+
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}
|
40
|
+
else
|
41
|
+
classes = PacketFu.packet_classes.select {|pclass| pclass.can_parse? packet}.reject {|pclass| pclass.layer_symbol == :application}
|
42
|
+
end
|
43
|
+
p = classes.sort {|x,y| x.layer <=> y.layer}.last.new
|
44
|
+
parsed_packet = p.read(packet,args)
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_is_identity(ptype)
|
48
|
+
idx = PacketFu.packet_prefixes.index(ptype.to_s.downcase)
|
49
|
+
if idx
|
50
|
+
self.kind_of? PacketFu.packet_classes[idx]
|
51
|
+
else
|
52
|
+
raise NoMethodError, "Undefined method `is_#{ptype}?' for #{self.class}."
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Get the binary string of the entire packet.
|
57
|
+
def to_s
|
58
|
+
@headers[0].to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# In the event of no proper decoding, at least send it to the inner-most header.
|
62
|
+
def write(io)
|
63
|
+
@headers[0].write(io)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get the outermost payload (body) of the packet; this is why all packet headers
|
67
|
+
# should have a body type.
|
68
|
+
def payload
|
69
|
+
@headers.last.body
|
70
|
+
end
|
71
|
+
|
72
|
+
# Set the outermost payload (body) of the packet.
|
73
|
+
def payload=(args)
|
74
|
+
@headers.last.body=(args)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Converts a packet to libpcap format. Bit of a hack?
|
78
|
+
def to_pcap(args={})
|
79
|
+
p = PcapPacket.new(:endian => args[:endian],
|
80
|
+
:timestamp => Timestamp.new.to_s,
|
81
|
+
:incl_len => self.to_s.size,
|
82
|
+
:orig_len => self.to_s.size,
|
83
|
+
:data => self)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Put the entire packet into a libpcap file. XXX: this is a
|
87
|
+
# hack for now just to confirm that packets are getting created
|
88
|
+
# correctly. Now with append! XXX: Document this!
|
89
|
+
def to_f(filename=nil,mode='w')
|
90
|
+
filename ||= 'out.pcap'
|
91
|
+
mode = mode.to_s[0,1] + "b"
|
92
|
+
raise ArgumentError, "Unknown mode: #{mode.to_s}" unless mode =~ /^[wa]/
|
93
|
+
if(mode == 'w' || !(File.exists?(filename)))
|
94
|
+
data = [PcapHeader.new, self.to_pcap].map {|x| x.to_s}.join
|
95
|
+
else
|
96
|
+
data = self.to_pcap
|
97
|
+
end
|
98
|
+
File.open(filename, mode) {|f| f.write data}
|
99
|
+
return [filename, 1, data.size]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Put the entire packet on the wire by creating a temporary PacketFu::Inject object.
|
103
|
+
# TODO: Do something with auto-checksumming?
|
104
|
+
def to_w(iface=nil)
|
105
|
+
iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
|
106
|
+
inj = PacketFu::Inject.new(:iface => iface)
|
107
|
+
inj.array = [@headers[0].to_s]
|
108
|
+
inj.inject
|
109
|
+
end
|
110
|
+
|
111
|
+
# Recalculates all the calcuated fields for all headers in the packet.
|
112
|
+
# This is important since read() wipes out all the calculated fields
|
113
|
+
# such as length and checksum and what all.
|
114
|
+
def recalc(arg=:all)
|
115
|
+
case arg
|
116
|
+
when :ip
|
117
|
+
ip_recalc(:all)
|
118
|
+
when :icmp
|
119
|
+
icmp_recalc(:all)
|
120
|
+
when :udp
|
121
|
+
udp_recalc(:all)
|
122
|
+
when :tcp
|
123
|
+
tcp_recalc(:all)
|
124
|
+
when :all
|
125
|
+
ip_recalc(:all) if @ip_header
|
126
|
+
icmp_recalc(:all) if @icmp_header
|
127
|
+
udp_recalc(:all) if @udp_header
|
128
|
+
tcp_recalc(:all) if @tcp_header
|
129
|
+
else
|
130
|
+
raise ArgumentError, "Recalculating `#{arg}' unsupported. Try :all"
|
131
|
+
end
|
132
|
+
@headers[0]
|
133
|
+
end
|
134
|
+
|
135
|
+
# Read() takes (and trusts) the io input and shoves it all into a well-formed Packet.
|
136
|
+
# Note that read is a destructive process, so any existing data will be lost.
|
137
|
+
#
|
138
|
+
# A note on the :strip => true argument: If :strip is set, defined lengths of data will
|
139
|
+
# be believed, and any trailers (such as frame check sequences) will be chopped off. This
|
140
|
+
# helps to ensure well-formed packets, at the cost of losing perhaps important FCS data.
|
141
|
+
#
|
142
|
+
# If :strip is false, header lengths are /not/ believed, and all data will be piped in.
|
143
|
+
# When capturing from the wire, this is usually fine, but recalculating the length before
|
144
|
+
# saving or re-transmitting will absolutely change the data payload; FCS data will become
|
145
|
+
# part of the TCP data as far as tcp_len is concerned. Some effort has been made to preserve
|
146
|
+
# the "real" payload for the purposes of checksums, but currently, it's impossible to seperate
|
147
|
+
# new payload data from old trailers, so things like pkt.payload += "some data" will not work
|
148
|
+
# correctly.
|
149
|
+
#
|
150
|
+
# So, to summarize; if you intend to alter the data, use :strip. If you don't, don't. Also,
|
151
|
+
# this is a horrid hack. Stripping is useful (and fun!), but the default behavior really
|
152
|
+
# should be to create payloads correctly, and /not/ treat extra FCS data as a payload.
|
153
|
+
#
|
154
|
+
# Finally, packet subclasses should take two arguments: the string that is the data
|
155
|
+
# to be transmuted into a packet, as well as args. This superclass method is merely
|
156
|
+
# concerned with handling args common to many packet formats (namely, fixing packets
|
157
|
+
# on the fly)
|
158
|
+
def read(args={})
|
159
|
+
if args[:fix] || args[:recalc]
|
160
|
+
ip_recalc(:ip_sum) if self.is_ip?
|
161
|
+
recalc(:tcp) if self.is_tcp?
|
162
|
+
recalc(:udp) if self.is_udp?
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# Packets are bundles of lots of objects, so copying them
|
167
|
+
# is a little complicated -- a dup of a packet is actually
|
168
|
+
# full of pass-by-reference stuff in the @headers, so
|
169
|
+
# if you change one, you're changing all this copies, too.
|
170
|
+
#
|
171
|
+
# Normally, this doesn't seem to be a big deal, and it's
|
172
|
+
# a pretty decent performance tradeoff. But, if you're going
|
173
|
+
# to be creating a template packet to base a bunch of slightly
|
174
|
+
# different ones off of (like a fuzzer might), you'll want
|
175
|
+
# to use clone()
|
176
|
+
def clone
|
177
|
+
Packet.parse(self.to_s)
|
178
|
+
end
|
179
|
+
|
180
|
+
# If two packets are represented as the same binary string, and
|
181
|
+
# they're both actually PacketFu packets of the same sort, they're equal.
|
182
|
+
#
|
183
|
+
# The intuitive result is that a packet of a higher layer (like DNSPacket)
|
184
|
+
# can be equal to a packet of a lower level (like UDPPacket) as long as
|
185
|
+
# the bytes are equal (this can come up if a transport-layer packet has
|
186
|
+
# a hand-crafted payload that is identical to what would have been created
|
187
|
+
# by using an application layer packet)
|
188
|
+
def ==(other)
|
189
|
+
return false unless other.kind_of? self.class
|
190
|
+
return false unless other.respond_to? :to_s
|
191
|
+
self.to_s == other.to_s
|
192
|
+
end
|
193
|
+
|
194
|
+
# Peek provides summary data on packet contents.
|
195
|
+
#
|
196
|
+
# Each packet type should provide a peek_format.
|
197
|
+
def peek(args={})
|
198
|
+
idx = @headers.reverse.map {|h| h.respond_to? peek_format}.index(true)
|
199
|
+
if idx
|
200
|
+
@headers.reverse[idx].peek_format
|
201
|
+
else
|
202
|
+
peek_format
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# The peek_format is used to display a single line
|
207
|
+
# of packet data useful for eyeballing. It should not exceed
|
208
|
+
# 80 characters. The Packet superclass defines an example
|
209
|
+
# peek_format, but it should hardly ever be triggered, since
|
210
|
+
# peek traverses the @header list in reverse to find a suitable
|
211
|
+
# format.
|
212
|
+
#
|
213
|
+
# === Format
|
214
|
+
#
|
215
|
+
# * A one or two character protocol initial. It should be unique
|
216
|
+
# * The packet size
|
217
|
+
# * Useful data in a human-usable form.
|
218
|
+
#
|
219
|
+
# Ideally, related peek_formats will all line up with each other
|
220
|
+
# when printed to the screen.
|
221
|
+
#
|
222
|
+
# === Example
|
223
|
+
#
|
224
|
+
# tcp_packet.peek
|
225
|
+
# #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
|
226
|
+
# tcp_packet.peek.size
|
227
|
+
# #=> 79
|
228
|
+
#
|
229
|
+
def peek_format
|
230
|
+
peek_data = ["? "]
|
231
|
+
peek_data << "%-5d" % self.to_s.size
|
232
|
+
peek_data << "%68s" % self.to_s[0,34].unpack("H*")[0]
|
233
|
+
peek_data.join
|
234
|
+
end
|
235
|
+
|
236
|
+
# Defines the layer this packet type lives at, based on the number of headers it
|
237
|
+
# requires. Note that this has little to do with the OSI model, since TCP/IP
|
238
|
+
# doesn't really have Session and Presentation layers.
|
239
|
+
#
|
240
|
+
# Ethernet and the like are layer 1, IP, IPv6, and ARP are layer 2,
|
241
|
+
# TCP, UDP, and other transport protocols are layer 3, and application
|
242
|
+
# protocols are at layer 4 or higher. InvalidPackets have an arbitrary
|
243
|
+
# layer 0 to distinguish them.
|
244
|
+
#
|
245
|
+
# Because these don't change much, it's cheaper just to case through them,
|
246
|
+
# and only resort to counting headers if we don't have a match -- this
|
247
|
+
# makes adding protocols somewhat easier, but of course you can just
|
248
|
+
# override this method over there, too. This is merely optimized
|
249
|
+
# for the most likely protocols you see on the Internet.
|
250
|
+
def self.layer
|
251
|
+
case self.name # Lol ran into case's fancy treatment of classes
|
252
|
+
when /InvalidPacket$/; 0
|
253
|
+
when /EthPacket$/; 1
|
254
|
+
when /IPPacket$/, /ARPPacket$/, /IPv6Packet$/; 2
|
255
|
+
when /TCPPacket$/, /UDPPacket$/, /ICMPPacket$/; 3
|
256
|
+
when /HSRPPacket$/; 4
|
257
|
+
else; self.new.headers.size
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def layer
|
262
|
+
self.class.layer
|
263
|
+
end
|
264
|
+
|
265
|
+
def self.layer_symbol
|
266
|
+
case self.layer
|
267
|
+
when 0; :invalid
|
268
|
+
when 1; :link
|
269
|
+
when 2; :internet
|
270
|
+
when 3; :transport
|
271
|
+
else; :application
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def layer_symbol
|
276
|
+
self.class.layer_symbol
|
277
|
+
end
|
278
|
+
|
279
|
+
# Packet subclasses must override this, since the Packet superclass
|
280
|
+
# can't actually parse anything.
|
281
|
+
def self.can_parse?(str)
|
282
|
+
false
|
283
|
+
end
|
284
|
+
|
285
|
+
# Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
|
286
|
+
def hexify(str)
|
287
|
+
str.force_encoding("ASCII-8BIT") if str.respond_to? :force_encoding
|
288
|
+
hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
|
289
|
+
regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
|
290
|
+
chars = str.to_s.gsub(regex,'.')
|
291
|
+
chars_lines = chars.scan(/.{1,16}/)
|
292
|
+
ret = []
|
293
|
+
hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
|
294
|
+
ret.join("\n")
|
295
|
+
end
|
296
|
+
|
297
|
+
# If @inspect_style is :default (or :ugly), the inspect output is the usual
|
298
|
+
# inspect.
|
299
|
+
#
|
300
|
+
# If @inspect_style is :hex (or :pretty), the inspect output is
|
301
|
+
# a much more compact hexdump-style, with a shortened set of packet header
|
302
|
+
# names at the top.
|
303
|
+
#
|
304
|
+
# If @inspect_style is :dissect (or :verbose), the inspect output is the
|
305
|
+
# longer, but more readable, dissection of the packet. This is the default.
|
306
|
+
#
|
307
|
+
# TODO: Have an option for colors. Everyone loves colorized irb output.
|
308
|
+
def inspect_hex(arg=0)
|
309
|
+
case arg
|
310
|
+
when :layers
|
311
|
+
ret = []
|
312
|
+
@headers.size.times do |i|
|
313
|
+
ret << hexify(@headers[i])
|
314
|
+
end
|
315
|
+
ret
|
316
|
+
when (0..9)
|
317
|
+
if @headers[arg]
|
318
|
+
hexify(@headers[arg])
|
319
|
+
else
|
320
|
+
nil
|
321
|
+
end
|
322
|
+
when :all
|
323
|
+
inspect_hex(0)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
def dissection_table
|
328
|
+
table = []
|
329
|
+
@headers.each_with_index do |header,table_idx|
|
330
|
+
proto = header.class.name.sub(/^.*::/,"")
|
331
|
+
table << [proto,[]]
|
332
|
+
header.class.members.each do |elem|
|
333
|
+
elem_sym = elem.to_sym # to_sym needed for 1.8
|
334
|
+
next if elem_sym == :body
|
335
|
+
elem_type_value = []
|
336
|
+
elem_type_value[0] = elem
|
337
|
+
readable_element = "#{elem}_readable"
|
338
|
+
if header.respond_to? readable_element
|
339
|
+
elem_type_value[1] = header.send(readable_element)
|
340
|
+
else
|
341
|
+
elem_type_value[1] = header.send(elem)
|
342
|
+
end
|
343
|
+
elem_type_value[2] = header[elem.to_sym].class.name
|
344
|
+
table[table_idx][1] << elem_type_value
|
345
|
+
end
|
346
|
+
end
|
347
|
+
table
|
348
|
+
if @headers.last.members.map {|x| x.to_sym }.include? :body
|
349
|
+
body_part = [:body, self.payload, @headers.last.body.class.name]
|
350
|
+
end
|
351
|
+
table << body_part
|
352
|
+
end
|
353
|
+
|
354
|
+
# Renders the dissection_table suitable for screen printing. Can take
|
355
|
+
# one or two arguments. If just the one, only that layer will be displayed
|
356
|
+
# take either a range or a number -- if a range, only protos within
|
357
|
+
# that range will be rendered. If an integer, only that proto
|
358
|
+
# will be rendered.
|
359
|
+
def dissect
|
360
|
+
dtable = self.dissection_table
|
361
|
+
hex_body = nil
|
362
|
+
if dtable.last.kind_of?(Array) and dtable.last.first == :body
|
363
|
+
body = dtable.pop
|
364
|
+
hex_body = hexify(body[1])
|
365
|
+
end
|
366
|
+
elem_widths = [0,0,0]
|
367
|
+
dtable.each do |proto_table|
|
368
|
+
proto_table[1].each do |elems|
|
369
|
+
elems.each_with_index do |e,i|
|
370
|
+
width = e.size
|
371
|
+
elem_widths[i] = width if width > elem_widths[i]
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
total_width = elem_widths.inject(0) {|sum,x| sum+x}
|
376
|
+
table = ""
|
377
|
+
dtable.each do |proto|
|
378
|
+
table << "--"
|
379
|
+
table << proto[0]
|
380
|
+
if total_width > proto[0].size
|
381
|
+
table << ("-" * (total_width - proto[0].size + 2))
|
382
|
+
else
|
383
|
+
table << ("-" * (total_width + 2))
|
384
|
+
end
|
385
|
+
table << "\n"
|
386
|
+
proto[1].each do |elems|
|
387
|
+
table << " "
|
388
|
+
elems_table = []
|
389
|
+
(0..2).each do |i|
|
390
|
+
elems_table << ("%-#{elem_widths[i]}s" % elems[i])
|
391
|
+
end
|
392
|
+
table << elems_table.join("\s")
|
393
|
+
table << "\n"
|
394
|
+
end
|
395
|
+
end
|
396
|
+
if hex_body && !hex_body.empty?
|
397
|
+
table << "-" * 66
|
398
|
+
table << "\n"
|
399
|
+
table << "00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f---0123456789abcdef\n"
|
400
|
+
table << "-" * 66
|
401
|
+
table << "\n"
|
402
|
+
table << hex_body
|
403
|
+
end
|
404
|
+
table
|
405
|
+
end
|
406
|
+
|
407
|
+
alias :orig_kind_of? :kind_of?
|
408
|
+
|
409
|
+
def kind_of?(klass)
|
410
|
+
return true if orig_kind_of? klass
|
411
|
+
packet_types = proto.map {|p| PacketFu.const_get("#{p}Packet")}
|
412
|
+
match = false
|
413
|
+
packet_types.each do |p|
|
414
|
+
if p.ancestors.include? klass
|
415
|
+
match = true
|
416
|
+
break
|
417
|
+
end
|
418
|
+
end
|
419
|
+
return match
|
420
|
+
end
|
421
|
+
|
422
|
+
# For packets, inspect is overloaded as inspect_hex(0).
|
423
|
+
# Not sure if this is a great idea yet, but it sure makes
|
424
|
+
# the irb output more sane.
|
425
|
+
#
|
426
|
+
# If you hate this, you can run PacketFu.toggle_inspect to return
|
427
|
+
# to the typical (and often unreadable) Object#inspect format.
|
428
|
+
def inspect
|
429
|
+
case @inspect_style
|
430
|
+
when :dissect
|
431
|
+
self.dissect
|
432
|
+
when :hex
|
433
|
+
self.proto.join("|") + "\n" + self.inspect_hex
|
434
|
+
else
|
435
|
+
super
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
# Returns the size of the packet (as a binary string)
|
440
|
+
def size
|
441
|
+
self.to_s.size
|
442
|
+
end
|
443
|
+
|
444
|
+
# Returns an array of protocols contained in this packet. For example:
|
445
|
+
#
|
446
|
+
# t = PacketFu::TCPPacket.new
|
447
|
+
# => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E.
|
448
|
+
# 00 28 3c ab 00 00 ff 06 7f 25 00 00 00 00 00 00 .(<......%......
|
449
|
+
# 00 00 93 5e 00 00 ad 4f e4 a4 00 00 00 00 50 00 ...^...O......P.
|
450
|
+
# 40 00 4a 92 00 00 @.J...
|
451
|
+
# t.proto
|
452
|
+
# => ["Eth", "IP", "TCP"]
|
453
|
+
#
|
454
|
+
def proto
|
455
|
+
type_array = []
|
456
|
+
self.headers.each {|header| type_array << header.class.to_s.split('::').last.gsub(/Header$/,'')}
|
457
|
+
type_array
|
458
|
+
end
|
459
|
+
|
460
|
+
alias_method :protocol, :proto
|
461
|
+
alias_method :length, :size
|
462
|
+
|
463
|
+
# the Packet class should not be instantiated directly, since it's an
|
464
|
+
# abstract class that real packet types inherit from. Sadly, this
|
465
|
+
# makes the Packet class more difficult to test directly.
|
466
|
+
def initialize(args={})
|
467
|
+
if self.class.name =~ /(::|^)PacketFu::Packet$/
|
468
|
+
raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
|
469
|
+
end
|
470
|
+
@inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
|
471
|
+
if args[:config]
|
472
|
+
args[:config].each_pair do |k,v|
|
473
|
+
case k
|
474
|
+
when :eth_daddr; @eth_header.eth_daddr=v if @eth_header
|
475
|
+
when :eth_saddr; @eth_header.eth_saddr=v if @eth_header
|
476
|
+
when :ip_saddr; @ip_header.ip_saddr=v if @ip_header
|
477
|
+
when :iface; @iface = v
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
# Delegate to PacketFu's inspect_style, since the
|
484
|
+
# class variable name is the same. Yay for namespace
|
485
|
+
# pollution!
|
486
|
+
def inspect_style=()
|
487
|
+
PacketFu.inspect_style(arg)
|
488
|
+
end
|
489
|
+
|
490
|
+
#method_missing() delegates protocol-specific field actions to the apporpraite
|
491
|
+
#class variable (which contains the associated packet type)
|
492
|
+
#This register-of-protocols style switch will work for the
|
493
|
+
#forseeable future (there aren't /that/ many packet types), and it's a handy
|
494
|
+
#way to know at a glance what packet types are supported.
|
495
|
+
def method_missing(sym, *args, &block)
|
496
|
+
case sym.to_s
|
497
|
+
when /^is_([a-zA-Z0-9]+)\?/
|
498
|
+
ptype = $1
|
499
|
+
if PacketFu.packet_prefixes.index(ptype)
|
500
|
+
self.send(:handle_is_identity, $1)
|
501
|
+
else
|
502
|
+
super
|
503
|
+
end
|
504
|
+
when /^([a-zA-Z0-9]+)_.+/
|
505
|
+
ptype = $1
|
506
|
+
if PacketFu.packet_prefixes.index(ptype)
|
507
|
+
self.instance_variable_get("@#{ptype}_header").send(sym,*args, &block)
|
508
|
+
else
|
509
|
+
super
|
510
|
+
end
|
511
|
+
else
|
512
|
+
super
|
513
|
+
end
|
514
|
+
end
|
515
|
+
|
516
|
+
def respond_to?(sym, include_private = false)
|
517
|
+
if sym.to_s =~ /^(invalid|eth|arp|ip|icmp|udp|hsrp|tcp|ipv6)_/
|
518
|
+
self.instance_variable_get("@#{$1}_header").respond_to? sym
|
519
|
+
elsif sym.to_s =~ /^is_([a-zA-Z0-9]+)\?/
|
520
|
+
if PacketFu.packet_prefixes.index($1)
|
521
|
+
true
|
522
|
+
else
|
523
|
+
super
|
524
|
+
end
|
525
|
+
else
|
526
|
+
super
|
527
|
+
end
|
528
|
+
end
|
529
|
+
|
530
|
+
end # class Packet
|
531
|
+
end
|
532
|
+
|
533
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|