packetfu 1.0.3.pre → 1.0.4.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +1 -2
- data/README +35 -3
- data/examples/new-simple-stats.rb +51 -0
- data/examples/simple-stats.rb +8 -0
- data/lib/packetfu/packet.rb +6 -7
- data/lib/packetfu/pcap.rb +100 -10
- data/lib/packetfu/protos/tcp.rb +28 -8
- data/lib/packetfu/structfu.rb +13 -9
- data/lib/packetfu/version.rb +3 -2
- data/lib/packetfu.rb +21 -14
- data/test/icmp_test.pcap +0 -0
- data/test/ip_test.pcap +0 -0
- data/test/packetfu_spec.rb +6 -1
- data/test/tcp_spec.rb +100 -0
- data/test/tcp_test.pcap +0 -0
- data/test/test_arp.rb +1 -1
- data/test/test_hsrp.rb +0 -51
- data/test/test_icmp.rb +1 -1
- data/test/test_ip.rb +1 -1
- data/test/test_ip6.rb +1 -1
- data/test/test_tcp.rb +1 -1
- data/test/test_udp.rb +1 -1
- data/test/udp_test.pcap +0 -0
- metadata +5 -5
- data/CHANGES +0 -36
- data/test/dissect_thinger.rb +0 -15
data/.document
CHANGED
data/README
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
= PacketFu
|
2
2
|
|
3
3
|
A library for reading a writing packets to an interface or to a libpcap-formatted file.
|
4
|
-
|
4
|
+
|
5
|
+
It is maintained at http://code.google.com/p/packetfu and https://github.com/todb/packetfu (which repository will win?)
|
5
6
|
|
6
7
|
== Documentation
|
7
8
|
|
8
|
-
PacketFu is rdoc-
|
9
|
+
PacketFu is rdoc-compatible, which means it's sdoc compatible. In the same directory as this file, run "sdoc" by itself (gem install sdoc), and then view doc/index.html with your favored browser. Once that's done, navigate at the top, and read up on how to create a Packet or Capture from an interface with show_live or whatever.
|
9
10
|
|
10
11
|
== Requirements
|
11
12
|
|
@@ -19,10 +20,41 @@ $ rvm gem install pcaprub
|
|
19
20
|
|
20
21
|
Marshall Beddoe's PcapRub is required only for packet reading and writing from a network interfaces (which is a pretty big only). PcapRub itself relies on libpcap 0.9.8 or later for packet injection. PcapRub also requires root privilieges to access the interface directly.
|
21
22
|
|
23
|
+
=== Platforms
|
24
|
+
|
25
|
+
I tend to test with the following (with bash):
|
26
|
+
|
27
|
+
date > /tmp/tests.txt; for i in default 1.8.6-p420 1.8.7-p334 1.9.1-p431 1.9.2-p180 1.9.3-head
|
28
|
+
do rvm use $i >> /tmp/tests.txt
|
29
|
+
echo Testing with $i
|
30
|
+
echo $i >> /tmp/tests.txt; echo +++++++++++++++++++++++ >> /tmp/tests.txt
|
31
|
+
rvmsudo ./all_tests.rb >> /tmp/tests.txt; rspec . >> /tmp/tests.txt
|
32
|
+
done
|
33
|
+
|
34
|
+
==== Passing Platforms
|
35
|
+
|
36
|
+
* 1.9.1-p378 -- my favorite and my best
|
37
|
+
* 1.8.7-p334
|
38
|
+
* 1.9.2-p180
|
39
|
+
* 1.9.3-head
|
40
|
+
|
41
|
+
==== Problem Platforms
|
42
|
+
|
43
|
+
* 1.8.6-p420 -- Has problems with pcaprub and capture/inject
|
44
|
+
* 1.9.1-p431 -- Has problems with loading gems in general, see http://redmine.ruby-lang.org/issues/2404
|
45
|
+
|
46
|
+
Technically, these are pcaprub problems and not PacketFu problems, but PacketFu should at least fail better at them.
|
47
|
+
|
48
|
+
Incidentally, I suspect these Ruby problems are the crux of the Mac OSX problems that people report. Try a different Ruby build and please let me know what works for you.
|
49
|
+
|
50
|
+
|
22
51
|
== Examples
|
23
52
|
|
24
53
|
PacketFu ships with dozens and dozens of tests, built on Test::Unit. These should give good pointers on how you're expected to use it. See the /tests directory. Furthermore, PacketFu also ships with packetfu-shell.rb, which should be run via IRB (as root, if you intend to use your interfaces).
|
25
54
|
|
26
55
|
== Author
|
27
56
|
|
28
|
-
PacketFu is maintained primarily by Tod Beardsley
|
57
|
+
PacketFu is maintained primarily by Tod Beardsley todb@planb-security.net, with help from Open Source Land.
|
58
|
+
|
59
|
+
See LICENSE for licensing details.
|
60
|
+
|
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# new-simple-stats.rb demonstrates the performance difference
|
4
|
+
# between the old and busted way to parse pcap files and the
|
5
|
+
# new hotness of stream parsing. Spoiler alert: Against a pcap
|
6
|
+
# file of 1GB, the old way would eat all your memory and take
|
7
|
+
# forever. This still takes kinda forever, but at 5000 packets
|
8
|
+
# every 11 seconds (my own benchmark) for this script, at least
|
9
|
+
# it doesn't hog up all your memory.
|
10
|
+
|
11
|
+
require 'examples' # For path setting slight-of-hand
|
12
|
+
require 'packetfu'
|
13
|
+
|
14
|
+
def print_results(stats)
|
15
|
+
stats.each_pair { |k,v| puts "%-12s: %10d" % [k,v] }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Takes a file name, parses the packets, and records the packet
|
19
|
+
# type based on its PacketFu class.
|
20
|
+
def count_packet_types(file)
|
21
|
+
stats = {}
|
22
|
+
count = 0
|
23
|
+
start_time = Time.now
|
24
|
+
PacketFu::PcapFile.read_packets(file) do |pkt|
|
25
|
+
kind = pkt.proto.last.to_sym
|
26
|
+
stats[kind] ? stats[kind] += 1 : stats[kind] = 1
|
27
|
+
count += 1
|
28
|
+
elapsed = (Time.now - start_time).to_i
|
29
|
+
if count % 5_000 == 0
|
30
|
+
puts "After #{count} packets (#{elapsed} seconds elapsed):"
|
31
|
+
print_results(stats)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
puts "Final results for #{count} packets (#{elapsed} seconds elapsed):"
|
35
|
+
print_results(stats)
|
36
|
+
end
|
37
|
+
|
38
|
+
if File.readable?(infile = (ARGV[0] || 'in.pcap'))
|
39
|
+
title = "Packets by packet type in '#{infile}'"
|
40
|
+
puts "-" * title.size
|
41
|
+
puts title
|
42
|
+
puts "-" * title.size
|
43
|
+
count_packet_types(infile)
|
44
|
+
else
|
45
|
+
raise RuntimeError, "Need an infile, like so: #{$0} in.pcap"
|
46
|
+
end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
data/examples/simple-stats.rb
CHANGED
@@ -3,6 +3,11 @@
|
|
3
3
|
# Simple-stats.rb takes a pcap file, and gives some simple
|
4
4
|
# stastics on the protocols found. It's mainly used to
|
5
5
|
# demonstrate a method to parse pcap files.
|
6
|
+
#
|
7
|
+
# XXX: DO NOT USE THIS METHOD TO READ PCAP FILES.
|
8
|
+
#
|
9
|
+
# See new-simple-stats.rb for an example of the streaming
|
10
|
+
# parsing method.
|
6
11
|
|
7
12
|
require 'examples' # For path setting slight-of-hand
|
8
13
|
require 'packetfu'
|
@@ -12,6 +17,7 @@ require 'packetfu'
|
|
12
17
|
def count_packet_types(file)
|
13
18
|
file = File.open(file) {|f| f.read}
|
14
19
|
stats = {}
|
20
|
+
count = 0
|
15
21
|
pcapfile = PacketFu::PcapPackets.new
|
16
22
|
pcapfile.read(file)
|
17
23
|
pcapfile.each do |p|
|
@@ -23,6 +29,8 @@ def count_packet_types(file)
|
|
23
29
|
else
|
24
30
|
stats[kind] = 0
|
25
31
|
end
|
32
|
+
count += 1
|
33
|
+
break if count >= 1_000
|
26
34
|
end
|
27
35
|
stats.each_pair { |k,v| puts "%-12s: %4d" % [k,v] }
|
28
36
|
end
|
data/lib/packetfu/packet.rb
CHANGED
@@ -173,7 +173,7 @@ module PacketFu
|
|
173
173
|
# different ones off of (like a fuzzer might), you'll want
|
174
174
|
# to use clone()
|
175
175
|
def clone
|
176
|
-
|
176
|
+
Packet.parse(self.to_s)
|
177
177
|
end
|
178
178
|
|
179
179
|
# If two packets are represented as the same binary string, and
|
@@ -209,7 +209,7 @@ module PacketFu
|
|
209
209
|
# peek traverses the @header list in reverse to find a suitable
|
210
210
|
# format.
|
211
211
|
#
|
212
|
-
#
|
212
|
+
# === Format
|
213
213
|
#
|
214
214
|
# * A one or two character protocol initial. It should be unique
|
215
215
|
# * The packet size
|
@@ -218,7 +218,7 @@ module PacketFu
|
|
218
218
|
# Ideally, related peek_formats will all line up with each other
|
219
219
|
# when printed to the screen.
|
220
220
|
#
|
221
|
-
#
|
221
|
+
# === Example
|
222
222
|
#
|
223
223
|
# tcp_packet.peek
|
224
224
|
# #=> "T 1054 10.10.10.105:55000 -> 192.168.145.105:80 [......] S:adc7155b|I:8dd0"
|
@@ -283,11 +283,10 @@ module PacketFu
|
|
283
283
|
|
284
284
|
# Hexify provides a neatly-formatted dump of binary data, familar to hex readers.
|
285
285
|
def hexify(str)
|
286
|
-
if str.respond_to? :force_encoding
|
287
|
-
str.force_encoding("ASCII-8BIT")
|
288
|
-
end
|
286
|
+
str.force_encoding("ASCII-8BIT") if str.respond_to? :force_encoding
|
289
287
|
hexascii_lines = str.to_s.unpack("H*")[0].scan(/.{1,32}/)
|
290
|
-
|
288
|
+
regex = Regexp.new('[\x00-\x1f\x7f-\xff]', nil, 'n')
|
289
|
+
chars = str.to_s.gsub(regex,'.')
|
291
290
|
chars_lines = chars.scan(/.{1,16}/)
|
292
291
|
ret = []
|
293
292
|
hexascii_lines.size.times {|i| ret << "%-48s %s" % [hexascii_lines[i].gsub(/(.{2})/,"\\1 "),chars_lines[i]]}
|
data/lib/packetfu/pcap.rb
CHANGED
@@ -44,6 +44,10 @@ module PacketFu
|
|
44
44
|
:thiszone, :sigfigs, :snaplen, :network)
|
45
45
|
include StructFu
|
46
46
|
|
47
|
+
MAGIC_INT32 = 0xa1b2c3d4
|
48
|
+
MAGIC_LITTLE = [MAGIC_INT32].pack("V")
|
49
|
+
MAGIC_BIG = [MAGIC_INT32].pack("N")
|
50
|
+
|
47
51
|
def initialize(args={})
|
48
52
|
set_endianness(args[:endian] ||= :little)
|
49
53
|
init_fields(args)
|
@@ -54,7 +58,7 @@ module PacketFu
|
|
54
58
|
|
55
59
|
# Called by initialize to set the initial fields.
|
56
60
|
def init_fields(args={})
|
57
|
-
args[:magic] = @int32.new(args[:magic] ||
|
61
|
+
args[:magic] = @int32.new(args[:magic] || PcapHeader::MAGIC_INT32)
|
58
62
|
args[:ver_major] = @int16.new(args[:ver_major] || 2)
|
59
63
|
args[:ver_minor] ||= @int16.new(args[:ver_minor] || 4)
|
60
64
|
args[:thiszone] ||= @int32.new(args[:thiszone])
|
@@ -76,7 +80,7 @@ module PacketFu
|
|
76
80
|
force_binary(str)
|
77
81
|
return self if str.nil?
|
78
82
|
str.force_encoding("binary") if str.respond_to? :force_encoding
|
79
|
-
if str[0,4] == self[:magic].to_s
|
83
|
+
if str[0,4] == self[:magic].to_s
|
80
84
|
self[:magic].read str[0,4]
|
81
85
|
self[:ver_major].read str[4,2]
|
82
86
|
self[:ver_minor].read str[6,2]
|
@@ -84,6 +88,8 @@ module PacketFu
|
|
84
88
|
self[:sigfigs].read str[12,4]
|
85
89
|
self[:snaplen].read str[16,4]
|
86
90
|
self[:network].read str[20,4]
|
91
|
+
else
|
92
|
+
raise "Incorrect magic for libpcap"
|
87
93
|
end
|
88
94
|
self
|
89
95
|
end
|
@@ -163,6 +169,7 @@ module PacketFu
|
|
163
169
|
|
164
170
|
# Reads a string to populate the object.
|
165
171
|
def read(str)
|
172
|
+
return unless str
|
166
173
|
force_binary(str)
|
167
174
|
self[:timestamp].read str[0,8]
|
168
175
|
self[:incl_len].read str[8,4]
|
@@ -194,10 +201,9 @@ module PacketFu
|
|
194
201
|
def read(str)
|
195
202
|
force_binary(str)
|
196
203
|
return self if str.nil?
|
197
|
-
|
198
|
-
if str[0,4] == magic
|
204
|
+
if str[0,4] == PcapHeader::MAGIC_BIG
|
199
205
|
@endian = :big
|
200
|
-
elsif str[0,4] ==
|
206
|
+
elsif str[0,4] == PcapHeader::MAGIC_LITTLE
|
201
207
|
@endian = :little
|
202
208
|
else
|
203
209
|
raise ArgumentError, "Unknown file format for #{self.class}"
|
@@ -288,10 +294,6 @@ module PacketFu
|
|
288
294
|
|
289
295
|
alias_method :f2a, :file_to_array
|
290
296
|
|
291
|
-
def self.file_to_array(fname)
|
292
|
-
PcapFile.new.file_to_array(:f => fname)
|
293
|
-
end
|
294
|
-
|
295
297
|
# Takes an array of packets (as generated by file_to_array), and writes them
|
296
298
|
# to a file. Valid arguments are:
|
297
299
|
#
|
@@ -397,6 +399,94 @@ module PacketFu
|
|
397
399
|
|
398
400
|
end
|
399
401
|
|
402
|
+
# PcapFile also can behave as a singleton class, which is usually the better
|
403
|
+
# way to handle pcap files of really any size, since it doesn't require
|
404
|
+
# storing packets before handing them off to a given block. This is really
|
405
|
+
# the way to go.
|
406
|
+
class PcapFile
|
407
|
+
class << self
|
408
|
+
|
409
|
+
# Takes a given file and returns an array of the packet bytes. Here
|
410
|
+
# for backwards compatibilty.
|
411
|
+
def file_to_array(fname)
|
412
|
+
PcapFile.new.file_to_array(:f => fname)
|
413
|
+
end
|
414
|
+
|
415
|
+
# Takes a given file name, and reads out the packets. If given a block,
|
416
|
+
# it will yield back a PcapPacket object per packet found.
|
417
|
+
def read(fname,&block)
|
418
|
+
begin
|
419
|
+
file_handle = File.open(fname, "rb")
|
420
|
+
pcap_packets = PcapPackets.new unless block
|
421
|
+
file_header = PcapHeader.new
|
422
|
+
file_header.read file_handle.read(24)
|
423
|
+
packet_count = 0
|
424
|
+
pcap_packet = PcapPacket.new(:endian => file_header.endian)
|
425
|
+
while pcap_packet.read file_handle.read(16) do
|
426
|
+
len = pcap_packet.incl_len
|
427
|
+
pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
|
428
|
+
packet_count += 1
|
429
|
+
if pcap_packet.data.size < len.to_i
|
430
|
+
warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
|
431
|
+
break
|
432
|
+
end
|
433
|
+
if block
|
434
|
+
yield pcap_packet
|
435
|
+
else
|
436
|
+
pcap_packets << pcap_packet
|
437
|
+
end
|
438
|
+
end
|
439
|
+
unless block
|
440
|
+
return pcap_packets
|
441
|
+
end
|
442
|
+
ensure
|
443
|
+
file_handle.close
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
# Takes a filename, and an optional block. If a block is given,
|
448
|
+
# yield back the raw packet data from the given file. Otherwise,
|
449
|
+
# return an array of parsed packets.
|
450
|
+
def read_packet_bytes(fname,&block)
|
451
|
+
count = 0
|
452
|
+
packets = [] unless block
|
453
|
+
read(fname) do |packet|
|
454
|
+
if block
|
455
|
+
count += 1
|
456
|
+
yield packet.data.to_s
|
457
|
+
else
|
458
|
+
packets << packet.data.to_s
|
459
|
+
end
|
460
|
+
end
|
461
|
+
block ? count : packets
|
462
|
+
end
|
463
|
+
|
464
|
+
alias :file_to_array :read_packet_bytes
|
465
|
+
|
466
|
+
# Takes a filename, and an optional block. If a block is given,
|
467
|
+
# yield back parsed packets from the given file. Otherwise, return
|
468
|
+
# an array of parsed packets.
|
469
|
+
#
|
470
|
+
# This is a brazillian times faster than the old methods of extracting
|
471
|
+
# packets from files.
|
472
|
+
def read_packets(fname,&block)
|
473
|
+
count = 0
|
474
|
+
packets = [] unless block
|
475
|
+
read_packet_bytes(fname) do |packet|
|
476
|
+
if block
|
477
|
+
count += 1
|
478
|
+
yield Packet.parse(packet)
|
479
|
+
else
|
480
|
+
packets << Packet.parse(packet)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
block ? count : packets
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|
487
|
+
|
488
|
+
end
|
489
|
+
|
400
490
|
end
|
401
491
|
|
402
492
|
module PacketFu
|
@@ -411,7 +501,7 @@ module PacketFu
|
|
411
501
|
# Reads the magic string of a pcap file, and determines
|
412
502
|
# if it's :little or :big endian.
|
413
503
|
def get_byte_order(pcap_file)
|
414
|
-
byte_order = ((pcap_file[0,4] ==
|
504
|
+
byte_order = ((pcap_file[0,4] == PcapHeader::MAGIC_LITTLE) ? :little : :big)
|
415
505
|
return byte_order
|
416
506
|
end
|
417
507
|
|
data/lib/packetfu/protos/tcp.rb
CHANGED
@@ -609,7 +609,7 @@ module PacketFu
|
|
609
609
|
# Note that by using TcpOptions#encode, strings supplied as values which
|
610
610
|
# can be converted to numbers will be converted first.
|
611
611
|
#
|
612
|
-
#
|
612
|
+
# === Example
|
613
613
|
#
|
614
614
|
# t = TcpOptions.new
|
615
615
|
# t.encode("MS:1460,WS:6")
|
@@ -774,10 +774,15 @@ module PacketFu
|
|
774
774
|
|
775
775
|
# Getter for the TCP Header Length value.
|
776
776
|
def tcp_hlen; self[:tcp_hlen].to_i; end
|
777
|
-
# Setter for the TCP Header Length value.
|
777
|
+
# Setter for the TCP Header Length value. Can take
|
778
|
+
# either a string or an integer. Note that if it's
|
779
|
+
# a string, the top four bits are used.
|
778
780
|
def tcp_hlen=(i)
|
779
|
-
|
780
|
-
|
781
|
+
case i
|
782
|
+
when PacketFu::TcpHlen
|
783
|
+
self[:tcp_hlen] = i
|
784
|
+
when Numeric
|
785
|
+
self[:tcp_hlen] = TcpHlen.new(:hlen => i.to_i)
|
781
786
|
else
|
782
787
|
self[:tcp_hlen].read(i)
|
783
788
|
end
|
@@ -787,8 +792,15 @@ module PacketFu
|
|
787
792
|
def tcp_reserved; self[:tcp_reserved].to_i; end
|
788
793
|
# Setter for the TCP Reserved field.
|
789
794
|
def tcp_reserved=(i)
|
790
|
-
|
795
|
+
case i
|
796
|
+
when PacketFu::TcpReserved
|
791
797
|
self[:tcp_reserved]=i
|
798
|
+
when Numeric
|
799
|
+
args = {}
|
800
|
+
args[:r1] = (i & 0b100) >> 2
|
801
|
+
args[:r2] = (i & 0b010) >> 1
|
802
|
+
args[:r3] = (i & 0b001)
|
803
|
+
self[:tcp_reserved] = TcpReserved.new(args)
|
792
804
|
else
|
793
805
|
self[:tcp_reserved].read(i)
|
794
806
|
end
|
@@ -798,8 +810,15 @@ module PacketFu
|
|
798
810
|
def tcp_ecn; self[:tcp_ecn].to_i; end
|
799
811
|
# Setter for the ECN bits.
|
800
812
|
def tcp_ecn=(i)
|
801
|
-
|
813
|
+
case i
|
814
|
+
when PacketFu::TcpEcn
|
802
815
|
self[:tcp_ecn]=i
|
816
|
+
when Numeric
|
817
|
+
args = {}
|
818
|
+
args[:n] = (i & 0b100) >> 2
|
819
|
+
args[:c] = (i & 0b010) >> 1
|
820
|
+
args[:e] = (i & 0b001)
|
821
|
+
self[:tcp_ecn] = TcpEcn.new(args)
|
803
822
|
else
|
804
823
|
self[:tcp_ecn].read(i)
|
805
824
|
end
|
@@ -809,7 +828,8 @@ module PacketFu
|
|
809
828
|
def tcp_opts; self[:tcp_opts].to_s; end
|
810
829
|
# Setter for TCP Options.
|
811
830
|
def tcp_opts=(i)
|
812
|
-
|
831
|
+
case i
|
832
|
+
when PacketFu::TcpOptions
|
813
833
|
self[:tcp_opts]=i
|
814
834
|
else
|
815
835
|
self[:tcp_opts].read(i)
|
@@ -846,7 +866,7 @@ module PacketFu
|
|
846
866
|
def tcp_flags_dotmap
|
847
867
|
dotmap = tcp_flags.members.map do |flag|
|
848
868
|
status = self.tcp_flags.send flag
|
849
|
-
status == 0 ? "." : flag.to_s.upcase[0]
|
869
|
+
status == 0 ? "." : flag.to_s.upcase[0].chr
|
850
870
|
end
|
851
871
|
dotmap.join
|
852
872
|
end
|
data/lib/packetfu/structfu.rb
CHANGED
@@ -118,12 +118,12 @@ module StructFu
|
|
118
118
|
|
119
119
|
end
|
120
120
|
|
121
|
-
# Int16be is a two byte value in big-endian format.
|
121
|
+
# Int16be is a two byte value in big-endian format. The endianness cannot be altered.
|
122
122
|
class Int16be < Int16
|
123
123
|
undef :endian=
|
124
124
|
end
|
125
125
|
|
126
|
-
# Int16le is a two byte value in little-endian format.
|
126
|
+
# Int16le is a two byte value in little-endian format. The endianness cannot be altered.
|
127
127
|
class Int16le < Int16
|
128
128
|
undef :endian=
|
129
129
|
def initialize(v=nil, e=:little)
|
@@ -147,12 +147,12 @@ module StructFu
|
|
147
147
|
|
148
148
|
end
|
149
149
|
|
150
|
-
# Int32be is a four byte value in big-endian format.
|
150
|
+
# Int32be is a four byte value in big-endian format. The endianness cannot be altered.
|
151
151
|
class Int32be < Int32
|
152
152
|
undef :endian=
|
153
153
|
end
|
154
154
|
|
155
|
-
# Int32le is a four byte value in little-endian format.
|
155
|
+
# Int32le is a four byte value in little-endian format. The endianness cannot be altered.
|
156
156
|
class Int32le < Int32
|
157
157
|
undef :endian=
|
158
158
|
def initialize(v=nil, e=:little)
|
@@ -208,8 +208,8 @@ module StructFu
|
|
208
208
|
# is calculated upon assignment. If you'd prefer to have
|
209
209
|
# an incorrect value, use the syntax, obj[:string]="value"
|
210
210
|
# instead. Note, by using the alternate form, you must
|
211
|
-
# #calc before you can trust the int's value. Think of the
|
212
|
-
#
|
211
|
+
# #calc before you can trust the int's value. Think of the =
|
212
|
+
# assignment as "set to equal," while the []= assignment
|
213
213
|
# as "boxing in" the value. Maybe.
|
214
214
|
def string=(s)
|
215
215
|
self[:string] = s
|
@@ -246,9 +246,13 @@ module StructFu
|
|
246
246
|
# is used is dependant on which :mode is set (with self.mode).
|
247
247
|
#
|
248
248
|
# :parse : Read the length, and then read in that many bytes of the string.
|
249
|
-
#
|
250
|
-
#
|
251
|
-
#
|
249
|
+
# The string may be truncated or padded out with nulls, as dictated by the value.
|
250
|
+
#
|
251
|
+
# :fix : Skip the length, read the rest of the string, then set the length
|
252
|
+
# to what it ought to be.
|
253
|
+
#
|
254
|
+
# else : If neither of these modes are set, just perfom a normal read().
|
255
|
+
# This is the default.
|
252
256
|
def parse(s)
|
253
257
|
unless s[0,int.width].size == int.width
|
254
258
|
raise StandardError, "String is too short for type #{int.class}"
|
data/lib/packetfu/version.rb
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
module PacketFu
|
2
2
|
|
3
3
|
# Check the repo's for version release histories
|
4
|
-
VERSION = "1.0.
|
4
|
+
VERSION = "1.0.4"
|
5
5
|
|
6
|
+
# Returns PacketFu::VERSION
|
6
7
|
def self.version
|
7
8
|
VERSION
|
8
9
|
end
|
9
10
|
|
10
|
-
# Returns
|
11
|
+
# Returns a version string in a binary format for easy comparisons.
|
11
12
|
def self.binarize_version(str)
|
12
13
|
if(str.respond_to?(:split) && str =~ /^[0-9]+(\.([0-9]+)(\.[0-9]+)?)?\..+$/)
|
13
14
|
bin_major,bin_minor,bin_teeny = str.split(/\x2e/).map {|x| x.to_i}
|
data/lib/packetfu.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
|
2
2
|
# :title: PacketFu Documentation
|
3
|
+
# :main: ../README
|
3
4
|
# :include: ../README
|
4
|
-
# :include: ../INSTALL
|
5
5
|
# :include: ../LICENSE
|
6
6
|
|
7
7
|
cwd = File.expand_path(File.dirname(__FILE__))
|
@@ -14,6 +14,19 @@ require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
|
|
14
14
|
|
15
15
|
module PacketFu
|
16
16
|
|
17
|
+
# Picks up all the protocols defined in the protos subdirectory
|
18
|
+
def self.require_protos(cwd)
|
19
|
+
protos_dir = File.join(cwd, "packetfu", "protos")
|
20
|
+
Dir.new(protos_dir).each do |fname|
|
21
|
+
next unless fname[/\.rb$/]
|
22
|
+
begin
|
23
|
+
require File.join(protos_dir,fname)
|
24
|
+
rescue
|
25
|
+
warn "Warning: Could not load `#{fname}'. Skipping."
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
17
30
|
# Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
|
18
31
|
@byte_order = :little
|
19
32
|
|
@@ -32,6 +45,7 @@ module PacketFu
|
|
32
45
|
end
|
33
46
|
|
34
47
|
pcaprub_platform_require
|
48
|
+
|
35
49
|
if @pcaprub_loaded
|
36
50
|
if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
|
37
51
|
@pcaprub_loaded = false # Don't bother with broken versions
|
@@ -41,6 +55,7 @@ module PacketFu
|
|
41
55
|
require "packetfu/inject"
|
42
56
|
end
|
43
57
|
|
58
|
+
# Returns the status of pcaprub
|
44
59
|
def self.pcaprub_loaded?
|
45
60
|
@pcaprub_loaded
|
46
61
|
end
|
@@ -50,6 +65,7 @@ module PacketFu
|
|
50
65
|
constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
|
51
66
|
end
|
52
67
|
|
68
|
+
# Adds the class to PacketFu's list of packet classes -- used in packet parsing.
|
53
69
|
def self.add_packet_class(klass)
|
54
70
|
raise "Need a class" unless klass.kind_of? Class
|
55
71
|
if klass.name !~ /[A-Za-z0-9]Packet/
|
@@ -60,6 +76,7 @@ module PacketFu
|
|
60
76
|
@packet_classes.sort! {|x,y| x.name <=> y.name}
|
61
77
|
end
|
62
78
|
|
79
|
+
# Presumably, there may be a time where you'd like to remove a packet class.
|
63
80
|
def self.remove_packet_class(klass)
|
64
81
|
raise "Need a class" unless klass.kind_of? Class
|
65
82
|
@packet_classes ||= []
|
@@ -67,10 +84,12 @@ module PacketFu
|
|
67
84
|
@packet_classes
|
68
85
|
end
|
69
86
|
|
87
|
+
# Returns an array of packet classes
|
70
88
|
def self.packet_classes
|
71
89
|
@packet_classes || []
|
72
90
|
end
|
73
91
|
|
92
|
+
# Returns an array of packet types by packet prefix.
|
74
93
|
def self.packet_prefixes
|
75
94
|
return [] unless @packet_classes
|
76
95
|
@packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
|
@@ -78,22 +97,10 @@ module PacketFu
|
|
78
97
|
|
79
98
|
end
|
80
99
|
|
81
|
-
def require_protos(cwd)
|
82
|
-
protos_dir = File.join(cwd, "packetfu", "protos")
|
83
|
-
Dir.new(protos_dir).each do |fname|
|
84
|
-
next unless fname[/\.rb$/]
|
85
|
-
begin
|
86
|
-
require File.join(protos_dir,fname)
|
87
|
-
rescue
|
88
|
-
warn "Warning: Could not load `#{fname}'. Skipping."
|
89
|
-
end
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
100
|
require File.join(cwd,"packetfu","version")
|
94
101
|
require File.join(cwd,"packetfu","pcap")
|
95
102
|
require File.join(cwd,"packetfu","packet")
|
96
|
-
require_protos(cwd)
|
103
|
+
PacketFu.require_protos(cwd)
|
97
104
|
require File.join(cwd,"packetfu","utils")
|
98
105
|
require File.join(cwd,"packetfu","config")
|
99
106
|
|
data/test/icmp_test.pcap
CHANGED
Binary file
|
data/test/ip_test.pcap
CHANGED
Binary file
|
data/test/packetfu_spec.rb
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require File.join("..","lib","packetfu")
|
2
2
|
|
3
|
+
unless %x{#{$0} --version} =~ /^2\.6/
|
4
|
+
puts "PacketFu needs rspec 2.6 or so."
|
5
|
+
exit 1
|
6
|
+
end
|
7
|
+
|
3
8
|
describe PacketFu, "version information" do
|
4
9
|
it "reports a version number" do
|
5
|
-
PacketFu::VERSION.should
|
10
|
+
PacketFu::VERSION.should match /^1\.[0-9]\.[0-9]$/
|
6
11
|
end
|
7
12
|
its(:version) {should eq PacketFu::VERSION}
|
8
13
|
|
data/test/tcp_spec.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require File.join("..","lib","packetfu")
|
2
|
+
|
3
|
+
include PacketFu
|
4
|
+
|
5
|
+
def unusual_numeric_handling_headers(header,i)
|
6
|
+
camelized_header = header.to_s.split("_").map {|x| x.capitalize}.join
|
7
|
+
header_class = PacketFu.const_get camelized_header
|
8
|
+
specify { subject.send(header).should == i }
|
9
|
+
specify { subject.send(header).should be_kind_of Integer }
|
10
|
+
specify { subject.headers.last[header].should be_kind_of header_class }
|
11
|
+
end
|
12
|
+
|
13
|
+
def tcp_hlen_numeric(i)
|
14
|
+
unusual_numeric_handling_headers(:tcp_hlen,i)
|
15
|
+
end
|
16
|
+
|
17
|
+
def tcp_reserved_numeric(i)
|
18
|
+
unusual_numeric_handling_headers(:tcp_reserved,i)
|
19
|
+
end
|
20
|
+
|
21
|
+
def tcp_ecn_numeric(i)
|
22
|
+
unusual_numeric_handling_headers(:tcp_ecn,i)
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
describe TCPPacket do
|
27
|
+
|
28
|
+
subject do
|
29
|
+
bytes = PcapFile.file_to_array("sample2.pcap")[2]
|
30
|
+
packet = Packet.parse(bytes)
|
31
|
+
end
|
32
|
+
|
33
|
+
context "TcpHlen reading and setting" do
|
34
|
+
context "TcpHlen set via #read" do
|
35
|
+
tcp_hlen_numeric(8)
|
36
|
+
end
|
37
|
+
context "TcpHlen set via an Integer for the setter" do
|
38
|
+
(0..15).each do |i|
|
39
|
+
context "i is #{i}" do
|
40
|
+
before { subject.tcp_hlen = i }
|
41
|
+
tcp_hlen_numeric(i)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
context "TcpHlen set via a String for the setter" do
|
46
|
+
before { subject.tcp_hlen = "\x60" }
|
47
|
+
tcp_hlen_numeric(6)
|
48
|
+
end
|
49
|
+
context "TcpHlen set via a TcpHlen for the setter" do
|
50
|
+
before { subject.tcp_hlen = TcpHlen.new(:hlen => 7) }
|
51
|
+
tcp_hlen_numeric(7)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "TcpReserved reading and setting" do
|
56
|
+
context "TcpReserved set via #read" do
|
57
|
+
tcp_reserved_numeric(0)
|
58
|
+
end
|
59
|
+
context "TcpReserved set via an Integer for the setter" do
|
60
|
+
(0..7).each do |i|
|
61
|
+
context "i is #{i}" do
|
62
|
+
before { subject.tcp_reserved = i }
|
63
|
+
tcp_reserved_numeric(i)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
context "TcpReserved set via a String for the setter" do
|
68
|
+
before { subject.tcp_reserved = "\x03" }
|
69
|
+
tcp_reserved_numeric(3)
|
70
|
+
end
|
71
|
+
context "TcpReserved set via a TcpReserved for the setter" do
|
72
|
+
before { subject.tcp_reserved = TcpReserved.new(:r1 => 1, :r2 => 0, :r3 => 1) }
|
73
|
+
tcp_reserved_numeric(5)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
context "TcpEcn reading and setting" do
|
78
|
+
context "TcpEcn set via #read" do
|
79
|
+
tcp_ecn_numeric(0)
|
80
|
+
end
|
81
|
+
context "TcpEcn set via an Integer for the setter" do
|
82
|
+
(0..7).each do |i|
|
83
|
+
context "i is #{i}" do
|
84
|
+
before { subject.tcp_ecn = i }
|
85
|
+
tcp_ecn_numeric(i)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context "TcpEcn set via a String for the setter" do
|
90
|
+
before { subject.tcp_ecn = "\x00\xc0" }
|
91
|
+
tcp_ecn_numeric(3)
|
92
|
+
end
|
93
|
+
context "TcpEcn set via a TcpEcn for the setter" do
|
94
|
+
before { subject.tcp_ecn = TcpEcn.new(:n => 1, :c => 0, :e => 1) }
|
95
|
+
tcp_ecn_numeric(5)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
data/test/tcp_test.pcap
CHANGED
Binary file
|
data/test/test_arp.rb
CHANGED
data/test/test_hsrp.rb
CHANGED
@@ -15,57 +15,6 @@ class HSRPTest < Test::Unit::TestCase
|
|
15
15
|
# pkt.to_f('udp_test.pcap','a')
|
16
16
|
end
|
17
17
|
|
18
|
-
=begin
|
19
|
-
# The rest of these tests are snarfed from UDP. TODO: need to update
|
20
|
-
# these for hsrp, shouldn't be long.
|
21
|
-
def test_hsrp_pcap
|
22
|
-
u = UDPPacket.new
|
23
|
-
assert_kind_of UDPPacket, u
|
24
|
-
u.recalc
|
25
|
-
u.to_f('udp_test.pcap','a')
|
26
|
-
u.ip_saddr = "10.20.30.40"
|
27
|
-
u.ip_daddr = "50.60.70.80"
|
28
|
-
u.payload = "+some fakey-fake udp packet"
|
29
|
-
u.udp_src = 1205
|
30
|
-
u.udp_dst = 13013
|
31
|
-
u.recalc
|
32
|
-
u.to_f('udp_test.pcap','a')
|
33
|
-
end
|
34
|
-
|
35
|
-
def test_udp_peek
|
36
|
-
u = UDPPacket.new
|
37
|
-
u.ip_saddr = "10.20.30.40"
|
38
|
-
u.ip_daddr = "50.60.70.80"
|
39
|
-
u.udp_src = 53
|
40
|
-
u.udp_dport = 1305
|
41
|
-
u.payload = "abcdefghijklmnopqrstuvwxyz"
|
42
|
-
u.recalc
|
43
|
-
puts "\n"
|
44
|
-
puts "UDP Peek format: "
|
45
|
-
puts u.peek
|
46
|
-
assert_equal 78,u.peek.size
|
47
|
-
end
|
48
|
-
|
49
|
-
def test_udp_checksum
|
50
|
-
sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0]
|
51
|
-
pkt = Packet.parse(sample_packet)
|
52
|
-
assert_kind_of UDPPacket, pkt
|
53
|
-
pkt.recalc
|
54
|
-
assert_equal(0x8bf8, pkt.udp_sum.to_i)
|
55
|
-
pkt.to_f('udp_test.pcap','a')
|
56
|
-
end
|
57
|
-
|
58
|
-
def test_udp_alter
|
59
|
-
sample_packet = PcapFile.new.file_to_array(:f => 'sample.pcap')[0]
|
60
|
-
pkt = Packet.parse(sample_packet)
|
61
|
-
assert_kind_of UDPPacket, pkt
|
62
|
-
pkt.payload = pkt.payload.gsub(/metasploit/,"MeatPistol")
|
63
|
-
pkt.recalc
|
64
|
-
assert_equal(0x8341, pkt.udp_sum)
|
65
|
-
pkt.to_f('udp_test.pcap','a')
|
66
|
-
end
|
67
|
-
=end
|
68
|
-
|
69
18
|
end
|
70
19
|
|
71
20
|
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
data/test/test_icmp.rb
CHANGED
data/test/test_ip.rb
CHANGED
data/test/test_ip6.rb
CHANGED
data/test/test_tcp.rb
CHANGED
data/test/test_udp.rb
CHANGED
data/test/udp_test.pcap
CHANGED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packetfu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 199653271
|
5
5
|
prerelease: 6
|
6
6
|
segments:
|
7
7
|
- 1
|
8
8
|
- 0
|
9
|
-
-
|
9
|
+
- 4
|
10
10
|
- pre
|
11
|
-
version: 1.0.
|
11
|
+
version: 1.0.4.pre
|
12
12
|
platform: ruby
|
13
13
|
authors:
|
14
14
|
- Tod Beardsley
|
@@ -78,7 +78,6 @@ files:
|
|
78
78
|
- lib/packetfu/protos/hsrp.rb
|
79
79
|
- lib/packetfu/utils.rb
|
80
80
|
- lib/packetfu/packet.rb
|
81
|
-
- CHANGES
|
82
81
|
- INSTALL
|
83
82
|
- LICENSE
|
84
83
|
- README
|
@@ -88,7 +87,6 @@ files:
|
|
88
87
|
- test/udp_test.pcap
|
89
88
|
- test/sample2.pcap
|
90
89
|
- test/sample.pcap
|
91
|
-
- test/dissect_thinger.rb
|
92
90
|
- test/test_ip6.rb
|
93
91
|
- test/all_tests.rb
|
94
92
|
- test/test_invalid.rb
|
@@ -98,6 +96,7 @@ files:
|
|
98
96
|
- test/icmp_test.pcap
|
99
97
|
- test/test_udp.rb
|
100
98
|
- test/sample_hsrp_pcapr.cap
|
99
|
+
- test/tcp_spec.rb
|
101
100
|
- test/test_tcp.rb
|
102
101
|
- test/tcp_test.pcap
|
103
102
|
- test/test_arp.rb
|
@@ -127,6 +126,7 @@ files:
|
|
127
126
|
- examples/idsv2.rb
|
128
127
|
- examples/ackscan.rb
|
129
128
|
- examples/ids.rb
|
129
|
+
- examples/new-simple-stats.rb
|
130
130
|
has_rdoc: true
|
131
131
|
homepage: http://code.google.com/p/packetfu/
|
132
132
|
licenses: []
|
data/CHANGES
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
= Changelog
|
2
|
-
|
3
|
-
== Version 1.0.0 # to be released July 29, 2010
|
4
|
-
Missed a bunch of updates in the Changelog. Mea culpa.
|
5
|
-
Squashed all Ruby version bugs -- works now on 1.8.6, 1.8.7, 1.9.1, and 1.9.2-head.
|
6
|
-
Added ENV['IFACE'] parsing for interface selection. Sadly, rvmsudo doesn't preserve ENV by default, but system ruby should work fine.
|
7
|
-
Removed my own distros of pcaprub. You're on your own, now, but shadowbq's or Metasploit's versions should do just fine.
|
8
|
-
|
9
|
-
== Version 0.1.1 # February 18, 2009
|
10
|
-
Added .is_proto? functions, self.proto function, Packet.headers attr_accessor across the board.
|
11
|
-
Removed dependency on pcaprub to read pcap-formatted files. (r54)
|
12
|
-
Added ability to preserve timestamps when reading files using :keep_ts => true argument on Read#f2a and Write honors both saved timestamp or invented timestamps. (r55)
|
13
|
-
Various minor bugs. (r56)
|
14
|
-
Relaxed the requirement for PcapRub to 0.7-dev now that I handle packet reading on my own. Windows users and threading will be broke without 0.8-dev, though. (r57)
|
15
|
-
Endianness of pcap files (for both reading and writing) is now supported (r58).
|
16
|
-
Handle non-version-4 IP packets correctlier (r59) (thanks tmanning!).
|
17
|
-
Merge of Metasploit-local patches, including Write.append (byte-order safe). (r60)
|
18
|
-
|
19
|
-
== Version 0.1.0 # September 13, 2008
|
20
|
-
Various minor bugs fixed.
|
21
|
-
Added a Windows compatability mode via a compiled pcaprub.
|
22
|
-
Note: Works fine on XP, works okay on Vista. Users are encouraged to compile their own pcaprub installations.
|
23
|
-
|
24
|
-
== Verison 0.0.3 # September 3, 2008 # r25
|
25
|
-
First tagged version. Naturally, bugs were found that moment.
|
26
|
-
|
27
|
-
== Version 0.3.0 # January 11, 2010 # r129
|
28
|
-
Rewrote pretty much everything using Struct instead of BinData. Huge success.
|
29
|
-
Start to get back in the habit of documentation changes.
|
30
|
-
|
31
|
-
== Version 0.3.1 # FUTURE
|
32
|
-
r130: Add convenience methods for checking the version.
|
33
|
-
r131: Fix TCP option setting.
|
34
|
-
r132: pcaprub to 0.9-dev.
|
35
|
-
|
36
|
-
|
data/test/dissect_thinger.rb
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
# This just allows you to eyeball the dissection stuff to make sure it's all right.
|
2
|
-
|
3
|
-
require File.join("..","lib","packetfu")
|
4
|
-
puts "Loaded: PacketFu v#{PacketFu.version}"
|
5
|
-
# $: << File.join(File.expand_path(File.dirname(__FILE__)),"..","lib")
|
6
|
-
|
7
|
-
include PacketFu
|
8
|
-
|
9
|
-
packets = PcapFile.file_to_array "test/sample2.pcap"
|
10
|
-
packets.each do |packet|
|
11
|
-
puts packet.inspect
|
12
|
-
pkt = Packet.parse(packet)
|
13
|
-
puts pkt.dissect
|
14
|
-
sleep 1
|
15
|
-
end
|