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.
Files changed (67) hide show
  1. data/.gitignore +3 -0
  2. data/INSTALL.rdoc +40 -0
  3. data/LICENSE.txt +25 -0
  4. data/examples/100kpackets.rb +41 -0
  5. data/examples/ackscan.rb +38 -0
  6. data/examples/arp.rb +60 -0
  7. data/examples/arphood.rb +59 -0
  8. data/examples/dissect_thinger.rb +22 -0
  9. data/examples/ethernet.rb +10 -0
  10. data/examples/examples.rb +3 -0
  11. data/examples/ids.rb +4 -0
  12. data/examples/idsv2.rb +6 -0
  13. data/examples/new-simple-stats.rb +52 -0
  14. data/examples/oui.txt +84177 -0
  15. data/examples/packetfu-shell.rb +113 -0
  16. data/examples/simple-sniffer.rb +40 -0
  17. data/examples/simple-stats.rb +50 -0
  18. data/examples/slammer.rb +33 -0
  19. data/examples/uniqpcap.rb +15 -0
  20. data/lib/packetfu.rb +147 -0
  21. data/lib/packetfu/capture.rb +169 -0
  22. data/lib/packetfu/config.rb +58 -0
  23. data/lib/packetfu/inject.rb +65 -0
  24. data/lib/packetfu/packet.rb +533 -0
  25. data/lib/packetfu/pcap.rb +594 -0
  26. data/lib/packetfu/protos/arp.rb +268 -0
  27. data/lib/packetfu/protos/eth.rb +296 -0
  28. data/lib/packetfu/protos/hsrp.rb +206 -0
  29. data/lib/packetfu/protos/icmp.rb +179 -0
  30. data/lib/packetfu/protos/invalid.rb +55 -0
  31. data/lib/packetfu/protos/ip.rb +378 -0
  32. data/lib/packetfu/protos/ipv6.rb +250 -0
  33. data/lib/packetfu/protos/tcp.rb +1127 -0
  34. data/lib/packetfu/protos/udp.rb +240 -0
  35. data/lib/packetfu/structfu.rb +294 -0
  36. data/lib/packetfu/utils.rb +194 -0
  37. data/lib/packetfu/version.rb +50 -0
  38. data/packetfu.gemspec +21 -0
  39. data/setup.rb +1586 -0
  40. data/test/all_tests.rb +41 -0
  41. data/test/ethpacket_spec.rb +74 -0
  42. data/test/packet_spec.rb +73 -0
  43. data/test/packet_subclasses_spec.rb +13 -0
  44. data/test/packetfu_spec.rb +90 -0
  45. data/test/ptest.rb +16 -0
  46. data/test/sample-ipv6.pcap +0 -0
  47. data/test/sample.pcap +0 -0
  48. data/test/sample2.pcap +0 -0
  49. data/test/sample_hsrp_pcapr.cap +0 -0
  50. data/test/structfu_spec.rb +335 -0
  51. data/test/tcp_spec.rb +101 -0
  52. data/test/test_arp.rb +135 -0
  53. data/test/test_eth.rb +91 -0
  54. data/test/test_hsrp.rb +20 -0
  55. data/test/test_icmp.rb +54 -0
  56. data/test/test_inject.rb +31 -0
  57. data/test/test_invalid.rb +28 -0
  58. data/test/test_ip.rb +69 -0
  59. data/test/test_ip6.rb +68 -0
  60. data/test/test_octets.rb +37 -0
  61. data/test/test_packet.rb +174 -0
  62. data/test/test_pcap.rb +209 -0
  63. data/test/test_structfu.rb +112 -0
  64. data/test/test_tcp.rb +327 -0
  65. data/test/test_udp.rb +73 -0
  66. data/test/vlan-pcapr.cap +0 -0
  67. metadata +85 -6
