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.
- checksums.yaml +15 -0
- data/CHANGELOG +14 -0
- data/README.md +10 -6
- data/TODO +1 -1
- data/UPDATE +7 -9
- data/bin/dorothy_start +3 -3
- data/bin/dparser_start +14 -11
- data/bin/dparser_stop +3 -0
- data/dorothy2.gemspec +2 -1
- data/etc/ddl/dorothive.ddl +38 -32
- data/etc/extensions.yml +7 -0
- data/lib/doroParser.rb +8 -15
- data/lib/dorothy2.rb +10 -9
- data/lib/dorothy2/BFM.rb +1 -1
- data/lib/dorothy2/DEM.rb +371 -448
- data/lib/dorothy2/NAM.rb +3 -3
- data/lib/dorothy2/do-init.rb +7 -0
- data/lib/dorothy2/do-utils.rb +10 -7
- data/lib/dorothy2/version.rb +1 -1
- data/lib/mu/xtractr.rb +1 -1
- data/lib/mu/xtractr/host.rb +6 -3
- metadata +184 -230
- data/lib/dorothy2/environment.rb +0 -25
data/lib/dorothy2.rb
CHANGED
@@ -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 '
|
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
|
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
|
-
|
497
|
-
@vtotal_threads = []
|
498
|
-
@analysis_threads = []
|
499
|
+
|
499
500
|
|
500
501
|
infinite = true
|
501
502
|
|
data/lib/dorothy2/BFM.rb
CHANGED
@@ -114,7 +114,7 @@ module Dorothy
|
|
114
114
|
db = Insertdb.new
|
115
115
|
db.begin_t
|
116
116
|
|
117
|
-
unless db.select("samples", "
|
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
|
data/lib/dorothy2/DEM.rb
CHANGED
@@ -16,453 +16,376 @@
|
|
16
16
|
|
17
17
|
module DoroParser
|
18
18
|
|
19
|
-
class
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
end
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
end
|
249
|
-
|
250
|
-
class
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
end
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
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
|