dorothy2 1.2.0 → 2.0.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.
Files changed (47) hide show
  1. checksums.yaml +8 -8
  2. data/CHANGELOG +39 -14
  3. data/README.md +80 -62
  4. data/UPDATE +6 -14
  5. data/bin/dorothy2 +472 -0
  6. data/dorothy2.gemspec +22 -16
  7. data/etc/ddl/dorothive.ddl +619 -373
  8. data/etc/sources.yml.example +27 -2
  9. data/lib/doroGUI.rb +232 -0
  10. data/lib/doroParser.rb +34 -78
  11. data/lib/dorothy2.rb +288 -248
  12. data/lib/dorothy2/BFM.rb +114 -61
  13. data/lib/dorothy2/DEM.rb +3 -1
  14. data/lib/dorothy2/NAM.rb +2 -2
  15. data/lib/dorothy2/Settings.rb +2 -1
  16. data/lib/dorothy2/VSM.rb +2 -1
  17. data/lib/dorothy2/deep_symbolize.rb +2 -7
  18. data/lib/dorothy2/do-init.rb +286 -19
  19. data/lib/dorothy2/do-logger.rb +1 -1
  20. data/lib/dorothy2/do-utils.rb +382 -33
  21. data/lib/dorothy2/version.rb +1 -1
  22. data/lib/dorothy2/vtotal.rb +30 -20
  23. data/lib/mu/xtractr.rb +11 -11
  24. data/lib/mu/xtractr/stream.rb +1 -1
  25. data/lib/www/public/reset.css +153 -0
  26. data/lib/www/public/style.css +65 -0
  27. data/lib/www/views/analyses.erb +28 -0
  28. data/lib/www/views/email.erb +63 -0
  29. data/lib/www/views/flows.erb +30 -0
  30. data/lib/www/views/layout.erb +27 -0
  31. data/lib/www/views/profile.erb +49 -0
  32. data/lib/www/views/queue.erb +28 -0
  33. data/lib/www/views/resume.erb +135 -0
  34. data/lib/www/views/resume.erb~ +88 -0
  35. data/lib/www/views/samples.erb +20 -0
  36. data/lib/www/views/upload.erb +154 -0
  37. data/share/img/The_big_picture.pdf +0 -0
  38. data/test/tc_dorothy_full.rb +3 -0
  39. metadata +169 -70
  40. data/TODO +0 -27
  41. data/bin/dorothy_start +0 -225
  42. data/bin/dorothy_stop +0 -28
  43. data/bin/dparser_start +0 -94
  44. data/bin/dparser_stop +0 -31
  45. data/etc/dorothy copy.yml.example +0 -39
  46. data/etc/extensions.yml +0 -41
  47. data/share/update-dorothive.sql +0 -19
@@ -7,7 +7,8 @@
7
7
 
8
8
  ##.for irb debug:
9
9
  ##from $home, irb and :
10
- ##load 'lib/dorothy2.rb'; include Dorothy; LOGGER = DoroLogger.new(STDOUT, "weekly"); DoroSettings.load!('etc/dorothy.yml'); VERBOSE = true
10
+ #load 'dorothy2.rb'; include Dorothy; LOGGER = DoroLogger.new(STDOUT, "weekly"); DoroSettings.load!("#{File.expand_path("~")}/.dorothy.yml")
11
+ ##/
11
12
 
12
13
  require 'net/ssh'
13
14
  require 'net/scp'
@@ -21,8 +22,16 @@ require 'pg'
21
22
  require 'filemagic'
22
23
  require 'rbvmomi'
23
24
  require 'timeout'
24
- require 'virustotal'
25
+ require 'uirusu'
25
26
  require 'digest'
27
+ require 'mail'
28
+ require 'io/console'
29
+ require 'base64'
30
+ require 'open-uri'
31
+ require 'csv'
32
+ require 'whois'
33
+
34
+
26
35
 
27
36
  require File.dirname(__FILE__) + '/dorothy2/do-init'
28
37
  require File.dirname(__FILE__) + '/dorothy2/Settings'
@@ -35,60 +44,77 @@ require File.dirname(__FILE__) + '/dorothy2/do-utils'
35
44
  require File.dirname(__FILE__) + '/dorothy2/do-logger'
36
45
  require File.dirname(__FILE__) + '/dorothy2/version'
37
46
 
38
- module Dorothy
39
47
 
40
- def get_time(local=Time.new)
41
- time = local
42
- time.utc.strftime("%Y-%m-%d %H:%M:%S")
43
- end
44
48
 
45
49
 
46
- def start_analysis(bins)
50
+
51
+ module Dorothy
52
+
53
+ def start_analysis(queue)
47
54
  #Create a mutex for monitoring the access to the methods
