packetfu 1.0.0

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 (52) hide show
  1. data/.document +4 -0
  2. data/CHANGES +36 -0
  3. data/INSTALL +40 -0
  4. data/LICENSE +28 -0
  5. data/README +25 -0
  6. data/TODO +25 -0
  7. data/examples/ackscan.rb +38 -0
  8. data/examples/arp.rb +60 -0
  9. data/examples/arphood.rb +56 -0
  10. data/examples/ethernet.rb +10 -0
  11. data/examples/examples.rb +3 -0
  12. data/examples/ids.rb +4 -0
  13. data/examples/idsv2.rb +6 -0
  14. data/examples/oui.txt +84177 -0
  15. data/examples/packetfu-shell.rb +111 -0
  16. data/examples/simple-stats.rb +42 -0
  17. data/examples/slammer.rb +33 -0
  18. data/examples/uniqpcap.rb +15 -0
  19. data/lib/packetfu.rb +108 -0
  20. data/lib/packetfu/arp.rb +239 -0
  21. data/lib/packetfu/capture.rb +169 -0
  22. data/lib/packetfu/config.rb +55 -0
  23. data/lib/packetfu/eth.rb +264 -0
  24. data/lib/packetfu/icmp.rb +153 -0
  25. data/lib/packetfu/inject.rb +65 -0
  26. data/lib/packetfu/invalid.rb +41 -0
  27. data/lib/packetfu/ip.rb +318 -0
  28. data/lib/packetfu/ipv6.rb +230 -0
  29. data/lib/packetfu/packet.rb +492 -0
  30. data/lib/packetfu/pcap.rb +502 -0
  31. data/lib/packetfu/structfu.rb +274 -0
  32. data/lib/packetfu/tcp.rb +1061 -0
  33. data/lib/packetfu/udp.rb +210 -0
  34. data/lib/packetfu/utils.rb +182 -0
  35. data/test/all_tests.rb +37 -0
  36. data/test/ptest.rb +10 -0
  37. data/test/sample.pcap +0 -0
  38. data/test/sample2.pcap +0 -0
  39. data/test/test_arp.rb +135 -0
  40. data/test/test_eth.rb +90 -0
  41. data/test/test_icmp.rb +54 -0
  42. data/test/test_inject.rb +33 -0
  43. data/test/test_invalid.rb +28 -0
  44. data/test/test_ip.rb +69 -0
  45. data/test/test_ip6.rb +68 -0
  46. data/test/test_octets.rb +37 -0
  47. data/test/test_packet.rb +41 -0
  48. data/test/test_pcap.rb +210 -0
  49. data/test/test_structfu.rb +112 -0
  50. data/test/test_tcp.rb +327 -0
  51. data/test/test_udp.rb +73 -0
  52. metadata +144 -0
