dot11 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,51 @@
1
+ module Dot11
2
+ class MACAddress
3
+ include Comparable
4
+
5
+ attr_accessor :prefix_length
6
+
7
+ def initialize(address)
8
+ if address.kind_of?(Integer)
9
+ @address = [address].pack("Q").unpack("C8")[0, 6].reverse
10
+ elsif address.kind_of?(String) && address =~ /^(.*?)(?:\/(.*))?$/
11
+ @prefix_length = $2.to_i
12
+
13
+ @address = $1.split(":").map {|octet| octet.to_i(16)}
14
+ elsif address.kind_of?(Array)
15
+ @address = address
16
+ end
17
+ end
18
+
19
+ def to_i
20
+ ("\x00\x00" + @address.pack("C6")).reverse.unpack("Q")[0]
21
+ end
22
+
23
+ def to_s
24
+ @address.map{|byte| "%02x" % byte}.join(":") + (@prefix_length == 0 ? "" : "/#{@prefix_length}")
25
+ end
26
+
27
+ def inspect
28
+ to_s
29
+ end
30
+
31
+ def to_arr
32
+ @address
33
+ end
34
+
35
+ def [](*index)
36
+ @address[*index]
37
+ end
38
+
39
+ def eql?(other)
40
+ to_i == other.to_i
41
+ end
42
+
43
+ def ==(other)
44
+ eql?(other)
45
+ end
46
+
47
+ def hash
48
+ to_i.hash
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packetset'))
2
+
3
+ module Dot11
4
+ class Packet
5
+ class <<self
6
+ alias older_new new
7
+
8
+ def new(parameters = {})
9
+ # Maybe we shouldn't do it this way, but it looks prettier
10
+ if !parameters.kind_of?(String) && parameters.values.any? {|v| v.kind_of?(Array) || v.kind_of?(Range)}
11
+ PacketSet.new(self, parameters)
12
+ else
13
+ older_new(parameters)
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(parameters = {})
19
+ if parameters.kind_of?(String)
20
+ dissect(parameters)
21
+ elsif parameters.kind_of?(Hash)
22
+ parameters.each_pair do |key, value|
23
+ send((key.to_s + "=").intern, value)
24
+ end
25
+ end
26
+ end
27
+
28
+ def =~(other)
29
+ if other.kind_of?(Hash)
30
+ other.each_pair do |key, value|
31
+ return false if self.send(key) != value
32
+ end
33
+
34
+ return true
35
+ end
36
+ end
37
+
38
+ def to_filter
39
+ PacketSet.new(self.class, self.instance_variables.inject({}) do |accum, var|
40
+ accum[var[1..-1].intern] = self.instance_variable_get(var).kind_of?(MACAddress) ? self.instance_variable_get(var).to_s : self.instance_variable_get(var).to_s
41
+ accum
42
+ end).to_filter
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,253 @@
1
+ module Dot11
2
+ class PacketSet
3
+ Pair = Struct.new(:name, :value)
4
+
5
+ attr_accessor :packet_class
6
+ attr_reader :fields
7
+
8
+ def initialize(klass, parameters)
9
+ @fields = []
10
+
11
+ @packet_class = klass
12
+
13
+ parameters.each_pair do |key, value|
14
+ @fields << Pair.new(key, value)
15
+ end
16
+
17
+ @field_sizes = @fields.map do |field|
18
+ if field.value.respond_to?(:size) && !(field.value.kind_of?(String) || field.value.kind_of?(Numeric))
19
+ field.value.size
20
+ elsif field.value.respond_to?(:entries)
21
+ field.value.entries.size
22
+ else
23
+ field.value = [field.value]
24
+ 1
25
+ end
26
+ end
27
+ end
28
+
29
+ def [](index)
30
+ indices = @field_sizes.inject([]) do |accumulator, size|
31
+ remainder = index % size
32
+ index /= size # yeah textmate isn't perfect and that's why I need this slash: /
33
+ accumulator << remainder
34
+ end
35
+
36
+ field_hash = {}
37
+
38
+ indices.each_with_index do |index, i|
39
+ value = @fields[i].value
40
+
41
+ if value.kind_of?(Array) or value.kind_of?(Range)
42
+ field_hash[@fields[i].name] = @fields[i].value.entries[index]
43
+ else
44
+ field_hash[@fields[i].name] = @fields[i].value
45
+ end
46
+ end
47
+
48
+ out = @packet_class.new(field_hash)
49
+
50
+ out
51
+ end
52
+
53
+ def size
54
+ @field_sizes.inject(1) { |product, size| size * product }
55
+ end
56
+
57
+ def each(prefix = [])
58
+ size.times do |i|
59
+ yield self[i]
60
+ end
61
+ end
62
+
63
+ def each_with_index
64
+ size.times do |i|
65
+ yield self[i], i
66
+ end
67
+ end
68
+
69
+ def include?(packet)
70
+ # This is horrrrrribly inefficient. FIXME
71
+ each do |pkt|
72
+ return true if packet == pkt
73
+ end
74
+
75
+ false
76
+ end
77
+
78
+ def bitrange_to_filter(offset, size, bitrange, desired, negated = false)
79
+ # TODO: Take bitrange.exclude_end? into account
80
+ mask = ((2 << (bitrange.end - bitrange.begin)) - 1) << bitrange.begin
81
+
82
+ value = "(ether[#{offset}:#{size}] & #{'%#x' % mask}) >> #{bitrange.begin}"
83
+
84
+ case desired
85
+ when Range
86
+ "(#{value} >= #{'%#x' % desired.begin} && #{value} #{desired.exclude_end? ? "<" : "<="} #{'%#x' % desired.end})"
87
+ when Array
88
+ # TODO: deal with arrays of ranges
89
+ '(' + desired.map {|d| "#{value} = #{'%#x' % d}"}.join(" || ") + ')'
90
+ when Numeric, String
91
+ "#{value} #{negated ? '!' : ''}= #{'%#x' % desired.to_i}"
92
+ else
93
+ raise "unknown"
94
+ end
95
+ end
96
+
97
+ def int_to_filter(offset, size, desired, negated = false)
98
+ value = "ether[#{offset}:#{size}]"
99
+
100
+ case desired
101
+ when Range
102
+ "(#{value} >= #{'%#x' % desired.begin} && #{value} #{desired.exclude_end? ? "<" : "<="} #{'%#x' % desired.end})"
103
+ when Array
104
+ # TODO: deal with arrays of ranges
105
+ desired.map {|d| "#{value} #{negated ? '!' : ''}= #{'%#x' % d}"}.join(" || ")
106
+ when Numeric
107
+ "#{value} #{negated ? '!' : ''}= #{'%#x' % desired}"
108
+ else
109
+ raise "unknown desired: #{desired.inspect}"
110
+ end
111
+ end
112
+
113
+ def mac_to_filter(offset, size, desired, negated = false)
114
+ case desired
115
+ when Range
116
+ raise "Range of MAC addresses in filter not yet implemented"
117
+ when Array
118
+ '(' + desired.map do |d|
119
+ desired = MACAddress.new(desired) if desired.kind_of?(String)
120
+ first_four = desired[0, 4].pack("CCCC").unpack("N")[0]
121
+ second_two = desired[4, 2].pack("CC").unpack("n")[0]
122
+
123
+ "(ether[#{offset}:2] = #{'%#x' % first_four} && ether[#{offset + 4}:2] = #{'%#x' % second_two})"
124
+ end.join(" || ") + ')'
125
+ when Numeric
126
+
127
+ when MACAddress, String
128
+ desired = MACAddress.new(desired) if desired.kind_of?(String)
129
+ first_four = desired[0, 4].pack("CCCC").unpack("N")[0]
130
+ second_two = desired[4, 2].pack("CC").unpack("n")[0]
131
+
132
+
133
+ if desired.prefix_length == 32
134
+ "ether[#{offset}:4] = #{'%#x' % first_four}"
135
+ elsif desired.prefix_length == 48 || desired.prefix_length == 0
136
+ "(ether[#{offset}:4] = #{'%#x' % first_four} && ether[#{offset + 4}:2] = #{'%#x' % second_two})"
137
+ else
138
+ raise "unsupport prefix length #{desired.prefix_length}"
139
+ end
140
+ else
141
+ raise "unknown"
142
+ end
143
+ end
144
+
145
+ def decode_condition(fields, condition)
146
+ case condition
147
+ when Array
148
+ condition.map{|x| decode_condition(fields, x)}.join(" || ")
149
+ when Hash
150
+ condition.map do |key, value|
151
+ # Decode the nasty negation syntax
152
+ negated, value = value.kind_of?(Array) && value.size == 1 ? [true, value[0]] : [false, value]
153
+
154
+ field_info = fields.find{|x| key == x[0]}[1]
155
+
156
+ case field_info[:type]
157
+ when :int
158
+ if field_info.has_key?(:bitrange)
159
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], value, negated)
160
+ else
161
+ int_to_filter(field_info[:offset], field_info[:size], value, negated)
162
+ end
163
+ when :mac
164
+ mac_to_filter(field_info[:offset], field_info[:size], value, negated)
165
+ else
166
+ raise "unknown"
167
+ end
168
+ end.join(" && ")
169
+ end
170
+ end
171
+
172
+ def to_filter
173
+ fields = @packet_class.fields
174
+
175
+ @fields.map do |field|
176
+ '(' + begin
177
+ field_info = fields.find{|x| field.name == x[0]}
178
+
179
+ if field_info.nil?
180
+ raise "nil field_info"
181
+ end
182
+
183
+ field_info = field_info[1]
184
+
185
+ # We don't really want to do this
186
+ if field_info.nil?
187
+ raise "nil field_info 2"
188
+ end
189
+
190
+ if field_info.has_key?(:condition)
191
+ if !field_info[:offset].kind_of?(Numeric)
192
+ field_info[:offset].map do |key, value|
193
+ '(' + begin
194
+ if key == :else
195
+ field_info[:offset].keys.reject{|k| k == :else}.map do |k|
196
+ '!(' + decode_condition(fields, k) + ')'
197
+ end.join(" && ")
198
+ else
199
+ '(' + decode_condition(fields, key) + ') && (' + begin
200
+ case field_info[:type]
201
+ when :int
202
+ if field_info.has_key?(:bitrange)
203
+ bitrange_to_filter(value, field_info[:size], field_info[:bitrange], field.value)
204
+ else
205
+ int_to_filter(value, field_info[:size], field.value)
206
+ end
207
+ when :mac
208
+ mac_to_filter(value, field_info[:size], field.value)
209
+ else
210
+ raise "unknown"
211
+ end
212
+ end
213
+ end
214
+ end + ')'
215
+ end.join(" && ")
216
+ else
217
+ '((' + decode_condition(fields, field_info[:condition]) +') && ('+ case field_info[:type]
218
+ when :int
219
+ if field_info.has_key?(:bitrange)
220
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], field.value)
221
+ else
222
+ int_to_filter(field_info[:offset], field_info[:size], field.value)
223
+ end
224
+ when :mac
225
+ mac_to_filter(field_info[:offset], field_info[:size], field.value)
226
+ else
227
+ raise "unknown"
228
+ end + '))'
229
+ end
230
+ else
231
+ if !field_info[:offset].kind_of?(Numeric)
232
+ p "moo"
233
+ "can"
234
+ else
235
+ case field_info[:type]
236
+ when :int
237
+ if field_info.has_key?(:bitrange)
238
+ bitrange_to_filter(field_info[:offset], field_info[:size], field_info[:bitrange], field.value)
239
+ else
240
+ int_to_filter(field_info[:offset], field_info[:size], field.value)
241
+ end
242
+ when :mac
243
+ mac_to_filter(field_info[:offset], field_info[:size], field.value)
244
+ else
245
+ raise "unknown"
246
+ end
247
+ end
248
+ end
249
+ end + ')'
250
+ end.join(" && ")
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packet'))
2
+
3
+ module Dot11
4
+ class Radiotap < Packet
5
+ attr_accessor :revision, :pad, :stuff_length, :stuff
6
+
7
+ def data
8
+ raise "This space intentionally left blank"
9
+ end
10
+
11
+ def to_s
12
+ "Radiotap\n" +
13
+ "-------------\n" +
14
+ "payload:\n" +
15
+ payload.to_s.indent(6)
16
+ end
17
+
18
+ def payload
19
+ return @payload if @payload
20
+
21
+ @payload = Dot11.new(@rest)
22
+ end
23
+
24
+ private
25
+
26
+ def dissect(data)
27
+ fields = data.unpack("CCv")
28
+
29
+ @data = data
30
+ @revision = fields[0]
31
+ @pad = fields[1]
32
+ @stuff_length = fields[2]
33
+
34
+ @rest = data[@stuff_length..-1]
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,19 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), 'packet'))
2
+
3
+ module Dot11
4
+ class Raw < Packet
5
+ attr_accessor :data
6
+
7
+ def to_s
8
+ "Raw\n" +
9
+ "------\n" +
10
+ "data: #{data.inspect}\n"
11
+ end
12
+
13
+ private
14
+
15
+ def dissect(data)
16
+ @data = data
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,133 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), '..', 'dot11', 'dot11'))
2
+
3
+ include Dot11
4
+
5
+ def test(klass, raw, packet, test_desc)
6
+ dissector = (klass.new(raw) == packet)
7
+ builder = (packet.data == raw)
8
+
9
+ puts "#{klass} dissector #{dissector ? 'passed' : 'failed'} on #{test_desc}"
10
+ puts "#{klass} builder #{builder ? 'passed' : 'failed'} on #{test_desc}"
11
+ end
12
+
13
+ #####################################################################################
14
+
15
+ beacon_raw = "\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e\x00\x01\xe3\x41\xbd\x6e\x00\x0e"
16
+ beacon = Dot11.new(:type => 0,
17
+ :subtype => 8,
18
+ :version => 0,
19
+ :flags => 0,
20
+ :duration => 0,
21
+ :addr1 => "ff:ff:ff:ff:ff:ff",
22
+ :addr2 => "00:01:e3:41:bd:6e",
23
+ :addr3 => "00:01:e3:41:bd:6e",
24
+ :sc => 0x0e00)
25
+
26
+ test(Dot11, beacon_raw, beacon, "beacon frame")
27
+
28
+ #####################################################################################
29
+
30
+ data_raw = "\x08\x42\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e\x00\x01\xe3\x42\x9e\x2b\x40\x07"
31
+ data = Dot11.new(:type => 2,
32
+ :subtype => 0,
33
+ :version => 0,
34
+ :flags => 0x42,
35
+ :duration => 0,
36
+ :addr1 => "ff:ff:ff:ff:ff:ff",
37
+ :addr2 => "00:01:e3:41:bd:6e",
38
+ :addr3 => "00:01:e3:42:9e:2b",
39
+ :sc => 0x0740)
40
+
41
+ test(Dot11, data_raw, data, "data frame")
42
+
43
+ #####################################################################################
44
+
45
+ complete_beacon_raw = "\x80\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x01\xe3\x41\xbd\x6e" +
46
+ "\x00\x01\xe3\x41\xbd\x6e\xf0\x02\x86\xf1\x1b\x6a\x02\x00\x00\x00" +
47
+ "\x64\x00\x11\x04\x00\x09\x6d\x61\x72\x74\x69\x6e\x65\x74\x33\x01" +
48
+ "\x08\x82\x84\x8b\x96\x24\x30\x48\x6c\x03\x01\x0b\x05\x04\x00\x01" +
49
+ "\x00\x00\x2a\x01\x04\x2f\x01\x04\x32\x04\x0c\x12\x18\x60\xdd\x06" +
50
+ "\x00\x10\x18\x01\x01\x00\xdd\x16\x00\x50\xf2\x01\x01\x00\x00\x50" +
51
+ "\xf2\x02\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02"
52
+ complete_beacon = Dot11.new(:type => 0,
53
+ :subtype => 8,
54
+ :version => 0,
55
+ :flags => 0,
56
+ :duration => 0,
57
+ :addr1 => "ff:ff:ff:ff:ff:ff",
58
+ :addr2 => "00:01:e3:41:bd:6e",
59
+ :addr3 => "00:01:e3:41:bd:6e",
60
+ :sc => 0x02f0) /
61
+ Dot11Beacon.new(:timestamp => 0x26a1bf186,
62
+ :beacon_interval => 0x64,
63
+ :capabilities => 0x1104) /
64
+ Dot11Elt.new(:id => 0,
65
+ :info_length => 9,
66
+ :info => "martinet3") /
67
+ Dot11Elt.new(:id => 1,
68
+ :info_length => 8,
69
+ :info => "\x82\x84\x8b\x96\x24\x30\x48\x6c") /
70
+ Dot11Elt.new(:id => 3,
71
+ :info_length => 1,
72
+ :info => "\x0b") /
73
+ Dot11Elt.new(:id => 5,
74
+ :info_length => 4,
75
+ :info => "\x00\x01\x00\x00") /
76
+ Dot11Elt.new(:id => 42,
77
+ :info_length => 1,
78
+ :info => "\x04") /
79
+ Dot11Elt.new(:id => 47,
80
+ :info_length => 1,
81
+ :info => "\x04") /
82
+ Dot11Elt.new(:id => 50,
83
+ :info_length => 4,
84
+ :info => "\x0c\x12\x18\x60") /
85
+ Dot11Elt.new(:id => 221,
86
+ :info_length => 6,
87
+ :info => "\x00\x10\x18\x01\x01\x00") /
88
+ Dot11Elt.new(:id => 221,
89
+ :info_length => 22,
90
+ :info => "\x00\x50\xf2\x01\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02\x01\x00\x00\x50\xf2\x02")
91
+
92
+ test(Dot11, complete_beacon_raw, complete_beacon, "complete beacon frame")
93
+ p complete_beacon
94
+
95
+ #####################################################################################
96
+
97
+ acknowledgement_raw = "\xd4\x00\x00\x00\x00\x15\x00\x34\x18\x52"
98
+ acknowledgement = Dot11.new(:type => 1,
99
+ :subtype => 13,
100
+ :version => 0,
101
+ :flags => 0,
102
+ :duration => 0,
103
+ :addr1 => "00:15:00:34:18:52")
104
+
105
+ test(Dot11, acknowledgement_raw, acknowledgement, "acknowledgement frame")
106
+ puts("Passed to_i test") if acknowledgement.addr1.to_i == 0x1500341852
107
+ p acknowledgement
108
+
109
+ #####################################################################################
110
+
111
+ probereq_raw = "\x40\x00\x00\x00\xff\xff\xff\xff\xff\xff\x00\x16\xbc\x3d\xaa\x57\xff" +
112
+ "\xff\xff\xff\xff\xff\xb0\x00\x00\x09\x6d\x61\x72\x74\x69\x6e\x65\x74" +
113
+ "\x33\x01\x08\x82\x84\x8b\x96\x0c\x12\x18\x24\x03\x01\x09\x32\x04\x30" +
114
+ "\x48\x60\x6c"
115
+ probereq = Dot11.new(:type => 0,
116
+ :subtype => 4,
117
+ :version => 0,
118
+ :flags => 0,
119
+ :duration => 0,
120
+ :addr1 => "ff:ff:ff:ff:ff:ff",
121
+ :addr2 => "00:16:bc:3d:aa:57",
122
+ :addr3 => "ff:ff:ff:ff:ff:ff",
123
+ :sc => 0x00b0) /
124
+ Dot11ProbeReq.new() /
125
+ Dot11Elt.new(:id => 0, :info_length => 9, :info => "martinet3") /
126
+ Dot11Elt.new(:id => 1, :info_length => 8, :info => "\x82\x84\x8b\x96\x0c\x12\x18\x24") /
127
+ Dot11Elt.new(:id => 3, :info_length => 1, :info => "\x09") /
128
+ Dot11Elt.new(:id => 50, :info_length => 4, :info => "\x30\x48\x60\x6c")
129
+
130
+ test(Dot11, probereq_raw, probereq, "probe request frame")
131
+ p probereq
132
+
133
+ #####################################################################################