48
- @binum = bins.size
49
- bins.each do |bin|
50
- next unless check_support(bin)
51
- scan(bin) unless DoroSettings.env[:testmode] #avoid to stress VT if we are just testing
52
- if MANUAL #no multithread
53
- db = Insertdb.new
54
- guestvm = db.find_vm
55
- analyze(bin, guestvm)
56
- db.free_vm(guestvm[0])
57
- db.close
58
- else #Use multithreading
59
- @analysis_threads << Thread.new(bin.filename){
60
- db = Insertdb.new
61
- sleep rand(@binum * 2) #OPTIMIZE #REVIEW
62
- sleep rand(30) while !(guestvm = db.find_vm) #guestvm struct: array ["sandbox id", "sandbox name", "ipaddress", "user", "password"]
63
- analyze(bin, guestvm)
64
- db.free_vm(guestvm[0])
65
- db.close
66
- }
55
+ @queue_size = queue.size
56
+
57
+ unless @queue_size == 0
58
+ queue.each do |qentry|
59
+
60
+ bin = Loadmalw.new(qentry["path"].strip, qentry["filename"])
61
+ profile = Util.load_profile(qentry['profile'])
62
+
63
+ next unless profile
64
+ next unless check_support(bin, qentry["id"], profile)
65
+ scan(bin) if profile[1]['vtotal_query'] #avoid to stress VT if we are just testing
66
+
67
+ if MANUAL #no multithread
68
+ execute_analysis(bin, qentry["id"], profile)
69
+ else #Use multithreading
70
+ @analysis_threads << Thread.new(bin.filename){
71
+ sleep rand(@queue_size * 2) #OPTIMIZE #REVIEW
72
+ execute_analysis(bin, qentry["id"],profile,rand(30))
73
+ }
74
+ end
67
75
  end
76
+ else
77
+ LOGGER.warn("Analyser", "The queue is currently empty!") if DEBUG
68
78
  end
69
79
  end
70
80
 
71
81
 
72
- def check_support(bin)
73
- if EXTENSIONS.key?(bin.extension)
74
- true
75
- else
76
- LOGGER.warn("VSM", "File extension #{bin.extension} currently not configured in etc/extensions.yml, skipping")
77
- LOGGER.debug("VSM", "Filtype: #{bin.type}") if VERBOSE
78
- dir_not_supported = File.dirname(bin.binpath) + "/not_supported"
79
- Dir.mkdir(dir_not_supported) unless File.exists?(dir_not_supported)
80
- FileUtils.cp(bin.binpath,dir_not_supported) #mv?
81
- FileUtils.rm(bin.binpath) ## mv?
82
- return false
82
+
83
+ def execute_analysis(bin, qentry, profile, timer=0)
84
+ db = Insertdb.new
85
+
86
+ prof_info = profile[1]
87
+
88
+ #guestvm struct: array ["sandbox id", "sandbox name", "ipaddress", "user", "password"]
89
+ sleep timer until (guestvm = db.find_vm(prof_info['OS']['type'], prof_info['OS']['version'], prof_info['OS']['lang']))
90
+
91
+ db.analysis_queue_mark(qentry, "processing")
92
+
93
+ begin
94
+ if analyze(bin, guestvm, qentry, profile)
95
+ db.analysis_queue_mark(qentry, "analysed")
96
+ else
97
+ db.analysis_queue_mark(qentry, "error")
98
+ end
99
+ rescue
100
+ db.analysis_queue_mark(qentry, "cancelled")
83
101
  end
102
+
103
+ db.free_vm(guestvm[0])
104
+ db.close
105
+
84
106
  end
85
107
 
108
+
109
+
110
+
86
111
  ###ANALYZE THE SOURCE
87
- def analyze(bin, guestvm)
112
+ def analyze(bin, guestvm, queueid, profile)
88
113
 
89
114
  #RESERVING AN ANALYSIS ID
90
115
  db = Insertdb.new
91
116
  anal_id = db.get_anal_id
117
+ prof_info = profile[1]
92
118
 
93
119
  #set home vars
94
120
  sample_home = DoroSettings.env[:analysis_dir] + "/#{anal_id}"
@@ -99,6 +125,7 @@ module Dorothy
99
125
 
100
126
  vm_log_header = "VM#{guestvm[0]} ".yellow + "[" + "#{anal_id}".red + "] "
101
127
 
128
+
102
129
  LOGGER.info "VSM", vm_log_header + "Analyzing binary #{bin.filename}"
103
130
 
104
131
  begin
@@ -111,12 +138,11 @@ module Dorothy
111
138
  Dir.mkdir bin.dir_screens
112
139
  Dir.mkdir bin.dir_downloads
113
140
 
114
- if VERBOSE
115
- LOGGER.debug "VSM", sample_home
116
- LOGGER.debug "VSM",bin.dir_bin
117
- LOGGER.debug "VSM",bin.dir_pcap
118
- LOGGER.debug "VSM",bin.dir_screens
119
- end
141
+ LOGGER.debug "VSM", sample_home
142
+ LOGGER.debug "VSM",bin.dir_bin
143
+ LOGGER.debug "VSM",bin.dir_pcap
144
+ LOGGER.debug "VSM",bin.dir_screens
145
+
120
146
 
