dorothy2 1.1.0 → 1.2.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.
@@ -1,3 +1,6 @@
1
+ #!/bin/env ruby
2
+ # encoding: utf-8
3
+
1
4
  # Copyright (C) 2010-2013 marco riccardi.
2
5
  # This file is part of Dorothy - http://www.honeynet.it/
3
6
  # See the file 'LICENSE' for copying permission.
@@ -19,14 +22,11 @@ require 'filemagic'
19
22
  require 'rbvmomi'
20
23
  require 'timeout'
21
24
  require 'virustotal'
22
- require 'ftools' #deprecated in ruby 1.9 !!!
23
- require 'filemagic'
24
- require 'md5'
25
+ require 'digest'
25
26
 
26
27
  require File.dirname(__FILE__) + '/dorothy2/do-init'
27
28
  require File.dirname(__FILE__) + '/dorothy2/Settings'
28
29
  require File.dirname(__FILE__) + '/dorothy2/deep_symbolize'
29
- require File.dirname(__FILE__) + '/dorothy2/environment'
30
30
  require File.dirname(__FILE__) + '/dorothy2/vtotal'
31
31
  require File.dirname(__FILE__) + '/dorothy2/VSM'
32
32
  require File.dirname(__FILE__) + '/dorothy2/NAM'
@@ -225,7 +225,7 @@ module Dorothy
225
225
 
226
226
  DoroSettings.sandbox[:num_screenshots].times do
227
227
  @screenshots.push vsm.screenshot
228
- sleep DoroSettings.sandbox[:screen2time] % DoroSettings.sandbox[:sleeptime]
228
+ sleep DoroSettings.sandbox[:screen2time] % DoroSettings.sandbox[:sleeptime] if DoroSettings.sandbox[:screen2time]
229
229
  end
230
230
 
231
231
  sleep DoroSettings.sandbox[:sleeptime]
@@ -268,7 +268,7 @@ module Dorothy
268
268
  File.chmod(0644, bin.dir_screens + File.basename(screen), bin.dir_screens + File.basename(screen) )
269
269
  end
270
270
 
271
- #UPDATE DOROTHIBE DB###################################
271
+ #UPDATE DOROTHIVE DB###################################
272
272
 
273
273
  dump = Loadmalw.new(bin.dir_pcap + dumpname + ".pcap")
274
274
 
@@ -471,7 +471,10 @@ module Dorothy
471
471
 
472
472
  def self.start(source=nil, daemon=nil)
473
473
 
474
+ @vtotal_threads = []
475
+ @analysis_threads = []
474
476
  @db = Insertdb.new
477
+
475
478
  daemon ||= false
476
479
 
477
480
  puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Process Started"
@@ -493,9 +496,7 @@ module Dorothy
493
496
  #Be sure that there are no open tcpdump instances opened
494
497
  @nam.init_sniffer
495
498
 
496
- @vtotal_threads = []
497
- @vtotal_threads = []
498
- @analysis_threads = []
499
+
499
500
 
500
501
  infinite = true
501
502
 
@@ -114,7 +114,7 @@ module Dorothy
114
114
  db = Insertdb.new
115
115
  db.begin_t
116
116
 
117
- unless db.select("samples", "hash", samplevalues[0]).one? #is bin.sha already present in my db?
117
+ unless db.select("samples", "sha256", samplevalues[0]).one? #is bin.sha already present in my db?
118
118
  raise "A DB error occurred" unless db.insert("samples", samplevalues) #no it isn't, insert it
119
119
 
120
120
  else #yes it is, don't insert in sample table
@@ -16,453 +16,376 @@
16
16
 
17
17
  module DoroParser
18
18
 
19
- class Mydns
20
- def initialize(data)
21
- raw = data.to_s.gsub(/(\000|\001|\002|\003|\004|\005|\006|\007|\008|\009|\010|\011|\012|\013|\014|\015|\016|\017|\018|\019|\020|\021\022|\023|\024|\025|\026|\027)/, '.')
22
- @query = raw.grep(/([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}/){$&}
23
- end
24
-
25
- def query
26
- return @query if @query
27
- end
28
-
29
- def query?
30
- return true if @query
31
- end
32
-
33
- def dns_class
34
- #todo
35
- end
36
-
37
- def type
38
- #todo
39
- end
40
-
41
- def response
42
- #todo
43
- end
44
- end
45
-
46
-
47
- class Parser_old
48
-
49
- def initialize(data)
50
- noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
51
- if data and data =~ /(USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG |KCIK |rssr )(.*)\n/
52
- @irc = true
53
- @command = $1
54
- begin
55
- #@command2 = noutf.iconv($2).gsub(/"|'|\\/, "-")
56
- rescue
57
- @command2 = "null"
58
- end
59
- elsif data =~ /from\W*([0-9]{1,3}(\.[0-9]{1,3}){3}).*by\W*(([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,6}).*From:.*<(\S+)>\W*To:.*<(\S+)>.*Subject:(.*)\W*Date/m
60
- @mail = true
61
- @fromip = $1
62
- @by =$3
63
- @from = $6
64
- @to = $7
65
- @subj = $8.chomp.lstrip
66
-
67
-
68
- elsif data and data =~ /^(GET|POST)\s+(\S+)/
69
- @http = true
70
- @method = $1
71
- @path = $2
72
-
73
- end
74
- end
75
-
76
- end
77
-
78
-
79
- class Parser
80
- attr_reader :service
81
-
82
- def self.guess(a)
83
-
84
- if a =~ /USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG/
85
- t = IRC.new a
86
- elsif a =~ /from(.*)From(.*)Subject/m
87
- t = SMTP.init a
88
- elsif a =~ /(\d*)(.*)\n/
89
- t = SMTP.new a
90
- end
91
-
92
- return t
93
- end
94
-
95
-
96
-
97
-
98
- class IRC
99
- attr_reader :command
100
- attr_reader :content
101
-
102
-
103
- def initialize(data)
104
- if data =~ /(USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG )(.*)\n/
105
- noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
106
- @command = $1
107
- @content = noutf.iconv($2).gsub(/"|'|\\/, "-") #xcode bug ->>> ")
108
- end
109
- #return true if !@command.nil?
110
- return self
111
- end
112
-
113
-
114
-
115
- end
116
-
117
-
118
-
119
- class SMTP
120
- #todo make a initialize class
121
- attr_reader :hcmd
122
- attr_reader :hcont
123
- attr_accessor :body
124
- attr_accessor :rdata
125
-
126
- def self.body(data)
127
-
128
- email = TMail::Mail.parse(data)
129
- return email
130
-
131
- end
132
-
133
- def self.header?(data)
134
-
135
- if data =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
136
- return true
137
- end
138
-
139
- end
140
-
141
- def self.hasbody?(data)
142
-
143
- if data =~ /from(.*)From(.*)Subject/m
144
- return true
145
- end
146
-
147
- end
148
-
149
- def self.response(data)
150
-
151
- if data =~ /(\d*)(.*)\n/
152
- rcode = $1
153
- rcont = $2
154
- return rcode, rcont
155
- end
156
-
157
- end
158
-
159
- def initialize a
160
-
161
- if a =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
162
- @hcmd = $1
163
- @hcont = $2
164
- end
165
-
166
- end
167
-
168
- end
169
-
170
- end
171
-
172
- class Geoinfo
173
- attr_reader :updated
174
- attr_reader :country
175
- attr_reader :coord
176
- attr_reader :asn
177
- attr_reader :city
178
- attr_reader :updated
179
-
180
-
181
- @updated = "null"
182
- LOCALNET = "10.10.10.0/24"
183
-
184
-
185
-
186
- def initialize(ip)
187
- noutf = Iconv.new('US-ASCII//TRANSLIT', 'UTF-8')
188
- @updated = 'null' #TODO take the creation file date of the .dat archive
189
-
190
- #year = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$2}
191
- #month = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$3}
192
- #day = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$4}
193
- #@updated = year.to_s + "/" + month.to_s + "/" + day.to_s
194
- localnetwork = IPAddr.new(LOCALNET)
195
-
196
- if !localnetwork.include?(ip)
197
-
198
- begin
199
-
200
- geoip = GeoIP.new(DoroSettings.env[:geoip])
201
- geoasn = GeoIP.new(DoroSettings.env[:geoasn])
202
-
203
- if geoip.country(ip)
204
- @city = noutf.iconv(geoip.country(ip).city_name).gsub(/"|'|\\/, "-") #xcode bug ->>> ")
205
- @country = geoip.country(ip).country_code2
206
- @coord = geoip.country(ip).latitude.to_s.gsub(/\(|\)/,'') + "," + geoip.country(ip).longitude.to_s.gsub(/\(|\)/,'')
207
-
208
- else
209
-
210
- @city, @country, @coord = "null", "null", "null"
211
-
212
- end
213
-
214
- @asn = (geoasn.asn(ip) ? geoasn.asn(ip).as_num.to_s.grep(/\d+/){$&} : "null" )
215
-
216
- rescue
217
- LOGGER_PARSER.fatal "GEO", "Error while fetching GeoIP dat file for IP: " + ip
218
- LOGGER_PARSER.fatal "GEO", "#{$!}"
219
- @city, @country, @coord, @asn = "null", "null", "null", "null"
220
- end
221
-
222
- else
223
- @city, @country, @coord, @asn = "null", "null", "null", "null"
224
-
225
- end
226
- end
227
-
228
- end
229
-
230
-
231
- class DoroHttp
232
- attr_reader :contype
233
- attr_reader :method
234
- attr_reader :uri
235
- attr_reader :ssl
236
- attr_reader :size
237
- attr_accessor :data
238
-
239
- def initialize(flowdeep)
240
- @data = 'null'
241
- @method = (flowdeep.values('http.request.method')[0] ? flowdeep.values('http.request.method')[0].value : 'null')
242
- @ssl = false #TODO
243
- @size = (flowdeep.values('http.content.length')[0] ? flowdeep.values('http.content.length')[0].value : 'null')
244
- @uri = (flowdeep.values('http.request.uri')[0] ? flowdeep.values('http.request.uri')[0].value : 'null' )
245
- @contype = (flowdeep.values('http.content.type')[0] ? flowdeep.values('http.content.type')[0].value : 'null')
246
- end
247
-
248
- end
249
-
250
- class DoroDNS
251
- attr_accessor :dns
252
- attr_accessor :ttl
253
- attr_accessor :name
254
- attr_accessor :type
255
- attr_accessor :type_i
256
- attr_accessor :cls
257
- attr_accessor :cls_i
258
- attr_accessor :address
259
- attr_accessor :data
260
-
261
- def initialize(c)
262
- @dns = Net::DNS::Packet::parse(c)
263
- if qry? #is a query
264
- q = @dns.question.first
265
- @cls_i = q.qClass #Net::DNS::Question.parse(c[12..offset]).qClass
266
- @name = q.qName
267
- @type_i = q.qType
268
- @ttl = 'null'
269
- @address = 'null'
270
- @data = 'null'
271
-
272
- elsif !@dns.answer.empty? #contain an asnwer
273
- #TODO only the first answer is parsed
274
- a = @dns.answer.each.first
275
- @ttl = a.ttl
276
- @name = a.name
277
-
278
- @type = a.type
279
- @type_i = Net::DNS::RR::Types.new @type
280
-
281
-
282
- @cls = a.cls
283
- @cls_i = Net::DNS::RR::Classes.new @cls
284
-
285
-
286
- case @type
287
-
288
- when "A"
289
- @address = @dns.answer.each.first.address
290
- @data = 'null'
291
-
292
- when "AAAA"
293
- @address = @dns.answer.each.first.address
294
- @data = 'null'
295
-
296
- when "MX" then
297
-
298
- @data = @dns.answer.each.first.exchange
299
- @address = 'null'
300
-
301
- when "CNAME" then
302
-
303
- @data = @dns.answer.each.first.cname
304
- @address = 'null'
305
-
306
- else
307
-
308
- @address = 'null'
309
- @data = 'null'
310
-
311
- end
312
- end
313
-
314
- end
315
-
316
-
317
- def qry?
318
- @dns.answer.empty?
319
- end
320
-
321
- end
322
-
323
- class DoroFile
324
- attr_accessor :sha2
325
- attr_accessor :cont
326
- attr_reader :path
327
- attr_reader :date
328
- attr_accessor :size
329
-
330
- def initialize(hash)
331
- repo = DoroSettings.env[:home] + "/downloads"
332
- @path = "#{repo}/#{hash}.exe"
333
- @date = Time.new.strftime("%m/%d/%y %H:%M:%S")
334
- end
335
-
336
- def self.sha2(content)
337
- @sha2 = Digest::SHA2.new
338
- @sha2 << content
339
- end
340
- end
341
-
342
-
343
- class Doroxtractr < Mu::Xtractr #PcaprLocal::Xtractr.new
344
-
345
- #def lol(id)
346
- # self.create "http://172.20.250.13:8080/pcaps/1/pcap/#{id}"
347
- #end
348
-
349
-
350
- def summaryhttp(fast=0, v=0)
351
- ids = []
352
- self.flows('flow.service:HTTP').each { |flow|
353
- method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
354
- if fast == 0
355
- puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method} - #{flow.stream.flow.contents.first.body.length}"
356
- else
357
- puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method}"
358
- end
359
- ids.push(flow.id)
360
- }
361
- return ids
362
- end
363
-
364
- def flowinfo(id)
365
- f = self.flows("flow.id:#{id}").first.inspect
366
- f << self.flows("flow.id:#{id}").first.time.to_s
367
- return f
368
- end
369
-
370
- #Find the HTTP requests made by the host (Zeus uses it to send stolen data to its dropzone)
371
- #The biggest post refers to the STATS one (by default is sent every 20 min)
372
- #the smallest post refers to the LOG one (by default is sent every minute)
373
- #the biggest GET refers to the Configuration file downloaded by the Zeus C&C
374
- def findzeusdata(re, type, cc='192.168.10.3')
375
- flowids = {}
376
- self.flows("flow.service:HTTP flow.dst: #{cc}").each do |flow|
377
- method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
378
- flowids[flow.id] = flow.stream.flow.contents.first.body.length if method =~ /#{Regexp.escape(re)}/
379
- end
380
- if type == "ping"
381
- return flowids.sort {|a,b| a[1]<=>b[1]}.first
382
- elsif type == "stat" || type == "conf"
383
- return flowids.sort {|a,b| a[1]<=>b[1]}.last
384
- else
385
- puts "Error, choose one argument from: ping, stat, conf"
386
- return 1
387
- end
388
- end
389
-
390
-
391
- #Find the HTTP GET request made by the host (Zeus uses it to send stolen data to its dropzone)
392
- #Is the first get request made to the C&C [!?]
393
- def findconfget
394
- self.flows("flow.service:HTTP flow.dst: #{cc}")
395
- end
396
-
397
- def summaryhttpmethod(re, fast=0)
398
- self.flows('flow.service:HTTP').each { |flow|
399
- flowdeep = self.flows("flow.id:#{flow.id}")
400
- if fast == 0
401
- puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{flow.stream.flow.contents.first.body.length}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
402
- else
403
- puts "#{flow.id} #{flow.src.address} > #{flow.dst.address}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
404
- end
405
- }
406
- end
407
-
408
- def flowsummary(verbose=0)
409
- self.flows.each { |flow|
410
- flowdeep = self.flows("flow.id:#{flow.id}")
411
- if verbose == 1
412
- puts "#{flow.id}: #{flow.time} : #{flow.src.address} > #{flow.dst.address} - #{flow.packets} - #{flow.bytes} - #{flow.duration} - #{flow.title}"
413
- else
414
- puts "| #{flow.id}: #{flow.src.address} > #{flow.service.name} > #{flow.dst.address} : #{flow.title}"
415
- end
416
- }
417
- end
418
-
419
-
420
- def summaryport(port)
421
- self.flows("flow.dport:#{port}").each do |f|
422
- f.contents.each do |c|
423
- puts "#{f.id}: #{flow.id} #{flow.src.address} > #{flow.dst.address} #{f.title} : #{c.body.length}"
424
- end
425
- end
426
- end
427
-
428
- def flowgrep(id, re)
429
- self.flows("flow.id:#{id}").each do |f|
430
- @t = false
431
- f.stream.each do |mex|
432
- if mex.bytes =~ /#{re}/
433
- puts "#{f.id}: > #{f.dst.address} - #{$1}"
434
- @t = true
435
- end
436
- end
437
- end
438
- return @t
439
- end
440
-
441
- def streamdata(id)
442
- data = []
443
- self.flows("flow.id:#{id}").each do |f|
444
- f.stream.each do |mex|
445
- t = [mex.bytes, mex.dir]
446
- data.push t
447
- end
448
- end
449
- return data
450
- end
451
-
452
-
453
-
454
-
455
- #Retrieve the content of a specified flow-ID
456
- def flowcontent(id)
457
- body = ""
458
- self.flows("flow.id:#{id}").each do |flow|
459
- flow.contents.each do |c|
460
- body << c.body
461
- end
462
- end
463
- return body
464
- end
465
-
466
- end
19
+ class Parser
20
+ attr_reader :service
21
+
22
+ def self.guess(a)
23
+
24
+ if a =~ /USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG/
25
+ t = IRC.new a
26
+ elsif a =~ /from(.*)From(.*)Subject/m
27
+ t = SMTP.init a
28
+ elsif a =~ /(\d*)(.*)\n/
29
+ t = SMTP.new a
30
+ else
31
+ t = nil
32
+ end
33
+
34
+ return t
35
+ end
36
+
37
+
38
+
39
+
40
+ class IRC
41
+ attr_reader :command
42
+ attr_reader :content
43
+
44
+
45
+ def initialize(data)
46
+ if data =~ /(USER |USERHOST |PRIVMSG |PASS |NICK |JOIN |MODE |MSG )(.*)\n/
47
+ @command = $1
48
+ @content = $2.force_encoding('UTF-8').gsub(/"|'|\\/, "-") #xcode bug ->>> ")
49
+ end
50
+ #return true if !@command.nil?
51
+ return self
52
+ end
53
+ end
54
+
55
+
56
+
57
+ class SMTP #todo to redo
58
+ attr_reader :hcmd
59
+ attr_reader :hcont
60
+ attr_accessor :body
61
+ attr_accessor :rdata
62
+
63
+ def self.body(data)
64
+ email = TMail::Mail.parse(data)
65
+ return email
66
+ end
67
+
68
+ def self.header?(data)
69
+ if data =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
70
+ return true
71
+ end
72
+ end
73
+
74
+ def self.hasbody?(data)
75
+ if data =~ /from(.*)From(.*)Subject/m
76
+ return true
77
+ end
78
+ end
79
+
80
+ def self.response(data)
81
+ if data =~ /(\d*)(.*)\n/
82
+ rcode = $1
83
+ rcont = $2
84
+ return rcode, rcont
85
+ end
86
+ end
87
+
88
+ def initialize a
89
+ if a =~ /(MAIL FROM: |EHLO |HELO |TO: |RSET)(.*)\n/
90
+ @hcmd = $1
91
+ @hcont = $2
92
+ end
93
+ end
94
+
95
+ end
96
+
97
+ end
98
+
99
+ class Geoinfo
100
+ attr_reader :updated
101
+ attr_reader :country
102
+ attr_reader :coord
103
+ attr_reader :asn
104
+ attr_reader :isp
105
+ attr_reader :city
106
+ attr_reader :updated
107
+
108
+ @updated = "null"
109
+
110
+ def initialize(ip)
111
+ @updated = 'null' #TODO take the creation file date of the .dat archive
112
+
113
+ #year = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$2}
114
+ #month = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$3}
115
+ #day = geoip.database_info.grep(/(\S+) (\d{4})(\d{2})(\d{2})/){$4}
116
+ #@updated = year.to_s + "/" + month.to_s + "/" + day.to_s
117
+ localnetwork = IPAddr.new(DoroSettings.sandbox[:network])
118
+
119
+ if !localnetwork.include?(ip)
120
+ begin
121
+
122
+ geoip = GeoIP.new(DoroSettings.env[:geoip])
123
+ geoasn = GeoIP.new(DoroSettings.env[:geoasn])
124
+
125
+ if geoip.country(ip)
126
+ @city = geoip.country(ip).city_name.force_encoding('UTF-8').gsub(/"|'|\\/, "-") #xcode bug ->>> ")
127
+ @country = geoip.country(ip).country_code2
128
+ @coord = geoip.country(ip).latitude.to_s.gsub(/\(|\)/,'') + "," + geoip.country(ip).longitude.to_s.gsub(/\(|\)/,'')
129
+ else
130
+ @city, @country, @coord = "null", "null", "null"
131
+ end
132
+
133
+ @asn = (geoasn.asn(ip) ? geoasn.asn(ip).as_num[2..-1] : "null" )
134
+
135
+ @isp = "null"
136
+
137
+ if File.exists?(DoroSettings.env[:geoisp])
138
+ geoisp = GeoIP.new(DoroSettings.env[:geoisp])
139
+ @isp = geoisp.isp(ip) ? geoisp.isp(ip) : "null"
140
+ end
141
+
142
+ rescue => e
143
+ LOGGER_PARSER.fatal "GEO", "Error while fetching GeoIP dat file for IP: " + ip
144
+ LOGGER_PARSER.fatal "GEO", e.inspect
145
+ LOGGER_PARSER.fatal "GEO" ,e.backtrace
146
+ @city, @country, @coord, @asn, @isp = "null", "null", "null", "null", "null"
147
+ end
148
+
149
+ else
150
+ @city, @country, @coord, @asn, @isp = "null", "null", "null", "null", "null"
151
+
152
+ end
153
+ end
154
+
155
+ end
156
+
157
+
158
+ class DoroHttp
159
+ attr_reader :contype
160
+ attr_reader :method
161
+ attr_reader :uri
162
+ attr_reader :ssl
163
+ attr_reader :size
164
+ attr_accessor :data
165
+
166
+ def initialize(flowdeep)
167
+ @data = 'null'
168
+ @method = (flowdeep.values('http.request.method')[0] ? flowdeep.values('http.request.method')[0].value : 'null')
169
+ @ssl = false #TODO
170
+ @size = (flowdeep.values('http.content.length')[0] ? flowdeep.values('http.content.length')[0].value : 'null')
171
+ @uri = (flowdeep.values('http.request.uri')[0] ? flowdeep.values('http.request.uri')[0].value : 'null' )
172
+ @contype = (flowdeep.values('http.content.type')[0] ? flowdeep.values('http.content.type')[0].value : 'null')
173
+ end
174
+
175
+ end
176
+
177
+ class DoroDNS
178
+ attr_accessor :dns
179
+ attr_accessor :ttl
180
+ attr_accessor :name
181
+ attr_accessor :type
182
+ attr_accessor :type_i
183
+ attr_accessor :cls
184
+ attr_accessor :cls_i
185
+ attr_accessor :address
186
+ attr_accessor :data
187
+
188
+ def initialize(c)
189
+ @dns = Net::DNS::Packet::parse(c)
190
+ if qry? #is a query
191
+ q = @dns.question.first
192
+ @cls_i = q.qClass #Net::DNS::Question.parse(c[12..offset]).qClass
193
+ @name = q.qName
194
+ @type_i = q.qType
195
+ @ttl = 'null'
196
+ @address = 'null'
197
+ @data = 'null'
198
+
199
+ elsif !@dns.answer.empty? #contain an asnwer
200
+ #TODO only the first answer is parsed
201
+ a = @dns.answer.each.first
202
+ @ttl = a.ttl
203
+ @name = a.name
204
+
205
+ @type = a.type
206
+ @type_i = Net::DNS::RR::Types.new @type
207
+
208
+
209
+ @cls = a.cls
210
+ @cls_i = Net::DNS::RR::Classes.new @cls
211
+
212
+
213
+ case @type
214
+
215
+ when "A"
216
+ @address = @dns.answer.each.first.address
217
+ @data = 'null'
218
+
219
+ when "AAAA"
220
+ @address = @dns.answer.each.first.address
221
+ @data = 'null'
222
+
223
+ when "MX" then
224
+
225
+ @data = @dns.answer.each.first.exchange
226
+ @address = 'null'
227
+
228
+ when "CNAME" then
229
+
230
+ @data = @dns.answer.each.first.cname
231
+ @address = 'null'
232
+
233
+ else
234
+
235
+ @address = 'null'
236
+ @data = 'null'
237
+
238
+ end
239
+ end
240
+
241
+ end
242
+
243
+
244
+ def qry?
245
+ @dns.answer.empty?
246
+ end
247
+
248
+ end
249
+
250
+ class DoroFile
251
+ attr_accessor :sha2
252
+ attr_accessor :cont
253
+ attr_reader :path
254
+ attr_reader :date
255
+ attr_accessor :size
256
+
257
+ def initialize(hash)
258
+ repo = DoroSettings.env[:home] + "/downloads"
259
+ @path = "#{repo}/#{hash}.exe"
260
+ @date = Time.new.strftime("%m/%d/%y %H:%M:%S")
261
+ end
262
+
263
+ def self.sha2(content)
264
+ @sha2 = Digest::SHA2.new
265
+ @sha2 << content
266
+ end
267
+ end
268
+
269
+
270
+ class Doroxtractr < Mu::Xtractr #PcaprLocal::Xtractr.new
271
+
272
+ #def lol(id)
273
+ # self.create "http://172.20.250.13:8080/pcaps/1/pcap/#{id}"
274
+ #end
275
+
276
+
277
+ def summaryhttp(fast=0, v=0)
278
+ ids = []
279
+ self.flows('flow.service:HTTP').each { |flow|
280
+ method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
281
+ if fast == 0
282
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method} - #{flow.stream.flow.contents.first.body.length}"
283
+ else
284
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{method}"
285
+ end
286
+ ids.push(flow.id)
287
+ }
288
+ return ids
289
+ end
290
+
291
+ def flowinfo(id)
292
+ f = self.flows("flow.id:#{id}").first.inspect
293
+ f << self.flows("flow.id:#{id}").first.time.to_s
294
+ end
295
+
296
+ #Find the HTTP requests made by the host (Zeus uses it to send stolen data to its dropzone)
297
+ #The biggest post refers to the STATS one (by default is sent every 20 min)
298
+ #the smallest post refers to the LOG one (by default is sent every minute)
299
+ #the biggest GET refers to the Configuration file downloaded by the Zeus C&C
300
+ def findzeusdata(re, type, cc='192.168.10.3')
301
+ flowids = {}
302
+ self.flows("flow.service:HTTP flow.dst: #{cc}").each do |flow|
303
+ method = self.flows("flow.id:#{flow.id}").values('http.request.method')[0].value
304
+ flowids[flow.id] = flow.stream.flow.contents.first.body.length if method =~ /#{Regexp.escape(re)}/
305
+ end
306
+ if type == "ping"
307
+ return flowids.sort {|a,b| a[1]<=>b[1]}.first
308
+ elsif type == "stat" || type == "conf"
309
+ return flowids.sort {|a,b| a[1]<=>b[1]}.last
310
+ else
311
+ puts "Error, choose one argument from: ping, stat, conf"
312
+ return 1
313
+ end
314
+ end
315
+
316
+
317
+ #Find the HTTP GET request made by the host (Zeus uses it to send stolen data to its dropzone)
318
+ #Is the first get request made to the C&C [!?]
319
+ def findconfget
320
+ self.flows("flow.service:HTTP flow.dst: #{cc}")
321
+ end
322
+
323
+ def summaryhttpmethod(re, fast=0)
324
+ self.flows('flow.service:HTTP').each { |flow|
325
+ flowdeep = self.flows("flow.id:#{flow.id}")
326
+ if fast == 0
327
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address} - #{flow.stream.flow.contents.first.body.length}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
328
+ else
329
+ puts "#{flow.id} #{flow.src.address} > #{flow.dst.address}" if flowdeep.values('http.request.method')[0] && flowdeep.values('http.request.method')[0].value =~ /#{Regexp.escape(re)}/
330
+ end
331
+ }
332
+ end
333
+
334
+ def flowsummary(verbose=0)
335
+ self.flows.each { |flow|
336
+ flowdeep = self.flows("flow.id:#{flow.id}")
337
+ if verbose == 1
338
+ puts "#{flow.id}: #{flow.time} : #{flow.src.address} > #{flow.dst.address} - #{flow.packets} - #{flow.bytes} - #{flow.duration} - #{flow.title}"
339
+ else
340
+ puts "| #{flow.id}: #{flow.src.address} > #{flow.service.name} > #{flow.dst.address} : #{flow.title}"
341
+ end
342
+ }
343
+ end
344
+
345
+
346
+ def summaryport(port)
347
+ self.flows("flow.dport:#{port}").each do |f|
348
+ f.contents.each do |c|
349
+ puts "#{f.id}: #{flow.id} #{flow.src.address} > #{flow.dst.address} #{f.title} : #{c.body.length}"
350
+ end
351
+ end
352
+ end
353
+
354
+ def flowgrep(id, re)
355
+ self.flows("flow.id:#{id}").each do |f|
356
+ @t = false
357
+ f.stream.each do |mex|
358
+ if mex.bytes =~ /#{re}/
359
+ puts "#{f.id}: > #{f.dst.address} - #{$1}"
360
+ @t = true
361
+ end
362
+ end
363
+ end
364
+ return @t
365
+ end
366
+
367
+ def streamdata(id)
368
+ data = []
369
+ self.flows("flow.id:#{id}").each do |f|
370
+ f.stream.each do |mex|
371
+ t = [mex.bytes, mex.dir]
372
+ data.push t
373
+ end
374
+ end
375
+ return data
376
+ end
377
+
378
+ #Retrieve the content of a specified flow-ID
379
+ def flowcontent(id)
380
+ body = ""
381
+ self.flows("flow.id:#{id}").each do |flow|
382
+ flow.contents.each do |c|
383
+ body << c.body
384
+ end
385
+ end
386
+ return body
387
+ end
388
+
389
+ end
467
390
 
468
391
  end