packetfu 1.1.8 → 1.1.9
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/README.rdoc +11 -12
- data/bench/octets.rb +9 -9
- data/examples/100kpackets.rb +13 -12
- data/examples/ackscan.rb +17 -16
- data/examples/arp.rb +36 -35
- data/examples/arphood.rb +37 -36
- data/examples/dissect_thinger.rb +7 -6
- data/examples/ethernet.rb +1 -0
- data/examples/examples.rb +1 -0
- data/examples/ifconfig.rb +1 -0
- data/examples/new-simple-stats.rb +24 -23
- data/examples/packetfu-shell.rb +26 -25
- data/examples/simple-sniffer.rb +10 -9
- data/examples/simple-stats.rb +24 -23
- data/examples/slammer.rb +4 -3
- data/lib/packetfu.rb +128 -127
- data/lib/packetfu/capture.rb +170 -169
- data/lib/packetfu/config.rb +53 -52
- data/lib/packetfu/inject.rb +57 -56
- data/lib/packetfu/packet.rb +529 -528
- data/lib/packetfu/pcap.rb +580 -579
- data/lib/packetfu/protos/arp.rb +91 -90
- data/lib/packetfu/protos/arp/header.rb +159 -158
- data/lib/packetfu/protos/arp/mixin.rb +37 -36
- data/lib/packetfu/protos/eth.rb +45 -44
- data/lib/packetfu/protos/eth/header.rb +244 -243
- data/lib/packetfu/protos/eth/mixin.rb +4 -3
- data/lib/packetfu/protos/hsrp.rb +70 -69
- data/lib/packetfu/protos/hsrp/header.rb +108 -107
- data/lib/packetfu/protos/hsrp/mixin.rb +30 -29
- data/lib/packetfu/protos/icmp.rb +72 -71
- data/lib/packetfu/protos/icmp/header.rb +83 -82
- data/lib/packetfu/protos/icmp/mixin.rb +15 -14
- data/lib/packetfu/protos/invalid.rb +50 -49
- data/lib/packetfu/protos/ip.rb +70 -69
- data/lib/packetfu/protos/ip/header.rb +292 -291
- data/lib/packetfu/protos/ip/mixin.rb +41 -40
- data/lib/packetfu/protos/ipv6.rb +51 -50
- data/lib/packetfu/protos/ipv6/header.rb +189 -188
- data/lib/packetfu/protos/ipv6/mixin.rb +30 -29
- data/lib/packetfu/protos/lldp.rb +3 -1
- data/lib/packetfu/protos/lldp/header.rb +1 -0
- data/lib/packetfu/protos/lldp/mixin.rb +1 -0
- data/lib/packetfu/protos/tcp.rb +177 -176
- data/lib/packetfu/protos/tcp/ecn.rb +36 -35
- data/lib/packetfu/protos/tcp/flags.rb +75 -74
- data/lib/packetfu/protos/tcp/header.rb +269 -268
- data/lib/packetfu/protos/tcp/hlen.rb +33 -32
- data/lib/packetfu/protos/tcp/mixin.rb +47 -46
- data/lib/packetfu/protos/tcp/option.rb +322 -321
- data/lib/packetfu/protos/tcp/options.rb +96 -95
- data/lib/packetfu/protos/tcp/reserved.rb +36 -35
- data/lib/packetfu/protos/udp.rb +117 -116
- data/lib/packetfu/protos/udp/header.rb +92 -91
- data/lib/packetfu/protos/udp/mixin.rb +4 -3
- data/lib/packetfu/structfu.rb +281 -280
- data/lib/packetfu/utils.rb +211 -208
- data/lib/packetfu/version.rb +42 -41
- data/packetfu.gemspec +1 -1
- data/spec/ethpacket_spec.rb +48 -48
- data/spec/packet_spec.rb +57 -57
- data/spec/packet_subclasses_spec.rb +8 -8
- data/spec/packetfu_spec.rb +59 -59
- data/spec/structfu_spec.rb +268 -268
- data/spec/tcp_spec.rb +75 -75
- data/test/all_tests.rb +13 -13
- data/test/func_lldp.rb +3 -3
- data/test/ptest.rb +2 -2
- data/test/test_arp.rb +116 -116
- data/test/test_capture.rb +45 -45
- data/test/test_eth.rb +68 -68
- data/test/test_hsrp.rb +9 -9
- data/test/test_icmp.rb +52 -52
- data/test/test_inject.rb +18 -18
- data/test/test_invalid.rb +16 -16
- data/test/test_ip.rb +36 -36
- data/test/test_ip6.rb +48 -48
- data/test/test_octets.rb +21 -21
- data/test/test_packet.rb +154 -154
- data/test/test_pcap.rb +170 -170
- data/test/test_structfu.rb +97 -97
- data/test/test_tcp.rb +320 -320
- data/test/test_udp.rb +76 -76
- metadata +2 -2
@@ -1,106 +1,107 @@
|
|
1
|
+
# -*- coding: binary -*-
|
1
2
|
require 'packetfu/protos/tcp/option'
|
2
3
|
|
3
4
|
module PacketFu
|
4
5
|
|
5
|
-
|
6
|
+
class TcpOptions < Array
|
6
7
|
|
7
|
-
|
8
|
+
include StructFu
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
# If args[:pad] is set, the options line is automatically padded out
|
11
|
+
# with NOPs.
|
12
|
+
def to_s(args={})
|
13
|
+
opts = self.map {|x| x.to_s}.join
|
14
|
+
if args[:pad]
|
15
|
+
unless (opts.size % 4).zero?
|
16
|
+
(4 - (opts.size % 4)).times { opts << "\x01" }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
opts
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
22
|
+
# Reads a string to populate the object.
|
23
|
+
def read(str)
|
24
|
+
self.clear
|
25
|
+
PacketFu.force_binary(str)
|
26
|
+
return self if(!str.respond_to? :to_s || str.nil?)
|
27
|
+
i = 0
|
28
|
+
while i < str.to_s.size
|
29
|
+
this_opt = case str[i,1].unpack("C").first
|
30
|
+
when 0; ::PacketFu::TcpOption::EOL.new
|
31
|
+
when 1; ::PacketFu::TcpOption::NOP.new
|
32
|
+
when 2; ::PacketFu::TcpOption::MSS.new
|
33
|
+
when 3; ::PacketFu::TcpOption::WS.new
|
34
|
+
when 4; ::PacketFu::TcpOption::SACKOK.new
|
35
|
+
when 5; ::PacketFu::TcpOption::SACK.new
|
36
|
+
when 6; ::PacketFu::TcpOption::ECHO.new
|
37
|
+
when 7; ::PacketFu::TcpOption::ECHOREPLY.new
|
38
|
+
when 8; ::PacketFu::TcpOption::TS.new
|
39
|
+
else; ::PacketFu::TcpOption.new
|
40
|
+
end
|
41
|
+
this_opt.read str[i,str.size]
|
42
|
+
unless this_opt.has_optlen?
|
43
|
+
this_opt.value = nil
|
44
|
+
this_opt.optlen = nil
|
45
|
+
end
|
46
|
+
self << this_opt
|
47
|
+
i += this_opt.sz
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
52
|
+
# Decode parses the TcpOptions object's member options, and produces a
|
53
|
+
# human-readable string by iterating over each element's decode() function.
|
54
|
+
# If TcpOptions elements were not initially created as TcpOptions, an
|
55
|
+
# attempt will be made to convert them.
|
56
|
+
#
|
57
|
+
# The output of decode is suitable as input for TcpOptions#encode.
|
58
|
+
def decode
|
59
|
+
decoded = self.map do |x|
|
60
|
+
if x.kind_of? TcpOption
|
61
|
+
x.decode
|
62
|
+
else
|
63
|
+
x = TcpOptions.new.read(x).decode
|
64
|
+
end
|
65
|
+
end
|
66
|
+
decoded.join(",")
|
67
|
+
end
|
67
68
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
69
|
+
# Encode takes a human-readable string and appends the corresponding
|
70
|
+
# binary options to the TcpOptions object. To completely replace the contents
|
71
|
+
# of the object, use TcpOptions#encode! instead.
|
72
|
+
#
|
73
|
+
# Options are comma-delimited, and are identical to the output of the
|
74
|
+
# TcpOptions#decode function. Note that the syntax can be unforgiving, so
|
75
|
+
# it may be easier to create the subclassed TcpOptions themselves directly,
|
76
|
+
# but this method can be less typing if you know what you're doing.
|
77
|
+
#
|
78
|
+
# Note that by using TcpOptions#encode, strings supplied as values which
|
79
|
+
# can be converted to numbers will be converted first.
|
80
|
+
#
|
81
|
+
# === Example
|
82
|
+
#
|
83
|
+
# t = TcpOptions.new
|
84
|
+
# t.encode("MS:1460,WS:6")
|
85
|
+
# t.to_s # => "\002\004\005\264\002\003\006"
|
86
|
+
# t.encode("NOP")
|
87
|
+
# t.to_s # => "\002\004\005\264\002\003\006\001"
|
88
|
+
def encode(str)
|
89
|
+
opts = str.split(/[\s]*,[\s]*/)
|
90
|
+
opts.each do |o|
|
91
|
+
kind,value = o.split(/[\s]*:[\s]*/)
|
92
|
+
klass = TcpOption.const_get(kind.upcase)
|
93
|
+
value = value.to_i if value =~ /^[0-9]+$/
|
94
|
+
this_opt = klass.new
|
95
|
+
this_opt.encode(value)
|
96
|
+
self << this_opt
|
97
|
+
end
|
98
|
+
self
|
99
|
+
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
# Like TcpOption#encode, except the entire contents are replaced.
|
102
|
+
def encode!(str)
|
103
|
+
self.clear if self.size > 0
|
104
|
+
encode(str)
|
105
|
+
end
|
106
|
+
end
|
106
107
|
end
|
@@ -1,42 +1,43 @@
|
|
1
|
+
# -*- coding: binary -*-
|
1
2
|
module PacketFu
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
3
|
+
# Implements the Reserved bits for TCPHeader.
|
4
|
+
#
|
5
|
+
# ==== Header Definition
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# Fixnum (1 bit) :r1
|
9
|
+
# Fixnum (1 bit) :r2
|
10
|
+
# Fixnum (1 bit) :r3
|
11
|
+
class TcpReserved < Struct.new(:r1, :r2, :r3)
|
11
12
|
|
12
|
-
|
13
|
+
include StructFu
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
def initialize(args={})
|
16
|
+
super(
|
17
|
+
args[:r1] || 0,
|
18
|
+
args[:r2] || 0,
|
19
|
+
args[:r3] || 0) if args.kind_of? Hash
|
20
|
+
end
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
# Returns the Reserved field as an integer.
|
23
|
+
def to_i
|
24
|
+
(r1.to_i << 2) + (r2.to_i << 1) + r3.to_i
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
27
|
+
# Reads a string to populate the object.
|
28
|
+
def read(str)
|
29
|
+
force_binary(str)
|
30
|
+
return self if str.nil? || str.size.zero?
|
31
|
+
if 1.respond_to? :ord
|
32
|
+
byte = str[0].ord
|
33
|
+
else
|
34
|
+
byte = str[0]
|
35
|
+
end
|
36
|
+
self[:r1] = byte & 0b00000100 == 0b00000100 ? 1 : 0
|
37
|
+
self[:r2] = byte & 0b00000010 == 0b00000010 ? 1 : 0
|
38
|
+
self[:r3] = byte & 0b00000001 == 0b00000001 ? 1 : 0
|
39
|
+
self
|
40
|
+
end
|
40
41
|
|
41
|
-
|
42
|
+
end
|
42
43
|
end
|
data/lib/packetfu/protos/udp.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
# -*- coding: binary -*-
|
1
2
|
require 'packetfu/protos/eth/header'
|
2
3
|
require 'packetfu/protos/eth/mixin'
|
3
4
|
|
@@ -9,133 +10,133 @@ require 'packetfu/protos/udp/mixin'
|
|
9
10
|
|
10
11
|
module PacketFu
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
13
|
+
# UDPPacket is used to construct UDP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
|
14
|
+
#
|
15
|
+
# == Example
|
16
|
+
#
|
17
|
+
# udp_pkt = PacketFu::UDPPacket.new
|
18
|
+
# udp_pkt.udp_src=rand(0xffff-1024) + 1024
|
19
|
+
# udp_pkt.udp_dst=53
|
20
|
+
#
|
21
|
+
# udp_pkt.ip_saddr="1.2.3.4"
|
22
|
+
# udp_pkt.ip_daddr="10.20.30.40"
|
23
|
+
#
|
24
|
+
# udp_pkt.recalc
|
25
|
+
# udp_pkt.to_f('/tmp/udp.pcap')
|
26
|
+
#
|
27
|
+
# == Parameters
|
28
|
+
#
|
29
|
+
# :eth
|
30
|
+
# A pre-generated EthHeader object.
|
31
|
+
# :ip
|
32
|
+
# A pre-generated IPHeader object.
|
33
|
+
# :flavor
|
34
|
+
# TODO: Sets the "flavor" of the UDP packet. UDP packets don't tend have a lot of
|
35
|
+
# flavor, but their underlying ip headers do.
|
36
|
+
# :config
|
37
|
+
# A hash of return address details, often the output of Utils.whoami?
|
38
|
+
class UDPPacket < Packet
|
38
39
|
include ::PacketFu::EthHeaderMixin
|
39
40
|
include ::PacketFu::IPHeaderMixin
|
40
41
|
include ::PacketFu::UDPHeaderMixin
|
41
42
|
|
42
|
-
|
43
|
+
attr_accessor :eth_header, :ip_header, :udp_header
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
def self.can_parse?(str)
|
46
|
+
return false unless str.size >= 28
|
47
|
+
return false unless EthPacket.can_parse? str
|
48
|
+
return false unless IPPacket.can_parse? str
|
49
|
+
return false unless str[23,1] == "\x11"
|
50
|
+
return true
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
53
|
+
def read(str=nil, args={})
|
54
|
+
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
55
|
+
@eth_header.read(str)
|
56
|
+
if args[:strip]
|
57
|
+
udp_body_len = self.ip_len - self.ip_hlen - 8
|
58
|
+
@udp_header.body.read(@udp_header.body.to_s[0,udp_body_len])
|
59
|
+
end
|
60
|
+
super(args)
|
61
|
+
self
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
64
|
+
def initialize(args={})
|
65
|
+
@eth_header = EthHeader.new(args).read(args[:eth])
|
66
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
67
|
+
@ip_header.ip_proto=0x11
|
68
|
+
@udp_header = UDPHeader.new(args).read(args[:icmp])
|
69
|
+
@ip_header.body = @udp_header
|
70
|
+
@eth_header.body = @ip_header
|
71
|
+
@headers = [@eth_header, @ip_header, @udp_header]
|
72
|
+
super
|
73
|
+
udp_calc_sum
|
74
|
+
end
|
74
75
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
76
|
+
# udp_calc_sum() computes the UDP checksum, and is called upon intialization.
|
77
|
+
# It usually should be called just prior to dropping packets to a file or on the wire.
|
78
|
+
def udp_calc_sum
|
79
|
+
# This is /not/ delegated down to @udp_header since we need info
|
80
|
+
# from the IP header, too.
|
81
|
+
checksum = (ip_src.to_i >> 16)
|
82
|
+
checksum += (ip_src.to_i & 0xffff)
|
83
|
+
checksum += (ip_dst.to_i >> 16)
|
84
|
+
checksum += (ip_dst.to_i & 0xffff)
|
85
|
+
checksum += 0x11
|
86
|
+
checksum += udp_len.to_i
|
87
|
+
checksum += udp_src.to_i
|
88
|
+
checksum += udp_dst.to_i
|
89
|
+
checksum += udp_len.to_i
|
90
|
+
if udp_len.to_i >= 8
|
91
|
+
# For IP trailers. This isn't very reliable. :/
|
92
|
+
real_udp_payload = payload.to_s[0,(udp_len.to_i-8)]
|
93
|
+
else
|
94
|
+
# I'm not going to mess with this right now.
|
95
|
+
real_udp_payload = payload
|
96
|
+
end
|
97
|
+
chk_payload = (real_udp_payload.size % 2 == 0 ? real_udp_payload : real_udp_payload + "\x00")
|
98
|
+
chk_payload.unpack("n*").each {|x| checksum = checksum+x}
|
99
|
+
checksum = checksum % 0xffff
|
100
|
+
checksum = 0xffff - checksum
|
101
|
+
checksum == 0 ? 0xffff : checksum
|
102
|
+
@udp_header.udp_sum = checksum
|
103
|
+
end
|
103
104
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
105
|
+
# udp_recalc() recalculates various fields of the UDP packet. Valid arguments are:
|
106
|
+
#
|
107
|
+
# :all
|
108
|
+
# Recomputes all calculated fields.
|
109
|
+
# :udp_sum
|
110
|
+
# Recomputes the UDP checksum.
|
111
|
+
# :udp_len
|
112
|
+
# Recomputes the UDP length.
|
113
|
+
def udp_recalc(args=:all)
|
114
|
+
case args
|
115
|
+
when :udp_len
|
116
|
+
@udp_header.udp_recalc
|
117
|
+
when :udp_sum
|
118
|
+
udp_calc_sum
|
119
|
+
when :all
|
120
|
+
@udp_header.udp_recalc
|
121
|
+
udp_calc_sum
|
122
|
+
else
|
123
|
+
raise ArgumentError, "No such field `#{arg}'"
|
124
|
+
end
|
125
|
+
end
|
125
126
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
127
|
+
# Peek provides summary data on packet contents.
|
128
|
+
def peek_format
|
129
|
+
peek_data = ["U "]
|
130
|
+
peek_data << "%-5d" % self.to_s.size
|
131
|
+
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.udp_sport}"
|
132
|
+
peek_data << "->"
|
133
|
+
peek_data << "%21s" % "#{self.ip_daddr}:#{self.udp_dport}"
|
134
|
+
peek_data << "%23s" % "I:"
|
135
|
+
peek_data << "%04x" % self.ip_id
|
136
|
+
peek_data.join
|
137
|
+
end
|
137
138
|
|
138
|
-
|
139
|
+
end
|
139
140
|
|
140
141
|
end
|
141
142
|
|