121
147
  else
122
148
  LOGGER.warn "VSM",vm_log_header + "Malware #{bin.md5} sample_home already present, WTF!? Skipping.."
@@ -125,8 +151,7 @@ module Dorothy
125
151
  end
126
152
 
127
153
 
128
-
129
- FileUtils.cp(bin.binpath,bin.dir_bin) # mv?
154
+ FileUtils.ln_s(bin.binpath,bin.dir_bin + bin.filename) # put a symbolic link from the analysis folder to the bins repo
130
155
 
131
156
 
132
157
  #Creating a new VSM object for managing the SandBox VM
@@ -164,29 +189,31 @@ module Dorothy
164
189
  dumpname = anal_id.to_s + "-" + bin.md5
165
190
  pid = @nam.start_sniffer(guestvm[2],DoroSettings.nam[:interface], dumpname, DoroSettings.nam[:pcaphome])
166
191
  LOGGER.info "NAM",vm_log_header + "Start sniffing module"
167
- LOGGER.debug "NAM",vm_log_header + "Tcpdump instance #{pid} started" if VERBOSE
192
+ LOGGER.debug "NAM",vm_log_header + "Tcpdump instance #{pid} started"
168
193
 
169
194
  #sleep 5
170
195
 
171
196
  @screenshots = Array.new
172
197
 
173
198
  #Execute File into VM
174
- LOGGER.info "VSM",vm_log_header + "Executing #{bin.full_filename} with #{EXTENSIONS[bin.extension]["prog_name"]}"
199
+ LOGGER.info "VSM",vm_log_header + "Executing #{bin.full_filename} with #{prof_info['extensions'][bin.extension]['prog_name']}"
175
200
 
176
201
  if MANUAL
177
- LOGGER.debug "VSM",vm_log_header + " MANUAL mode detected. You can now logon to rdp:// "
202
+ LOGGER.debug "MANUAL-MODE",vm_log_header + " MANUAL mode detected. You can now logon to rdp://#{guestvm[2]} "
178
203
 
179
204
  menu="
180
-
181
- Choose your next action:
182
- 1) Take Screenshot
183
- 2) Take ProcessList
184
- 3) Execute #{bin.full_filename}
185
- 0) Continue and revert the machine.
205
+ #{"Choose your next action:".yellow}
206
+ ------------------------
207
+ #{"1".yellow}) Take Screenshot
208
+ #{"2".yellow}) Take ProcessList
209
+ #{"3".yellow}) Execute #{bin.full_filename}
210
+ #{"0".yellow}) Continue and revert the machine.
211
+ ------------------------
186
212
 
187
213
  Select a nuber:"
188
214
 
189
- LOGGER.info "MANUAL-MODE",vm_log_header + menu
215
+ print menu
216
+ $stdout.flush
190
217
  answer = gets.chop
191
218
 
192
219
  until answer == "0"
@@ -201,11 +228,12 @@ module Dorothy
201
228
  LOGGER.info "MANUAL-MODE", vm_log_header + "[" + "+".red + "]" + " PID: #{pid}, NAME: #{@current_procs[pid]["pname"]}, COMMAND: #{@current_procs[pid]["cmdLine"]}"
202
229
  end
203
230
  when "3"
204
- guestpid = vsm.exec_file("C:\\#{bin.full_filename}",EXTENSIONS[bin.extension])
231
+ guestpid = vsm.exec_file("C:\\#{bin.full_filename}",prof_info['extensions'][bin.extension])
205
232
  LOGGER.debug "MANUAL-MODE",vm_log_header + "Program executed with PID #{guestpid}"
206
233
  #when "x" then -- More interactive actions to add
207
234
  else
208
- LOGGER.info "MANUAL-MODE",vm_log_header + menu
235
+ print menu
236
+ $stdout.flush
209
237
  end
210
238
  answer = gets.chop
211
239
  end
@@ -214,21 +242,21 @@ module Dorothy
214
242
 
215
243
 
216
244
  else
217
- guestpid = vsm.exec_file("C:\\#{bin.full_filename}",EXTENSIONS[bin.extension])
218
- LOGGER.debug "VSM",vm_log_header + "Program executed with PID #{guestpid}" if VERBOSE
245
+ guestpid = vsm.exec_file("C:\\#{bin.full_filename}",prof_info['extensions'][bin.extension])
246
+ LOGGER.debug "VSM",vm_log_header + "Program executed with PID #{guestpid}"
219
247
  sleep 1
220
248
  returncode = vsm.get_status(guestpid)
221
249
  raise "The program was not correctly executed into the Sandbox. Status code: #{returncode}" unless returncode == 0 || returncode.nil?
222
250
 
