dap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +6 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +15 -0
  4. data/Gemfile.lock +55 -0
  5. data/LICENSE +20 -0
  6. data/README.md +15 -0
  7. data/bin/dap +137 -0
  8. data/dap.gemspec +42 -0
  9. data/data/.gitkeep +0 -0
  10. data/lib/dap.rb +101 -0
  11. data/lib/dap/filter.rb +8 -0
  12. data/lib/dap/filter/base.rb +37 -0
  13. data/lib/dap/filter/geoip.rb +72 -0
  14. data/lib/dap/filter/http.rb +173 -0
  15. data/lib/dap/filter/names.rb +151 -0
  16. data/lib/dap/filter/openssl.rb +53 -0
  17. data/lib/dap/filter/recog.rb +23 -0
  18. data/lib/dap/filter/simple.rb +340 -0
  19. data/lib/dap/filter/udp.rb +401 -0
  20. data/lib/dap/input.rb +74 -0
  21. data/lib/dap/input/csv.rb +60 -0
  22. data/lib/dap/input/warc.rb +81 -0
  23. data/lib/dap/output.rb +117 -0
  24. data/lib/dap/proto/addp.rb +0 -0
  25. data/lib/dap/proto/dtls.rb +21 -0
  26. data/lib/dap/proto/ipmi.rb +94 -0
  27. data/lib/dap/proto/natpmp.rb +19 -0
  28. data/lib/dap/proto/wdbrpc.rb +58 -0
  29. data/lib/dap/utils/oui.rb +16586 -0
  30. data/lib/dap/version.rb +3 -0
  31. data/samples/http_get_reply.ic12.bz2 +0 -0
  32. data/samples/http_get_reply.ic12.sh +1 -0
  33. data/samples/http_get_reply_iframes.json.bz2 +0 -0
  34. data/samples/http_get_reply_iframes.json.sh +1 -0
  35. data/samples/http_get_reply_links.json.sh +1 -0
  36. data/samples/iawide.warc.bz2 +0 -0
  37. data/samples/iawide_warc.sh +1 -0
  38. data/samples/ipmi_chan_auth_replies.crd.bz2 +0 -0
  39. data/samples/ipmi_chan_auth_replies.sh +1 -0
  40. data/samples/ssl_certs.bz2 +0 -0
  41. data/samples/ssl_certs_geo.sh +1 -0
  42. data/samples/ssl_certs_names.sh +1 -0
  43. data/samples/ssl_certs_names_expanded.sh +1 -0
  44. data/samples/ssl_certs_org.sh +1 -0
  45. data/samples/udp-netbios.csv.bz2 +0 -0
  46. data/samples/udp-netbios.sh +1 -0
  47. data/spec/dap/proto/ipmi_spec.rb +19 -0
  48. data/tools/geo-ip-summary.rb +149 -0
  49. data/tools/ipmi-vulns.rb +27 -0
  50. data/tools/json-summarize.rb +81 -0
  51. data/tools/netbios-counts.rb +271 -0
  52. data/tools/upnp-vulns.rb +35 -0
  53. data/tools/value-counts-to-md-table.rb +23 -0
  54. 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