packetfu 1.1.9 → 1.1.10

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