223
- LOGGER.info "VSM",vm_log_header + " Sleeping #{DoroSettings.sandbox[:sleeptime]} seconds".yellow
224
- sleep DoroSettings.sandbox[:screen1time] % DoroSettings.sandbox[:sleeptime]
251
+ LOGGER.info "VSM",vm_log_header + " Sleeping #{prof_info['sleeptime']} seconds".yellow
252
+ sleep prof_info['screenshots']['delay_first'] % prof_info['sleeptime']
225
253
 
226
- DoroSettings.sandbox[:num_screenshots].times do
254
+ prof_info['screenshots']['number'].times do
227
255
  @screenshots.push vsm.screenshot
228
- sleep DoroSettings.sandbox[:screen2time] % DoroSettings.sandbox[:sleeptime] if DoroSettings.sandbox[:screen2time]
256
+ sleep prof_info['screenshots']['delay_inbetween'] % prof_info['sleeptime'] if prof_info['screenshots']['delay_inbetween']
229
257
  end
230
258
 
231
- sleep DoroSettings.sandbox[:sleeptime]
259
+ sleep prof_info['sleeptime']
232
260
 
233
261
  #Get Procs
234
262
  @current_procs = vsm.get_running_procs
@@ -246,7 +274,7 @@ module Dorothy
246
274
  LOGGER.info "VSM", vm_log_header + "Checking for spowned processes"
247
275
 
248
276
  unless @current_procs.nil?
249
- @procs = vsm.get_new_procs(@current_procs)
277
+ @procs = vsm.get_new_procs(@current_procs, "#{DoroSettings.env[:home]}/etc/#{profile[0]}_baseline_procs.yml")
250
278
  if @procs.size > 0
251
279
  LOGGER.info "VSM", vm_log_header + "#{@procs.size} new process(es) found"
252
280
  @procs.each_key do |pid|
@@ -284,7 +312,7 @@ module Dorothy
284
312
  pcaprid = Loadmalw.calc_pcaprid(dump.filename, dump.size).rstrip
285
313
  end
286
314
 
287
- LOGGER.debug "NAM", vm_log_header + "Pcaprid: " + pcaprid if VERBOSE
315
+ LOGGER.debug "NAM", vm_log_header + "Pcaprid: " + pcaprid
288
316
 
289
317
  empty_pcap = false
290
318
 
@@ -296,7 +324,7 @@ module Dorothy
296
324
 
297
325
  dumpvalues = [dump.sha, dump.size, pcaprid, dump.binpath, 'false']
298
326
  dump.sha = "EMPTYPCAP" if empty_pcap
299
- analysis_values = [anal_id, bin.sha, guestvm[0], dump.sha, get_time]
327
+ analysis_values = [anal_id, bin.sha, guestvm[0], dump.sha, Util.get_time, queueid]
300
328
 
301
329
  if pcaprid.nil? || bin.dir_pcap.nil? || bin.sha.nil? || bin.md5.nil?
302
330
  LOGGER.error "VSM", "VM#{guestvm[0]} Can't retrieve the required information"
@@ -304,7 +332,7 @@ module Dorothy
304
332
  end
305
333
 
306
334
 
307
- LOGGER.debug "DB", "VM#{guestvm[0]} Database insert phase" if VERBOSE
335
+ LOGGER.debug "DB", "VM#{guestvm[0]} Database insert phase"
308
336
 
309
337
  db.begin_t #needed for rollbacks
310
338
  in_transaction = true
@@ -324,9 +352,9 @@ module Dorothy
324
352
  end
325
353
 
326
354
  @procs.each_key do |pid|
327
- @procs[pid]["endTime"] ? end_time = get_time(@procs[pid]["endTime"]) : end_time = "null"
355
+ @procs[pid]["endTime"] ? end_time = Util.get_time(@procs[pid]["endTime"]) : end_time = "null"
328
356
  @procs[pid]["exitCode"] ? exit_code = @procs[pid]["exitCode"] : exit_code = "null"
329
- sys_procs_values = [anal_id, pid, @procs[pid]["pname"], @procs[pid]["owner"], @procs[pid]["cmdLine"], get_time(@procs[pid]["startTime"]), end_time, exit_code ]
357
+ sys_procs_values = [anal_id, pid, @procs[pid]["pname"], @procs[pid]["owner"], @procs[pid]["cmdLine"], Util.get_time(@procs[pid]["startTime"]), end_time, exit_code ]
330
358
  unless db.insert("sys_procs", sys_procs_values)
331
359
  LOGGER.fatal "DB", vm_log_header + "Error while inserting data into table sys_procs. Skipping binary #{bin.md5}"
332
360
  raise "DB-ERROR"
@@ -336,33 +364,33 @@ module Dorothy
336
364
 
337
365
  #TODO ADD RT CODE
338
366
 
367
+
339
368
  db.commit
340
369
  in_transaction = false