@@ -0,0 +1,240 @@
1
+ module PacketFu
2
+
3
+ # UDPHeader is a complete UDP struct, used in UDPPacket. Many Internet-critical protocols
4
+ # rely on UDP, such as DNS and World of Warcraft.
5
+ #
6
+ # For more on UDP packets, see http://www.networksorcery.com/enp/protocol/udp.htm
7
+ #
8
+ # ==== Header Definition
9
+ # Int16 :udp_src
10
+ # Int16 :udp_dst
11
+ # Int16 :udp_len Default: calculated
12
+ # Int16 :udp_sum Default: 0. Often calculated.
13
+ # String :body
14
+ class UDPHeader < Struct.new(:udp_src, :udp_dst, :udp_len, :udp_sum, :body)
15
+
16
+ include StructFu
17
+
18
+ def initialize(args={})
19
+ super(
20
+ Int16.new(args[:udp_src]),
21
+ Int16.new(args[:udp_dst]),
22
+ Int16.new(args[:udp_len] || udp_calc_len),
23
+ Int16.new(args[:udp_sum]),
24
+ StructFu::String.new.read(args[:body])
25
+ )
26
+ end
27
+
28
+ # Returns the object in string form.
29
+ def to_s
30
+ self.to_a.map {|x| x.to_s}.join
31
+ end
32
+
33
+ # Reads a string to populate the object.
34
+ def read(str)
35
+ force_binary(str)
36
+ return self if str.nil?
37
+ self[:udp_src].read(str[0,2])
38
+ self[:udp_dst].read(str[2,2])
39
+ self[:udp_len].read(str[4,2])
40
+ self[:udp_sum].read(str[6,2])
41
+ self[:body].read(str[8,str.size])
42
+ self
43
+ end
44
+
45
+ # Setter for the UDP source port.
46
+ def udp_src=(i); typecast i; end
47
+ # Getter for the UDP source port.
48
+ def udp_src; self[:udp_src].to_i; end
49
+ # Setter for the UDP destination port.
50
+ def udp_dst=(i); typecast i; end
51
+ # Getter for the UDP destination port.
52
+ def udp_dst; self[:udp_dst].to_i; end
53
+ # Setter for the length field. Usually should be recalc()'ed instead.
54
+ def udp_len=(i); typecast i; end
55
+ # Getter for the length field.
56
+ def udp_len; self[:udp_len].to_i; end
57
+ # Setter for the checksum. Usually should be recalc()'ed instad.
58
+ def udp_sum=(i); typecast i; end
59
+ # Getter for the checksum.
60
+ def udp_sum; self[:udp_sum].to_i; end
61
+
62
+ # Returns the true length of the UDP packet.
63
+ def udp_calc_len
64
+ body.to_s.size + 8
65
+ end
66
+
67
+ # Recalculates calculated fields for UDP.
68
+ def udp_recalc(args=:all)
69
+ arg = arg.intern if arg.respond_to? :intern
70
+ case args
71
+ when :udp_len
72
+ self.udp_len = udp_calc_len
73
+ when :all
74
+ self.udp_recalc(:udp_len)
75
+ else
76
+ raise ArgumentError, "No such field `#{arg}'"
77
+ end
78
+ end
79
+
80
+ # Equivalent to udp_src.to_i
81
+ def udp_sport
82
+ self.udp_src
83
+ end
84
+
85
+ # Equivalent to udp_src=
86
+ def udp_sport=(arg)
87
+ self.udp_src=(arg)
88
+ end
89
+
90
+ # Equivalent to udp_dst
91
+ def udp_dport
92
+ self.udp_dst
93
+ end
94
+
95
+ # Equivalent to udp_dst=
96
+ def udp_dport=(arg)
97
+ self.udp_dst=(arg)
98
+ end
99
+
100
+ # Readability aliases
101
+
102
+ def udp_sum_readable
103
+ "0x%04x" % udp_sum
104
+ end
105
+
106
+ end
107
+
108
+ # UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
109
+ #
110
+ # == Example
111
+ #
112
+ # udp_pkt = PacketFu::UDPPacket.new
113
+ # udp_pkt.udp_src=rand(0xffff-1024) + 1024
114
+ # udp_pkt.udp_dst=53
115
+ #
116
+ # udp_pkt.ip_saddr="1.2.3.4"
117
+ # udp_pkt.ip_daddr="10.20.30.40"
118
+ #
119
+ # udp_pkt.recalc
120
+ # udp_pkt.to_f('/tmp/udp.pcap')
121
+ #
122
+ # == Parameters
123
+ #
124
+ # :eth
125
+ # A pre-generated EthHeader object.
126
+ # :ip
127
+ # A pre-generated IPHeader object.
128
+ # :flavor
129
+ # TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
130
+ # flavor, but their underlying ip headers do.
131
+ # :config
132
+ # A hash of return address details, often the output of Utils.whoami?
133
+ class UDPPacket < Packet
134
+
135
+ attr_accessor :eth_header, :ip_header, :udp_header
136
+
137
+ def self.can_parse?(str)
138
+ return false unless str.size >= 28
139
+ return false unless EthPacket.can_parse? str
140
+ return false unless IPPacket.can_parse? str
141
+ return false unless str[23,1] == "\x11"
142
+ return true
143
+ end
144
+
145
+ def read(str=nil, args={})
146
+ raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
147
+ @eth_header.read(str)
148
+ @ip_header.read(str[14,str.size])
149
+ @eth_header.body = @ip_header
150
+ if args[:strip]
151
+ udp_len = str[16,2].unpack("n")[0] - 20
152
+ @udp_header.read(str[14+(@ip_header.ip_hlen),udp_len])
153
+ else
154
+ @udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
155
+ end
156
+ @ip_header.body = @udp_header
157
+ super(args)
158
+ self
159
+ end
160
+
161
+ def initialize(args={})
162
+ @eth_header = EthHeader.new(args).read(args[:eth])
163
+ @ip_header = IPHeader.new(args).read(args[:ip])
164
+ @ip_header.ip_proto=0x11
165
+ @udp_header = UDPHeader.new(args).read(args[:icmp])
166
+ @ip_header.body = @udp_header
167
+ @eth_header.body = @ip_header
168
+ @headers = [@eth_header, @ip_header, @udp_header]
169
+ super
170
+ udp_calc_sum
171
+ end
172
+
173
+ # udp_calc_sum() computes the UDP checksum, and is called upon intialization.
174
+ # It usually should be called just prior to dropping packets to a file or on the wire.
175
+ def udp_calc_sum
176
+ # This is /not/ delegated down to @udp_header since we need info
177
+ # from the IP header, too.
178
+ checksum = (ip_src.to_i >> 16)
179
+ checksum += (ip_src.to_i & 0xffff)
180
+ checksum += (ip_dst.to_i >> 16)
181
+ checksum += (ip_dst.to_i & 0xffff)
182
+ checksum += 0x11
183
+ checksum += udp_len.to_i
184
+ checksum += udp_src.to_i
185
+ checksum += udp_dst.to_i
186
+ checksum += udp_len.to_i
187
+ if udp_len.to_i >= 8
188
+ # For IP trailers. This isn't very reliable. :/
189
+ real_udp_payload = payload.to_s[0,(udp_len.to_i-8)]
190
+ else
191
+ # I'm not going to mess with this right now.
192
+ real_udp_payload = payload
193
+ end
194
+ chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
195
+ chk_payload.unpack("n*").each {|x| checksum = checksum+x}
196
+ checksum = checksum % 0xffff
197
+ checksum = 0xffff - checksum
198
+ checksum == 0 ? 0xffff : checksum
199
+ @udp_header.udp_sum = checksum
200
+ end
201
+
202
+ # udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
203
+ #
204
+ # :all
205
+ # Recomputes all calculated fields.
206
+ # :udp_sum
207
+ # Recomputes the UDP checksum.
208
+ # :udp_len
209
+ # Recomputes the UDP length.
210
+ def udp_recalc(args=:all)
211
+ case args
212
+ when :udp_len
213
+ @udp_header.udp_recalc
214
+ when :udp_sum
215
+ udp_calc_sum
216
+ when :all
217
+ @udp_header.udp_recalc
218
+ udp_calc_sum
219
+ else
220
+ raise ArgumentError, "No such field `#{arg}'"
221
+ end
222
+ end
223
+
224
+ # Peek provides summary data on packet contents.
225
+ def peek_format
226
+ peek_data = ["U "]
227
+ peek_data << "%-5d" % self.to_s.size
228
+ peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
229
+ peek_data << "->"
230
+ peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
231
+ peek_data << "%23s" % "I:"
232
+ peek_data << "%04x" % self.ip_id
233
+ peek_data.join
234
+ end
235
+
236
+ end
237
+
238
+ end
239
+
240
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
@@ -0,0 +1,294 @@
1
+ # StructFu, a nifty way to leverage Ruby's built in Struct class
2
+ # to create meaningful binary data.
3
+
4
+ module StructFu
5
+
6
+ # Normally, self.size and self.length will refer to the Struct
7
+ # size as an array. It's a hassle to redefine, so this introduces some
8
+ # shorthand to get at the size of the resultant string.
9
+ def sz
10
+ self.to_s.size
11
+ end
12
+
13
+ alias len sz
14
+
15
+ # Typecast is used mostly by packet header classes, such as IPHeader,
16
+ # TCPHeader, and the like. It takes an argument, and casts it to the
17
+ # expected type for that element.
18
+ def typecast(i)
19
+ c = caller[0].match(/.*`([^']+)='/)[1]
20
+ self[c.intern].read i
21
+ end
22
+
23
+ # Used like typecast(), but specifically for casting Strings to StructFu::Strings.
24
+ def body=(i)
25
+ if i.kind_of? ::String
26
+ typecast(i)
27
+ elsif i.kind_of? StructFu
28
+ self[:body] = i
29
+ elsif i.nil?
30
+ self[:body] = StructFu::String.new.read("")
31
+ else
32
+ raise ArgumentError, "Can't cram a #{i.class} into a StructFu :body"
33
+ end
34
+ end
35
+
36
+ # Handle deep copies correctly. Marshal in 1.9, re-read myself on 1.8
37
+ def clone
38
+ begin
39
+ Marshal.load(Marshal.dump(self))
40
+ rescue
41
+ self.class.new.read(self.to_s)
42
+ end
43
+ end
44
+
45
+ # Ints all have a value, an endianness, and a default value.
46
+ # Note that the signedness of Int values are implicit as
47
+ # far as the subclasses are concerned; to_i and to_f will
48
+ # return Integer/Float versions of the input value, instead
49
+ # of attempting to unpack the pack value. (This can be a useful
50
+ # hint to other functions).
51
+ #
52
+ # ==== Header Definition
53
+ #
54
+ # Fixnum :value
55
+ # Symbol :endian
56
+ # Fixnum :width
57
+ # Fixnum :default
58
+ class Int < Struct.new(:value, :endian, :width, :default)
59
+ alias :v= :value=
60
+ alias :v :value
61
+ alias :e= :endian=
62
+ alias :e :endian
63
+ alias :w= :width=
64
+ alias :w :width
65
+ alias :d= :default=
66
+ alias :d :default
67
+
68
+ # This is a parent class definition and should not be used directly.
69
+ def to_s
70
+ raise StandardError, "StructFu::Int#to_s accessed, must be redefined."
71
+ end
72
+
73
+ # Returns the Int as an Integer.
74
+ def to_i
75
+ (self.v || self.d).to_i
76
+ end
77
+
78
+ # Returns the Int as a Float.
79
+ def to_f
80
+ (self.v || self.d).to_f
81
+ end
82
+
83
+ def initialize(value=nil, endian=nil, width=nil, default=nil)
84
+ super(value,endian,width,default=0)
85
+ end
86
+
87
+ # Reads either an Integer or a packed string, and populates the value accordingly.
88
+ def read(i)
89
+ self.v = i.kind_of?(Integer) ? i.to_i : i.to_s.unpack(@packstr).first
90
+ self
91
+ end
92
+
93
+ end
94
+
95
+ # Int8 is a one byte value.
96
+ class Int8 < Int
97
+
98
+ def initialize(v=nil)
99
+ super(v,nil,w=1)
100
+ @packstr = "C"
101
+ end
102
+
103
+ # Returns a one byte value as a packed string.
104
+ def to_s
105
+ [(self.v || self.d)].pack("C")
106
+ end
107
+
108
+ end
109
+
110
+ # Int16 is a two byte value.
111
+ class Int16 < Int
112
+ def initialize(v=nil, e=:big)
113
+ super(v,e,w=2)
114
+ @packstr = (self.e == :big) ? "n" : "v"
115
+ end
116
+
117
+ # Returns a two byte value as a packed string.
118
+ def to_s
119
+ @packstr = (self.e == :big) ? "n" : "v"
120
+ [(self.v || self.d)].pack(@packstr)
121
+ end
122
+
123
+ end
124
+
125
+ # Int16be is a two byte value in big-endian format. The endianness cannot be altered.
126
+ class Int16be < Int16
127
+ undef :endian=
128
+ end
129
+
130
+ # Int16le is a two byte value in little-endian format. The endianness cannot be altered.
131
+ class Int16le < Int16
132
+ undef :endian=
133
+ def initialize(v=nil, e=:little)
134
+ super(v,e)
135
+ @packstr = (self.e == :big) ? "n" : "v"
136
+ end
137
+ end
138
+
139
+ # Int32 is a four byte value.
140
+ class Int32 < Int
141
+ def initialize(v=nil, e=:big)
142
+ super(v,e,w=4)
143
+ @packstr = (self.e == :big) ? "N" : "V"
144
+ end
145
+
146
+ # Returns a four byte value as a packed string.
147
+ def to_s
148
+ @packstr = (self.e == :big) ? "N" : "V"
149
+ [(self.v || self.d)].pack(@packstr)
150
+ end
151
+
152
+ end
153
+
154
+ # Int32be is a four byte value in big-endian format. The endianness cannot be altered.
155
+ class Int32be < Int32
156
+ undef :endian=
157
+ end
158
+
159
+ # Int32le is a four byte value in little-endian format. The endianness cannot be altered.
160
+ class Int32le < Int32
161
+ undef :endian=
162
+ def initialize(v=nil, e=:little)
163
+ super(v,e)
164
+ end
165
+ end
166
+
167
+ # Strings are just like regular strings, except it comes with a read() function
168
+ # so that it behaves like other StructFu elements.
169
+ class String < ::String
170
+ def read(str)
171
+ str = str.to_s
172
+ self.replace str
173
+ self
174
+ end
175
+ end
176
+
177
+ # Provides a primitive for creating strings, preceeded by
178
+ # an Int type of length. By default, a string of length zero with
179
+ # a one-byte length is presumed.
180
+ #
181
+ # Note that IntStrings aren't used for much, but it seemed like a good idea at the time.
182
+ class IntString < Struct.new(:int, :string, :mode)
183
+
184
+ def initialize(string='',int=Int8,mode=nil)
185
+ if int < Int
186
+ super(int.new,string,mode)
187
+ calc
188
+ else
189
+ raise "IntStrings need a StructFu::Int for a length."
190
+ end
191
+ end
192
+
193
+ # Calculates the size of a string, and sets it as the value.
194
+ def calc
195
+ int.v = string.to_s.size
196
+ self.to_s
197
+ end
198
+
199
+ # Returns the object as a string, depending on the mode set upon object creation.
200
+ def to_s
201
+ if mode == :parse
202
+ "#{int}" + [string].pack("a#{len}")
203
+ elsif mode == :fix
204
+ self.int.v = string.size
205
+ "#{int}#{string}"
206
+ else
207
+ "#{int}#{string}"
208
+ end
209
+ end
210
+
211
+ # By redefining #string=, we can ensure the correct value
212
+ # is calculated upon assignment. If you'd prefer to have
213
+ # an incorrect value, use the syntax, obj[:string]="value"
214
+ # instead. Note, by using the alternate form, you must
215
+ # #calc before you can trust the int's value. Think of the =
216
+ # assignment as "set to equal," while the []= assignment
217
+ # as "boxing in" the value. Maybe.
218
+ def string=(s)
219
+ self[:string] = s
220
+ calc
221
+ end
222
+
223
+ # Shorthand for querying a length. Note that the usual "length"
224
+ # and "size" refer to the number of elements of this struct.
225
+ def len
226
+ self[:int].value
227
+ end
228
+
229
+ # Override the size, if you must.
230
+ def len=(i)
231
+ self[:int].value=i
232
+ end
233
+
234
+ # Read takes a string, assumes an int width as previously
235
+ # defined upon initialization, but makes no guarantees
236
+ # the int value isn't lying. You're on your own to test
237
+ # for that (or use parse() with a :mode set).
238
+ def read(s)
239
+ unless s[0,int.width].size == int.width
240
+ raise StandardError, "String is too short for type #{int.class}"
241
+ else
242
+ int.read(s[0,int.width])
243
+ self[:string] = s[int.width,s.size]
244
+ end
245
+ self.to_s
246
+ end
247
+
248
+ # parse() is like read(), except that it interprets the string, either
249
+ # based on the declared length, or the actual length. Which strategy
250
+ # is used is dependant on which :mode is set (with self.mode).
251
+ #
252
+ # :parse : Read the length, and then read in that many bytes of the string.
253
+ # The string may be truncated or padded out with nulls, as dictated by the value.
254
+ #
255
+ # :fix : Skip the length, read the rest of the string, then set the length
256
+ # to what it ought to be.
257
+ #
258
+ # else : If neither of these modes are set, just perfom a normal read().
259
+ # This is the default.
260
+ def parse(s)
261
+ unless s[0,int.width].size == int.width
262
+ raise StandardError, "String is too short for type #{int.class}"
263
+ else
264
+ case mode
265
+ when :parse
266
+ int.read(s[0,int.width])
267
+ self[:string] = s[int.width,int.value]
268
+ if string.size < int.value
269
+ self[:string] += ("\x00" * (int.value - self[:string].size))
270
+ end
271
+ when :fix
272
+ self.string = s[int.width,s.size]
273
+ else
274
+ return read(s)
275
+ end
276
+ end
277
+ self.to_s
278
+ end
279
+
280
+ end
281
+
282
+ end
283
+
284
+ class Struct
285
+
286
+ # Monkeypatch for Struct to include some string safety -- anything that uses
287
+ # Struct is going to presume binary strings anyway.
288
+ def force_binary(str)
289
+ PacketFu.force_binary(str)
290
+ end
291
+
292
+ end
293
+
294
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby