packetfu 1.1.2 → 1.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|