341
370
  db.close
342
371
 
343
- LOGGER.info "VSM", vm_log_header + "Removing file from /bins directory"
344
- FileUtils.rm(bin.binpath)
345
372
  LOGGER.info "VSM", vm_log_header + "Process compleated successfully"
346
373
 
347
- rescue SignalException, RuntimeError
374
+ rescue SignalException #, RuntimeError
348
375
  LOGGER.warn "DOROTHY", "SIGINT".red + " Catched, exiting gracefully."
349
376
  stop_nam_revertvm(@nam, pid, vsm, reverted, vm_log_header)
350
377
  LOGGER.debug "VSM", vm_log_header + "Removing working dir"
351
378
  FileUtils.rm_r(sample_home)
379
+
352
380
  if in_transaction
353
381
  db.rollback #rollback in case there is a transaction on going
354
382
  db.close
355
383
  end
356
384
 
385
+ raise
357
386
  rescue Exception => e
358
387
  LOGGER.error "VSM", vm_log_header + "An error occurred while analyzing #{bin.filename}, skipping\n"
359
- LOGGER.debug "Dorothy" , "#{$!}\n #{e.inspect} \n #{e.backtrace}" if VERBOSE
388
+ LOGGER.debug "Analyser" , "#{$!}\n #{e.inspect} \n #{e.backtrace}"
360
389
 
361
- LOGGER.warn "Dorothy", vm_log_header + "Stopping NAM instances if presents, reverting the Sandbox, and removing working directory"
390
+ LOGGER.warn "Analyser", vm_log_header + "Stopping NAM instances if presents, reverting the Sandbox, and removing working directory"
362
391
 
363
392
  stop_nam_revertvm(@nam, pid, vsm, reverted, vm_log_header)
364
393
  LOGGER.debug "VSM", vm_log_header + "Removing working dir"
365
-
366
394
  FileUtils.rm_r(sample_home)
367
395
 
368
396
  if in_transaction
@@ -371,228 +399,240 @@ module Dorothy
371
399
  end
372
400
 
373
401
  LOGGER.warn "VSM", vm_log_header + "Recover finished."
374
-
402
+ false
375
403
 
376
404
  end
377
405
 
378
406
  end
379
407
 
380
- #Stop NAM instance and Revert VM
381
- def stop_nam_revertvm(nam, pid, vsm, reverted, vm_log_header)
382
408
 
383
- if pid
384
- LOGGER.info "VSM", vm_log_header + " Stopping sniffing module " + pid.to_s
385
- nam.stop_sniffer(pid)
386
- end
387
409
 
388
- unless reverted || vsm.nil?
389
- LOGGER.info "VSM", vm_log_header + " Reverting VM"
390
- vsm.revert_vm
391
- sleep 3 #wait some seconds for letting the vm revert..
392
- end
393
- end
410
+ #########################
411
+ ## MAIN #
412
+ #########################
394
413
 
395
- ###Create Baseline
396
- def self.run_baseline
397
- db = Insertdb.new
398
- db.vm_init
399
- guestvm = db.find_vm
400
- if guestvm
401
- begin
402
- LOGGER.info "VSM","VM#{guestvm[0]}".red + " Executng the baseline run"
403
- vsm = Doro_VSM::ESX.new(DoroSettings.esx[:host],DoroSettings.esx[:user],DoroSettings.esx[:pass],guestvm[1], guestvm[3], guestvm[4])
404
- vsm.check_internet
405
- LOGGER.info "VSM","VM#{guestvm[0]}".red + " Sleeping #{DoroSettings.sandbox[:sleeptime]} seconds".yellow
406
- sleep DoroSettings.sandbox[:sleeptime]
407
- vsm.get_running_procs(nil, true) #save on file
408
- LOGGER.info "VSM", "VM#{guestvm[0]} ".red + "Reverting VM".yellow
409
- vsm.revert_vm
410
- db.free_vm(guestvm[0])
411
- db.close
412
- rescue => e
413
- LOGGER.error "VSM", "VM#{guestvm[0]} ".yellow + "An error occurred while performing the BASELINE run, please retry"
414
- LOGGER.debug "Dorothy" , "VM#{guestvm[0]} ".yellow + "#{$!}\n #{e.inspect} \n #{e.backtrace}" if VERBOSE
415
- LOGGER.warn "VSM", "VM#{guestvm[0]} ".yellow + "[RECOVER] Reverting VM"
416
- vsm.revert_vm
417
- db.free_vm(guestvm[0])
418
- db.close
419
- end
420
- else
421
- LOGGER.fatal "VSM", "[CRITICAL]".red + " There are no free VM at the moment..how it is possible?"
422
- end
423
- end
414
+ def self.start(daemon=false)
415
+ @vtotal_threads = []
416
+ @analysis_threads = []
417
+ @bins = []
418
+ @db = Insertdb.new
419
+
420
+
421
+ LOGGER.info "Analyser", "Started".yellow
422
+
423
+
424
+ #Creating a new NAM object for managing the sniffer
425
+ @nam = Doro_NAM.new(DoroSettings.nam)
426
+ #Be sure that there are no open tcpdump instances opened
427
+ @nam.init_sniffer
428
+
429
+
430
+ finish = false
431
+ infinite = true
424
432
 
