packetfu 1.0.5.pre → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/examples/100kpackets.rb +41 -0
- data/examples/ackscan.rb +0 -0
- data/examples/simple-sniffer.rb +40 -0
- data/lib/packetfu.rb +42 -0
- data/lib/packetfu/capture.rb +1 -1
- data/lib/packetfu/config.rb +8 -5
- data/lib/packetfu/packet.rb +9 -21
- data/lib/packetfu/pcap.rb +26 -24
- data/lib/packetfu/protos/arp.rb +5 -2
- data/lib/packetfu/protos/eth.rb +13 -1
- data/lib/packetfu/protos/icmp.rb +4 -3
- data/lib/packetfu/protos/ip.rb +20 -2
- data/lib/packetfu/protos/tcp.rb +2 -6
- data/lib/packetfu/structfu.rb +7 -3
- data/lib/packetfu/utils.rb +27 -15
- data/lib/packetfu/version.rb +1 -1
- data/test/all_tests.rb +1 -0
- data/test/ethpacket_spec.rb +74 -0
- data/test/icmp_test.pcap +0 -0
- data/test/ip_test.pcap +0 -0
- data/test/packet_spec.rb +2 -1
- data/test/packet_subclasses_spec.rb +2 -1
- data/test/packetfu_spec.rb +2 -1
- data/test/structfu_spec.rb +2 -1
- data/test/tcp_spec.rb +2 -1
- data/test/tcp_test.pcap +0 -0
- data/test/test_arp.rb +1 -1
- data/test/test_eth.rb +1 -1
- data/test/test_hsrp.rb +1 -1
- data/test/test_icmp.rb +1 -1
- data/test/test_inject.rb +2 -4
- data/test/test_invalid.rb +1 -1
- data/test/test_ip.rb +1 -1
- data/test/test_ip6.rb +1 -1
- data/test/test_octets.rb +1 -1
- data/test/test_packet.rb +0 -1
- data/test/test_pcap.rb +1 -2
- data/test/test_structfu.rb +1 -1
- data/test/test_tcp.rb +1 -1
- data/test/test_udp.rb +1 -1
- data/test/udp_test.pcap +0 -0
- data/test/vlan-pcapr.cap +0 -0
- metadata +11 -27
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Used mainly to test for memory leaks and to demo the preferred ways of
|
4
|
+
# reading and writing packets to and from pcap files.
|
5
|
+
require './examples' # For path setting slight-of-hand
|
6
|
+
require 'packetfu'
|
7
|
+
|
8
|
+
include PacketFu
|
9
|
+
puts "Generating packets... (#{Time.now.utc})"
|
10
|
+
|
11
|
+
File.unlink("/tmp/out.pcap") if File.exists? "/tmp/out.pcap"
|
12
|
+
start_time = Time.now.utc
|
13
|
+
count = 0
|
14
|
+
|
15
|
+
100.times do
|
16
|
+
@pcaps = []
|
17
|
+
1000.times do
|
18
|
+
u = UDPPacket.new
|
19
|
+
u.ip_src = [rand(2**32-1)].pack("N")
|
20
|
+
u.ip_dst = [rand(2**32-1)].pack("N")
|
21
|
+
u.recalc
|
22
|
+
@pcaps << u
|
23
|
+
end
|
24
|
+
pfile = PcapFile.new
|
25
|
+
res = pfile.array_to_file(:filename => "/tmp/out.pcap", :array => @pcaps, :append => true)
|
26
|
+
count += res.last
|
27
|
+
puts "Wrote #{count} packets in #{Time.now.utc - start_time} seconds"
|
28
|
+
end
|
29
|
+
|
30
|
+
read_bytes_start = Time.now.utc
|
31
|
+
puts "Reading packet bytes..."
|
32
|
+
packet_bytes = PcapFile.read_packet_bytes "/tmp/out.pcap"
|
33
|
+
puts "Read #{packet_bytes.size} packet byte blobs in #{Time.now.utc - read_bytes_start} seconds."
|
34
|
+
|
35
|
+
read_packets_start = Time.now.utc
|
36
|
+
puts "Reading packets..."
|
37
|
+
packet_bytes = PcapFile.read_packets "/tmp/out.pcap"
|
38
|
+
puts "Read #{packet_bytes.size} parsed packets in #{Time.now.utc - read_packets_start} seconds."
|
39
|
+
|
40
|
+
|
41
|
+
|
data/examples/ackscan.rb
CHANGED
File without changes
|
@@ -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
|
data/lib/packetfu.rb
CHANGED
@@ -25,6 +25,11 @@ module PacketFu
|
|
25
25
|
end
|
26
26
|
end
|
27
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
|
+
|
28
33
|
# Sets the expected byte order for a pcap file. See PacketFu::Read.set_byte_order
|
29
34
|
@byte_order = :little
|
30
35
|
|
@@ -93,6 +98,43 @@ module PacketFu
|
|
93
98
|
@packet_classes.map {|p| p.to_s.split("::").last.to_s.downcase.gsub(/packet$/,"")}
|
94
99
|
end
|
95
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
|
+
|
96
138
|
end
|
97
139
|
|
98
140
|
require File.join(cwd,"packetfu","version")
|
data/lib/packetfu/capture.rb
CHANGED
@@ -31,7 +31,7 @@ module PacketFu
|
|
31
31
|
def initialize(args={})
|
32
32
|
@array = [] # Where the packet array goes.
|
33
33
|
@stream = [] # Where the stream goes.
|
34
|
-
@iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
|
34
|
+
@iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s
|
35
35
|
@snaplen = args[:snaplen] || 0xffff
|
36
36
|
@promisc = args[:promisc] || false # Sensible for some Intel wifi cards
|
37
37
|
@timeout = args[:timeout] || 1
|
data/lib/packetfu/config.rb
CHANGED
@@ -36,17 +36,20 @@ module PacketFu
|
|
36
36
|
@iface = args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo"
|
37
37
|
end
|
38
38
|
@pcapfile = "/tmp/out.pcap"
|
39
|
-
args.each_pair { |k,v| self.instance_variable_set(("
|
39
|
+
args.each_pair { |k,v| self.instance_variable_set(("@#{k}"),v) }
|
40
40
|
end
|
41
41
|
|
42
42
|
# Returns all instance variables as a hash (including custom variables set at initialization).
|
43
43
|
def config(arg=nil)
|
44
|
-
if arg
|
44
|
+
if arg
|
45
|
+
arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
|
46
|
+
else
|
45
47
|
config_hash = {}
|
46
|
-
self.instance_variables.each
|
48
|
+
self.instance_variables.each do |v|
|
49
|
+
key = v.to_s.gsub(/^@/,"").to_sym
|
50
|
+
config_hash[key] = self.instance_variable_get(v)
|
51
|
+
end
|
47
52
|
config_hash
|
48
|
-
else
|
49
|
-
arg.each_pair {|k,v| self.instance_variable_set(("@" + k.to_s).intern, v)}
|
50
53
|
end
|
51
54
|
end
|
52
55
|
|
data/lib/packetfu/packet.rb
CHANGED
@@ -102,7 +102,7 @@ module PacketFu
|
|
102
102
|
# Put the entire packet on the wire by creating a temporary PacketFu::Inject object.
|
103
103
|
# TODO: Do something with auto-checksumming?
|
104
104
|
def to_w(iface=nil)
|
105
|
-
iface = iface || self.iface || PacketFu::Config.new.config[:iface]
|
105
|
+
iface = (iface || self.iface || PacketFu::Config.new.config[:iface]).to_s
|
106
106
|
inj = PacketFu::Inject.new(:iface => iface)
|
107
107
|
inj.array = [@headers[0].to_s]
|
108
108
|
inj.inject
|
@@ -467,11 +467,7 @@ module PacketFu
|
|
467
467
|
if self.class.name =~ /(::|^)PacketFu::Packet$/
|
468
468
|
raise NoMethodError, "method `new' called for abstract class #{self.class.name}"
|
469
469
|
end
|
470
|
-
|
471
|
-
@inspect_style = args[:inspect_style]
|
472
|
-
else
|
473
|
-
@inspect_style = :dissect
|
474
|
-
end
|
470
|
+
@inspect_style = args[:inspect_style] || PacketFu.inspect_style || :dissect
|
475
471
|
if args[:config]
|
476
472
|
args[:config].each_pair do |k,v|
|
477
473
|
case k
|
@@ -484,6 +480,13 @@ module PacketFu
|
|
484
480
|
end
|
485
481
|
end
|
486
482
|
|
483
|
+
# Delegate to PacketFu's inspect_style, since the
|
484
|
+
# class variable name is the same. Yay for namespace
|
485
|
+
# pollution!
|
486
|
+
def inspect_style=()
|
487
|
+
PacketFu.inspect_style(arg)
|
488
|
+
end
|
489
|
+
|
487
490
|
#method_missing() delegates protocol-specific field actions to the apporpraite
|
488
491
|
#class variable (which contains the associated packet type)
|
489
492
|
#This register-of-protocols style switch will work for the
|
@@ -525,21 +528,6 @@ module PacketFu
|
|
525
528
|
end
|
526
529
|
|
527
530
|
end # class Packet
|
528
|
-
|
529
|
-
# Switches inspect styles between :dissect, :default, and :hex
|
530
|
-
def toggle_inspect
|
531
|
-
case @inspect_style
|
532
|
-
when :hex, :pretty
|
533
|
-
@inspect_style = :dissect
|
534
|
-
when :dissect, :verbose
|
535
|
-
@inspect_style = :default
|
536
|
-
when :default, :ugly
|
537
|
-
@inspect_style = :hex
|
538
|
-
else
|
539
|
-
@inspect_style = :dissect
|
540
|
-
end
|
541
|
-
end
|
542
|
-
|
543
531
|
end
|
544
532
|
|
545
533
|
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
data/lib/packetfu/pcap.rb
CHANGED
@@ -246,34 +246,32 @@ module PacketFu
|
|
246
246
|
|
247
247
|
# Takes a given file name, and reads out the packets. If given a block,
|
248
248
|
# it will yield back a PcapPacket object per packet found.
|
249
|
-
def read(fname,&block)
|
249
|
+
def read(fname,&block)
|
250
|
+
file_header = PcapHeader.new
|
251
|
+
pcap_packets = PcapPackets.new
|
252
|
+
unless File.readable? fname
|
253
|
+
raise ArgumentError, "Cannot read file `#{fname}'"
|
254
|
+
end
|
250
255
|
begin
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
packet_count
|
261
|
-
|
262
|
-
warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
|
263
|
-
break
|
264
|
-
end
|
265
|
-
if block
|
266
|
-
yield pcap_packet
|
267
|
-
else
|
268
|
-
pcap_packets << pcap_packet
|
269
|
-
end
|
270
|
-
end
|
271
|
-
unless block
|
272
|
-
return pcap_packets
|
256
|
+
file_handle = File.open(fname, "rb")
|
257
|
+
file_header.read file_handle.read(24)
|
258
|
+
packet_count = 0
|
259
|
+
pcap_packet = PcapPacket.new(:endian => file_header.endian)
|
260
|
+
while pcap_packet.read file_handle.read(16) do
|
261
|
+
len = pcap_packet.incl_len
|
262
|
+
pcap_packet.data = StructFu::String.new.read(file_handle.read(len.to_i))
|
263
|
+
packet_count += 1
|
264
|
+
if pcap_packet.data.size < len.to_i
|
265
|
+
warn "Packet ##{packet_count} is corrupted: expected #{len.to_i}, got #{pcap_packet.data.size}. Exiting."
|
266
|
+
break
|
273
267
|
end
|
268
|
+
pcap_packets << pcap_packet.clone
|
269
|
+
yield pcap_packets.last if block
|
270
|
+
end
|
274
271
|
ensure
|
275
272
|
file_handle.close
|
276
273
|
end
|
274
|
+
block ? packet_count : pcap_packets
|
277
275
|
end
|
278
276
|
|
279
277
|
# Takes a filename, and an optional block. If a block is given,
|
@@ -453,7 +451,11 @@ module PacketFu
|
|
453
451
|
end
|
454
452
|
append = args[:append]
|
455
453
|
if append
|
456
|
-
File.
|
454
|
+
if File.exists? filename
|
455
|
+
File.open(filename,'ab') {|file| file.write(self.body.to_s)}
|
456
|
+
else
|
457
|
+
File.open(filename,'wb') {|file| file.write(self.to_s)}
|
458
|
+
end
|
457
459
|
else
|
458
460
|
File.open(filename,'wb') {|file| file.write(self.to_s)}
|
459
461
|
end
|
data/lib/packetfu/protos/arp.rb
CHANGED
@@ -26,14 +26,17 @@ module PacketFu
|
|
26
26
|
include StructFu
|
27
27
|
|
28
28
|
def initialize(args={})
|
29
|
+
src_mac = args[:arp_src_mac] || (args[:config][:eth_src] if args[:config])
|
30
|
+
src_ip_bin = args[:arp_src_ip] || (args[:config][:ip_src_bin] if args[:config])
|
31
|
+
|
29
32
|
super(
|
30
33
|
Int16.new(args[:arp_hw] || 1),
|
31
34
|
Int16.new(args[:arp_proto] ||0x0800),
|
32
35
|
Int8.new(args[:arp_hw_len] || 6),
|
33
36
|
Int8.new(args[:arp_proto_len] || 4),
|
34
37
|
Int16.new(args[:arp_opcode] || 1),
|
35
|
-
EthMac.new.read(
|
36
|
-
Octets.new.read(
|
38
|
+
EthMac.new.read(src_mac),
|
39
|
+
Octets.new.read(src_ip_bin),
|
37
40
|
EthMac.new.read(args[:arp_dst_mac]),
|
38
41
|
Octets.new.read(args[:arp_dst_ip]),
|
39
42
|
StructFu::String.new.read(args[:body])
|
data/lib/packetfu/protos/eth.rb
CHANGED
@@ -132,6 +132,12 @@ module PacketFu
|
|
132
132
|
# For more on the construction on MAC addresses, see
|
133
133
|
# http://en.wikipedia.org/wiki/MAC_address
|
134
134
|
#
|
135
|
+
# TODO: Need to come up with a good way of dealing with vlan
|
136
|
+
# tagging. Having a usually empty struct member seems weird,
|
137
|
+
# but there may not be another way to do it if I want to preserve
|
138
|
+
# the Eth-ness of vlan-tagged 802.1Q packets. Also, may as well
|
139
|
+
# deal with 0x88a8 as well (http://en.wikipedia.org/wiki/802.1ad)
|
140
|
+
#
|
135
141
|
# ==== Header Definition
|
136
142
|
#
|
137
143
|
# EthMac :eth_dst # See EthMac
|
@@ -251,11 +257,17 @@ module PacketFu
|
|
251
257
|
# eth_pkt.eth_daddr="00:1c:24:aa:bb:cc"
|
252
258
|
#
|
253
259
|
# eth_pkt.to_w('eth0') # Inject on the wire. (require root)
|
260
|
+
#
|
254
261
|
class EthPacket < Packet
|
255
262
|
attr_accessor :eth_header
|
256
263
|
|
257
264
|
def self.can_parse?(str)
|
258
|
-
|
265
|
+
# XXX Temporary fix. Need to extend the EthHeader class to handle more.
|
266
|
+
valid_eth_types = [0x0800, 0x0806, 0x86dd]
|
267
|
+
return false unless str.size >= 14
|
268
|
+
type = str[12,2].unpack("n").first rescue nil
|
269
|
+
return false unless valid_eth_types.include? type
|
270
|
+
true
|
259
271
|
end
|
260
272
|
|
261
273
|
def read(str=nil,args={})
|
data/lib/packetfu/protos/icmp.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
module PacketFu
|
2
2
|
|
3
|
-
# ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is
|
4
|
-
# administration and connectivity testing.
|
3
|
+
# ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is
|
4
|
+
# typically used for network administration and connectivity testing.
|
5
5
|
#
|
6
|
-
# For more on ICMP packets, see
|
6
|
+
# For more on ICMP packets, see
|
7
|
+
# http://www.networksorcery.com/enp/protocol/icmp.htm
|
7
8
|
#
|
8
9
|
# ==== Header Definition
|
9
10
|
#
|
data/lib/packetfu/protos/ip.rb
CHANGED
@@ -162,11 +162,29 @@ module PacketFu
|
|
162
162
|
# Getter for the checksum.
|
163
163
|
def ip_sum; self[:ip_sum].to_i; end
|
164
164
|
# Setter for the source IP address.
|
165
|
-
def ip_src=(i)
|
165
|
+
def ip_src=(i)
|
166
|
+
case i
|
167
|
+
when Numeric
|
168
|
+
self[:ip_src] = Octets.new.read([i].pack("N"))
|
169
|
+
when Octets
|
170
|
+
self[:ip_src] = i
|
171
|
+
else
|
172
|
+
typecast i
|
173
|
+
end
|
174
|
+
end
|
166
175
|
# Getter for the source IP address.
|
167
176
|
def ip_src; self[:ip_src].to_i; end
|
168
177
|
# Setter for the destination IP address.
|
169
|
-
def ip_dst=(i)
|
178
|
+
def ip_dst=(i)
|
179
|
+
case i
|
180
|
+
when Numeric
|
181
|
+
self[:ip_dst] = Octets.new.read([i].pack("N"))
|
182
|
+
when Octets
|
183
|
+
self[:ip_dst] = i
|
184
|
+
else
|
185
|
+
typecast i
|
186
|
+
end
|
187
|
+
end
|
170
188
|
# Getter for the destination IP address.
|
171
189
|
def ip_dst; self[:ip_dst].to_i; end
|
172
190
|
|
data/lib/packetfu/protos/tcp.rb
CHANGED
@@ -546,14 +546,10 @@ module PacketFu
|
|
546
546
|
opts
|
547
547
|
end
|
548
548
|
|
549
|
-
def force_binary(str)
|
550
|
-
str.force_encoding "binary" if str.respond_to? :force_encoding
|
551
|
-
end
|
552
|
-
|
553
549
|
# Reads a string to populate the object.
|
554
550
|
def read(str)
|
555
|
-
self.clear
|
556
|
-
force_binary(str)
|
551
|
+
self.clear
|
552
|
+
PacketFu.force_binary(str)
|
557
553
|
return self if(!str.respond_to? :to_s || str.nil?)
|
558
554
|
i = 0
|
559
555
|
while i < str.to_s.size
|
data/lib/packetfu/structfu.rb
CHANGED
@@ -33,9 +33,13 @@ module StructFu
|
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
# Handle deep copies correctly
|
36
|
+
# Handle deep copies correctly. Marshal in 1.9, re-read myself on 1.8
|
37
37
|
def clone
|
38
|
-
|
38
|
+
begin
|
39
|
+
Marshal.load(Marshal.dump(self))
|
40
|
+
rescue
|
41
|
+
self.class.new.read(self.to_s)
|
42
|
+
end
|
39
43
|
end
|
40
44
|
|
41
45
|
# Ints all have a value, an endianness, and a default value.
|
@@ -282,7 +286,7 @@ class Struct
|
|
282
286
|
# Monkeypatch for Struct to include some string safety -- anything that uses
|
283
287
|
# Struct is going to presume binary strings anyway.
|
284
288
|
def force_binary(str)
|
285
|
-
|
289
|
+
PacketFu.force_binary(str)
|
286
290
|
end
|
287
291
|
|
288
292
|
end
|
data/lib/packetfu/utils.rb
CHANGED
@@ -27,13 +27,12 @@ module PacketFu
|
|
27
27
|
# It goes without saying, spewing forged ARP packets on your network is a great way to really
|
28
28
|
# irritate your co-workers.
|
29
29
|
def self.arp(target_ip,args={})
|
30
|
-
|
31
|
-
|
30
|
+
iface = args[:iface] || :eth0
|
31
|
+
args[:config] ||= whoami?(:iface => iface)
|
32
|
+
arp_pkt = PacketFu::ARPPacket.new(:flavor => (args[:flavor] || :none), :config => args[:config])
|
32
33
|
arp_pkt.eth_daddr = "ff:ff:ff:ff:ff:ff"
|
33
34
|
arp_pkt.arp_daddr_mac = "00:00:00:00:00:00"
|
34
|
-
arp_pkt.
|
35
|
-
arp_pkt.arp_daddr_ip = target_ip
|
36
|
-
iface = (args[:iface] || ($packetfu_default.iface if $packetfu_default) || "eth0")
|
35
|
+
arp_pkt.arp_daddr_ip = target_ip
|
37
36
|
# Stick the Capture object in its own thread.
|
38
37
|
cap_thread = Thread.new do
|
39
38
|
target_mac = nil
|
@@ -59,9 +58,15 @@ module PacketFu
|
|
59
58
|
# operation; a UDP packet is generated and dropped on to the default (or named)
|
60
59
|
# interface, and then captured (which means you need to be root to do this).
|
61
60
|
#
|
62
|
-
# whoami? returns a hash of :eth_saddr, :eth_src, :ip_saddr, :ip_src,
|
63
|
-
# :eth_dst, and :eth_daddr (the last two are usually suitable
|
64
|
-
# gateway mac address). It's most useful as an argument to
|
61
|
+
# whoami? returns a hash of :eth_saddr, :eth_src, :ip_saddr, :ip_src,
|
62
|
+
# :ip_src_bin, :eth_dst, and :eth_daddr (the last two are usually suitable
|
63
|
+
# for a gateway mac address). It's most useful as an argument to
|
64
|
+
# PacketFu::Config.new, or as an argument to the many Packet constructors.
|
65
|
+
#
|
66
|
+
# Note that if you have multiple interfaces with the same route (such as when
|
67
|
+
# wlan0 and eth0 are associated to the same network), the "first" one
|
68
|
+
# according to Pcap.lookupdev will be used, regardless of which :iface you
|
69
|
+
# pick.
|
65
70
|
#
|
66
71
|
# === Parameters
|
67
72
|
# :iface => "eth0"
|
@@ -72,7 +77,10 @@ module PacketFu
|
|
72
77
|
# Since this network is IANA reserved (for now), this network should be handled by your default gateway
|
73
78
|
# and default interface.
|
74
79
|
def self.whoami?(args={})
|
75
|
-
|
80
|
+
unless args.kind_of? Hash
|
81
|
+
raise ArgumentError, "Argument to `whoami?' must be a Hash"
|
82
|
+
end
|
83
|
+
if args[:iface].to_s =~ /^lo/ # Linux loopback more or less. Need a switch for windows loopback, too.
|
76
84
|
dst_host = "127.0.0.1"
|
77
85
|
else
|
78
86
|
dst_host = (args[:target] || IPAddr.new((rand(16777216) + 2969567232), Socket::AF_INET).to_s)
|
@@ -80,8 +88,11 @@ module PacketFu
|
|
80
88
|
|
81
89
|
dst_port = rand(0xffff-1024)+1024
|
82
90
|
msg = "PacketFu whoami? packet #{(Time.now.to_i + rand(0xffffff)+1)}"
|
83
|
-
|
84
|
-
|
91
|
+
iface = (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || :lo ).to_s
|
92
|
+
cap = PacketFu::Capture.new(:iface => iface, :promisc => false, :start => true, :filter => "udp and dst host #{dst_host} and dst port #{dst_port}")
|
93
|
+
udp_sock = UDPSocket.new
|
94
|
+
udp_sock.send(msg,0,dst_host,dst_port)
|
95
|
+
udp_sock = nil
|
85
96
|
cap.save
|
86
97
|
pkt = Packet.parse(cap.array[0]) unless cap.save.zero?
|
87
98
|
timeout = 0
|
@@ -90,12 +101,13 @@ module PacketFu
|
|
90
101
|
timeout = 1.1 # Cancel the timeout
|
91
102
|
if pkt.payload == msg
|
92
103
|
my_data = {
|
93
|
-
:iface => args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo",
|
104
|
+
:iface => (args[:iface] || ENV['IFACE'] || Pcap.lookupdev || "lo").to_s,
|
94
105
|
:pcapfile => args[:pcapfile] || "/tmp/out.pcap",
|
95
106
|
:eth_saddr => pkt.eth_saddr,
|
96
107
|
:eth_src => pkt.eth_src.to_s,
|
97
108
|
:ip_saddr => pkt.ip_saddr,
|
98
|
-
:ip_src => pkt.ip_src
|
109
|
+
:ip_src => pkt.ip_src,
|
110
|
+
:ip_src_bin => [pkt.ip_src].pack("N"),
|
99
111
|
:eth_dst => pkt.eth_dst.to_s,
|
100
112
|
:eth_daddr => pkt.eth_daddr
|
101
113
|
}
|
@@ -107,7 +119,7 @@ module PacketFu
|
|
107
119
|
cap.save
|
108
120
|
pkt = Packet.parse(cap.array[0]) unless cap.save.zero?
|
109
121
|
end
|
110
|
-
raise SocketError, "Didn't receive the whomi() packet." if !pkt
|
122
|
+
raise SocketError, "Didn't receive the whomi() packet, can't automatically configure." if !pkt
|
111
123
|
cap = nil
|
112
124
|
end
|
113
125
|
my_data
|
@@ -161,7 +173,7 @@ module PacketFu
|
|
161
173
|
end
|
162
174
|
ifconfig_data.each do |s|
|
163
175
|
case s
|
164
|
-
when /inet addr:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9])(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]))?/i
|
176
|
+
when /inet addr:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i
|
165
177
|
ret[:ip_saddr] = $1
|
166
178
|
ret[:ip_src] = [IPAddr.new($1).to_i].pack("N")
|
167
179
|
ret[:ip4_obj] = IPAddr.new($1)
|
data/lib/packetfu/version.rb
CHANGED
data/test/all_tests.rb
CHANGED
@@ -0,0 +1,74 @@
|
|
1
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib")
|
2
|
+
require 'packetfu'
|
3
|
+
|
4
|
+
include PacketFu
|
5
|
+
|
6
|
+
describe EthPacket, "when read from a pcap file" do
|
7
|
+
|
8
|
+
before :all do
|
9
|
+
parsed_packets = PcapFile.read_packets(File.join(".","sample.pcap"))
|
10
|
+
@eth_packet = parsed_packets.first
|
11
|
+
end
|
12
|
+
|
13
|
+
context "is a regular ethernet packet" do
|
14
|
+
|
15
|
+
subject { @eth_packet }
|
16
|
+
|
17
|
+
it "should be an EthPacket kind of packet" do
|
18
|
+
subject.should be_kind_of EthPacket
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should have a dest mac address" do
|
22
|
+
subject.eth_daddr.should == "00:03:2f:1a:74:de"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should have a source mac address" do
|
26
|
+
subject.eth_saddr.should == "00:1b:11:51:b7:ce"
|
27
|
+
end
|
28
|
+
|
29
|
+
its(:size) { should == 78 }
|
30
|
+
|
31
|
+
it "should have a payload in its first header" do
|
32
|
+
subject.headers.first.body.should_not be_nil
|
33
|
+
end
|
34
|
+
|
35
|
+
context "an EthPacket's first header" do
|
36
|
+
|
37
|
+
subject { @eth_packet.headers.first }
|
38
|
+
|
39
|
+
it "should be 64 bytes" do
|
40
|
+
subject.body.sz.should == 64
|
41
|
+
end
|
42
|
+
|
43
|
+
context "EthHeader struct members" do
|
44
|
+
if RUBY_VERSION =~ /^1\.8/
|
45
|
+
its(:members) { should include :eth_dst.to_s }
|
46
|
+
its(:members) { should include :eth_src.to_s }
|
47
|
+
its(:members) { should include :eth_proto.to_s }
|
48
|
+
its(:members) { should include :body.to_s }
|
49
|
+
else
|
50
|
+
its(:members) { should include :eth_dst }
|
51
|
+
its(:members) { should include :eth_src }
|
52
|
+
its(:members) { should include :eth_proto }
|
53
|
+
its(:members) { should include :body }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "isn't a regular Ethernet packet" do
|
62
|
+
|
63
|
+
subject {
|
64
|
+
parsed_packets = PcapFile.read_packets(File.join(".","vlan-pcapr.cap"))
|
65
|
+
parsed_packets.first
|
66
|
+
}
|
67
|
+
|
68
|
+
it "should not be an EthPacket" do
|
69
|
+
subject.should_not be_kind_of EthPacket
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
data/test/icmp_test.pcap
CHANGED
Binary file
|
data/test/ip_test.pcap
CHANGED
Binary file
|
data/test/packet_spec.rb
CHANGED
data/test/packetfu_spec.rb
CHANGED
data/test/structfu_spec.rb
CHANGED
data/test/tcp_spec.rb
CHANGED
data/test/tcp_test.pcap
CHANGED
Binary file
|
data/test/test_arp.rb
CHANGED
data/test/test_eth.rb
CHANGED
data/test/test_hsrp.rb
CHANGED
data/test/test_icmp.rb
CHANGED
data/test/test_inject.rb
CHANGED
@@ -1,11 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
$:.unshift File.expand_path(File.dirname(__FILE__) + "/../lib/")
|
3
|
-
|
4
2
|
require 'test/unit'
|
5
|
-
|
6
|
-
# Needed if you're using the gem version of pcaprub. Obviated in 1.9.
|
3
|
+
$:.unshift File.join(File.expand_path(File.dirname(__FILE__)), "..", "lib")
|
7
4
|
require 'packetfu'
|
8
5
|
|
6
|
+
|
9
7
|
class InjectTest < Test::Unit::TestCase
|
10
8
|
|
11
9
|
def test_cap
|
data/test/test_invalid.rb
CHANGED
data/test/test_ip.rb
CHANGED
data/test/test_ip6.rb
CHANGED
data/test/test_octets.rb
CHANGED
data/test/test_packet.rb
CHANGED
data/test/test_pcap.rb
CHANGED
data/test/test_structfu.rb
CHANGED
data/test/test_tcp.rb
CHANGED
data/test/test_udp.rb
CHANGED
data/test/udp_test.pcap
CHANGED
Binary file
|
data/test/vlan-pcapr.cap
ADDED
Binary file
|
metadata
CHANGED
@@ -1,14 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: packetfu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
- 5
|
10
|
-
- pre
|
11
|
-
version: 1.0.5.pre
|
4
|
+
prerelease:
|
5
|
+
version: 1.1.0
|
12
6
|
platform: ruby
|
13
7
|
authors:
|
14
8
|
- Tod Beardsley
|
@@ -16,7 +10,7 @@ autorequire:
|
|
16
10
|
bindir: bin
|
17
11
|
cert_chain: []
|
18
12
|
|
19
|
-
date: 2011-06-
|
13
|
+
date: 2011-06-11 00:00:00 -05:00
|
20
14
|
default_executable:
|
21
15
|
dependencies:
|
22
16
|
- !ruby/object:Gem::Dependency
|
@@ -27,11 +21,6 @@ dependencies:
|
|
27
21
|
requirements:
|
28
22
|
- - ">="
|
29
23
|
- !ruby/object:Gem::Version
|
30
|
-
hash: 63
|
31
|
-
segments:
|
32
|
-
- 0
|
33
|
-
- 9
|
34
|
-
- 2
|
35
24
|
version: 0.9.2
|
36
25
|
type: :development
|
37
26
|
version_requirements: *id001
|
@@ -43,7 +32,6 @@ extensions: []
|
|
43
32
|
|
44
33
|
extra_rdoc_files:
|
45
34
|
- README
|
46
|
-
- INSTALL
|
47
35
|
- .document
|
48
36
|
files:
|
49
37
|
- lib/packetfu.rb
|
@@ -89,7 +77,9 @@ files:
|
|
89
77
|
- test/arp_test.pcap
|
90
78
|
- test/test_inject.rb
|
91
79
|
- test/test_eth.rb
|
80
|
+
- test/ethpacket_spec.rb
|
92
81
|
- test/packet_spec.rb
|
82
|
+
- test/vlan-pcapr.cap
|
93
83
|
- test/sample-ipv6.pcap
|
94
84
|
- test/test_hsrp.rb
|
95
85
|
- test/test_structfu.rb
|
@@ -105,6 +95,7 @@ files:
|
|
105
95
|
- examples/examples.rb
|
106
96
|
- examples/simple-stats.rb
|
107
97
|
- examples/arphood.rb
|
98
|
+
- examples/simple-sniffer.rb
|
108
99
|
- examples/ethernet.rb
|
109
100
|
- examples/arp.rb
|
110
101
|
- examples/slammer.rb
|
@@ -113,8 +104,9 @@ files:
|
|
113
104
|
- examples/ackscan.rb
|
114
105
|
- examples/ids.rb
|
115
106
|
- examples/new-simple-stats.rb
|
107
|
+
- examples/100kpackets.rb
|
116
108
|
has_rdoc: true
|
117
|
-
homepage:
|
109
|
+
homepage: https://github.com/todb/packetfu
|
118
110
|
licenses:
|
119
111
|
- BSD
|
120
112
|
post_install_message:
|
@@ -127,27 +119,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
119
|
requirements:
|
128
120
|
- - ">="
|
129
121
|
- !ruby/object:Gem::Version
|
130
|
-
hash: 3
|
131
|
-
segments:
|
132
|
-
- 0
|
133
122
|
version: "0"
|
134
123
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
124
|
none: false
|
136
125
|
requirements:
|
137
|
-
- - "
|
126
|
+
- - ">="
|
138
127
|
- !ruby/object:Gem::Version
|
139
|
-
|
140
|
-
segments:
|
141
|
-
- 1
|
142
|
-
- 3
|
143
|
-
- 1
|
144
|
-
version: 1.3.1
|
128
|
+
version: "0"
|
145
129
|
requirements:
|
146
130
|
- sdoc, for generating local documentation
|
147
131
|
- rspec, v2.6.2 or later, for testing
|
148
132
|
- pcaprub v0.9.2 or later, for packet capture/inject
|
149
133
|
rubyforge_project: packetfu
|
150
|
-
rubygems_version: 1.
|
134
|
+
rubygems_version: 1.6.2
|
151
135
|
signing_key:
|
152
136
|
specification_version: 3
|
153
137
|
summary: PacketFu is a mid-level packet manipulation library.
|