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