425
- ########################
426
- ## VTOTAL SCAN ####
427
- ########################
428
- private
429
- def scan(bin)
430
- #puts "TOTAL", "Forking for VTOTAL"
431
- @vtotal_threads << Thread.new(bin.sha) {
432
- LOGGER.info "VTOTAL", "Scanning file #{bin.md5}".yellow
433
+ #be sure that all the vm are available by forcing their release
434
+ @db.vm_init
433
435
 
434
- vt = Vtotal.new
435
- id = vt.analyze_file(bin.binpath)
436
+ #Check if the are some analysis pending in the queue
437
+ unless @db.analysis_queue_pull.empty? || daemon
438
+ LOGGER.warn "WARNING", "There are some pending analyses in the queue, what do you want to do?"
439
+ menu="
440
+ --------------------------------------
441
+ #{"1".yellow}) Mark as analysed and continue
442
+ #{"2".yellow}) Append the new files and analyse also the pending ones
443
+ #{"3".yellow}) List pending analyses
444
+ --------------------------------------
445
+ Select a nuber:"
446
+
447
+ print menu
448
+ $stdout.flush
449
+ answer = gets.chop
450
+
451
+ until finish
452
+ case answer
453
+ when "1" then
454
+ @db.analysis_queue_mark_all
455
+ LOGGER.info "Analyser", "Queue Cleared, proceding.."
456
+ finish = true
436
457
 
437
- LOGGER.debug "VTOTAL", "Sleeping"
458
+ when "2"
459
+ LOGGER.info "Analyser", "Proceding.."
460
+ finish = true
438
461
 
439
- sleep 15
462
+ when "3"
463
+ @db.analysis_queue_view
440
464
 
441
- until vt.get_report(id)
442
- LOGGER.info "VTOTAL", "Waiting a while and keep retring..."
443
- sleep 30
465
+ else
466
+ LOGGER.warn "Analyser", "There are some pending analyses in the queue, what do you want to do?"
467
+ print menu
468
+ $stdout.flush
444
469
  end
445
470
 
446
- LOGGER.info "VTOTAL", "#{bin.md5} Detection Rate: #{vt.rate}"
447
- LOGGER.info "VTOTAL", "#{bin.md5} Family by McAfee: #{vt.family}"
471
+ answer = gets.chop unless finish
472
+ end
473
+ end
474
+
475
+
476
+ begin
477
+ while infinite #infinite loop
448
478
 
449
- LOGGER.info "VTOTAL", "Updating DB"
450
- vtvalues = [bin.sha, vt.family, vt.vendor, vt.version, vt.rate, vt.updated, vt.detected]
451
- db = Insertdb.new
452
- db.begin
453
479
  begin
454
- db.insert("malwares", vtvalues)
455
- db.close
456
- rescue
457
- db.rollback
458
- LOGGER.error "VTOTAL", "Error while inserting values in malware table"
480
+ start_analysis(@db.analysis_queue_pull)
481
+ infinite = daemon #exit if wasn't set
482
+ rescue SignalException #, RuntimeError
483
+ LOGGER.warn "DOROTHY", "SIGINT".red + " Catched [2], exiting gracefully."
484
+ stop_running_analyses
485
+ Process.kill('HUP',Process.pid)
459
486
  end
460
487
 
461
- #TODO upload evidence to RT
462
- }
488
+ # Sleeping a while if -d wasn't set, then quit.
489
+ if daemon
490
+ LOGGER.info "Analyser", "SLEEPING" if DEBUG
491
+ sleep DoroSettings.env[:sleeptime].to_i
492
+ end
463
493
 
464
- end
494
+ wait_end #TODO: is really required (here)?
465
495
 
496
+ end
497
+ rescue SignalException #, RuntimeError
498
+ LOGGER.warn "DOROTHY", "SIGINT".red + " Catched [3], exiting gracefully."
499
+ end
500
+ @db.close
466
501
 
502
+ end
467
503
 
468
- #########################
469
- ## MAIN #
470
- #########################
504
+ def wait_end
471
505
 
472
- def self.start(source=nil, daemon=nil)
506
+ unless @vtotal_threads.empty?
507
+ @vtotal_threads.each { |aThread| aThread.join}
508
+ LOGGER.info "VTOTAL","Process compleated successfully" if DEBUG
509
+ end
473
510
 
474
- @vtotal_threads = []
475
- @analysis_threads = []
476
- @db = Insertdb.new
511
+ @analysis_threads.each { |aThread| aThread.join }
512
+ LOGGER.info "Analyser", "Process finished" if DEBUG
477
513
 
478
- daemon ||= false
514
+ end
479
515
 
480
- puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Process Started"
516
+ ############# END OF MAIN
481
517
 
482
518
 
483
- LOGGER.info "Dorothy", "Started".yellow
484
519
 
485
- if daemon
486
- check_pid_file DoroSettings.env[:pidfile]
487
- puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Going in backround with pid #{Process.pid}"
488
- puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Logging on #{DoroSettings.env[:logfile]}"
489
- Process.daemon
490
- create_pid_file DoroSettings.env[:pidfile]
491
- puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Going in backround with pid #{Process.pid}"
492
- end
493
520
 
494
- #Creating a new NAM object for managing the sniffer
495
- @nam = Doro_NAM.new(DoroSettings.nam)
496
- #Be sure that there are no open tcpdump instances opened
497
- @nam.init_sniffer
498
521
 
499
522
 
500
523
 
501
- infinite = true
502
524
 
503
- #be sure that all the vm are available by forcing their release
504
- @db.vm_init
505
-
506
- if source # a source has been specified
507
- while infinite #infinite loop
508
- dfm = DorothyFetcher.new(source)
509
- start_analysis(dfm.bins)
510
- infinite = daemon #exit if wasn't set
511
- wait_end
512
- LOGGER.info "Dorothy", "SLEEPING" if daemon
513
- sleep DoroSettings.env[:dtimeout] if daemon # Sleeping a while if -d wasn't set, then quit.
514
- end
515
- else # no sources specified, analyze all of them
516
- while infinite #infinite loop
517
- sources = YAML.load_file(DoroSettings.env[:home] + '/etc/sources.yml')
518
- sources.keys.each do |sname|
519
- dfm = DorothyFetcher.new(sources[sname])
520
- start_analysis(dfm.bins)
521
- end
522
- infinite = daemon #exit if wasn't set
523
- wait_end
524
- LOGGER.info "Dorothy", "SLEEPING" if daemon
525
- sleep DoroSettings.env[:dtimeout].to_i if daemon # Sleeping a while if -d wasn't set, then quit.
526
- end
527
- end
525
+ #Stop NAM instance and Revert VM
526
+ def stop_nam_revertvm(nam, pid, vsm, reverted, vm_log_header)
528
527
 
529
- @db.close
528
+ if pid
529
+ LOGGER.info "VSM", vm_log_header + " Stopping sniffing module " + pid.to_s
530
+ nam.stop_sniffer(pid)
531
+ end
530
532
 
533
+ unless reverted || vsm.nil?
534
+ LOGGER.info "VSM", vm_log_header + " Reverting VM"
535
+ vsm.revert_vm
536
+ sleep 3 #wait some seconds for letting the vm revert..
531
537
  end
538
+ end
532
539
 
533
- def wait_end
534
540
 
535
- unless @vtotal_threads.empty?
536
- @vtotal_threads.each { |aThread| aThread.join}
537
- LOGGER.info "VTOTAL","Process compleated successfully"
538
- end
541
+ #Check the sample's md5 hash with VirusTotal
542
+ def scan(bin)
543
+ #puts "TOTAL", "Forking for VTOTAL"
544
+ @vtotal_threads << Thread.new(bin.sha) {
545
+ LOGGER.info "VTOTAL", "Scanning file #{bin.md5}".yellow
539
546
 
540
- @analysis_threads.each { |aThread| aThread.join }
541
- LOGGER.info "Dorothy", "Process finished"
547
+ vt_results = Vtotal.check_hash(bin.md5)
542
548
 
543
- end
549
+ if vt_results != false
550
+
551
+ LOGGER.info "VTOTAL", vt_results[:rate]
552
+ db = Insertdb.new
553
+ db.begin_t
544
554
 
545
- def check_pid_file(file)
546
- if File.exist? file
547
- # If we get Errno::ESRCH then process does not exist and
548
- # we can safely cleanup the pid file.
549
- pid = File.read(file).to_i
550
555
  begin
551
- Process.kill(0, pid)
552
- rescue Errno::ESRCH
553
- stale_pid = true
554
- end
556
+ @id = db.get_curr_malwares_id
557
+ vtvalues = [bin.sha, vt_results[:rate], vt_results[:positive], vt_results[:date], vt_results[:link], @id]
558
+ db.insert("malwares", vtvalues)
559
+
560
+ #Instert DB
561
+ vt_results[:results].each do |av|
562
+ vendor = av[0]
563
+ if av[1]["detected"]
564
+ family = av[1]["result"]
565
+ updated = (av[1]["update"] != "-" ? av[1]["update"] : "null")
566
+ version = (av[1]["version"] != "-" ? av[1]["version"] : "null")
567
+ vtvalues = [@id, vendor, family, version, updated]
568
+ db.insert("av_signs", vtvalues)
569
+ end
570
+ end
555
571
 
556
- unless stale_pid
557
- puts "[" + "+".red + "] " + "[Dorothy]".yellow + " Dorothy is already running (pid=#{pid})"
558
- exit(1)
572
+ rescue => e
573
+ LOGGER.debug "VTOTAL" , "#{$!}\n #{e.inspect} \n #{e.backtrace}"
574
+ db.rollback
559
575
  end
576
+ db.commit
577
+ db.close
560
578
  end
561
- end
579
+ }
580
+ end
562
581
 