@@ -0,0 +1,502 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ module StructFu
4
+
5
+ # Set the endianness for the various Int classes. Takes either :little or :big.
6
+ def set_endianness(e=nil)
7
+ unless [:little, :big].include? e
8
+ raise ArgumentError, "Unknown endianness for #{self.class}"
9
+ end
10
+ @int32 = e == :little ? Int32le : Int32be
11
+ @int16 = e == :little ? Int16le : Int16be
12
+ return e
13
+ end
14
+
15
+ # Instead of returning the "size" of the object, which is usually the
16
+ # number of elements of the Struct, returns the size of the object after
17
+ # a to_s. Essentially, a short version of self.to_size.size
18
+ def sz
19
+ self.to_s.size
20
+ end
21
+
22
+ end
23
+
24
+ module PacketFu
25
+
26
+ # PcapHeader represents the header portion of a libpcap file (the packets
27
+ # themselves are in the PcapPackets array). See
28
+ # http://wiki.wireshark.org/Development/LibpcapFileFormat for details.
29
+ #
30
+ # Depending on the endianness (set with :endian), elements are either
31
+ # :little endian or :big endian.
32
+ #
33
+ # ==== PcapHeader Definition
34
+ #
35
+ # Symbol :endian Default: :little
36
+ # Int32 :magic Default: 0xa1b2c3d4 # :big is 0xd4c3b2a1
37
+ # Int16 :ver_major Default: 2
38
+ # Int16 :ver_minor Default: 4
39
+ # Int32 :thiszone
40
+ # Int32 :sigfigs
41
+ # Int32 :snaplen Default: 0xffff
42
+ # Int32 :network Default: 1
43
+ class PcapHeader < Struct.new(:endian, :magic, :ver_major, :ver_minor,
44
+ :thiszone, :sigfigs, :snaplen, :network)
45
+ include StructFu
46
+
47
+ def initialize(args={})
48
+ set_endianness(args[:endian] ||= :little)
49
+ init_fields(args)
50
+ super(args[:endian], args[:magic], args[:ver_major],
51
+ args[:ver_minor], args[:thiszone], args[:sigfigs],
52
+ args[:snaplen], args[:network])
53
+ end
54
+
55
+ # Called by initialize to set the initial fields.
56
+ def init_fields(args={})
57
+ args[:magic] = @int32.new(args[:magic] || 0xa1b2c3d4)
58
+ args[:ver_major] = @int16.new(args[:ver_major] || 2)
59
+ args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
60
+ args[:thiszone] ||= @int32.new(args[:thiszone])
61
+ args[:sigfigs] ||= @int32.new(args[:sigfigs])
62
+ args[:snaplen] ||= @int32.new(args[:snaplen] || 0xffff)
63
+ args[:network] ||= @int32.new(args[:network] || 1)
64
+ return args
65
+ end
66
+
67
+ # Returns the object in string form.
68
+ def to_s
69
+ self.to_a[1,7].map {|x| x.to_s}.join
70
+ end
71
+
72
+ # Reads a string to populate the object.
73
+ # TODO: Need to test this by getting a hold of a big endian pcap file.
74
+ # Conversion from big to little shouldn't be that big of a deal.
75
+ def read(str)
76
+ force_binary(str)
77
+ return self if str.nil?
78
+ str.force_encoding("binary") if str.respond_to? :force_encoding
79
+ if str[0,4] == self[:magic].to_s || true # TODO: raise if it's not magic.
80
+ self[:magic].read str[0,4]
81
+ self[:ver_major].read str[4,2]
82
+ self[:ver_minor].read str[6,2]
83
+ self[:thiszone].read str[8,4]
84
+ self[:sigfigs].read str[12,4]
85
+ self[:snaplen].read str[16,4]
86
+ self[:network].read str[20,4]
87
+ end
88
+ self
89
+ end
90
+
91
+ end
92
+
93
+ # The Timestamp class defines how Timestamps appear in libpcap files.
94
+ #
95
+ # ==== Header Definition
96
+ #
97
+ # Symbol :endian Default: :little
98
+ # Int32 :sec
99
+ # Int32 :usec
100
+ class Timestamp < Struct.new(:endian, :sec, :usec)
101
+ include StructFu
102
+
103
+ def initialize(args={})
104
+ set_endianness(args[:endian] ||= :little)
105
+ init_fields(args)
106
+ super(args[:endian], args[:sec], args[:usec])
107
+ end
108
+
109
+ # Called by initialize to set the initial fields.
110
+ def init_fields(args={})
111
+ args[:sec] = @int32.new(args[:sec])
112
+ args[:usec] = @int32.new(args[:usec])
113
+ return args
114
+ end
115
+
116
+ # Returns the object in string form.
117
+ def to_s
118
+ self.to_a[1,2].map {|x| x.to_s}.join
119
+ end
120
+
121
+ # Reads a string to populate the object.
122
+ def read(str)
123
+ force_binary(str)
124
+ return self if str.nil?
125
+ self[:sec].read str[0,4]
126
+ self[:usec].read str[4,4]
127
+ self
128
+ end
129
+
130
+ end
131
+
132
+ # PcapPacket defines how individual packets are stored in a libpcap-formatted
133
+ # file.
134
+ #
135
+ # ==== Header Definition
136
+ #
137
+ # Timestamp :timestamp
138
+ # Int32 :incl_len
139
+ # Int32 :orig_len
140
+ # String :data
141
+ class PcapPacket < Struct.new(:endian, :timestamp, :incl_len,
142
+ :orig_len, :data)
143
+ include StructFu
144
+ def initialize(args={})
145
+ set_endianness(args[:endian] ||= :little)
146
+ init_fields(args)
147
+ super(args[:endian], args[:timestamp], args[:incl_len],
148
+ args[:orig_len], args[:data])
149
+ end
150
+
151
+ # Called by initialize to set the initial fields.
152
+ def init_fields(args={})
153
+ args[:timestamp] = Timestamp.new(:endian => args[:endian]).read(args[:timestamp])
154
+ args[:incl_len] = args[:incl_len].nil? ? @int32.new(args[:data].to_s.size) : @int32.new(args[:incl_len])
155
+ args[:orig_len] = @int32.new(args[:orig_len])
156
+ args[:data] = StructFu::String.new.read(args[:data])
157
+ end
158
+
159
+ # Returns the object in string form.
160
+ def to_s
161
+ self.to_a[1,4].map {|x| x.to_s}.join
162
+ end
163
+
164
+ # Reads a string to populate the object.
165
+ def read(str)
166
+ force_binary(str)
167
+ self[:timestamp].read str[0,8]
168
+ self[:incl_len].read str[8,4]
169
+ self[:orig_len].read str[12,4]
170
+ self[:data].read str[16,self[:incl_len].to_i]
171
+ self
172
+ end
173
+
174
+ end
175
+
176
+ # PcapPackets is a collection of PcapPacket objects.
177
+ class PcapPackets < Array
178
+
179
+ include StructFu
180
+
181
+ attr_accessor :endian # probably ought to be read-only but who am i.
182
+
183
+ def initialize(args={})
184
+ @endian = args[:endian] || :little
185
+ end
186
+
187
+ def force_binary(str)
188
+ str.force_encoding "binary" if str.respond_to? :force_encoding
189
+ end
190
+
191
+ # Reads a string to populate the object. Note, this read takes in the
192
+ # whole pcap file, since we need to see the magic to know what
193
+ # endianness we're dealing with.
194
+ def read(str)
195
+ force_binary(str)
196
+ return self if str.nil?
197
+ magic = "\xa1\xb2\xc3\xd4"
198
+ if str[0,4] == magic
199
+ @endian = :big
200
+ elsif str[0,4] == magic.reverse
201
+ @endian = :little
202
+ else
203
+ raise ArgumentError, "Unknown file format for #{self.class}"
204
+ end
205
+ body = str[24,str.size]
206
+ while body.size > 16 # TODO: catch exceptions on malformed packets at end
207
+ p = PcapPacket.new(:endian => @endian)
208
+ p.read(body)
209
+ self<<p
210
+ body = body[p.sz,body.size]
211
+ end
212
+ self
213
+ end
214
+
215
+ def to_s
216
+ self.join
217
+ end
218
+
219
+ end
220
+
221
+ # PcapFile is a complete libpcap file struct, made up of two elements, a
222
+ # PcapHeader and PcapPackets.
223
+ #
224
+ # See http://wiki.wireshark.org/Development/LibpcapFileFormat
225
+ class PcapFile < Struct.new(:endian, :head, :body)
226
+ include StructFu
227
+
228
+ def initialize(args={})
229
+ init_fields(args)
230
+ super(args[:endian], args[:head], args[:body])
231
+ end
232
+
233
+ # Called by initialize to set the initial fields.
234
+ def init_fields(args={})
235
+ args[:head] = PcapHeader.new(:endian => args[:endian]).read(args[:head])
236
+ args[:body] = PcapPackets.new(:endian => args[:endian]).read(args[:body])
237
+ return args
238
+ end
239
+
240
+ # Returns the object in string form.
241
+ def to_s
242
+ self[:head].to_s + self[:body].map {|p| p.to_s}.join
243
+ end
244
+
245
+ # Clears the contents of the PcapFile.
246
+ def clear
247
+ self[:body].clear
248
+ end
249
+
250
+ # Reads a string to populate the object. Note that this appends new packets to
251
+ # any existing packets in the PcapFile.
252
+ def read(str)
253
+ force_binary(str)
254
+ self[:head].read str[0,24]
255
+ self[:body].read str
256
+ self
257
+ end
258
+
259
+ # Clears the contents of the PcapFile prior to reading in a new string.
260
+ def read!(str)
261
+ clear
262
+ self.read str
263
+ end
264
+
265
+ # A shorthand method for opening a file and reading in the packets. Note
266
+ # that readfile clears any existing packets, since that seems to be the
267
+ # typical use.
268
+ def readfile(file)
269
+ f = File.open(file, "rb") {|f| f.read}
270
+ self.read! f
271
+ end
272
+
273
+ # file_to_array() translates a libpcap file into an array of packets.
274
+ # Note that this strips out pcap timestamps -- if you'd like to retain
275
+ # timestamps and other libpcap file information, you will want to
276
+ # use read() instead.
277
+ #
278
+ # Note, invoking this requires the somewhat clumsy sytax of,
279
+ # PcapFile.new.file_to_array(:f => 'filename.pcap')
280
+ def file_to_array(args={})
281
+ filename = args[:filename] || args[:file] || args[:f]
282
+ if filename
283
+ self.read! File.open(filename, "rb") {|f| f.read}
284
+ end
285
+ if args[:keep_timestamps] || args[:keep_ts] || args[:ts]
286
+ self[:body].map {|x| {x.timestamp.to_s => x.data.to_s} }
287
+ else
288
+ self[:body].map {|x| x.data.to_s}
289
+ end
290
+ end
291
+
292
+ alias_method :f2a, :file_to_array
293
+
294
+ # Takes an array of packets (as generated by file_to_array), and writes them
295
+ # to a file. Valid arguments are:
296
+ #
297
+ # :filename
298
+ # :array # Can either be an array of packet data, or a hash-value pair of timestamp => data.
299
+ # :timestamp # Sets an initial timestamp
300
+ # :ts_inc # Sets the increment between timestamps. Defaults to 1 second.
301
+ # :append # If true, then the packets are appended to the end of a file.
302
+ def array_to_file(args={})
303
+ if args.kind_of? Hash
304
+ filename = args[:filename] || args[:file] || args[:f]
305
+ arr = args[:array] || args[:arr] || args[:a]
306
+ ts = args[:timestamp] || args[:ts] || Time.now.to_i
307
+ ts_inc = args[:timestamp_increment] || args[:ts_inc] || 1
308
+ append = !!args[:append]
309
+ elsif args.kind_of? Array
310
+ arr = args
311
+ filename = append = nil
312
+ else
313
+ raise ArgumentError, "Unknown argument. Need either a Hash or Array."
314
+ end
315
+ unless arr.kind_of? Array
316
+ raise ArgumentError, "Need an array to read packets from"
317
+ end
318
+ arr.each_with_index do |p,i|
319
+ if p.kind_of? Hash # Binary timestamps are included
320
+ this_ts = p.keys.first
321
+ this_incl_len = p.values.first.size
322
+ this_orig_len = this_incl_len
323
+ this_data = p.values.first
324
+ else # it's an array
325
+ this_ts = Timestamp.new(:endian => self[:endian], :sec => ts + (ts_inc * i)).to_s
326
+ this_incl_len = p.to_s.size
327
+ this_orig_len = this_incl_len
328
+ this_data = p.to_s
329
+ end
330
+ this_pkt = PcapPacket.new({:endian => self[:endian],
331
+ :timestamp => this_ts,
332
+ :incl_len => this_incl_len,
333
+ :orig_len => this_orig_len,
334
+ :data => this_data }
335
+ )
336
+ self[:body] << this_pkt
337
+ end
338
+ if filename
339
+ self.to_f(:filename => filename, :append => append)
340
+ else
341
+ self
342
+ end
343
+ end
344
+
345
+ alias_method :a2f, :array_to_file
346
+
347
+ # Just like array_to_file, but clears any existing packets from the array first.
348
+ def array_to_file!(arr)
349
+ clear
350
+ array_to_file(arr)
351
+ end
352
+
353
+ alias_method :a2f!, :array_to_file!
354
+
355
+ # Writes the PcapFile to a file. Takes the following arguments:
356
+ #
357
+ # :filename # The file to write to.
358
+ # :append # If set to true, the packets are appended to the file, rather than overwriting.
359
+ def to_file(args={})
360
+ filename = args[:filename] || args[:file] || args[:f]
361
+ unless (!filename.nil? || filename.kind_of?(String))
362
+ raise ArgumentError, "Need a :filename for #{self.class}"
363
+ end
364
+ append = args[:append]
365
+ if append
366
+ File.open(filename,'ab') {|file| file.write(self.body.to_s)}
367
+ else
368
+ File.open(filename,'wb') {|file| file.write(self.to_s)}
369
+ end
370
+ [filename, self.body.sz, self.body.size]
371
+ end
372
+
373
+ alias_method :to_f, :to_file
374
+
375
+ # Shorthand method for writing to a file. Can take either :file => 'name.pcap' or
376
+ # simply 'name.pcap'
377
+ def write(filename='out.pcap')
378
+ if filename.kind_of?(Hash)
379
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
380
+ else
381
+ f = filename.to_s
382
+ end
383
+ self.to_file(:filename => f.to_s, :append => false)
384
+ end
385
+
386
+ # Shorthand method for appending to a file. Can take either :file => 'name.pcap' or
387
+ # simply 'name.pcap'
388
+ def append(filename='out.pcap')
389
+ if filename.kind_of?(Hash)
390
+ f = filename[:filename] || filename[:file] || filename[:f] || 'out.pcap'
391
+ else
392
+ f = filename.to_s
393
+ end
394
+ self.to_file(:filename => f, :append => true)
395
+ end
396
+
397
+ end
398
+
399
+ end
400
+
401
+ module PacketFu
402
+
403
+ # Read is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
404
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0. So, the main use
405
+ # of this class is to learn how to do exactly the same things using the PcapFile object.
406
+ class Read
407
+
408
+ class << self
409
+
410
+ # Reads the magic string of a pcap file, and determines
411
+ # if it's :little or :big endian.
412
+ def get_byte_order(pcap_file)
413
+ byte_order = ((pcap_file[0,4] == "\xd4\xc3\xb2\xa1") ? :little : :big)
414
+ return byte_order
415
+ end
416
+
417
+ # set_byte_order is pretty much totally deprecated.
418
+ def set_byte_order(byte_order)
419
+ PacketFu.instance_variable_set("@byte_order",byte_order)
420
+ return true
421
+ end
422
+
423
+ # A wrapper for PcapFile#file_to_array, but only returns the array. Actually
424
+ # using the PcapFile object is going to be more useful.
425
+ def file_to_array(args={})
426
+ filename = args[:filename] || args[:file] || args[:out]
427
+ raise ArgumentError, "Need a :filename in string form to read from." if (filename.nil? || filename.class != String)
428
+ PcapFile.new.file_to_array(args)
429
+ end
430
+
431
+ alias_method :f2a, :file_to_array
432
+
433
+ end
434
+
435
+ end
436
+
437
+ end
438
+
439
+ module PacketFu
440
+
441
+ # Write is largely deprecated. It was current in PacketFu 0.2.0, but isn't all that useful
442
+ # in 0.3.0 and beyond. Expect it to go away completely by version 1.0, as working with
443
+ # PacketFu::PcapFile directly is generally going to be more rewarding.
444
+ class Write
445
+
446
+ class << self
447
+
448
+ # format_packets: Pretty much totally deprecated.
449
+ def format_packets(args={})
450
+ arr = args[:arr] || args[:array] || []
451
+ ts = args[:ts] || args[:timestamp] || Time.now.to_i
452
+ ts_inc = args[:ts_inc] || args[:timestamp_increment]
453
+ pkts = PcapFile.new.array_to_file(:endian => PacketFu.instance_variable_get("@byte_order"),
454
+ :arr => arr,
455
+ :ts => ts,
456
+ :ts_inc => ts_inc)
457
+ pkts.body
458
+ end
459
+
460
+ # array_to_file is a largely deprecated function for writing arrays of pcaps to a file.
461
+ # Use PcapFile#array_to_file instead.
462
+ def array_to_file(args={})
463
+ filename = args[:filename] || args[:file] || args[:out] || :nowrite
464
+ arr = args[:arr] || args[:array] || []
465
+ ts = args[:ts] || args[:timestamp] || args[:time_stamp] || Time.now.to_f
466
+ ts_inc = args[:ts_inc] || args[:timestamp_increment] || args[:time_stamp_increment]
467
+ byte_order = args[:byte_order] || args[:byteorder] || args[:endian] || args[:endianness] || :little
468
+ append = args[:append]
469
+ Read.set_byte_order(byte_order) if [:big, :little].include? byte_order
470
+ pf = PcapFile.new
471
+ pf.array_to_file(:endian => PacketFu.instance_variable_get("@byte_order"),
472
+ :arr => arr,
473
+ :ts => ts,
474
+ :ts_inc => ts_inc)
475
+ if filename && filename != :nowrite
476
+ if append
477
+ pf.append(filename)
478
+ else
479
+ pf.write(filename)
480
+ end
481
+ return [filename,pf.to_s.size,arr.size,ts,ts_inc]
482
+ else
483
+ return [nil,pf.to_s.size,arr.size,ts,ts_inc]
484
+ end
485
+
486
+ end
487
+
488
+ alias_method :a2f, :array_to_file
489
+
490
+ # Shorthand method for appending to a file. Also shouldn't use.
491
+ def append(args={})
492
+ array_to_file(args.merge(:append => true))
493
+ end
494
+
495
+ end
496
+
497
+ end
498
+
499
+ end
500
+
501
+
502
+ # vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby