packetfu 1.0.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/.document +4 -0
- data/CHANGES +36 -0
- data/INSTALL +40 -0
- data/LICENSE +28 -0
- data/README +25 -0
- data/TODO +25 -0
- data/examples/ackscan.rb +38 -0
- data/examples/arp.rb +60 -0
- data/examples/arphood.rb +56 -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/oui.txt +84177 -0
- data/examples/packetfu-shell.rb +111 -0
- data/examples/simple-stats.rb +42 -0
- data/examples/slammer.rb +33 -0
- data/examples/uniqpcap.rb +15 -0
- data/lib/packetfu.rb +108 -0
- data/lib/packetfu/arp.rb +239 -0
- data/lib/packetfu/capture.rb +169 -0
- data/lib/packetfu/config.rb +55 -0
- data/lib/packetfu/eth.rb +264 -0
- data/lib/packetfu/icmp.rb +153 -0
- data/lib/packetfu/inject.rb +65 -0
- data/lib/packetfu/invalid.rb +41 -0
- data/lib/packetfu/ip.rb +318 -0
- data/lib/packetfu/ipv6.rb +230 -0
- data/lib/packetfu/packet.rb +492 -0
- data/lib/packetfu/pcap.rb +502 -0
- data/lib/packetfu/structfu.rb +274 -0
- data/lib/packetfu/tcp.rb +1061 -0
- data/lib/packetfu/udp.rb +210 -0
- data/lib/packetfu/utils.rb +182 -0
- data/test/all_tests.rb +37 -0
- data/test/ptest.rb +10 -0
- data/test/sample.pcap +0 -0
- data/test/sample2.pcap +0 -0
- data/test/test_arp.rb +135 -0
- data/test/test_eth.rb +90 -0
- data/test/test_icmp.rb +54 -0
- data/test/test_inject.rb +33 -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 +41 -0
- data/test/test_pcap.rb +210 -0
- data/test/test_structfu.rb +112 -0
- data/test/test_tcp.rb +327 -0
- data/test/test_udp.rb +73 -0
- metadata +144 -0
@@ -0,0 +1,274 @@
|
|
1
|
+
# StructFu, a nifty way to leverage Ruby's built in Struct class
|
2
|
+
# to create meaningful binary data.
|
3
|
+
|
4
|
+
module StructFu
|
5
|
+
# Normally, self.size and self.length will refer to the Struct
|
6
|
+
# size as an array. It's a hassle to redefine, so this introduces some
|
7
|
+
# shorthand to get at the size of the resultant string.
|
8
|
+
def sz
|
9
|
+
self.to_s.size
|
10
|
+
end
|
11
|
+
|
12
|
+
alias len sz
|
13
|
+
|
14
|
+
# Typecast is used mostly by packet header classes, such as IPHeader,
|
15
|
+
# TCPHeader, and the like. It takes an argument, and casts it to the
|
16
|
+
# expected type for that element.
|
17
|
+
def typecast(i)
|
18
|
+
c = caller[0].match(/.*`([^']+)='/)[1]
|
19
|
+
self[c.intern].read i
|
20
|
+
end
|
21
|
+
|
22
|
+
# Used like typecast(), but specifically for casting Strings to StructFu::Strings.
|
23
|
+
def body=(i)
|
24
|
+
if i.kind_of? ::String
|
25
|
+
typecast(i)
|
26
|
+
elsif i.kind_of? StructFu
|
27
|
+
self[:body] = i
|
28
|
+
elsif i.nil?
|
29
|
+
self[:body] = StructFu::String.new.read("")
|
30
|
+
else
|
31
|
+
raise ArgumentError, "Can't cram a #{i.class} into a StructFu :body"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Ints all have a value, an endianness, and a default value.
|
36
|
+
# Note that the signedness of Int values are implicit as
|
37
|
+
# far as the subclasses are concerned; to_i and to_f will
|
38
|
+
# return Integer/Float versions of the input value, instead
|
39
|
+
# of attempting to unpack the pack value. (This can be a useful
|
40
|
+
# hint to other functions).
|
41
|
+
#
|
42
|
+
# ==== Header Definition
|
43
|
+
#
|
44
|
+
# Fixnum :value
|
45
|
+
# Symbol :endian
|
46
|
+
# Fixnum :width
|
47
|
+
# Fixnum :default
|
48
|
+
class Int < Struct.new(:value, :endian, :width, :default)
|
49
|
+
alias :v= :value=
|
50
|
+
alias :v :value
|
51
|
+
alias :e= :endian=
|
52
|
+
alias :e :endian
|
53
|
+
alias :w= :width=
|
54
|
+
alias :w :width
|
55
|
+
alias :d= :default=
|
56
|
+
alias :d :default
|
57
|
+
|
58
|
+
# This is a parent class definition and should not be used directly.
|
59
|
+
def to_s
|
60
|
+
raise StandardError, "StructFu::Int#to_s accessed, must be redefined."
|
61
|
+
end
|
62
|
+
|
63
|
+
# Returns the Int as an Integer.
|
64
|
+
def to_i
|
65
|
+
(self.v || self.d).to_i
|
66
|
+
end
|
67
|
+
|
68
|
+
# Returns the Int as a Float.
|
69
|
+
def to_f
|
70
|
+
(self.v || self.d).to_f
|
71
|
+
end
|
72
|
+
|
73
|
+
def initialize(value=nil, endian=nil, width=nil, default=nil)
|
74
|
+
super(value,endian,width,default=0)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Reads either an Integer or a packed string, and populates the value accordingly.
|
78
|
+
def read(i)
|
79
|
+
self.v = i.kind_of?(Integer) ? i.to_i : i.to_s.unpack(@packstr).first
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
# Int8 is a one byte value.
|
86
|
+
class Int8 < Int
|
87
|
+
|
88
|
+
def initialize(v=nil)
|
89
|
+
super(v,nil,w=1)
|
90
|
+
@packstr = "C"
|
91
|
+
end
|
92
|
+
|
93
|
+
# Returns a one byte value as a packed string.
|
94
|
+
def to_s
|
95
|
+
[(self.v || self.d)].pack("C")
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
# Int16 is a two byte value.
|
101
|
+
class Int16 < Int
|
102
|
+
def initialize(v=nil, e=:big)
|
103
|
+
super(v,e,w=2)
|
104
|
+
@packstr = (self.e == :big) ? "n" : "v"
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns a two byte value as a packed string.
|
108
|
+
def to_s
|
109
|
+
[(self.v || self.d)].pack(@packstr)
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# Int16be is a two byte value in big-endian format.
|
115
|
+
class Int16be < Int16
|
116
|
+
end
|
117
|
+
|
118
|
+
# Int16le is a two byte value in little-endian format.
|
119
|
+
class Int16le < Int16
|
120
|
+
def initialize(v=nil, e=:little)
|
121
|
+
super(v,e)
|
122
|
+
@packstr = (self.e == :big) ? "n" : "v"
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Int32 is a four byte value.
|
127
|
+
class Int32 < Int
|
128
|
+
def initialize(v=nil, e=:big)
|
129
|
+
super(v,e,w=4)
|
130
|
+
@packstr = (self.e == :big) ? "N" : "V"
|
131
|
+
end
|
132
|
+
|
133
|
+
# Returns a four byte value as a packed string.
|
134
|
+
def to_s
|
135
|
+
[(self.v || self.d)].pack(@packstr)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
# Int32be is a four byte value in big-endian format.
|
141
|
+
class Int32be < Int32
|
142
|
+
end
|
143
|
+
|
144
|
+
# Int32le is a four byte value in little-endian format.
|
145
|
+
class Int32le < Int32
|
146
|
+
def initialize(v=nil, e=:little)
|
147
|
+
super(v,e)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Strings are just like regular strings, except it comes with a read() function
|
152
|
+
# so that it behaves like other StructFu elements.
|
153
|
+
class String < ::String
|
154
|
+
def read(str)
|
155
|
+
str = str.to_s
|
156
|
+
self.replace str
|
157
|
+
self
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Provides a primitive for creating strings, preceeded by
|
162
|
+
# an Int type of length. By default, a string of length zero with
|
163
|
+
# a one-byte length is presumed.
|
164
|
+
#
|
165
|
+
# Note that IntStrings aren't used for much, but it seemed like a good idea at the time.
|
166
|
+
class IntString < Struct.new(:int, :string, :mode)
|
167
|
+
|
168
|
+
def initialize(string='',int=Int8,mode=nil)
|
169
|
+
unless int.respond_to?(:ancestors) && int.ancestors.include?(StructFu::Int)
|
170
|
+
raise StandardError, "Invalid length (#{int.inspect}) associated with this String."
|
171
|
+
else
|
172
|
+
super(int.new,string,mode)
|
173
|
+
calc
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Calculates the size of a string, and sets it as the value.
|
178
|
+
def calc
|
179
|
+
int.v = string.to_s.size
|
180
|
+
self.to_s
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns the object as a string, depending on the mode set upon object creation.
|
184
|
+
def to_s
|
185
|
+
if mode == :parse
|
186
|
+
"#{int}" + [string].pack("a#{len}")
|
187
|
+
elsif mode == :fix
|
188
|
+
self.int.v = string.size
|
189
|
+
"#{int}#{string}"
|
190
|
+
else
|
191
|
+
"#{int}#{string}"
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# By redefining #string=, we can ensure the correct value
|
196
|
+
# is calculated upon assignment. If you'd prefer to have
|
197
|
+
# an incorrect value, use the syntax, obj[:string]="value"
|
198
|
+
# instead. Note, by using the alternate form, you must
|
199
|
+
# #calc before you can trust the int's value. Think of the
|
200
|
+
# = assignment as "set to equal," while the []= assignment
|
201
|
+
# as "boxing in" the value. Maybe.
|
202
|
+
def string=(s)
|
203
|
+
self[:string] = s
|
204
|
+
calc
|
205
|
+
end
|
206
|
+
|
207
|
+
# Shorthand for querying a length. Note that the usual "length"
|
208
|
+
# and "size" refer to the number of elements of this struct.
|
209
|
+
def len
|
210
|
+
self[:int].value
|
211
|
+
end
|
212
|
+
|
213
|
+
# Override the size, if you must.
|
214
|
+
def len=(i)
|
215
|
+
self[:int].value=i
|
216
|
+
end
|
217
|
+
|
218
|
+
# Read takes a string, assumes an int width as previously
|
219
|
+
# defined upon initialization, but makes no guarantees
|
220
|
+
# the int value isn't lying. You're on your own to test
|
221
|
+
# for that (or use parse() with a :mode set).
|
222
|
+
def read(s)
|
223
|
+
unless s[0,int.width].size == int.width
|
224
|
+
raise StandardError, "String is too short for type #{int.class}"
|
225
|
+
else
|
226
|
+
int.read(s[0,int.width])
|
227
|
+
self[:string] = s[int.width,s.size]
|
228
|
+
end
|
229
|
+
self.to_s
|
230
|
+
end
|
231
|
+
|
232
|
+
# parse() is like read(), except that it interprets the string, either
|
233
|
+
# based on the declared length, or the actual length. Which strategy
|
234
|
+
# is used is dependant on which :mode is set (with self.mode).
|
235
|
+
#
|
236
|
+
# :parse : Read the length, and then read in that many bytes of the string.
|
237
|
+
# The string may be truncated or padded out with nulls, as dictated by the value.
|
238
|
+
# :fix : Skip the length, read the rest of the string, then set the length to what it ought to be.
|
239
|
+
# else : If neither of these modes are set, just perfom a normal read(). This is the default.
|
240
|
+
def parse(s)
|
241
|
+
unless s[0,int.width].size == int.width
|
242
|
+
raise StandardError, "String is too short for type #{int.class}"
|
243
|
+
else
|
244
|
+
case mode
|
245
|
+
when :parse
|
246
|
+
int.read(s[0,int.width])
|
247
|
+
self[:string] = s[int.width,int.value]
|
248
|
+
if string.size < int.value
|
249
|
+
self[:string] += ("\x00" * (int.value - self[:string].size))
|
250
|
+
end
|
251
|
+
when :fix
|
252
|
+
self.string = s[int.width,s.size]
|
253
|
+
else
|
254
|
+
return read(s)
|
255
|
+
end
|
256
|
+
end
|
257
|
+
self.to_s
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
class Struct
|
265
|
+
|
266
|
+
# Monkeypatch for Struct to include some string safety -- anything that uses
|
267
|
+
# Struct is going to presume binary strings anyway.
|
268
|
+
def force_binary(str)
|
269
|
+
str.force_encoding "binary" if str.respond_to? :force_encoding
|
270
|
+
end
|
271
|
+
|
272
|
+
end
|
273
|
+
|
274
|
+
# vim: nowrap sw=2 sts=0 ts=2 ff=unix ft=ruby
|
data/lib/packetfu/tcp.rb
ADDED
@@ -0,0 +1,1061 @@
|
|
1
|
+
module PacketFu
|
2
|
+
|
3
|
+
# Implements the Explict Congestion Notification for TCPHeader.
|
4
|
+
#
|
5
|
+
# ==== Header Definition
|
6
|
+
#
|
7
|
+
#
|
8
|
+
# Fixnum (1 bit) :n
|
9
|
+
# Fixnum (1 bit) :c
|
10
|
+
# Fixnum (1 bit) :e
|
11
|
+
class TcpEcn < Struct.new(:n, :c, :e)
|
12
|
+
|
13
|
+
include StructFu
|
14
|
+
|
15
|
+
def initialize(args={})
|
16
|
+
super(args[:n], args[:c], args[:e]) if args
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns the TcpEcn field as an integer... even though it's going
|
20
|
+
# to be split across a byte boundary.
|
21
|
+
def to_i
|
22
|
+
(n.to_i << 2) + (c.to_i << 1) + e.to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
# Reads a string to populate the object.
|
26
|
+
def read(str)
|
27
|
+
force_binary(str)
|
28
|
+
return self if str.nil? || str.size < 2
|
29
|
+
if 1.respond_to? :ord
|
30
|
+
byte1 = str[0].ord
|
31
|
+
byte2 = str[1].ord
|
32
|
+
else
|
33
|
+
byte1 = str[0]
|
34
|
+
byte2 = str[1]
|
35
|
+
end
|
36
|
+
self[:n] = byte1 & 0b00000001 == 0b00000001 ? 1 : 0
|
37
|
+
self[:c] = byte2 & 0b10000000 == 0b10000000 ? 1 : 0
|
38
|
+
self[:e] = byte2 & 0b01000000 == 0b01000000 ? 1 : 0
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
# Implements the Header Length for TCPHeader.
|
45
|
+
#
|
46
|
+
# ==== Header Definition
|
47
|
+
#
|
48
|
+
# Fixnum (4 bits) :hlen
|
49
|
+
class TcpHlen < Struct.new(:hlen)
|
50
|
+
|
51
|
+
include StructFu
|
52
|
+
|
53
|
+
def initialize(args={})
|
54
|
+
super(args[:hlen])
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns the TcpHlen field as an integer. Note these will become the high
|
58
|
+
# bits at the TCP header's offset, even though the lower 4 bits
|
59
|
+
# will be further chopped up.
|
60
|
+
def to_i
|
61
|
+
hlen.to_i & 0b1111
|
62
|
+
end
|
63
|
+
|
64
|
+
# Reads a string to populate the object.
|
65
|
+
def read(str)
|
66
|
+
force_binary(str)
|
67
|
+
return self if str.nil? || str.size.zero?
|
68
|
+
if 1.respond_to? :ord
|
69
|
+
self[:hlen] = (str[0].ord & 0b11110000) >> 4
|
70
|
+
else
|
71
|
+
self[:hlen] = (str[0] & 0b11110000) >> 4
|
72
|
+
end
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
# Returns the object in string form.
|
77
|
+
def to_s
|
78
|
+
[self.to_i].pack("C")
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# Implements the Reserved bits for TCPHeader.
|
84
|
+
#
|
85
|
+
# ==== Header Definition
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# Fixnum (1 bit) :r1
|
89
|
+
# Fixnum (1 bit) :r2
|
90
|
+
# Fixnum (1 bit) :r3
|
91
|
+
class TcpReserved < Struct.new(:r1, :r2, :r3)
|
92
|
+
|
93
|
+
include StructFu
|
94
|
+
|
95
|
+
def initialize(args={})
|
96
|
+
super(
|
97
|
+
args[:r1] || 0,
|
98
|
+
args[:r2] || 0,
|
99
|
+
args[:r3] || 0) if args.kind_of? Hash
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns the Reserved field as an integer.
|
103
|
+
def to_i
|
104
|
+
(r1.to_i << 2) + (r2.to_i << 1) + r3.to_i
|
105
|
+
end
|
106
|
+
|
107
|
+
# Reads a string to populate the object.
|
108
|
+
def read(str)
|
109
|
+
force_binary(str)
|
110
|
+
return self if str.nil? || str.size.zero?
|
111
|
+
if 1.respond_to? :ord
|
112
|
+
byte = str[0].ord
|
113
|
+
else
|
114
|
+
byte = str[0]
|
115
|
+
end
|
116
|
+
self[:r1] = byte & 0b00000100 == 0b00000100 ? 1 : 0
|
117
|
+
self[:r2] = byte & 0b00000010 == 0b00000010 ? 1 : 0
|
118
|
+
self[:r3] = byte & 0b00000001 == 0b00000001 ? 1 : 0
|
119
|
+
self
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
# Implements flags for TCPHeader.
|
125
|
+
#
|
126
|
+
# ==== Header Definition
|
127
|
+
#
|
128
|
+
# Fixnum (1 bit) :urg
|
129
|
+
# Fixnum (1 bit) :ack
|
130
|
+
# Fixnum (1 bit) :psh
|
131
|
+
# Fixnum (1 bit) :rst
|
132
|
+
# Fixnum (1 bit) :syn
|
133
|
+
# Fixnum (1 bit) :fin
|
134
|
+
#
|
135
|
+
# Flags can typically be set by setting them either to 1 or 0, or to true or false.
|
136
|
+
class TcpFlags < Struct.new(:urg, :ack, :psh, :rst, :syn, :fin)
|
137
|
+
|
138
|
+
include StructFu
|
139
|
+
|
140
|
+
def initialize(args={})
|
141
|
+
# This technique attemts to ensure that flags are always 0 (off)
|
142
|
+
# or 1 (on). Statements like nil and false shouldn't be lurking in here.
|
143
|
+
if args.nil? || args.size.zero?
|
144
|
+
super( 0, 0, 0, 0, 0, 0)
|
145
|
+
else
|
146
|
+
super(
|
147
|
+
(args[:urg] ? 1 : 0),
|
148
|
+
(args[:ack] ? 1 : 0),
|
149
|
+
(args[:psh] ? 1 : 0),
|
150
|
+
(args[:rst] ? 1 : 0),
|
151
|
+
(args[:syn] ? 1 : 0),
|
152
|
+
(args[:fin] ? 1 : 0)
|
153
|
+
)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Returns the TcpFlags as an integer.
|
158
|
+
# Also not a great candidate for to_s due to the short bitspace.
|
159
|
+
def to_i
|
160
|
+
(urg.to_i << 5) + (ack.to_i << 4) + (psh.to_i << 3) +
|
161
|
+
(rst.to_i << 2) + (syn.to_i << 1) + fin.to_i
|
162
|
+
end
|
163
|
+
|
164
|
+
# Helper to determine if this flag is a 1 or a 0.
|
165
|
+
def zero_or_one(i=0)
|
166
|
+
if i == 0 || i == false || i == nil
|
167
|
+
0
|
168
|
+
else
|
169
|
+
1
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Setter for the Urgent flag.
|
174
|
+
def urg=(i); self[:urg] = zero_or_one(i); end
|
175
|
+
# Setter for the Acknowlege flag.
|
176
|
+
def ack=(i); self[:ack] = zero_or_one(i); end
|
177
|
+
# Setter for the Push flag.
|
178
|
+
def psh=(i); self[:psh] = zero_or_one(i); end
|
179
|
+
# Setter for the Reset flag.
|
180
|
+
def rst=(i); self[:rst] = zero_or_one(i); end
|
181
|
+
# Setter for the Synchronize flag.
|
182
|
+
def syn=(i); self[:syn] = zero_or_one(i); end
|
183
|
+
# Setter for the Finish flag.
|
184
|
+
def fin=(i); self[:fin] = zero_or_one(i); end
|
185
|
+
|
186
|
+
# Reads a string to populate the object.
|
187
|
+
def read(str)
|
188
|
+
force_binary(str)
|
189
|
+
return self if str.nil?
|
190
|
+
if 1.respond_to? :ord
|
191
|
+
byte = str[0].ord
|
192
|
+
else
|
193
|
+
byte = str[0]
|
194
|
+
end
|
195
|
+
self[:urg] = byte & 0b00100000 == 0b00100000 ? 1 : 0
|
196
|
+
self[:ack] = byte & 0b00010000 == 0b00010000 ? 1 : 0
|
197
|
+
self[:psh] = byte & 0b00001000 == 0b00001000 ? 1 : 0
|
198
|
+
self[:rst] = byte & 0b00000100 == 0b00000100 ? 1 : 0
|
199
|
+
self[:syn] = byte & 0b00000010 == 0b00000010 ? 1 : 0
|
200
|
+
self[:fin] = byte & 0b00000001 == 0b00000001 ? 1 : 0
|
201
|
+
self
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
module PacketFu
|
209
|
+
|
210
|
+
# TcpOption is the base class for all TCP options. Note that TcpOption#len
|
211
|
+
# returns the size of the entire option, while TcpOption#optlen is the struct
|
212
|
+
# for the TCP Option Length field.
|
213
|
+
#
|
214
|
+
# Subclassed options should set the correct TcpOption#kind by redefining
|
215
|
+
# initialize. They should also deal with various value types there by setting
|
216
|
+
# them explicitly with an accompanying StructFu#typecast for the setter.
|
217
|
+
#
|
218
|
+
# By default, values are presumed to be strings, unless they are Numeric, in
|
219
|
+
# which case a guess is made to the width of the Numeric based on the given
|
220
|
+
# optlen.
|
221
|
+
#
|
222
|
+
# Note that normally, optlen is /not/ enforced for directly setting values,
|
223
|
+
# so the user is perfectly capable of setting incorrect lengths.
|
224
|
+
class TcpOption < Struct.new(:kind, :optlen, :value)
|
225
|
+
|
226
|
+
include StructFu
|
227
|
+
|
228
|
+
def initialize(args={})
|
229
|
+
super(
|
230
|
+
Int8.new(args[:kind]),
|
231
|
+
Int8.new(args[:optlen])
|
232
|
+
)
|
233
|
+
if args[:value].kind_of? Numeric
|
234
|
+
self[:value] = case args[:optlen]
|
235
|
+
when 3; Int8.new(args[:value])
|
236
|
+
when 4; Int16.new(args[:value])
|
237
|
+
when 6; Int32.new(args[:value])
|
238
|
+
else; StructFu::String.new.read(args[:value])
|
239
|
+
end
|
240
|
+
else
|
241
|
+
self[:value] = StructFu::String.new.read(args[:value])
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Returns the object in string form.
|
246
|
+
def to_s
|
247
|
+
self[:kind].to_s +
|
248
|
+
(self[:optlen].value.nil? ? nil : self[:optlen]).to_s +
|
249
|
+
(self[:value].nil? ? nil : self[:value]).to_s
|
250
|
+
end
|
251
|
+
|
252
|
+
# Reads a string to populate the object.
|
253
|
+
def read(str)
|
254
|
+
force_binary(str)
|
255
|
+
return self if str.nil?
|
256
|
+
self[:kind].read(str[0,1])
|
257
|
+
if str[1,1]
|
258
|
+
self[:optlen].read(str[1,1])
|
259
|
+
if str[2,1] && optlen.value > 2
|
260
|
+
self[:value].read(str[2,optlen.value-2])
|
261
|
+
end
|
262
|
+
end
|
263
|
+
self
|
264
|
+
end
|
265
|
+
|
266
|
+
# The default decode for an unknown option. Known options should redefine this.
|
267
|
+
def decode
|
268
|
+
unk = "unk-#{self.kind.to_i}"
|
269
|
+
(self[:optlen].to_i > 2 && self[:value].to_s.size > 1) ? [unk,self[:value]].join(":") : unk
|
270
|
+
end
|
271
|
+
|
272
|
+
# Setter for the "kind" byte of this option.
|
273
|
+
def kind=(i); typecast i; end
|
274
|
+
# Setter for the "option length" byte for this option.
|
275
|
+
def optlen=(i); typecast i; end
|
276
|
+
|
277
|
+
# Setter for the value of this option.
|
278
|
+
def value=(i)
|
279
|
+
if i.kind_of? Numeric
|
280
|
+
typecast i
|
281
|
+
elsif i.respond_to? :to_s
|
282
|
+
self[:value] = i
|
283
|
+
else
|
284
|
+
self[:value] = ''
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
# Generally, encoding a value is going to be just a read. Some
|
289
|
+
# options will treat things a little differently; TS for example,
|
290
|
+
# takes two values and concatenates them.
|
291
|
+
def encode(str)
|
292
|
+
self[:value] = self.class.new(:value => str).value
|
293
|
+
end
|
294
|
+
|
295
|
+
# Returns true if this option has an optlen. Some don't.
|
296
|
+
def has_optlen?
|
297
|
+
(kind.value && kind.value < 2) ? false : true
|
298
|
+
end
|
299
|
+
|
300
|
+
# Returns true if this option has a value. Some don't.
|
301
|
+
def has_value?
|
302
|
+
(value.respond_to? :to_s && value.to_s.size > 0) ? false : true
|
303
|
+
end
|
304
|
+
|
305
|
+
# End of Line option. Usually used to terminate a string of options.
|
306
|
+
#
|
307
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option000.htm
|
308
|
+
class EOL < TcpOption
|
309
|
+
def initialize(args={})
|
310
|
+
super(
|
311
|
+
args.merge(:kind => 0)
|
312
|
+
)
|
313
|
+
end
|
314
|
+
|
315
|
+
def decode
|
316
|
+
"EOL"
|
317
|
+
end
|
318
|
+
|
319
|
+
end
|
320
|
+
|
321
|
+
# No Operation option. Usually used to pad out options to fit a 4-byte alignment.
|
322
|
+
#
|
323
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option001.htm
|
324
|
+
class NOP < TcpOption
|
325
|
+
def initialize(args={})
|
326
|
+
super(
|
327
|
+
args.merge(:kind => 1)
|
328
|
+
)
|
329
|
+
end
|
330
|
+
|
331
|
+
def decode
|
332
|
+
"NOP"
|
333
|
+
end
|
334
|
+
|
335
|
+
end
|
336
|
+
|
337
|
+
# Maximum Segment Size option.
|
338
|
+
#
|
339
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option002.htm
|
340
|
+
class MSS < TcpOption
|
341
|
+
def initialize(args={})
|
342
|
+
super(
|
343
|
+
args.merge(:kind => 2,
|
344
|
+
:optlen => 4
|
345
|
+
)
|
346
|
+
)
|
347
|
+
self[:value] = Int16.new(args[:value])
|
348
|
+
end
|
349
|
+
|
350
|
+
def value=(i); typecast i; end
|
351
|
+
|
352
|
+
# MSS options with lengths other than 4 are malformed.
|
353
|
+
def decode
|
354
|
+
if self[:optlen].to_i == 4
|
355
|
+
"MSS:#{self[:value].to_i}"
|
356
|
+
else
|
357
|
+
"MSS-bad:#{self[:value]}"
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
end
|
362
|
+
|
363
|
+
# Window Size option.
|
364
|
+
#
|
365
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option003.htm
|
366
|
+
class WS < TcpOption
|
367
|
+
def initialize(args={})
|
368
|
+
super(
|
369
|
+
args.merge(:kind => 3,
|
370
|
+
:optlen => 3
|
371
|
+
)
|
372
|
+
)
|
373
|
+
self[:value] = Int8.new(args[:value])
|
374
|
+
end
|
375
|
+
|
376
|
+
def value=(i); typecast i; end
|
377
|
+
|
378
|
+
# WS options with lengths other than 3 are malformed.
|
379
|
+
def decode
|
380
|
+
if self[:optlen].to_i == 3
|
381
|
+
"WS:#{self[:value].to_i}"
|
382
|
+
else
|
383
|
+
"WS-bad:#{self[:value]}"
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
# Selective Acknowlegment OK option.
|
390
|
+
#
|
391
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option004.htm
|
392
|
+
class SACKOK < TcpOption
|
393
|
+
def initialize(args={})
|
394
|
+
super(
|
395
|
+
args.merge(:kind => 4,
|
396
|
+
:optlen => 2)
|
397
|
+
)
|
398
|
+
end
|
399
|
+
|
400
|
+
# SACKOK options with sizes other than 2 are malformed.
|
401
|
+
def decode
|
402
|
+
if self[:optlen].to_i == 2
|
403
|
+
"SACKOK"
|
404
|
+
else
|
405
|
+
"SACKOK-bad:#{self[:value]}"
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
end
|
410
|
+
|
411
|
+
# Selective Acknowledgement option.
|
412
|
+
#
|
413
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option004.htm
|
414
|
+
#
|
415
|
+
# Note that SACK always takes its optlen from the size of the string.
|
416
|
+
class SACK < TcpOption
|
417
|
+
def initialize(args={})
|
418
|
+
super(
|
419
|
+
args.merge(:kind => 5,
|
420
|
+
:optlen => ((args[:value] || "").size + 2)
|
421
|
+
)
|
422
|
+
)
|
423
|
+
end
|
424
|
+
|
425
|
+
def optlen=(i); typecast i; end
|
426
|
+
|
427
|
+
def value=(i)
|
428
|
+
self[:optlen] = Int8.new(i.to_s.size + 2)
|
429
|
+
self[:value] = StructFu::String.new(i)
|
430
|
+
end
|
431
|
+
|
432
|
+
def decode
|
433
|
+
"SACK:#{self[:value]}"
|
434
|
+
end
|
435
|
+
|
436
|
+
def encode(str)
|
437
|
+
temp_obj = self.class.new(:value => str)
|
438
|
+
self[:value] = temp_obj.value
|
439
|
+
self[:optlen] = temp_obj.optlen.value
|
440
|
+
self
|
441
|
+
end
|
442
|
+
|
443
|
+
end
|
444
|
+
|
445
|
+
# Echo option.
|
446
|
+
#
|
447
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option006.htm
|
448
|
+
class ECHO < TcpOption
|
449
|
+
def initialize(args={})
|
450
|
+
super(
|
451
|
+
args.merge(:kind => 6,
|
452
|
+
:optlen => 6
|
453
|
+
)
|
454
|
+
)
|
455
|
+
end
|
456
|
+
|
457
|
+
# ECHO options with lengths other than 6 are malformed.
|
458
|
+
def decode
|
459
|
+
if self[:optlen].to_i == 6
|
460
|
+
"ECHO:#{self[:value]}"
|
461
|
+
else
|
462
|
+
"ECHO-bad:#{self[:value]}"
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
end
|
467
|
+
|
468
|
+
# Echo Reply option.
|
469
|
+
#
|
470
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option007.htm
|
471
|
+
class ECHOREPLY < TcpOption
|
472
|
+
def initialize(args={})
|
473
|
+
super(
|
474
|
+
args.merge(:kind => 7,
|
475
|
+
:optlen => 6
|
476
|
+
)
|
477
|
+
)
|
478
|
+
end
|
479
|
+
|
480
|
+
# ECHOREPLY options with lengths other than 6 are malformed.
|
481
|
+
def decode
|
482
|
+
if self[:optlen].to_i == 6
|
483
|
+
"ECHOREPLY:#{self[:value]}"
|
484
|
+
else
|
485
|
+
"ECHOREPLY-bad:#{self[:value]}"
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
end
|
490
|
+
|
491
|
+
# Timestamp option
|
492
|
+
#
|
493
|
+
# http://www.networksorcery.com/enp/protocol/tcp/option008.htm
|
494
|
+
class TS < TcpOption
|
495
|
+
def initialize(args={})
|
496
|
+
super(
|
497
|
+
args.merge(:kind => 8,
|
498
|
+
:optlen => 10
|
499
|
+
)
|
500
|
+
)
|
501
|
+
self[:value] = StructFu::String.new.read(args[:value] || "\x00" * 8)
|
502
|
+
end
|
503
|
+
|
504
|
+
# TS options with lengths other than 10 are malformed.
|
505
|
+
def decode
|
506
|
+
if self[:optlen].to_i == 10
|
507
|
+
val1,val2 = self[:value].unpack("NN")
|
508
|
+
"TS:#{val1};#{val2}"
|
509
|
+
else
|
510
|
+
"TS-bad:#{self[:value]}"
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
# TS options are in the format of "TS:[timestamp value];[timestamp secret]" Both
|
515
|
+
# should be written as decimal numbers.
|
516
|
+
def encode(str)
|
517
|
+
if str =~ /^([0-9]+);([0-9]+)$/
|
518
|
+
tsval,tsecr = str.split(";").map {|x| x.to_i}
|
519
|
+
if tsval <= 0xffffffff && tsecr <= 0xffffffff
|
520
|
+
self[:value] = StructFu::String.new([tsval,tsecr].pack("NN"))
|
521
|
+
else
|
522
|
+
self[:value] = StructFu::String.new(str)
|
523
|
+
end
|
524
|
+
else
|
525
|
+
self[:value] = StructFu::String.new(str)
|
526
|
+
end
|
527
|
+
end
|
528
|
+
|
529
|
+
end
|
530
|
+
|
531
|
+
end
|
532
|
+
|
533
|
+
class TcpOptions < Array
|
534
|
+
|
535
|
+
include StructFu
|
536
|
+
|
537
|
+
# If args[:pad] is set, the options line is automatically padded out
|
538
|
+
# with NOPs.
|
539
|
+
def to_s(args={})
|
540
|
+
opts = self.map {|x| x.to_s}.join
|
541
|
+
if args[:pad]
|
542
|
+
unless (opts.size % 4).zero?
|
543
|
+
(4 - (opts.size % 4)).times { opts << "\x01" }
|
544
|
+
end
|
545
|
+
end
|
546
|
+
opts
|
547
|
+
end
|
548
|
+
|
549
|
+
def force_binary(str)
|
550
|
+
str.force_encoding "binary" if str.respond_to? :force_encoding
|
551
|
+
end
|
552
|
+
|
553
|
+
# Reads a string to populate the object.
|
554
|
+
def read(str)
|
555
|
+
self.clear if self.size > 0
|
556
|
+
force_binary(str)
|
557
|
+
return self if(!str.respond_to? :to_s || str.nil?)
|
558
|
+
i = 0
|
559
|
+
while i < str.to_s.size
|
560
|
+
this_opt = case str[i,1].unpack("C").first
|
561
|
+
when 0; TcpOption::EOL.new
|
562
|
+
when 1; TcpOption::NOP.new
|
563
|
+
when 2; TcpOption::MSS.new
|
564
|
+
when 3; TcpOption::WS.new
|
565
|
+
when 4; TcpOption::SACKOK.new
|
566
|
+
when 5; TcpOption::SACK.new
|
567
|
+
when 6; TcpOption::ECHO.new
|
568
|
+
when 7; TcpOption::ECHOREPLY.new
|
569
|
+
when 8; TcpOption::TS.new
|
570
|
+
else; TcpOption.new
|
571
|
+
end
|
572
|
+
this_opt.read str[i,str.size]
|
573
|
+
unless this_opt.has_optlen?
|
574
|
+
this_opt.value = nil
|
575
|
+
this_opt.optlen = nil
|
576
|
+
end
|
577
|
+
self << this_opt
|
578
|
+
i += this_opt.sz
|
579
|
+
end
|
580
|
+
self
|
581
|
+
end
|
582
|
+
|
583
|
+
# Decode parses the TcpOptions object's member options, and produces a
|
584
|
+
# human-readable string by iterating over each element's decode() function.
|
585
|
+
# If TcpOptions elements were not initially created as TcpOptions, an
|
586
|
+
# attempt will be made to convert them.
|
587
|
+
#
|
588
|
+
# The output of decode is suitable as input for TcpOptions#encode.
|
589
|
+
def decode
|
590
|
+
decoded = self.map do |x|
|
591
|
+
if x.kind_of? TcpOption
|
592
|
+
x.decode
|
593
|
+
else
|
594
|
+
x = TcpOptions.new.read(x).decode
|
595
|
+
end
|
596
|
+
end
|
597
|
+
decoded.join(",")
|
598
|
+
end
|
599
|
+
|
600
|
+
# Encode takes a human-readable string and appends the corresponding
|
601
|
+
# binary options to the TcpOptions object. To completely replace the contents
|
602
|
+
# of the object, use TcpOptions#encode! instead.
|
603
|
+
#
|
604
|
+
# Options are comma-delimited, and are identical to the output of the
|
605
|
+
# TcpOptions#decode function. Note that the syntax can be unforgiving, so
|
606
|
+
# it may be easier to create the subclassed TcpOptions themselves directly,
|
607
|
+
# but this method can be less typing if you know what you're doing.
|
608
|
+
#
|
609
|
+
# Note that by using TcpOptions#encode, strings supplied as values which
|
610
|
+
# can be converted to numbers will be converted first.
|
611
|
+
#
|
612
|
+
# == Example
|
613
|
+
#
|
614
|
+
# t = TcpOptions.new
|
615
|
+
# t.encode("MS:1460,WS:6")
|
616
|
+
# t.to_s # => "\002\004\005\264\002\003\006"
|
617
|
+
# t.encode("NOP")
|
618
|
+
# t.to_s # => "\002\004\005\264\002\003\006\001"
|
619
|
+
def encode(str)
|
620
|
+
opts = str.split(/[\s]*,[\s]*/)
|
621
|
+
opts.each do |o|
|
622
|
+
kind,value = o.split(/[\s]*:[\s]*/)
|
623
|
+
klass = TcpOption.const_get(kind.upcase)
|
624
|
+
value = value.to_i if value =~ /^[0-9]+$/
|
625
|
+
this_opt = klass.new
|
626
|
+
this_opt.encode(value)
|
627
|
+
self << this_opt
|
628
|
+
end
|
629
|
+
self
|
630
|
+
end
|
631
|
+
|
632
|
+
# Like TcpOption#encode, except the entire contents are replaced.
|
633
|
+
def encode!(str)
|
634
|
+
self.clear if self.size > 0
|
635
|
+
encode(str)
|
636
|
+
end
|
637
|
+
|
638
|
+
end
|
639
|
+
|
640
|
+
end
|
641
|
+
|
642
|
+
module PacketFu
|
643
|
+
|
644
|
+
# TCPHeader is a complete TCP struct, used in TCPPacket. Most IP traffic is TCP-based, by
|
645
|
+
# volume.
|
646
|
+
#
|
647
|
+
# For more on TCP packets, see http://www.networksorcery.com/enp/protocol/tcp.htm
|
648
|
+
#
|
649
|
+
# ==== Header Definition
|
650
|
+
#
|
651
|
+
# Int16 :tcp_src Default: random
|
652
|
+
# Int16 :tcp_dst
|
653
|
+
# Int32 :tcp_seq Default: random
|
654
|
+
# Int32 :tcp_ack
|
655
|
+
# TcpHlen :tcp_hlen Default: 5 # Must recalc as options are set.
|
656
|
+
# TcpReserved :tcp_reserved Default: 0
|
657
|
+
# TcpEcn :tcp_ecn
|
658
|
+
# TcpFlags :tcp_flags
|
659
|
+
# Int16 :tcp_win, Default: 0 # WinXP's default syn packet
|
660
|
+
# Int16 :tcp_sum, Default: calculated # Must set this upon generation.
|
661
|
+
# Int16 :tcp_urg
|
662
|
+
# TcpOptions :tcp_opts
|
663
|
+
# String :body
|
664
|
+
#
|
665
|
+
# See also TcpHlen, TcpReserved, TcpEcn, TcpFlags, TcpOpts
|
666
|
+
class TCPHeader < Struct.new(:tcp_src, :tcp_dst,
|
667
|
+
:tcp_seq,
|
668
|
+
:tcp_ack,
|
669
|
+
:tcp_hlen, :tcp_reserved, :tcp_ecn, :tcp_flags, :tcp_win,
|
670
|
+
:tcp_sum, :tcp_urg,
|
671
|
+
:tcp_opts, :body)
|
672
|
+
include StructFu
|
673
|
+
|
674
|
+
def initialize(args={})
|
675
|
+
@random_seq = rand(0xffffffff)
|
676
|
+
@random_src = rand_port
|
677
|
+
super(
|
678
|
+
Int16.new(args[:tcp_src] || tcp_calc_src),
|
679
|
+
Int16.new(args[:tcp_dst]),
|
680
|
+
Int32.new(args[:tcp_seq] || tcp_calc_seq),
|
681
|
+
Int32.new(args[:tcp_ack]),
|
682
|
+
TcpHlen.new(:hlen => (args[:tcp_hlen] || 5)),
|
683
|
+
TcpReserved.new(args[:tcp_reserved] || 0),
|
684
|
+
TcpEcn.new(args[:tcp_ecn]),
|
685
|
+
TcpFlags.new(args[:tcp_flags]),
|
686
|
+
Int16.new(args[:tcp_win] || 0x4000),
|
687
|
+
Int16.new(args[:tcp_sum] || 0),
|
688
|
+
Int16.new(args[:tcp_urg]),
|
689
|
+
TcpOptions.new.read(args[:tcp_opts]),
|
690
|
+
StructFu::String.new.read(args[:body])
|
691
|
+
)
|
692
|
+
end
|
693
|
+
|
694
|
+
attr_accessor :flavor
|
695
|
+
|
696
|
+
# Helper function to create the string for Hlen, Reserved, ECN, and Flags.
|
697
|
+
def bits_to_s
|
698
|
+
bytes = []
|
699
|
+
bytes[0] = (self[:tcp_hlen].to_i << 4) +
|
700
|
+
(self[:tcp_reserved].to_i << 1) +
|
701
|
+
self[:tcp_ecn].n.to_i
|
702
|
+
bytes[1] = (self[:tcp_ecn].c.to_i << 7) +
|
703
|
+
(self[:tcp_ecn].e.to_i << 6) +
|
704
|
+
self[:tcp_flags].to_i
|
705
|
+
bytes.pack("CC")
|
706
|
+
end
|
707
|
+
|
708
|
+
# Returns the object in string form.
|
709
|
+
def to_s
|
710
|
+
hdr = self.to_a.map do |x|
|
711
|
+
if x.kind_of? TcpHlen
|
712
|
+
bits_to_s
|
713
|
+
elsif x.kind_of? TcpReserved
|
714
|
+
next
|
715
|
+
elsif x.kind_of? TcpEcn
|
716
|
+
next
|
717
|
+
elsif x.kind_of? TcpFlags
|
718
|
+
next
|
719
|
+
else
|
720
|
+
x.to_s
|
721
|
+
end
|
722
|
+
end
|
723
|
+
hdr.flatten.join
|
724
|
+
end
|
725
|
+
|
726
|
+
# Reads a string to populate the object.
|
727
|
+
def read(str)
|
728
|
+
force_binary(str)
|
729
|
+
return self if str.nil?
|
730
|
+
self[:tcp_src].read(str[0,2])
|
731
|
+
self[:tcp_dst].read(str[2,2])
|
732
|
+
self[:tcp_seq].read(str[4,4])
|
733
|
+
self[:tcp_ack].read(str[8,4])
|
734
|
+
self[:tcp_hlen].read(str[12,1])
|
735
|
+
self[:tcp_reserved].read(str[12,1])
|
736
|
+
self[:tcp_ecn].read(str[12,2])
|
737
|
+
self[:tcp_flags].read(str[13,1])
|
738
|
+
self[:tcp_win].read(str[14,2])
|
739
|
+
self[:tcp_sum].read(str[16,2])
|
740
|
+
self[:tcp_urg].read(str[18,2])
|
741
|
+
self[:tcp_opts].read(str[20,((self[:tcp_hlen].to_i * 4) - 20)])
|
742
|
+
self[:body].read(str[(self[:tcp_hlen].to_i * 4),str.size])
|
743
|
+
self
|
744
|
+
end
|
745
|
+
|
746
|
+
# Setter for the TCP source port.
|
747
|
+
def tcp_src=(i); typecast i; end
|
748
|
+
# Getter for the TCP source port.
|
749
|
+
def tcp_src; self[:tcp_src].to_i; end
|
750
|
+
# Setter for the TCP destination port.
|
751
|
+
def tcp_dst=(i); typecast i; end
|
752
|
+
# Getter for the TCP destination port.
|
753
|
+
def tcp_dst; self[:tcp_dst].to_i; end
|
754
|
+
# Setter for the TCP sequence number.
|
755
|
+
def tcp_seq=(i); typecast i; end
|
756
|
+
# Getter for the TCP sequence number.
|
757
|
+
def tcp_seq; self[:tcp_seq].to_i; end
|
758
|
+
# Setter for the TCP ackowlegement number.
|
759
|
+
def tcp_ack=(i); typecast i; end
|
760
|
+
# Getter for the TCP ackowlegement number.
|
761
|
+
def tcp_ack; self[:tcp_ack].to_i; end
|
762
|
+
# Setter for the TCP window size number.
|
763
|
+
def tcp_win=(i); typecast i; end
|
764
|
+
# Getter for the TCP window size number.
|
765
|
+
def tcp_win; self[:tcp_win].to_i; end
|
766
|
+
# Setter for the TCP checksum.
|
767
|
+
def tcp_sum=(i); typecast i; end
|
768
|
+
# Getter for the TCP checksum.
|
769
|
+
def tcp_sum; self[:tcp_sum].to_i; end
|
770
|
+
# Setter for the TCP urgent field.
|
771
|
+
def tcp_urg=(i); typecast i; end
|
772
|
+
# Getter for the TCP urgent field.
|
773
|
+
def tcp_urg; self[:tcp_urg].to_i; end
|
774
|
+
|
775
|
+
# Getter for the TCP Header Length value.
|
776
|
+
def tcp_hlen; self[:tcp_hlen].to_i; end
|
777
|
+
# Setter for the TCP Header Length value.
|
778
|
+
def tcp_hlen=(i)
|
779
|
+
if i.kind_of? PacketFu::TcpHlen
|
780
|
+
self[:tcp_hlen]=i
|
781
|
+
else
|
782
|
+
self[:tcp_hlen].read(i)
|
783
|
+
end
|
784
|
+
end
|
785
|
+
|
786
|
+
# Getter for the TCP Reserved field.
|
787
|
+
def tcp_reserved; self[:tcp_reserved].to_i; end
|
788
|
+
# Setter for the TCP Reserved field.
|
789
|
+
def tcp_reserved=(i)
|
790
|
+
if i.kind_of? PacketFu::TcpReserved
|
791
|
+
self[:tcp_reserved]=i
|
792
|
+
else
|
793
|
+
self[:tcp_reserved].read(i)
|
794
|
+
end
|
795
|
+
end
|
796
|
+
|
797
|
+
# Getter for the ECN bits.
|
798
|
+
def tcp_ecn; self[:tcp_ecn].to_i; end
|
799
|
+
# Setter for the ECN bits.
|
800
|
+
def tcp_ecn=(i)
|
801
|
+
if i.kind_of? PacketFu::TcpEcn
|
802
|
+
self[:tcp_ecn]=i
|
803
|
+
else
|
804
|
+
self[:tcp_ecn].read(i)
|
805
|
+
end
|
806
|
+
end
|
807
|
+
|
808
|
+
# Getter for TCP Options.
|
809
|
+
def tcp_opts; self[:tcp_opts].to_s; end
|
810
|
+
# Setter for TCP Options.
|
811
|
+
def tcp_opts=(i)
|
812
|
+
if i.kind_of? PacketFu::TcpOptions
|
813
|
+
self[:tcp_opts]=i
|
814
|
+
else
|
815
|
+
self[:tcp_opts].read(i)
|
816
|
+
end
|
817
|
+
end
|
818
|
+
|
819
|
+
# Resets the sequence number to a new random number.
|
820
|
+
def tcp_calc_seq; @random_seq; end
|
821
|
+
# Resets the source port to a new random number.
|
822
|
+
def tcp_calc_src; @random_src; end
|
823
|
+
|
824
|
+
# Returns the actual length of the TCP options.
|
825
|
+
def tcp_opts_len
|
826
|
+
self[:tcp_opts].to_s.size
|
827
|
+
end
|
828
|
+
|
829
|
+
# Sets and returns the true length of the TCP Header.
|
830
|
+
# TODO: Think about making all the option stuff safer.
|
831
|
+
def tcp_calc_hlen
|
832
|
+
self[:tcp_hlen] = TcpHlen.new(:hlen => ((20 + tcp_opts_len) / 4))
|
833
|
+
end
|
834
|
+
|
835
|
+
# Generates a random high port. This is affected by packet flavor.
|
836
|
+
def rand_port
|
837
|
+
rand(0xffff - 1025) + 1025
|
838
|
+
end
|
839
|
+
|
840
|
+
# Gets a more readable option list.
|
841
|
+
def tcp_options
|
842
|
+
self[:tcp_opts].decode
|
843
|
+
end
|
844
|
+
|
845
|
+
# Sets a more readable option list.
|
846
|
+
def tcp_options=(arg)
|
847
|
+
self[:tcp_opts].encode arg
|
848
|
+
end
|
849
|
+
|
850
|
+
# Equivalent to tcp_src.
|
851
|
+
def tcp_sport
|
852
|
+
self.tcp_src.to_i
|
853
|
+
end
|
854
|
+
|
855
|
+
# Equivalent to tcp_src=.
|
856
|
+
def tcp_sport=(arg)
|
857
|
+
self.tcp_src=(arg)
|
858
|
+
end
|
859
|
+
|
860
|
+
# Equivalent to tcp_dst.
|
861
|
+
def tcp_dport
|
862
|
+
self.tcp_dst.to_i
|
863
|
+
end
|
864
|
+
|
865
|
+
# Equivalent to tcp_dst=.
|
866
|
+
def tcp_dport=(arg)
|
867
|
+
self.tcp_dst=(arg)
|
868
|
+
end
|
869
|
+
|
870
|
+
# Recalculates calculated fields for TCP (except checksum which is at the Packet level).
|
871
|
+
def tcp_recalc(arg=:all)
|
872
|
+
case arg
|
873
|
+
when :tcp_hlen
|
874
|
+
tcp_calc_hlen
|
875
|
+
when :tcp_src
|
876
|
+
@random_tcp_src = rand_port
|
877
|
+
when :tcp_sport
|
878
|
+
@random_tcp_src = rand_port
|
879
|
+
when :tcp_seq
|
880
|
+
@random_tcp_seq = rand(0xffffffff)
|
881
|
+
when :all
|
882
|
+
tcp_calc_hlen
|
883
|
+
@random_tcp_src = rand_port
|
884
|
+
@random_tcp_seq = rand(0xffffffff)
|
885
|
+
else
|
886
|
+
raise ArgumentError, "No such field `#{arg}'"
|
887
|
+
end
|
888
|
+
end
|
889
|
+
|
890
|
+
end
|
891
|
+
|
892
|
+
# TCPPacket is used to construct TCP packets. They contain an EthHeader, an IPHeader, and a TCPHeader.
|
893
|
+
#
|
894
|
+
# == Example
|
895
|
+
#
|
896
|
+
# tcp_pkt = PacketFu::TCPPacket.new
|
897
|
+
# tcp_pkt.tcp_flags.syn=1
|
898
|
+
# tcp_pkt.tcp_dst=80
|
899
|
+
# tcp_pkt.tcp_win=5840
|
900
|
+
# tcp_pkt.tcp_options="mss:1460,sack.ok,ts:#{rand(0xffffffff)};0,nop,ws:7"
|
901
|
+
#
|
902
|
+
# tcp_pkt.ip_saddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.')
|
903
|
+
# tcp_pkt.ip_daddr=[rand(0xff),rand(0xff),rand(0xff),rand(0xff)].join('.')
|
904
|
+
#
|
905
|
+
# tcp_pkt.recalc
|
906
|
+
# tcp_pkt.to_f('/tmp/tcp.pcap')
|
907
|
+
#
|
908
|
+
# == Parameters
|
909
|
+
# :eth
|
910
|
+
# A pre-generated EthHeader object.
|
911
|
+
# :ip
|
912
|
+
# A pre-generated IPHeader object.
|
913
|
+
# :flavor
|
914
|
+
# TODO: Sets the "flavor" of the TCP packet. This will include TCP options and the initial window
|
915
|
+
# size, per stack. There is a lot of variety here, and it's one of the most useful methods to
|
916
|
+
# remotely fingerprint devices. :flavor will span both ip and tcp for consistency.
|
917
|
+
# :type
|
918
|
+
# TODO: Set up particular types of packets (syn, psh_ack, rst, etc). This can change the initial flavor.
|
919
|
+
# :config
|
920
|
+
# A hash of return address details, often the output of Utils.whoami?
|
921
|
+
class TCPPacket < Packet
|
922
|
+
|
923
|
+
attr_accessor :eth_header, :ip_header, :tcp_header, :headers
|
924
|
+
|
925
|
+
def initialize(args={})
|
926
|
+
@eth_header = (args[:eth] || EthHeader.new)
|
927
|
+
@ip_header = (args[:ip] || IPHeader.new)
|
928
|
+
@tcp_header = (args[:tcp] || TCPHeader.new)
|
929
|
+
@tcp_header.flavor = args[:flavor].to_s.downcase
|
930
|
+
|
931
|
+
@ip_header.body = @tcp_header
|
932
|
+
@eth_header.body = @ip_header
|
933
|
+
@headers = [@eth_header, @ip_header, @tcp_header]
|
934
|
+
|
935
|
+
@ip_header.ip_proto=0x06
|
936
|
+
super
|
937
|
+
if args[:flavor]
|
938
|
+
tcp_calc_flavor(@tcp_header.flavor)
|
939
|
+
else
|
940
|
+
tcp_calc_sum
|
941
|
+
end
|
942
|
+
end
|
943
|
+
|
944
|
+
# Sets the correct flavor for TCP Packets. Recognized flavors are:
|
945
|
+
# windows, linux, freebsd
|
946
|
+
def tcp_calc_flavor(str)
|
947
|
+
ts_val = Time.now.to_i + rand(0x4fffffff)
|
948
|
+
ts_sec = rand(0xffffff)
|
949
|
+
case @tcp_header.flavor = str.to_s.downcase
|
950
|
+
when "windows" # WinXP's default syn
|
951
|
+
@tcp_header.tcp_win = 0x4000
|
952
|
+
@tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK"
|
953
|
+
@tcp_header.tcp_src = rand(5000 - 1026) + 1026
|
954
|
+
@ip_header.ip_ttl = 64
|
955
|
+
when "linux" # Ubuntu Linux 2.6.24-19-generic default syn
|
956
|
+
@tcp_header.tcp_win = 5840
|
957
|
+
@tcp_header.tcp_options="MSS:1460,SACKOK,TS:#{ts_val};0,NOP,WS:7"
|
958
|
+
@tcp_header.tcp_src = rand(61_000 - 32_000) + 32_000
|
959
|
+
@ip_header.ip_ttl = 64
|
960
|
+
when "freebsd" # Freebsd
|
961
|
+
@tcp_header.tcp_win = 0xffff
|
962
|
+
@tcp_header.tcp_options="MSS:1460,NOP,WS:3,NOP,NOP,TS:#{ts_val};#{ts_sec},SACKOK,EOL,EOL"
|
963
|
+
@ip_header.ip_ttl = 64
|
964
|
+
else
|
965
|
+
@tcp_header.tcp_options="MSS:1460,NOP,NOP,SACKOK"
|
966
|
+
end
|
967
|
+
tcp_calc_sum
|
968
|
+
end
|
969
|
+
|
970
|
+
# tcp_calc_sum() computes the TCP checksum, and is called upon intialization. It usually
|
971
|
+
# should be called just prior to dropping packets to a file or on the wire.
|
972
|
+
#--
|
973
|
+
# This is /not/ delegated down to @tcp_header since we need info
|
974
|
+
# from the IP header, too.
|
975
|
+
#++
|
976
|
+
def tcp_calc_sum
|
977
|
+
checksum = (ip_src.to_i >> 16)
|
978
|
+
checksum += (ip_src.to_i & 0xffff)
|
979
|
+
checksum += (ip_dst.to_i >> 16)
|
980
|
+
checksum += (ip_dst.to_i & 0xffff)
|
981
|
+
checksum += 0x06 # TCP Protocol.
|
982
|
+
checksum += (ip_len.to_i - ((ip_hl.to_i) * 4))
|
983
|
+
checksum += tcp_src
|
984
|
+
checksum += tcp_dst
|
985
|
+
checksum += (tcp_seq.to_i >> 16)
|
986
|
+
checksum += (tcp_seq.to_i & 0xffff)
|
987
|
+
checksum += (tcp_ack.to_i >> 16)
|
988
|
+
checksum += (tcp_ack.to_i & 0xffff)
|
989
|
+
checksum += ((tcp_hlen << 12) +
|
990
|
+
(tcp_reserved << 9) +
|
991
|
+
(tcp_ecn.to_i << 6) +
|
992
|
+
tcp_flags.to_i
|
993
|
+
)
|
994
|
+
checksum += tcp_win
|
995
|
+
checksum += tcp_urg
|
996
|
+
|
997
|
+
chk_tcp_opts = (tcp_opts.to_s.size % 2 == 0 ? tcp_opts.to_s : tcp_opts.to_s + "\x00")
|
998
|
+
chk_tcp_opts.unpack("n*").each {|x| checksum = checksum + x }
|
999
|
+
if (ip_len - ((ip_hl + tcp_hlen) * 4)) >= 0
|
1000
|
+
real_tcp_payload = payload[0,( ip_len - ((ip_hl + tcp_hlen) * 4) )] # Can't forget those pesky FCSes!
|
1001
|
+
else
|
1002
|
+
real_tcp_payload = payload # Something's amiss here so don't bother figuring out where the real payload is.
|
1003
|
+
end
|
1004
|
+
chk_payload = (real_tcp_payload.size % 2 == 0 ? real_tcp_payload : real_tcp_payload + "\x00") # Null pad if it's odd.
|
1005
|
+
chk_payload.unpack("n*").each {|x| checksum = checksum+x }
|
1006
|
+
checksum = checksum % 0xffff
|
1007
|
+
checksum = 0xffff - checksum
|
1008
|
+
checksum == 0 ? 0xffff : checksum
|
1009
|
+
@tcp_header.tcp_sum = checksum
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
# Recalculates various fields of the TCP packet.
|
1013
|
+
#
|
1014
|
+
# ==== Parameters
|
1015
|
+
#
|
1016
|
+
# :all
|
1017
|
+
# Recomputes all calculated fields.
|
1018
|
+
# :tcp_sum
|
1019
|
+
# Recomputes the TCP checksum.
|
1020
|
+
# :tcp_hlen
|
1021
|
+
# Recomputes the TCP header length. Useful after options are added.
|
1022
|
+
def tcp_recalc(arg=:all)
|
1023
|
+
case arg
|
1024
|
+
when :tcp_sum
|
1025
|
+
tcp_calc_sum
|
1026
|
+
when :tcp_hlen
|
1027
|
+
@tcp_header.tcp_recalc :tcp_hlen
|
1028
|
+
when :all
|
1029
|
+
@tcp_header.tcp_recalc :all
|
1030
|
+
tcp_calc_sum
|
1031
|
+
else
|
1032
|
+
raise ArgumentError, "No such field `#{arg}'"
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
# Peek provides summary data on packet contents.
|
1037
|
+
def peek(args={})
|
1038
|
+
peek_data = ["T "]
|
1039
|
+
peek_data << "%-5d" % self.to_s.size
|
1040
|
+
peek_data << "%-21s" % "#{self.ip_saddr}:#{self.tcp_src}"
|
1041
|
+
peek_data << "->"
|
1042
|
+
peek_data << "%21s" % "#{self.ip_daddr}:#{self.tcp_dst}"
|
1043
|
+
flags = ' ['
|
1044
|
+
flags << (self.tcp_flags.urg.zero? ? "." : "U")
|
1045
|
+
flags << (self.tcp_flags.ack.zero? ? "." : "A")
|
1046
|
+
flags << (self.tcp_flags.psh.zero? ? "." : "P")
|
1047
|
+
flags << (self.tcp_flags.rst.zero? ? "." : "R")
|
1048
|
+
flags << (self.tcp_flags.syn.zero? ? "." : "S")
|
1049
|
+
flags << (self.tcp_flags.fin.zero? ? "." : "F")
|
1050
|
+
flags << '] '
|
1051
|
+
peek_data << flags
|
1052
|
+
peek_data << "S:"
|
1053
|
+
peek_data << "%08x" % self.tcp_seq
|
1054
|
+
peek_data << "|I:"
|
1055
|
+
peek_data << "%04x" % self.ip_id
|
1056
|
+
peek_data.join
|
1057
|
+
end
|
1058
|
+
|
1059
|
+
end
|
1060
|
+
|
1061
|
+
end
|