563
- def create_pid_file(file)
564
- File.open(file, "w") { |f| f.puts Process.pid }
565
582
 
566
- # Remove pid file during shutdown
567
- at_exit do
568
- LOGGER.info "Dorothy", "Shutting down." rescue nil
569
- if File.exist? file
570
- File.unlink file
571
- end
583
+ ###Create Baseline
584
+ def self.run_baseline(profile)
585
+ db = Insertdb.new
586
+ db.vm_init
587
+ prof_info = profile[1]
588
+ guestvm = db.find_vm(prof_info['OS']['type'], prof_info['OS']['version'], prof_info['OS']['lang'])
589
+ if guestvm
590
+ begin
591
+ LOGGER.info "VSM","VM#{guestvm[0]}".red + " Executng the baseline run"
592
+ vsm = Doro_VSM::ESX.new(DoroSettings.esx[:host],DoroSettings.esx[:user],DoroSettings.esx[:pass],guestvm[1], guestvm[3], guestvm[4])
593
+ LOGGER.info "VSM","VM#{guestvm[0]}".red + " Sleeping #{prof_info['sleeptime']} seconds".yellow
594
+ sleep prof_info['sleeptime']
595
+ vsm.get_running_procs(nil, true, "#{DoroSettings.env[:home]}/etc/#{profile[0]}_baseline_procs.yml") #save on file
596
+ LOGGER.info "VSM", "VM#{guestvm[0]} ".red + "Reverting VM".yellow
597
+ vsm.revert_vm
598
+ db.free_vm(guestvm[0])
599
+ db.close
600
+ rescue => e
601
+ LOGGER.error "VSM", "VM#{guestvm[0]} ".yellow + "An error occurred while performing the BASELINE run, please retry"
602
+ LOGGER.debug "Analyser" , "VM#{guestvm[0]} ".yellow + "#{$!}\n #{e.inspect} \n #{e.backtrace}"
603
+ LOGGER.warn "VSM", "VM#{guestvm[0]} ".yellow + "[RECOVER] Reverting VM"
604
+ vsm.revert_vm #TODO vsm var might be nil here
605
+ db.free_vm(guestvm[0])
606
+ db.close
572
607
  end
608
+ else
609
+ LOGGER.fatal "VSM", "[CRITICAL]".red + " There are no free VM at the moment..how it is possible?"
573
610
  end
611
+ end
574
612
 
575
- def self.stop_running_analyses
576
- LOGGER.info "Dorothy", "Killing curent live analysis threads.."
577
- @analysis_threads.each { |aThread|
578
- aThread.raise
579
- aThread.join
580
- }
581
- end
582
- ## Sends SIGTERM to process in pidfile. Server should trap this
583
- # and shutdown cleanly.
584
- def self.stop
585
- puts "[" + "+".red + "]" + " Dorothy is shutting now.."
586
- LOGGER.info "Dorothy", "Shutting down."
587
- pid_file = DoroSettings.env[:pidfile]
588
- if pid_file and File.exist? pid_file
589
- pid = Integer(File.read(pid_file))
590
- Process.kill(-2,-pid)
591
- LOGGER.info "Dorothy", "Process #{pid} terminated"
592
- puts "[" + "+".red + "]" + " Dorothy Process #{pid} terminated"
593
- else
594
- LOGGER.info "Dorothy", "Can't find PID file, is Dorothy really running?"
595
- end
613
+
614
+ #Check if the sample extension is supported (= is configured into the extension.yml).
615
+ def check_support(bin, qentry, profile)
616
+ if profile[1]['extensions'].key?(bin.extension)
617
+ true
618
+ else
619
+ db = Insertdb.new #TODO too many db sessions opened. review, and try to use less
620
+ db.analysis_queue_mark(qentry, "error")
621
+ db.close
622
+ LOGGER.warn("VSM", "File extension #{bin.extension} currently not configured in the selected profile #{profile[0]}, skipping")
623
+ LOGGER.debug("VSM", "Filtype: #{bin.type}")
624
+ false
596
625
  end
626
+ end
627
+
597
628
 
629
+
630
+ def self.stop_running_analyses
631
+ LOGGER.info "Analyser", "Killing curent live analysis threads.."
632
+ @analysis_threads.each { |aThread|
633
+ aThread.raise
634
+ aThread.join
635
+ }
598
636
  end
637
+
638
+ end