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,206 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# HSRPHeader is a complete HSRP struct, used in HSRPPacket. HSRP is typically used for
|
4
|
+
# fault-tolerant default gateway in IP routing environment.
|
5
|
+
#
|
6
|
+
# For more on HSRP packets, see http://www.networksorcery.com/enp/protocol/hsrp.htm
|
7
|
+
#
|
8
|
+
# Submitted by fropert@packetfault.org. Thanks, Francois!
|
9
|
+
#
|
10
|
+
# ==== Header Definition
|
11
|
+
#
|
12
|
+
# Int8 :hsrp_version Default: 0 # Version
|
13
|
+
# Int8 :hsrp_opcode # Opcode
|
14
|
+
# Int8 :hsrp_state # State
|
15
|
+
# Int8 :hsrp_hellotime Default: 3 # Hello Time
|
16
|
+
# Int8 :hsrp_holdtime Default: 10 # Hold Time
|
17
|
+
# Int8 :hsrp_priority # Priority
|
18
|
+
# Int8 :hsrp_group # Group
|
19
|
+
# Int8 :hsrp_reserved Default: 0 # Reserved
|
20
|
+
# String :hsrp_password # Authentication Data
|
21
|
+
# Octets :hsrp_vip # Virtual IP Address
|
22
|
+
# String :body
|
23
|
+
class HSRPHeader < Struct.new(:hsrp_version, :hsrp_opcode, :hsrp_state,
|
24
|
+
:hsrp_hellotime, :hsrp_holdtime,
|
25
|
+
:hsrp_priority, :hsrp_group,
|
26
|
+
:hsrp_reserved, :hsrp_password,
|
27
|
+
:hsrp_vip, :body)
|
28
|
+
|
29
|
+
include StructFu
|
30
|
+
|
31
|
+
def initialize(args={})
|
32
|
+
super(
|
33
|
+
Int8.new(args[:hsrp_version] || 0),
|
34
|
+
Int8.new(args[:hsrp_opcode]),
|
35
|
+
Int8.new(args[:hsrp_state]),
|
36
|
+
Int8.new(args[:hsrp_hellotime] || 3),
|
37
|
+
Int8.new(args[:hsrp_holdtime] || 10),
|
38
|
+
Int8.new(args[:hsrp_priority]),
|
39
|
+
Int8.new(args[:hsrp_group]),
|
40
|
+
Int8.new(args[:hsrp_reserved] || 0),
|
41
|
+
StructFu::String.new.read(args[:hsrp_password] || "cisco\x00\x00\x00"),
|
42
|
+
Octets.new.read(args[:hsrp_vip] || ("\x00" * 4)),
|
43
|
+
StructFu::String.new.read(args[:body])
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns the object in string form.
|
48
|
+
def to_s
|
49
|
+
self.to_a.map {|x| x.to_s}.join
|
50
|
+
end
|
51
|
+
|
52
|
+
# Reads a string to populate the object.
|
53
|
+
def read(str)
|
54
|
+
force_binary(str)
|
55
|
+
return self if str.nil?
|
56
|
+
self[:hsrp_version].read(str[0,1])
|
57
|
+
self[:hsrp_opcode].read(str[1,1])
|
58
|
+
self[:hsrp_state].read(str[2,1])
|
59
|
+
self[:hsrp_hellotime].read(str[3,1])
|
60
|
+
self[:hsrp_holdtime].read(str[4,1])
|
61
|
+
self[:hsrp_priority].read(str[5,1])
|
62
|
+
self[:hsrp_group].read(str[6,1])
|
63
|
+
self[:hsrp_reserved].read(str[7,1])
|
64
|
+
self[:hsrp_password].read(str[8,8])
|
65
|
+
self[:hsrp_vip].read(str[16,4])
|
66
|
+
self[:body].read(str[20,str.size]) if str.size > 20
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Setter for the type.
|
71
|
+
def hsrp_version=(i); typecast i; end
|
72
|
+
# Getter for the type.
|
73
|
+
def hsrp_version; self[:hsrp_version].to_i; end
|
74
|
+
# Setter for the type.
|
75
|
+
def hsrp_opcode=(i); typecast i; end
|
76
|
+
# Getter for the type.
|
77
|
+
def hsrp_opcode; self[:hsrp_opcode].to_i; end
|
78
|
+
# Setter for the type.
|
79
|
+
def hsrp_state=(i); typecast i; end
|
80
|
+
# Getter for the type.
|
81
|
+
def hsrp_state; self[:hsrp_state].to_i; end
|
82
|
+
# Setter for the type.
|
83
|
+
def hsrp_hellotime=(i); typecast i; end
|
84
|
+
# Getter for the type.
|
85
|
+
def hsrp_hellotime; self[:hsrp_hellotime].to_i; end
|
86
|
+
# Setter for the type.
|
87
|
+
def hsrp_holdtime=(i); typecast i; end
|
88
|
+
# Getter for the type.
|
89
|
+
def hsrp_holdtime; self[:hsrp_holdtime].to_i; end
|
90
|
+
# Setter for the type.
|
91
|
+
def hsrp_priority=(i); typecast i; end
|
92
|
+
# Getter for the type.
|
93
|
+
def hsrp_priority; self[:hsrp_priority].to_i; end
|
94
|
+
# Setter for the type.
|
95
|
+
def hsrp_group=(i); typecast i; end
|
96
|
+
# Getter for the type.
|
97
|
+
def hsrp_group; self[:hsrp_group].to_i; end
|
98
|
+
# Setter for the type.
|
99
|
+
def hsrp_reserved=(i); typecast i; end
|
100
|
+
# Getter for the type.
|
101
|
+
def hsrp_reserved; self[:hsrp_reserved].to_i; end
|
102
|
+
|
103
|
+
def hsrp_addr=(addr)
|
104
|
+
self[:hsrp_vip].read_quad(addr)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns a more readable IP source address.
|
108
|
+
def hsrp_addr
|
109
|
+
self[:hsrp_vip].to_x
|
110
|
+
end
|
111
|
+
|
112
|
+
# Readability aliases
|
113
|
+
|
114
|
+
alias :hsrp_vip_readable :hsrp_addr
|
115
|
+
|
116
|
+
def hsrp_password_readable
|
117
|
+
hsrp_password.to_s.inspect
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
# HSRPPacket is used to construct HSRP Packets. They contain an EthHeader, an IPHeader, and a UDPHeader.
|
123
|
+
#
|
124
|
+
# == Example
|
125
|
+
#
|
126
|
+
# hsrp_pkt.new
|
127
|
+
# hsrp_pkt.hsrp_opcode = 0
|
128
|
+
# hsrp_pkt.hsrp_state = 16
|
129
|
+
# hsrp_pkt.hsrp_priority = 254
|
130
|
+
# hsrp_pkt.hsrp_group = 1
|
131
|
+
# hsrp_pkt.hsrp_vip = 10.100.100.254
|
132
|
+
# hsrp_pkt.recalc
|
133
|
+
# hsrp_pkt.to_f('/tmp/hsrp.pcap')
|
134
|
+
#
|
135
|
+
# == Parameters
|
136
|
+
#
|
137
|
+
# :eth
|
138
|
+
# A pre-generated EthHeader object.
|
139
|
+
# :ip
|
140
|
+
# A pre-generated IPHeader object.
|
141
|
+
# :udp
|
142
|
+
# A pre-generated UDPHeader object.
|
143
|
+
# :flavor
|
144
|
+
# TODO: HSRP packets don't tend have any flavor.
|
145
|
+
# :config
|
146
|
+
# A hash of return address details, often the output of Utils.whoami?
|
147
|
+
class HSRPPacket < Packet
|
148
|
+
|
149
|
+
attr_accessor :eth_header, :ip_header, :udp_header, :hsrp_header
|
150
|
+
|
151
|
+
def self.can_parse?(str)
|
152
|
+
return false unless str.size >= 54
|
153
|
+
return false unless EthPacket.can_parse? str
|
154
|
+
return false unless IPPacket.can_parse? str
|
155
|
+
return false unless UDPPacket.can_parse? str
|
156
|
+
temp_packet = UDPPacket.new
|
157
|
+
temp_packet.read(str)
|
158
|
+
if temp_packet.ip_ttl == 1 and [temp_packet.udp_sport,temp_packet.udp_dport] == [1985,1985]
|
159
|
+
return true
|
160
|
+
else
|
161
|
+
return false
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def read(str=nil, args={})
|
166
|
+
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
167
|
+
@eth_header.read(str)
|
168
|
+
@ip_header.read(str[14,str.size])
|
169
|
+
@eth_header.body = @ip_header
|
170
|
+
@udp_header.read(str[14+(@ip_header.ip_hlen),str.size])
|
171
|
+
@ip_header.body = @udp_header
|
172
|
+
@hsrp_header.read(str[14+(@ip_header.ip_hlen)+8,str.size])
|
173
|
+
@udp_header.body = @hsrp_header
|
174
|
+
super(args)
|
175
|
+
self
|
176
|
+
end
|
177
|
+
|
178
|
+
def initialize(args={})
|
179
|
+
@eth_header = EthHeader.new(args).read(args[:eth])
|
180
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
181
|
+
@ip_header.ip_proto = 0x11
|
182
|
+
@udp_header = UDPHeader.new(args).read(args[:udp])
|
183
|
+
@hsrp_header = HSRPHeader.new(args).read(args[:hsrp])
|
184
|
+
@udp_header.body = @hsrp_header
|
185
|
+
@ip_header.body = @udp_header
|
186
|
+
@eth_header.body = @ip_header
|
187
|
+
@headers = [@eth_header, @ip_header, @udp_header, @hsrp_header]
|
188
|
+
super
|
189
|
+
end
|
190
|
+
|
191
|
+
# Peek provides summary data on packet contents.
|
192
|
+
def peek_format
|
193
|
+
peek_data = ["UH "]
|
194
|
+
peek_data << "%-5d" % self.to_s.size
|
195
|
+
peek_data << "%-16s" % self.hsrp_addr
|
196
|
+
peek_data << "%-4d" % self.hsrp_group
|
197
|
+
peek_data << "%-35s" % self.hsrp_password_readable
|
198
|
+
peek_data << "%-15s" % self.ip_saddr
|
199
|
+
peek_data.join
|
200
|
+
end
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
@@ -0,0 +1,179 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# ICMPHeader is a complete ICMP struct, used in ICMPPacket. ICMP is
|
4
|
+
# typically used for network administration and connectivity testing.
|
5
|
+
#
|
6
|
+
# For more on ICMP packets, see
|
7
|
+
# http://www.networksorcery.com/enp/protocol/icmp.htm
|
8
|
+
#
|
9
|
+
# ==== Header Definition
|
10
|
+
#
|
11
|
+
# Int8 :icmp_type # Type
|
12
|
+
# Int8 :icmp_code # Code
|
13
|
+
# Int16 :icmp_sum Default: calculated # Checksum
|
14
|
+
# String :body
|
15
|
+
class ICMPHeader < Struct.new(:icmp_type, :icmp_code, :icmp_sum, :body)
|
16
|
+
|
17
|
+
include StructFu
|
18
|
+
|
19
|
+
def initialize(args={})
|
20
|
+
super(
|
21
|
+
Int8.new(args[:icmp_type]),
|
22
|
+
Int8.new(args[:icmp_code]),
|
23
|
+
Int16.new(args[:icmp_sum] || icmp_calc_sum),
|
24
|
+
StructFu::String.new.read(args[:body])
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the object in string form.
|
29
|
+
def to_s
|
30
|
+
self.to_a.map {|x| x.to_s}.join
|
31
|
+
end
|
32
|
+
|
33
|
+
# Reads a string to populate the object.
|
34
|
+
def read(str)
|
35
|
+
force_binary(str)
|
36
|
+
return self if str.nil?
|
37
|
+
self[:icmp_type].read(str[0,1])
|
38
|
+
self[:icmp_code].read(str[1,1])
|
39
|
+
self[:icmp_sum].read(str[2,2])
|
40
|
+
self[:body].read(str[4,str.size])
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Setter for the type.
|
45
|
+
def icmp_type=(i); typecast i; end
|
46
|
+
# Getter for the type.
|
47
|
+
def icmp_type; self[:icmp_type].to_i; end
|
48
|
+
# Setter for the code.
|
49
|
+
def icmp_code=(i); typecast i; end
|
50
|
+
# Getter for the code.
|
51
|
+
def icmp_code; self[:icmp_code].to_i; end
|
52
|
+
# Setter for the checksum. Note, this is calculated automatically with
|
53
|
+
# icmp_calc_sum.
|
54
|
+
def icmp_sum=(i); typecast i; end
|
55
|
+
# Getter for the checksum.
|
56
|
+
def icmp_sum; self[:icmp_sum].to_i; end
|
57
|
+
|
58
|
+
# Calculates and sets the checksum for the object.
|
59
|
+
def icmp_calc_sum
|
60
|
+
checksum = (icmp_type.to_i << 8) + icmp_code.to_i
|
61
|
+
chk_body = (body.to_s.size % 2 == 0 ? body.to_s : body.to_s + "\x00")
|
62
|
+
if 1.respond_to? :ord
|
63
|
+
chk_body.scan(/../).map { |x| (x[0].ord << 8) + x[1].ord }.each { |y| checksum += y }
|
64
|
+
else
|
65
|
+
chk_body.scan(/../).map { |x| (x[0] << 8) + x[1] }.each { |y| checksum += y }
|
66
|
+
end
|
67
|
+
checksum = checksum % 0xffff
|
68
|
+
checksum = 0xffff - checksum
|
69
|
+
checksum == 0 ? 0xffff : checksum
|
70
|
+
end
|
71
|
+
|
72
|
+
# Recalculates the calculatable fields for ICMP.
|
73
|
+
def icmp_recalc(arg=:all)
|
74
|
+
# How silly is this, you can't intern a symbol in ruby 1.8.7pl72?
|
75
|
+
# I'm this close to monkey patching Symbol so you can force it...
|
76
|
+
arg = arg.intern if arg.respond_to? :intern
|
77
|
+
case arg
|
78
|
+
when :icmp_sum
|
79
|
+
self.icmp_sum=icmp_calc_sum
|
80
|
+
when :all
|
81
|
+
self.icmp_sum=icmp_calc_sum
|
82
|
+
else
|
83
|
+
raise ArgumentError, "No such field `#{arg}'"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# Readability aliases
|
88
|
+
|
89
|
+
def icmp_sum_readable
|
90
|
+
"0x%04x" % icmp_sum
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
# ICMPPacket is used to construct ICMP Packets. They contain an EthHeader, an IPHeader, and a ICMPHeader.
|
96
|
+
#
|
97
|
+
# == Example
|
98
|
+
#
|
99
|
+
# icmp_pkt.new
|
100
|
+
# icmp_pkt.icmp_type = 8
|
101
|
+
# icmp_pkt.icmp_code = 0
|
102
|
+
# icmp_pkt.payload = "ABC, easy as 123. As simple as do-re-mi. ABC, 123, baby, you and me!"
|
103
|
+
#
|
104
|
+
# icmp_pkt.ip_saddr="1.2.3.4"
|
105
|
+
# icmp_pkt.ip_daddr="5.6.7.8"
|
106
|
+
#
|
107
|
+
# icmp_pkt.recalc
|
108
|
+
# icmp_pkt.to_f('/tmp/icmp.pcap')
|
109
|
+
#
|
110
|
+
# == Parameters
|
111
|
+
#
|
112
|
+
# :eth
|
113
|
+
# A pre-generated EthHeader object.
|
114
|
+
# :ip
|
115
|
+
# A pre-generated IPHeader object.
|
116
|
+
# :flavor
|
117
|
+
# TODO: Sets the "flavor" of the ICMP packet. Pings, in particular, often betray their true
|
118
|
+
# OS.
|
119
|
+
# :config
|
120
|
+
# A hash of return address details, often the output of Utils.whoami?
|
121
|
+
class ICMPPacket < Packet
|
122
|
+
|
123
|
+
attr_accessor :eth_header, :ip_header, :icmp_header
|
124
|
+
|
125
|
+
def self.can_parse?(str)
|
126
|
+
return false unless str.size >= 54
|
127
|
+
return false unless EthPacket.can_parse? str
|
128
|
+
return false unless IPPacket.can_parse? str
|
129
|
+
return false unless str[23,1] == "\x01"
|
130
|
+
return true
|
131
|
+
end
|
132
|
+
|
133
|
+
def read(str=nil, args={})
|
134
|
+
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
135
|
+
@eth_header.read(str)
|
136
|
+
@ip_header.read(str[14,str.size])
|
137
|
+
@eth_header.body = @ip_header
|
138
|
+
@icmp_header.read(str[14+(@ip_header.ip_hlen),str.size])
|
139
|
+
@ip_header.body = @icmp_header
|
140
|
+
super(args)
|
141
|
+
self
|
142
|
+
end
|
143
|
+
|
144
|
+
def initialize(args={})
|
145
|
+
@eth_header = EthHeader.new(args).read(args[:eth])
|
146
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
147
|
+
@ip_header.ip_proto = 1
|
148
|
+
@icmp_header = ICMPHeader.new(args).read(args[:icmp])
|
149
|
+
|
150
|
+
@ip_header.body = @icmp_header
|
151
|
+
@eth_header.body = @ip_header
|
152
|
+
|
153
|
+
@headers = [@eth_header, @ip_header, @icmp_header]
|
154
|
+
super
|
155
|
+
end
|
156
|
+
|
157
|
+
# Peek provides summary data on packet contents.
|
158
|
+
def peek_format
|
159
|
+
peek_data = ["IC "] # I is taken by IP
|
160
|
+
peek_data << "%-5d" % self.to_s.size
|
161
|
+
type = case self.icmp_type.to_i
|
162
|
+
when 8
|
163
|
+
"ping"
|
164
|
+
when 0
|
165
|
+
"pong"
|
166
|
+
else
|
167
|
+
"%02x-%02x" % [self.icmp_type, self.icmp_code]
|
168
|
+
end
|
169
|
+
peek_data << "%-21s" % "#{self.ip_saddr}:#{type}"
|
170
|
+
peek_data << "->"
|
171
|
+
peek_data << "%21s" % "#{self.ip_daddr}"
|
172
|
+
peek_data << "%23s" % "I:"
|
173
|
+
peek_data << "%04x" % self.ip_id
|
174
|
+
peek_data.join
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# InvalidHeader catches all packets that we don't already have a Struct for,
|
4
|
+
# or for whatever reason, violates some basic packet rules for other packet
|
5
|
+
# types.
|
6
|
+
class InvalidHeader < Struct.new(:body)
|
7
|
+
include StructFu
|
8
|
+
|
9
|
+
def initialize(args={})
|
10
|
+
args[:body] ||= StructFu::String.new
|
11
|
+
super(args[:body])
|
12
|
+
end
|
13
|
+
|
14
|
+
# Returns the object in string form.
|
15
|
+
def to_s
|
16
|
+
self.to_a.map {|x| x.to_s}.join
|
17
|
+
end
|
18
|
+
|
19
|
+
# Reads a string to populate the object.
|
20
|
+
def read(str)
|
21
|
+
force_binary(str)
|
22
|
+
return self if str.nil?
|
23
|
+
self[:body].read str
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
# You probably don't want to write invalid packets on purpose.
|
30
|
+
class InvalidPacket < Packet
|
31
|
+
attr_accessor :invalid_header
|
32
|
+
|
33
|
+
# Any packet is potentially an invalid packet
|
34
|
+
def self.can_parse?(str)
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.layer
|
39
|
+
0
|
40
|
+
end
|
41
|
+
|
42
|
+
def read(str=nil,args={})
|
43
|
+
@invalid_header.read(str)
|
44
|
+
self
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(args={})
|
48
|
+
@invalid_header = (args[:invalid] || InvalidHeader.new)
|
49
|
+
@headers = [@invalid_header]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end # module PacketFu
|
54
|
+
|
55
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
@@ -0,0 +1,378 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
module PacketFu
|
3
|
+
|
4
|
+
# Octets implements the addressing scheme for IP.
|
5
|
+
#
|
6
|
+
# ==== Header Definition
|
7
|
+
#
|
8
|
+
# Int8 :o1
|
9
|
+
# Int8 :o2
|
10
|
+
# Int8 :o3
|
11
|
+
# Int8 :o4
|
12
|
+
class Octets < Struct.new(:o1, :o2, :o3, :o4)
|
13
|
+
include StructFu
|
14
|
+
|
15
|
+
def initialize(args={})
|
16
|
+
super(
|
17
|
+
Int8.new(args[:o1]),
|
18
|
+
Int8.new(args[:o2]),
|
19
|
+
Int8.new(args[:o3]),
|
20
|
+
Int8.new(args[:o4]))
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the object in string form.
|
24
|
+
def to_s
|
25
|
+
self.to_a.map {|x| x.to_s}.join
|
26
|
+
end
|
27
|
+
|
28
|
+
# Reads a string to populate the object.
|
29
|
+
def read(str)
|
30
|
+
force_binary(str)
|
31
|
+
return self if str.nil?
|
32
|
+
self[:o1].read str[0,1]
|
33
|
+
self[:o2].read str[1,1]
|
34
|
+
self[:o3].read str[2,1]
|
35
|
+
self[:o4].read str[3,1]
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an address in dotted-quad format.
|
40
|
+
def to_x
|
41
|
+
ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
|
42
|
+
IPAddr.new(ip_str).to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# Returns an address in numerical format.
|
46
|
+
def to_i
|
47
|
+
ip_str = [o1, o2, o3, o4].map {|x| x.to_i.to_s}.join('.')
|
48
|
+
IPAddr.new(ip_str).to_i
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set the IP Address by reading a dotted-quad address.
|
52
|
+
def read_quad(str)
|
53
|
+
read([IPAddr.new(str).to_i].pack("N"))
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
# IPHeader is a complete IP struct, used in IPPacket. Most traffic on most networks today is IP-based.
|
59
|
+
#
|
60
|
+
# For more on IP packets, see http://www.networksorcery.com/enp/protocol/ip.htm
|
61
|
+
#
|
62
|
+
# ==== Header Definition
|
63
|
+
#
|
64
|
+
# Fixnum (4 bits) :ip_v, Default: 4
|
65
|
+
# Fixnum (4 bits) :ip_hl, Default: 5
|
66
|
+
# Int8 :ip_tos, Default: 0 # TODO: Break out the bits
|
67
|
+
# Int16 :ip_len, Default: calculated
|
68
|
+
# Int16 :ip_id, Default: calculated # IRL, hardly random.
|
69
|
+
# Int16 :ip_frag, Default: 0 # TODO: Break out the bits
|
70
|
+
# Int8 :ip_ttl, Default: 0xff # Changes per flavor
|
71
|
+
# Int8 :ip_proto, Default: 0x01 # TCP: 0x06, UDP 0x11, ICMP 0x01
|
72
|
+
# Int16 :ip_sum, Default: calculated
|
73
|
+
# Octets :ip_src
|
74
|
+
# Octets :ip_dst
|
75
|
+
# String :body
|
76
|
+
#
|
77
|
+
# Note that IPPackets will always be somewhat incorrect upon initalization,
|
78
|
+
# and want an IPHeader#recalc() to become correct before a
|
79
|
+
# Packet#to_f or Packet#to_w.
|
80
|
+
class IPHeader < Struct.new(:ip_v, :ip_hl, :ip_tos, :ip_len,
|
81
|
+
:ip_id, :ip_frag, :ip_ttl, :ip_proto,
|
82
|
+
:ip_sum, :ip_src, :ip_dst, :body)
|
83
|
+
include StructFu
|
84
|
+
|
85
|
+
def initialize(args={})
|
86
|
+
@random_id = rand(0xffff)
|
87
|
+
super(
|
88
|
+
(args[:ip_v] || 4),
|
89
|
+
(args[:ip_hl] || 5),
|
90
|
+
Int8.new(args[:ip_tos]),
|
91
|
+
Int16.new(args[:ip_len] || 20),
|
92
|
+
Int16.new(args[:ip_id] || ip_calc_id),
|
93
|
+
Int16.new(args[:ip_frag]),
|
94
|
+
Int8.new(args[:ip_ttl] || 32),
|
95
|
+
Int8.new(args[:ip_proto]),
|
96
|
+
Int16.new(args[:ip_sum] || ip_calc_sum),
|
97
|
+
Octets.new.read(args[:ip_src] || "\x00\x00\x00\x00"),
|
98
|
+
Octets.new.read(args[:ip_dst] || "\x00\x00\x00\x00"),
|
99
|
+
StructFu::String.new.read(args[:body])
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns the object in string form.
|
104
|
+
def to_s
|
105
|
+
byte_v_hl = [(self.ip_v << 4) + self.ip_hl].pack("C")
|
106
|
+
byte_v_hl + (self.to_a[2,10].map {|x| x.to_s}.join)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Reads a string to populate the object.
|
110
|
+
def read(str)
|
111
|
+
force_binary(str)
|
112
|
+
return self if str.nil?
|
113
|
+
self[:ip_v] = str[0,1].unpack("C").first >> 4
|
114
|
+
self[:ip_hl] = str[0,1].unpack("C").first.to_i & 0x0f
|
115
|
+
self[:ip_tos].read(str[1,1])
|
116
|
+
self[:ip_len].read(str[2,2])
|
117
|
+
self[:ip_id].read(str[4,2])
|
118
|
+
self[:ip_frag].read(str[6,2])
|
119
|
+
self[:ip_ttl].read(str[8,1])
|
120
|
+
self[:ip_proto].read(str[9,1])
|
121
|
+
self[:ip_sum].read(str[10,2])
|
122
|
+
self[:ip_src].read(str[12,4])
|
123
|
+
self[:ip_dst].read(str[16,4])
|
124
|
+
self[:body].read(str[20,str.size]) if str.size > 20
|
125
|
+
self
|
126
|
+
end
|
127
|
+
|
128
|
+
# Setter for the version.
|
129
|
+
def ip_v=(i); self[:ip_v] = i.to_i; end
|
130
|
+
# Getter for the version.
|
131
|
+
def ip_v; self[:ip_v].to_i; end
|
132
|
+
# Setter for the header length (divide by 4)
|
133
|
+
def ip_hl=(i); self[:ip_hl] = i.to_i; end
|
134
|
+
# Getter for the header length (multiply by 4)
|
135
|
+
def ip_hl; self[:ip_hl].to_i; end
|
136
|
+
# Setter for the differentiated services
|
137
|
+
def ip_tos=(i); typecast i; end
|
138
|
+
# Getter for the differentiated services
|
139
|
+
def ip_tos; self[:ip_tos].to_i; end
|
140
|
+
# Setter for total length.
|
141
|
+
def ip_len=(i); typecast i; end
|
142
|
+
# Getter for total length.
|
143
|
+
def ip_len; self[:ip_len].to_i; end
|
144
|
+
# Setter for the identication number.
|
145
|
+
def ip_id=(i); typecast i; end
|
146
|
+
# Getter for the identication number.
|
147
|
+
def ip_id; self[:ip_id].to_i; end
|
148
|
+
# Setter for the fragmentation ID.
|
149
|
+
def ip_frag=(i); typecast i; end
|
150
|
+
# Getter for the fragmentation ID.
|
151
|
+
def ip_frag; self[:ip_frag].to_i; end
|
152
|
+
# Setter for the time to live.
|
153
|
+
def ip_ttl=(i); typecast i; end
|
154
|
+
# Getter for the time to live.
|
155
|
+
def ip_ttl; self[:ip_ttl].to_i; end
|
156
|
+
# Setter for the protocol number.
|
157
|
+
def ip_proto=(i); typecast i; end
|
158
|
+
# Getter for the protocol number.
|
159
|
+
def ip_proto; self[:ip_proto].to_i; end
|
160
|
+
# Setter for the checksum.
|
161
|
+
def ip_sum=(i); typecast i; end
|
162
|
+
# Getter for the checksum.
|
163
|
+
def ip_sum; self[:ip_sum].to_i; end
|
164
|
+
# Setter for the source IP address.
|
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
|
175
|
+
# Getter for the source IP address.
|
176
|
+
def ip_src; self[:ip_src].to_i; end
|
177
|
+
# Setter for the destination IP address.
|
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
|
188
|
+
# Getter for the destination IP address.
|
189
|
+
def ip_dst; self[:ip_dst].to_i; end
|
190
|
+
|
191
|
+
# Calulcate the true length of the packet.
|
192
|
+
def ip_calc_len
|
193
|
+
(ip_hl * 4) + body.to_s.length
|
194
|
+
end
|
195
|
+
|
196
|
+
# Return the claimed header length
|
197
|
+
def ip_hlen
|
198
|
+
(ip_hl * 4)
|
199
|
+
end
|
200
|
+
|
201
|
+
# Calculate the true checksum of the packet.
|
202
|
+
# (Yes, this is the long way to do it, but it's e-z-2-read for mathtards like me.)
|
203
|
+
def ip_calc_sum
|
204
|
+
checksum = (((self.ip_v << 4) + self.ip_hl) << 8) + self.ip_tos
|
205
|
+
checksum += self.ip_len
|
206
|
+
checksum += self.ip_id
|
207
|
+
checksum += self.ip_frag
|
208
|
+
checksum += (self.ip_ttl << 8) + self.ip_proto
|
209
|
+
checksum += (self.ip_src >> 16)
|
210
|
+
checksum += (self.ip_src & 0xffff)
|
211
|
+
checksum += (self.ip_dst >> 16)
|
212
|
+
checksum += (self.ip_dst & 0xffff)
|
213
|
+
checksum = checksum % 0xffff
|
214
|
+
checksum = 0xffff - checksum
|
215
|
+
checksum == 0 ? 0xffff : checksum
|
216
|
+
end
|
217
|
+
|
218
|
+
# Retrieve the IP ID
|
219
|
+
def ip_calc_id
|
220
|
+
@random_id
|
221
|
+
end
|
222
|
+
|
223
|
+
# Sets a more readable IP address. If you wants to manipulate individual octets,
|
224
|
+
# (eg, for host scanning in one network), it would be better use ip_src.o1 through
|
225
|
+
# ip_src.o4 instead.
|
226
|
+
def ip_saddr=(addr)
|
227
|
+
self[:ip_src].read_quad(addr)
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns a more readable IP source address.
|
231
|
+
def ip_saddr
|
232
|
+
self[:ip_src].to_x
|
233
|
+
end
|
234
|
+
|
235
|
+
# Sets a more readable IP address.
|
236
|
+
def ip_daddr=(addr)
|
237
|
+
self[:ip_dst].read_quad(addr)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Returns a more readable IP destination address.
|
241
|
+
def ip_daddr
|
242
|
+
self[:ip_dst].to_x
|
243
|
+
end
|
244
|
+
|
245
|
+
# Translate various formats of IPv4 Addresses to an array of digits.
|
246
|
+
def self.octet_array(addr)
|
247
|
+
if addr.class == String
|
248
|
+
oa = addr.split('.').collect {|x| x.to_i}
|
249
|
+
elsif addr.class == Fixnum
|
250
|
+
oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
|
251
|
+
elsif addr.class == Bignum
|
252
|
+
oa = IPAddr.new(addr, Socket::AF_INET).to_s.split('.')
|
253
|
+
elsif addr.class == Array
|
254
|
+
oa = addr
|
255
|
+
else
|
256
|
+
raise ArgumentError, "IP Address should be a dotted quad string, an array of ints, or a bignum"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Recalculate the calculated IP fields. Valid arguments are:
|
261
|
+
# :all
|
262
|
+
# :ip_len
|
263
|
+
# :ip_sum
|
264
|
+
# :ip_id
|
265
|
+
def ip_recalc(arg=:all)
|
266
|
+
case arg
|
267
|
+
when :ip_len
|
268
|
+
self.ip_len=ip_calc_len
|
269
|
+
when :ip_sum
|
270
|
+
self.ip_sum=ip_calc_sum
|
271
|
+
when :ip_id
|
272
|
+
@random_id = rand(0xffff)
|
273
|
+
when :all
|
274
|
+
self.ip_id= ip_calc_id
|
275
|
+
self.ip_len= ip_calc_len
|
276
|
+
self.ip_sum= ip_calc_sum
|
277
|
+
else
|
278
|
+
raise ArgumentError, "No such field `#{arg}'"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Readability aliases
|
283
|
+
|
284
|
+
alias :ip_src_readable :ip_saddr
|
285
|
+
alias :ip_dst_readable :ip_daddr
|
286
|
+
|
287
|
+
def ip_id_readable
|
288
|
+
"0x%04x" % ip_id
|
289
|
+
end
|
290
|
+
|
291
|
+
def ip_sum_readable
|
292
|
+
"0x%04x" % ip_sum
|
293
|
+
end
|
294
|
+
|
295
|
+
end
|
296
|
+
|
297
|
+
# IPPacket is used to construct IP packets. They contain an EthHeader, an IPHeader, and usually
|
298
|
+
# a transport-layer protocol such as UDPHeader, TCPHeader, or ICMPHeader.
|
299
|
+
#
|
300
|
+
# == Example
|
301
|
+
#
|
302
|
+
# require 'packetfu'
|
303
|
+
# ip_pkt = PacketFu::IPPacket.new
|
304
|
+
# ip_pkt.ip_saddr="10.20.30.40"
|
305
|
+
# ip_pkt.ip_daddr="192.168.1.1"
|
306
|
+
# ip_pkt.ip_proto=1
|
307
|
+
# ip_pkt.ip_ttl=64
|
308
|
+
# ip_pkt.ip_payload="\x00\x00\x12\x34\x00\x01\x00\x01"+
|
309
|
+
# "Lovingly hand-crafted echo responses delivered directly to your door."
|
310
|
+
# ip_pkt.recalc
|
311
|
+
# ip_pkt.to_f('/tmp/ip.pcap')
|
312
|
+
#
|
313
|
+
# == Parameters
|
314
|
+
#
|
315
|
+
# :eth
|
316
|
+
# A pre-generated EthHeader object.
|
317
|
+
# :ip
|
318
|
+
# A pre-generated IPHeader object.
|
319
|
+
# :flavor
|
320
|
+
# TODO: Sets the "flavor" of the IP packet. This might include known sets of IP options, and
|
321
|
+
# certainly known starting TTLs.
|
322
|
+
# :config
|
323
|
+
# A hash of return address details, often the output of Utils.whoami?
|
324
|
+
class IPPacket < Packet
|
325
|
+
|
326
|
+
attr_accessor :eth_header, :ip_header
|
327
|
+
|
328
|
+
def self.can_parse?(str)
|
329
|
+
return false unless str.size >= 34
|
330
|
+
return false unless EthPacket.can_parse? str
|
331
|
+
if str[12,2] == "\x08\x00"
|
332
|
+
if 1.respond_to? :ord
|
333
|
+
ipv = str[14,1][0].ord >> 4
|
334
|
+
else
|
335
|
+
ipv = str[14,1][0] >> 4
|
336
|
+
end
|
337
|
+
return true if ipv == 4
|
338
|
+
else
|
339
|
+
return false
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
def read(str=nil, args={})
|
344
|
+
raise "Cannot parse `#{str}'" unless self.class.can_parse?(str)
|
345
|
+
@eth_header.read(str)
|
346
|
+
@ip_header.read(str[14,str.size])
|
347
|
+
@eth_header.body = @ip_header
|
348
|
+
super(args)
|
349
|
+
self
|
350
|
+
end
|
351
|
+
|
352
|
+
# Creates a new IPPacket object.
|
353
|
+
def initialize(args={})
|
354
|
+
@eth_header = EthHeader.new(args).read(args[:eth])
|
355
|
+
@ip_header = IPHeader.new(args).read(args[:ip])
|
356
|
+
@eth_header.body=@ip_header
|
357
|
+
|
358
|
+
@headers = [@eth_header, @ip_header]
|
359
|
+
super
|
360
|
+
end
|
361
|
+
|
362
|
+
# Peek provides summary data on packet contents.
|
363
|
+
def peek_format
|
364
|
+
peek_data = ["I "]
|
365
|
+
peek_data << "%-5d" % to_s.size
|
366
|
+
peek_data << "%-21s" % "#{ip_saddr}"
|
367
|
+
peek_data << "->"
|
368
|
+
peek_data << "%21s" % "#{ip_daddr}"
|
369
|
+
peek_data << "%23s" % "I:"
|
370
|
+
peek_data << "%04x" % ip_id.to_i
|
371
|
+
peek_data.join
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|