dap 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +2 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +55 -0
- data/LICENSE +20 -0
- data/README.md +15 -0
- data/bin/dap +137 -0
- data/dap.gemspec +42 -0
- data/data/.gitkeep +0 -0
- data/lib/dap.rb +101 -0
- data/lib/dap/filter.rb +8 -0
- data/lib/dap/filter/base.rb +37 -0
- data/lib/dap/filter/geoip.rb +72 -0
- data/lib/dap/filter/http.rb +173 -0
- data/lib/dap/filter/names.rb +151 -0
- data/lib/dap/filter/openssl.rb +53 -0
- data/lib/dap/filter/recog.rb +23 -0
- data/lib/dap/filter/simple.rb +340 -0
- data/lib/dap/filter/udp.rb +401 -0
- data/lib/dap/input.rb +74 -0
- data/lib/dap/input/csv.rb +60 -0
- data/lib/dap/input/warc.rb +81 -0
- data/lib/dap/output.rb +117 -0
- data/lib/dap/proto/addp.rb +0 -0
- data/lib/dap/proto/dtls.rb +21 -0
- data/lib/dap/proto/ipmi.rb +94 -0
- data/lib/dap/proto/natpmp.rb +19 -0
- data/lib/dap/proto/wdbrpc.rb +58 -0
- data/lib/dap/utils/oui.rb +16586 -0
- data/lib/dap/version.rb +3 -0
- data/samples/http_get_reply.ic12.bz2 +0 -0
- data/samples/http_get_reply.ic12.sh +1 -0
- data/samples/http_get_reply_iframes.json.bz2 +0 -0
- data/samples/http_get_reply_iframes.json.sh +1 -0
- data/samples/http_get_reply_links.json.sh +1 -0
- data/samples/iawide.warc.bz2 +0 -0
- data/samples/iawide_warc.sh +1 -0
- data/samples/ipmi_chan_auth_replies.crd.bz2 +0 -0
- data/samples/ipmi_chan_auth_replies.sh +1 -0
- data/samples/ssl_certs.bz2 +0 -0
- data/samples/ssl_certs_geo.sh +1 -0
- data/samples/ssl_certs_names.sh +1 -0
- data/samples/ssl_certs_names_expanded.sh +1 -0
- data/samples/ssl_certs_org.sh +1 -0
- data/samples/udp-netbios.csv.bz2 +0 -0
- data/samples/udp-netbios.sh +1 -0
- data/spec/dap/proto/ipmi_spec.rb +19 -0
- data/tools/geo-ip-summary.rb +149 -0
- data/tools/ipmi-vulns.rb +27 -0
- data/tools/json-summarize.rb +81 -0
- data/tools/netbios-counts.rb +271 -0
- data/tools/upnp-vulns.rb +35 -0
- data/tools/value-counts-to-md-table.rb +23 -0
- metadata +264 -0
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'recog'
|
2
|
+
|
3
|
+
module Dap
|
4
|
+
module Filter
|
5
|
+
|
6
|
+
class FilterRecog
|
7
|
+
include Base
|
8
|
+
|
9
|
+
def process(doc)
|
10
|
+
self.opts.each_pair do |k,v|
|
11
|
+
next unless doc.has_key?(k)
|
12
|
+
match = Recog::Nizer.match(v, doc[k])
|
13
|
+
next unless match
|
14
|
+
match.each_pair do |ok, ov|
|
15
|
+
doc["#{k}.recog.#{ok}"] = ov.to_s
|
16
|
+
end
|
17
|
+
end
|
18
|
+
[ doc ]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,340 @@
|
|
1
|
+
module Dap
|
2
|
+
module Filter
|
3
|
+
|
4
|
+
class FilterRename
|
5
|
+
include Base
|
6
|
+
def process(doc)
|
7
|
+
self.opts.each_pair do |k,v|
|
8
|
+
if doc.has_key?(k)
|
9
|
+
doc[v] = doc[k]
|
10
|
+
doc.delete(k)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
[ doc ]
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class FilterRemove
|
18
|
+
include Base
|
19
|
+
def process(doc)
|
20
|
+
self.opts.each_pair do |k,v|
|
21
|
+
if doc.has_key?(k)
|
22
|
+
doc.delete(k)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
[ doc ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class FilterSelect
|
30
|
+
include Base
|
31
|
+
def process(doc)
|
32
|
+
ndoc = {}
|
33
|
+
self.opts.each_pair do |k,v|
|
34
|
+
if doc.has_key?(k)
|
35
|
+
ndoc[k] = doc[k]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
(ndoc.keys.length == 0) ? [] : [ ndoc ]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class FilterInsert
|
43
|
+
include Base
|
44
|
+
def process(doc)
|
45
|
+
self.opts.each_pair do |k,v|
|
46
|
+
doc[k] = v
|
47
|
+
end
|
48
|
+
[ doc ]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class FilterInclude
|
53
|
+
include Base
|
54
|
+
def process(doc)
|
55
|
+
self.opts.each_pair do |k,v|
|
56
|
+
if doc.has_key?(k) and doc[k].to_s.index(v)
|
57
|
+
return [ doc ]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
[ ]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# where 'some.field == some_value'
|
65
|
+
# where 'some.field != some_value'
|
66
|
+
# TODO: do something other than basic string comparison. Would be nice to have where 'some.field > 2', etc
|
67
|
+
class FilterWhere
|
68
|
+
attr_accessor :query
|
69
|
+
|
70
|
+
def initialize(args)
|
71
|
+
fail "Expected 3 arguments to 'where' but got #{args.size}" unless args.size == 3
|
72
|
+
self.query = args
|
73
|
+
end
|
74
|
+
|
75
|
+
def process(doc)
|
76
|
+
field, operator, expected = self.query
|
77
|
+
return [ doc ] if doc.has_key?(field) and doc[field].send(operator, expected)
|
78
|
+
[ ]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class FilterExclude
|
83
|
+
include Base
|
84
|
+
def process(doc)
|
85
|
+
self.opts.each_pair do |k,v|
|
86
|
+
if doc.has_key?(k) and doc[k].to_s.index(v)
|
87
|
+
return [ ]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
[ doc ]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class FilterExists
|
95
|
+
include Base
|
96
|
+
def process(doc)
|
97
|
+
self.opts.each_pair do |k,v|
|
98
|
+
if doc.has_key?(k) and doc[k].to_s.length > 0
|
99
|
+
return [ doc ]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
[ ]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
class FilterNotExists < FilterExists
|
107
|
+
include Base
|
108
|
+
def process(doc)
|
109
|
+
exists_doc = super(doc)
|
110
|
+
exists_doc.empty? ? [ doc ] : [ ]
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Applies some simple annotation to the given fields, adding another
|
115
|
+
# field name with the appended annotation type, i.e.:
|
116
|
+
#
|
117
|
+
# $ echo '{"foo":"blah"}' | dap json stdin + annotate foo=length + json
|
118
|
+
# {"foo":"bar","foo.length":4}
|
119
|
+
class FilterAnnotate
|
120
|
+
include Base
|
121
|
+
def process(doc)
|
122
|
+
self.opts.each_pair do |k,v|
|
123
|
+
if doc.has_key?(k)
|
124
|
+
case v
|
125
|
+
when 'length'
|
126
|
+
doc["#{k}.length"] = doc[k].length
|
127
|
+
when 'size'
|
128
|
+
doc["#{k}.size"] = doc[k].size
|
129
|
+
else
|
130
|
+
fail "Unsupported annotation '#{v}'"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
[ doc ]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
class FilterTransform
|
139
|
+
include Base
|
140
|
+
def process(doc)
|
141
|
+
self.opts.each_pair do |k,v|
|
142
|
+
if doc.has_key?(k)
|
143
|
+
case v
|
144
|
+
when 'downcase'
|
145
|
+
doc[k] = doc[k].to_s.downcase
|
146
|
+
when 'upcase'
|
147
|
+
doc[k] = doc[k].to_s.upcase
|
148
|
+
when 'ascii'
|
149
|
+
doc[k] = doc[k].to_s.gsub(/[\x00-\x1f\x7f-\xff]/n, '')
|
150
|
+
when 'utf8encode'
|
151
|
+
doc[k] = doc[k].to_s.encode!('UTF-8', invalid: :replace, undef: :replace, replace: '')
|
152
|
+
when 'base64decode'
|
153
|
+
doc[k] = doc[k].to_s.unpack('m*').first
|
154
|
+
when 'base64encode'
|
155
|
+
doc[k] = [doc[k].to_s].pack('m*').gsub(/\s+/n, '')
|
156
|
+
when 'qprintdecode'
|
157
|
+
doc[k] = doc[k].to_s.gsub(/=([0-9A-Fa-f]{2})/n){ |x| [x[1,2]].pack("H*") }
|
158
|
+
when 'qprintencode'
|
159
|
+
doc[k] = doc[k].to_s.gsub(/[\x00-\x20\x3d\x7f-\xff]/n){|x| ( "=%.2x" % x.unpack("C").first ).upcase }
|
160
|
+
when 'hexdecode'
|
161
|
+
doc[k] = [ doc[k].to_s ].pack("H*")
|
162
|
+
when 'hexencode'
|
163
|
+
doc[k] = doc[k].to_s.unpack("H*").first
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
[ doc ]
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
class FilterTruncate
|
172
|
+
include Base
|
173
|
+
def process(doc)
|
174
|
+
self.opts.each_pair do |k,v|
|
175
|
+
if doc.has_key?(k)
|
176
|
+
doc[k] = doc[k].to_s[0, v.to_i]
|
177
|
+
end
|
178
|
+
end
|
179
|
+
[ doc ]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class FilterSplitLine
|
184
|
+
include Base
|
185
|
+
def process(doc)
|
186
|
+
lines = [ ]
|
187
|
+
self.opts.each_pair do |k,v|
|
188
|
+
if doc.has_key?(k)
|
189
|
+
doc[k].to_s.split(/\n/).each do |line|
|
190
|
+
lines << doc.merge({ "#{k}.line" => line })
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
lines.length == 0 ? [ doc ] : [ lines ]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
class FilterSplitWord
|
199
|
+
include Base
|
200
|
+
def process(doc)
|
201
|
+
lines = [ ]
|
202
|
+
self.opts.each_pair do |k,v|
|
203
|
+
if doc.has_key?(k)
|
204
|
+
doc[k].to_s.split(/\W/).each do |line|
|
205
|
+
lines << doc.merge({ "#{k}.word" => line })
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
lines.length == 0 ? [ doc ] : [ lines ]
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
class FilterSplitTab
|
214
|
+
include Base
|
215
|
+
def process(doc)
|
216
|
+
lines = [ ]
|
217
|
+
self.opts.each_pair do |k,v|
|
218
|
+
if doc.has_key?(k)
|
219
|
+
doc[k].to_s.split(/\t/).each do |line|
|
220
|
+
lines << doc.merge({ "#{k}.tab" => line })
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
lines.length == 0 ? [ doc ] : [ lines ]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
class FilterSplitComma
|
230
|
+
include Base
|
231
|
+
def process(doc)
|
232
|
+
lines = [ ]
|
233
|
+
self.opts.each_pair do |k,v|
|
234
|
+
if doc.has_key?(k)
|
235
|
+
doc[k].to_s.split(/m/).each do |line|
|
236
|
+
lines << doc.merge({ "#{k}.word" => line })
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
lines.length == 0 ? [ doc ] : [ lines ]
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
class FilterSplitArray
|
245
|
+
include Base
|
246
|
+
def process(doc)
|
247
|
+
lines = [ ]
|
248
|
+
self.opts.each_pair do |k,v|
|
249
|
+
if doc.has_key?(k) and doc[k].respond_to?(:each)
|
250
|
+
doc[k].each do |line|
|
251
|
+
lines << doc.merge({ "#{k}.item" => line })
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
lines.length == 0 ? [ doc ] : [ lines ]
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class FilterFieldSplitLine
|
260
|
+
include Base
|
261
|
+
def process(doc)
|
262
|
+
self.opts.each_pair do |k,v|
|
263
|
+
if doc.has_key?(k)
|
264
|
+
lcount = 1
|
265
|
+
doc[k].to_s.split(/\n/).each do |line|
|
266
|
+
doc.merge!({ "#{k}.f#{lcount}" => line })
|
267
|
+
lcount += 1
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
[ doc ]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
class FilterFieldSplitWord
|
276
|
+
include Base
|
277
|
+
def process(doc)
|
278
|
+
self.opts.each_pair do |k,v|
|
279
|
+
if doc.has_key?(k)
|
280
|
+
wcount = 1
|
281
|
+
doc[k].to_s.split(/\W/).each do |word|
|
282
|
+
doc.merge!({ "#{k}.f#{wcount}" => word })
|
283
|
+
wcount += 1
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
[ doc ]
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
class FilterFieldSplitTab
|
292
|
+
include Base
|
293
|
+
def process(doc)
|
294
|
+
self.opts.each_pair do |k,v|
|
295
|
+
if doc.has_key?(k)
|
296
|
+
wcount = 1
|
297
|
+
doc[k].to_s.split(/\t/).each do |word|
|
298
|
+
doc.merge!({ "#{k}.f#{wcount}" => word })
|
299
|
+
wcount += 1
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
303
|
+
[ doc ]
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
class FilterFieldSplitComma
|
308
|
+
include Base
|
309
|
+
def process(doc)
|
310
|
+
self.opts.each_pair do |k,v|
|
311
|
+
if doc.has_key?(k)
|
312
|
+
wcount = 1
|
313
|
+
doc[k].to_s.split(/,/).each do |word|
|
314
|
+
doc.merge!({ "#{k}.f#{wcount}" => word })
|
315
|
+
wcount += 1
|
316
|
+
end
|
317
|
+
end
|
318
|
+
end
|
319
|
+
[ doc ]
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
class FilterFieldSplitArray
|
324
|
+
include Base
|
325
|
+
def process(doc)
|
326
|
+
self.opts.each_pair do |k,v|
|
327
|
+
if doc.has_key?(k) and doc[k].respond_to?(:each)
|
328
|
+
wcount = 1
|
329
|
+
doc[k].each do |word|
|
330
|
+
doc.merge!({ "#{k}.f#{wcount}" => word })
|
331
|
+
wcount += 1
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
[ doc ]
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
end
|
340
|
+
end
|
@@ -0,0 +1,401 @@
|
|
1
|
+
module Dap
|
2
|
+
module Filter
|
3
|
+
|
4
|
+
require 'openssl'
|
5
|
+
require 'net/dns'
|
6
|
+
require 'bit-struct'
|
7
|
+
|
8
|
+
require 'dap/proto/addp'
|
9
|
+
require 'dap/proto/dtls'
|
10
|
+
require 'dap/proto/natpmp'
|
11
|
+
require 'dap/proto/wdbrpc'
|
12
|
+
require 'dap/proto/ipmi'
|
13
|
+
require 'dap/utils/oui'
|
14
|
+
|
15
|
+
#
|
16
|
+
# Decode a MDNS Services probe response ( zmap: mdns_5353.pkt )
|
17
|
+
#
|
18
|
+
class FilterDecodeMDNSSrvReply
|
19
|
+
include BaseDecoder
|
20
|
+
def decode(data)
|
21
|
+
begin
|
22
|
+
r = Net::DNS::Packet.parse(data)
|
23
|
+
return if not r
|
24
|
+
|
25
|
+
# XXX: This can throw an exception on bad data
|
26
|
+
svcs = r.answer.map {|x| (x.value.to_s) }
|
27
|
+
svcs.delete('')
|
28
|
+
return if not (svcs and svcs.length > 0)
|
29
|
+
return { "mdns_services" => svc.join(" ") }
|
30
|
+
rescue ::Exception
|
31
|
+
end
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Decode a DNS bind.version probe response ( zmap: dns_53.pkt )
|
38
|
+
#
|
39
|
+
class FilterDecodeDNSVersionReply
|
40
|
+
include BaseDecoder
|
41
|
+
def decode(data)
|
42
|
+
begin
|
43
|
+
r = Net::DNS::Packet.parse(data)
|
44
|
+
return if not r
|
45
|
+
|
46
|
+
# XXX: This can throw an exception on bad data
|
47
|
+
vers = r.answer.map{|x| x.txt.strip rescue nil }.reject{|x| x.nil? }.first
|
48
|
+
return if not vers
|
49
|
+
return { "dns_version" => vers }
|
50
|
+
rescue ::Exception
|
51
|
+
{ }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Decode a SSDP probe response ( zmap: upnp_1900.pkt )
|
58
|
+
#
|
59
|
+
class FilterDecodeUPNP_SSDP_Reply
|
60
|
+
include BaseDecoder
|
61
|
+
def decode(data)
|
62
|
+
head = { }
|
63
|
+
data.split(/\n/).each do |line|
|
64
|
+
k,v = line.strip.split(':', 2)
|
65
|
+
next if not k
|
66
|
+
head["upnp_#{k.downcase}"] = (v.to_s.strip)
|
67
|
+
end
|
68
|
+
head
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Decode a VxWorks WDBRPC probe response ( zmap: wdbrpc_17185.pkt )
|
74
|
+
#
|
75
|
+
class FilterDecodeWDBRPC_Reply
|
76
|
+
include BaseDecoder
|
77
|
+
def decode(data)
|
78
|
+
info = {}
|
79
|
+
head = buff.slice!(0,36)
|
80
|
+
info['agent_ver'] = wdbrpc_decode_str(buff)
|
81
|
+
info['agent_mtu'] = wdbrpc_decode_int(buff)
|
82
|
+
info['agent_mod'] = wdbrpc_decode_int(buff)
|
83
|
+
info['rt_type'] = wdbrpc_decode_int(buff)
|
84
|
+
info['rt_vers'] = wdbrpc_decode_str(buff)
|
85
|
+
info['rt_cpu_type'] = wdbrpc_decode_int(buff)
|
86
|
+
info['rt_has_fpp'] = wdbrpc_decode_bool(buff)
|
87
|
+
info['rt_has_wp'] = wdbrpc_decode_bool(buff)
|
88
|
+
info['rt_page_size'] = wdbrpc_decode_int(buff)
|
89
|
+
info['rt_endian'] = wdbrpc_decode_int(buff)
|
90
|
+
info['rt_bsp_name'] = wdbrpc_decode_str(buff)
|
91
|
+
info['rt_bootline'] = wdbrpc_decode_str(buff)
|
92
|
+
info['rt_membase'] = wdbrpc_decode_int(buff)
|
93
|
+
info['rt_memsize'] = wdbrpc_decode_int(buff)
|
94
|
+
info['rt_region_count'] = wdbrpc_decode_int(buff)
|
95
|
+
info['rt_regions'] = wdbrpc_decode_arr(buff, :int)
|
96
|
+
info['rt_hostpool_base'] = wdbrpc_decode_int(buff)
|
97
|
+
info['rt_hostpool_size'] = wdbrpc_decode_int(buff)
|
98
|
+
info
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
#
|
103
|
+
# Decode a SNMP GET probe response ( zmap: snmp1_161.pkt )
|
104
|
+
#
|
105
|
+
class FilterDecodeSNMPGetReply
|
106
|
+
include BaseDecoder
|
107
|
+
def decode(data)
|
108
|
+
asn = OpenSSL::ASN1.decode(data) rescue nil
|
109
|
+
return if not asn
|
110
|
+
|
111
|
+
snmp_error = asn.value[0].value rescue nil
|
112
|
+
snmp_comm = asn.value[1].value rescue nil
|
113
|
+
snmp_data = asn.value[2].value[3].value[0] rescue nil
|
114
|
+
snmp_oid = snmp_data.value[0].value rescue nil
|
115
|
+
snmp_info = snmp_data.value[1].value rescue nil
|
116
|
+
|
117
|
+
return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info)
|
118
|
+
snmp_info = snmp_info.to_s.gsub(/\s+/, ' ').gsub(/[\x00-\x1f]/, ' ')
|
119
|
+
|
120
|
+
return if not snmp_info
|
121
|
+
{ 'snmp_value' => snmp_info }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# Decode a IPMI GetChannelAuth probe response ( zmap: ipmi_623.pkt )
|
127
|
+
#
|
128
|
+
class FilterDecodeIPMIChanAuthReply
|
129
|
+
include BaseDecoder
|
130
|
+
def decode(data)
|
131
|
+
info = Dap::Proto::IPMI::Channel_Auth_Reply.new(data)
|
132
|
+
return unless info.valid?
|
133
|
+
{}.tap do |h|
|
134
|
+
info.fields.each do |f|
|
135
|
+
name = f.name
|
136
|
+
h[name] = info.send(name).to_s
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Decode a NAT-PMP External Address response
|
144
|
+
#
|
145
|
+
class FilterDecodeNATPMPExternalAddressResponse
|
146
|
+
include BaseDecoder
|
147
|
+
def decode(data)
|
148
|
+
return unless (data && data.size == Dap::Proto::NATPMP::REQUIRED_SIZE)
|
149
|
+
info = Dap::Proto::NATPMP::ExternalAddressResponse.new(data)
|
150
|
+
{}.tap do |h|
|
151
|
+
info.fields.each do |f|
|
152
|
+
name = f.name
|
153
|
+
h[name] = info.send(name).to_s
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Decode a NetBIOS status probe response ( zmap: netbios_137.pkt )
|
161
|
+
#
|
162
|
+
class FilterDecodeNetbiosStatusReply
|
163
|
+
include BaseDecoder
|
164
|
+
def decode(data)
|
165
|
+
ret = {}
|
166
|
+
head = data.slice!(0,12)
|
167
|
+
|
168
|
+
xid, flags, quests, answers, auths, adds = head.unpack('n6')
|
169
|
+
return if quests != 0
|
170
|
+
return if answers == 0
|
171
|
+
|
172
|
+
qname = data.slice!(0,34)
|
173
|
+
rtype,rclass,rttl,rlen = data.slice!(0,10).unpack('nnNn')
|
174
|
+
return if not rlen
|
175
|
+
|
176
|
+
buff = data.slice!(0,rlen)
|
177
|
+
|
178
|
+
names = []
|
179
|
+
|
180
|
+
case rtype
|
181
|
+
when 0x21
|
182
|
+
hname = nil
|
183
|
+
inf = ''
|
184
|
+
rcnt = buff.slice!(0,1).unpack("C")[0]
|
185
|
+
return unless rcnt
|
186
|
+
1.upto(rcnt) do
|
187
|
+
tname = buff.slice!(0,15).gsub(/\x00.*/, '').strip
|
188
|
+
ttype = buff.slice!(0,1).unpack("C")[0]
|
189
|
+
tflag = buff.slice!(0,2).unpack('n')[0]
|
190
|
+
names << [ tname, ttype, tflag ]
|
191
|
+
end
|
192
|
+
|
193
|
+
maddr = buff.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":")
|
194
|
+
names.each do |name|
|
195
|
+
inf << name[0]
|
196
|
+
|
197
|
+
next unless name[1]
|
198
|
+
inf << ":%.2x" % name[1]
|
199
|
+
|
200
|
+
next unless name[2]
|
201
|
+
if (name[2] & 0x8000 == 0)
|
202
|
+
inf << ":U "
|
203
|
+
else
|
204
|
+
inf << ":G "
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
return unless names.length > 0
|
210
|
+
|
211
|
+
{}.tap do |hash|
|
212
|
+
hash['netbios_names'] = (inf)
|
213
|
+
hash['netbios_mac'] = maddr
|
214
|
+
hash['netbios_hname'] = names[0][0]
|
215
|
+
unless maddr == '00:00:00:00:00:00'
|
216
|
+
hash['netbios_mac_company'] = mac_company(maddr)
|
217
|
+
hash['netbios_mac_company_name'] = mac_company_name(maddr)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def mac_company(address)
|
223
|
+
begin
|
224
|
+
name = Dap::Utils::Oui.lookup_oui_fullname(address)
|
225
|
+
name.split("/").first.strip
|
226
|
+
rescue => error
|
227
|
+
''
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def mac_company_name(address)
|
232
|
+
begin
|
233
|
+
Dap::Utils::Oui.lookup_oui_company_name(address)
|
234
|
+
rescue => error
|
235
|
+
''
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
#
|
240
|
+
# Decode a MSSQL reply
|
241
|
+
#
|
242
|
+
class FilterDecodeMSSQLReply
|
243
|
+
include BaseDecoder
|
244
|
+
def decode(data)
|
245
|
+
info = {}
|
246
|
+
# Some binary characters often proceed key, restrict to alphanumeric and a few other common chars
|
247
|
+
data.scan(/([A-Za-z0-9 \.\-_]+?);(.+?);/).each do | var, val|
|
248
|
+
info["mssql.#{var.encode!( 'UTF-8', invalid: :replace, undef: :replace, replace: '' )}"] = val.encode!( 'UTF-8', invalid: :replace, undef: :replace, replace: '' )
|
249
|
+
end
|
250
|
+
info
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
#
|
255
|
+
# Decode a SIP OPTIONS Reply
|
256
|
+
#
|
257
|
+
class FilterDecodeSIPOptionsReply
|
258
|
+
include BaseDecoder
|
259
|
+
def decode(data)
|
260
|
+
info = {}
|
261
|
+
|
262
|
+
return info unless (data and data.length > 0)
|
263
|
+
|
264
|
+
head,body = data.to_s.split(/\r?\n\r?\n/, 2)
|
265
|
+
|
266
|
+
head.split(/\r?\n/).each do |line|
|
267
|
+
case line
|
268
|
+
when /^SIP\/(\d+\.\d+) (\d+)(.*)/
|
269
|
+
info['sip_version'] = $1
|
270
|
+
info['sip_code'] = $2
|
271
|
+
if $3.length > 0
|
272
|
+
info['sip_message'] = $3.strip
|
273
|
+
end
|
274
|
+
when /^([a-zA-z0-9][^:]+):(.*)/
|
275
|
+
var = $1.strip
|
276
|
+
val = $2.strip
|
277
|
+
var = var.downcase.gsub(/[^a-zA-Z0-9_]/, '_').gsub(/_+/, '_')
|
278
|
+
info["sip_#{var}"] = val
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
if body and body.length > 0
|
283
|
+
info['sip_data'] = body
|
284
|
+
end
|
285
|
+
|
286
|
+
info
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
#
|
291
|
+
# Quickly decode a DTLS message
|
292
|
+
#
|
293
|
+
class FilterDecodeDTLS
|
294
|
+
include BaseDecoder
|
295
|
+
def decode(data)
|
296
|
+
return unless data.length >= 13
|
297
|
+
info = Dap::Proto::DTLS::RecordLayer.new(data)
|
298
|
+
return unless (info && info.valid?)
|
299
|
+
{}.tap do |h|
|
300
|
+
info.fields.each do |f|
|
301
|
+
name = f.name
|
302
|
+
h[name] = info.send(name).to_s
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
#
|
309
|
+
# Decode a NTP reply
|
310
|
+
#
|
311
|
+
class FilterDecodeNTPReply
|
312
|
+
include BaseDecoder
|
313
|
+
def decode(sdata)
|
314
|
+
info = {}
|
315
|
+
return if sdata.length < 4
|
316
|
+
|
317
|
+
# Make a copy since our parser is destructive
|
318
|
+
data = sdata.dup
|
319
|
+
|
320
|
+
# TODO: all of this with bitstruct?
|
321
|
+
# The format of the packet depends largely on the version, so extract just the version.
|
322
|
+
# Fortunately the version is in the same place regardless of NTP protocol version --
|
323
|
+
# The 3rd-5th bits of the first byte of the response
|
324
|
+
ntp_flags = data.slice!(0,1).unpack('C').first
|
325
|
+
ntp_version = (ntp_flags & 0b00111000) >> 3
|
326
|
+
info['ntp.version'] = ntp_version
|
327
|
+
|
328
|
+
# NTP 2 & 3 share a common header, so parse those together
|
329
|
+
if ntp_version == 2 || ntp_version == 3
|
330
|
+
# 0 1 2 3
|
331
|
+
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
332
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
333
|
+
# |R|M| VN | Mode|A| Sequence | Implementation| Req Code |
|
334
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
335
|
+
|
336
|
+
info['ntp.response'] = ntp_flags >> 7
|
337
|
+
info['ntp.more'] = (ntp_flags & 0b01000000) >> 6
|
338
|
+
info['ntp.mode'] = (ntp_flags & 0b00000111)
|
339
|
+
ntp_auth_seq, ntp_impl, ntp_rcode = data.slice!(0,3).unpack('C*')
|
340
|
+
info['ntp.implementation'] = ntp_impl
|
341
|
+
info['ntp.request_code'] = ntp_rcode
|
342
|
+
|
343
|
+
# if it is mode 7, parse that:
|
344
|
+
# 0 1 2 3
|
345
|
+
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
346
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
347
|
+
# | Err | Number of data items | MBZ | Size of data item |
|
348
|
+
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
349
|
+
# ... data ...
|
350
|
+
if info['ntp.mode'] == 7
|
351
|
+
return info if data.size < 4
|
352
|
+
mode7_data = data.slice!(0,4).unpack('n*')
|
353
|
+
info['ntp.mode7.err'] = mode7_data.first >> 11
|
354
|
+
info['ntp.mode7.data_items_count'] = mode7_data.first & 0b0000111111111111
|
355
|
+
info['ntp.mode7.mbz'] = mode7_data.last >> 11
|
356
|
+
info['ntp.mode7.data_item_size'] = mode7_data.last & 0b0000111111111111
|
357
|
+
|
358
|
+
# extra monlist response data
|
359
|
+
if ntp_rcode == 42
|
360
|
+
if info['ntp.mode7.data_item_size'] == 72
|
361
|
+
remote_addresses = []
|
362
|
+
local_addresses = []
|
363
|
+
idx = 0
|
364
|
+
1.upto(info['ntp.mode7.data_items_count']) do
|
365
|
+
|
366
|
+
#u_int32 firsttime; /* first time we received a packet */
|
367
|
+
#u_int32 lasttime; /* last packet from this host */
|
368
|
+
#u_int32 restr; /* restrict bits (was named lastdrop) */
|
369
|
+
#u_int32 count; /* count of packets received */
|
370
|
+
#u_int32 addr; /* host address V4 style */
|
371
|
+
#u_int32 daddr; /* destination host address */
|
372
|
+
#u_int32 flags; /* flags about destination */
|
373
|
+
#u_short port; /* port number of last reception */
|
374
|
+
|
375
|
+
firsttime,lasttime,restr,count,raddr,laddr,flags,dport = data[idx, 30].unpack("NNNNNNNn")
|
376
|
+
remote_addresses << [raddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".")
|
377
|
+
local_addresses << [laddr].pack("N").unpack("C*").map{|x| x.to_s }.join(".")
|
378
|
+
idx += info['ntp.mode7.data_item_size']
|
379
|
+
end
|
380
|
+
|
381
|
+
info['ntp.monlist.remote_addresses'] = remote_addresses.join(' ')
|
382
|
+
info['ntp.monlist.remote_addresses.count'] = remote_addresses.size
|
383
|
+
info['ntp.monlist.local_addresses'] = local_addresses.join(' ')
|
384
|
+
info['ntp.monlist.local_addresses.count'] = local_addresses.size
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
elsif ntp_version == 4
|
389
|
+
info['ntp.leap_indicator'] = ntp_flags >> 6
|
390
|
+
info['ntp.mode'] = ntp_flags & 0b00000111
|
391
|
+
info['ntp.peer.stratum'], info['ntp.peer.interval'], info['ntp.peer.precision'] = data.slice!(0,3).unpack('C*')
|
392
|
+
info['ntp.root.delay'], info['ntp.root.dispersion'], info['ntp.ref_id'] = data.slice!(0,12).unpack('N*')
|
393
|
+
info['ntp.timestamp.reference'], info['ntp.timestamp.origin'], info['ntp.timestamp.receive'], info['ntp.timestamp.transmit'] = data.slice!(0,32).unpack('Q*')
|
394
|
+
end
|
395
|
+
|
396
|
+
info
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
end
|