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
@@ -4,7 +4,7 @@
4
4
  ###
5
5
  ### type means the communication channel used
6
6
  ### to download the binaries, possible values
7
- ### are: system | ssh
7
+ ### are: system | ssh | mail
8
8
  ###
9
9
  ### typeid defines the type of the source, it
10
10
  ### depends on a userdefined-type in
@@ -17,10 +17,20 @@
17
17
  ### 2 - unknown
18
18
  #############################################
19
19
  ---
20
+ #dont change the name of the webgui source, or if wont work. Change only the local path, the priority and the profile
21
+ webgui:
22
+ type: web
23
+ localdir: /opt/dorothy2/opt/bins/web
24
+ typeid: 1
25
+
26
+ #Examples below
20
27
  malwarefolder:
21
28
  type: system
22
- localdir: /Users/m4rco/dorothy2/opt/bins/manual
29
+ localdir: /opt/dorothy2/opt/bins/manual
23
30
  typeid: 2
31
+ priority: 1
32
+ profile: test
33
+
24
34
  honeypot1:
25
35
  type: ssh
26
36
  ip: 1.2.3.4
@@ -30,3 +40,18 @@ honeypot1:
30
40
  remotedir: /asda/bins
31
41
  localdir: /opt/bins/honeypot
32
42
  typeid: 0
43
+ priority: 1
44
+ profile: test
45
+
46
+ mailpot@example.com:
47
+ type: mail
48
+ localdir: /opt/dorothy2/opt/bins/mailpot@example.com
49
+ typeid: 1
50
+ address: pop-mail.outlook.com
51
+ username: mailpot@example.com
52
+ password: myPASS
53
+ port: 995
54
+ enable_ssl: true
55
+ metodo_analisi: 1
56
+ priority: 2
57
+ profile: test
@@ -0,0 +1,232 @@
1
+ require 'sinatra'
2
+ require 'sinatra/activerecord'
3
+ require 'sinatra/namespace'
4
+
5
+
6
+ module Dorothy
7
+ class DoroGUI < Sinatra::Base
8
+ register Sinatra::Namespace
9
+
10
+ enable :logging, :dump_errors
11
+ file = File.new(DoroSettings.wgui[:logfile], 'a+')
12
+ file.sync = true
13
+ use Rack::CommonLogger, file
14
+ before { env['rack.errors'] = file }
15
+
16
+ set :app_file, __FILE__
17
+ root = File.expand_path(File.dirname(__FILE__))
18
+ set :public_folder, File.join(root, 'www/public')
19
+ set :views, Proc.new { File.join(root, 'www/views') }
20
+
21
+
22
+ ActiveRecord::Base.establish_connection(
23
+ adapter: 'postgresql',
24
+ host: DoroSettings.dorothive[:dbhost],
25
+ database: DoroSettings.dorothive[:dbname],
26
+ username: DoroSettings.dorothive[:dbuser],
27
+ password: DoroSettings.dorothive[:dbpass],
28
+ port: 5432,
29
+ schema_search_path: 'dorothy' )
30
+
31
+ sources = YAML.load_file(DoroSettings.env[:home] + '/etc/sources.yml')
32
+
33
+
34
+ class Analyses < ActiveRecord::Base
35
+ self.table_name = "analyses"
36
+ end
37
+
38
+ class Samples < ActiveRecord::Base
39
+ self.table_name = "samples"
40
+ end
41
+
42
+ class Flows < ActiveRecord::Base
43
+ self.table_name = "flows"
44
+ end
45
+
46
+ class Sandboxes < ActiveRecord::Base
47
+ self.table_name = "sandboxes"
48
+ end
49
+
50
+ class AnalysisQueue < ActiveRecord::Base
51
+ self.table_name = "analysis_queue"
52
+ end
53
+
54
+ class TrafficDumps < ActiveRecord::Base
55
+ self.table_name = "traffic_dumps"
56
+ end
57
+
58
+ class Sources < ActiveRecord::Base
59
+ self.table_name = "sources"
60
+ end
61
+
62
+ class Emails < ActiveRecord::Base
63
+ self.table_name = "emails"
64
+ end
65
+
66
+ class Receivers < ActiveRecord::Base
67
+ self.table_name = "email_receivers"
68
+ end
69
+
70
+ class Sightings < ActiveRecord::Base
71
+ self.table_name = "sightings"
72
+ end
73
+
74
+ class SystemProcs < ActiveRecord::Base
75
+ self.table_name = "sys_procs"
76
+ end
77
+
78
+ class Malwares < ActiveRecord::Base
79
+ self.table_name = "malwares"
80
+ end
81
+
82
+ class AvSigns < ActiveRecord::Base
83
+ self.table_name = "av_signs"
84
+ end
85
+
86
+ get '/' do
87
+ @title = "Analyses"
88
+ @analyses = Analyses.all
89
+ @samples = Samples.all
90
+ @queue = AnalysisQueue.all
91
+ @sightings = Sightings.all
92
+ @emails = Emails.all
93
+
94
+
95
+
96
+ erb :analyses
97
+ end
98
+
99
+
100
+
101
+ get '/queue' do
102
+ @title = "Queue Status"
103
+ @queue = AnalysisQueue.all.order(id: :asc, priority: :desc)
104
+ @sources = Sources.all
105
+ @analyses = Analyses.all
106
+ @emails = Emails.all
107
+ erb :queue
108
+ end
109
+
110
+
111
+ get '/resume/:analid' do
112
+ @title = "Sample Analysis Resume"
113
+ @analysis_dir = DoroSettings.env[:analysis_dir]
114
+ @analid = params[:analid]
115
+ @sample = Samples.where(:sha256 => Analyses.where(:id => @analid).first.sample).first
116
+ @sys_procs = SystemProcs.where(:analysis_id => params[:analid])
117
+ @malware = Malwares.where(:bin => Analyses.where(:id => @analid).first.sample).first
118
+ @sophos = @malware.nil? ? nil : AvSigns.where(:id => @malware.id).where(:av_name => 'Sophos').first
119
+
120
+ @mailid = Sightings.where(:id => AnalysisQueue.where( :id => Analyses.where(:id => @analid).first.queue_id).first.sighting).first.src_email
121
+
122
+
123
+
124
+ @net_dumps = TrafficDumps.where(:sha256 => Analyses.where(:id => @analid).first.traffic_dump)
125
+ @flows= Flows.where(:traffic_dump => Analyses.where(:id => @analid).first.traffic_dump)
126
+
127
+ @imgs = []
128
+ Dir[DoroSettings.env[:analysis_dir] + "/#{@analid}/screens/*.png"].each {|file| @imgs.push(File.basename(file)) }
129
+
130
+ erb :resume
131
+ end
132
+
133
+ get '/screens/:analid/:name' do
134
+ full_path = DoroSettings.env[:analysis_dir] + "/" + params[:analid] + "/screens/" + params[:name]
135
+ send_file full_path.strip, :filename => params[:name].strip, :disposition => 'inline'
136
+ end
137
+
138
+
139
+ get '/profile/:profile' do
140
+ @profile = Util.load_profile(params[:profile])
141
+
142
+ erb :profile
143
+ end
144
+
145
+
146
+ get '/upload' do
147
+ @sandboxes = Sandboxes.all
148
+ @profiles = YAML.load_file(DoroSettings.env[:home] + '/etc/profiles.yml')
149
+
150
+ erb :upload
151
+ end
152
+
153
+ post '/upload' do
154
+ localpath = sources["webgui"]["localdir"] + "/#{params[:uploaded_data][:filename]}"
155
+ FileUtils.mv(params[:uploaded_data][:tempfile].path, localpath) unless params[:uploaded_data].nil?
156
+ id = QueueManager.add(localpath, 'webgui', params[:profile], params[:priority])
157
+
158
+ #entry = AnalysisQueue.create(date: get_time, binary: localpath, filename: filename, source: "webgui", priority: params[:priority], profile: params[:OS], user: "webuser")
159
+ #entry.save
160
+ erb "Upload Complete. Prio #{params[:priority]} - OS #{params[:OS]} - Scheduled with Queue ID #{id}"
161
+ end
162
+
163
+
164
+
165
+ namespace '/email' do
166
+ get '/view/:mail_id' do
167
+ @email = Emails.where(:id => params[:mail_id]).first
168
+ @receivers = Receivers.where(:email_id => params[:mail_id])
169
+ erb :email
170
+ end
171
+
172
+ get '/download/:mail_id' do
173
+ email = Emails.where(:id => params[:mail_id]).first
174
+
175
+ content_type 'Application/octet-stream'
176
+ attachment( "Message_#{email.id}" + '.eml')
177
+ PG::Connection.unescape_bytea(email.data)
178
+ end
179
+
180
+ end
181
+
182
+
183
+
184
+ namespace '/samples/' do
185
+
186
+ get '/' do
187
+ @title = "Sample Information"
188
+ @samples = Samples.all
189
+ erb :samples
190
+ end
191
+
192
+ get ':sha256' do |sha256|
193
+ @samples = Samples.where(:sha256 => params[:sha256])
194
+
195
+ #list all the analysis that have been done on this file , including timestamp, OS, etc
196
+ erb :samples
197
+ end
198
+
199
+
200
+ get 'download/:sha256' do |sha256|
201
+ first_id = Analyses.where(:sample => params[:sha256]).first.id
202
+ filename = Samples.where(:sha256 => params[:sha256]).first.filename
203
+ full_path = DoroSettings.env[:analysis_dir] + "/#{first_id}/bin/" + filename
204
+ send_file full_path.strip, :filename => filename, :type => 'Application/octet-stream'
205
+ end
206
+
207
+ end
208
+
209
+
210
+ namespace '/net' do
211
+
212
+ namespace '/:dump_sha1' do
213
+
214
+ get '/' do
215
+ @title = "Network Flows"
216
+ @net_dumps = TrafficDumps.where(:sha256 => params[:dump_sha1])
217
+ @flows= Flows.where(:traffic_dump => params[:dump_sha1])
218
+
219
+ erb :flows
220
+ end
221
+
222
+ end
223
+ end
224
+
225
+
226
+ after do
227
+ ActiveRecord::Base.connection.close
228
+ end
229
+
230
+ end
231
+
232
+ end
@@ -13,26 +13,18 @@
13
13
  ## Data Definition Module ##
14
14
  ############################
15
15
 
16
- require 'digest'
17
- require 'rbvmomi'
18
- require 'rest_client'
16
+
19
17
  require 'net/dns'
20
18
  require 'net/dns/packet'
21
19
  require 'ipaddr'
22
- require 'colored'
23
- require 'filemagic'
24
20
  require 'geoip'
25
- require 'pg'
26
- require 'tmail'
27
21
  require 'ipaddr'
28
22
  require 'net/http'
29
23
  require 'json'
30
24
 
31
25
  require File.dirname(__FILE__) + '/mu/xtractr'
32
26
  require File.dirname(__FILE__) + '/dorothy2/DEM'
33
- require File.dirname(__FILE__) + '/dorothy2/do-utils'
34
- require File.dirname(__FILE__) + '/dorothy2/do-logger'
35
- require File.dirname(__FILE__) + '/dorothy2/deep_symbolize'
27
+
36
28
 
37
29
 
38
30
  module DoroParser
@@ -41,6 +33,7 @@ module DoroParser
41
33
  CCIRC = 1
42
34
  CCDROP = 3
43
35
  CCSUPPORT = 5
36
+ NONETBIOS = true
44
37
 
45
38
  def search_irc(streamdata)
46
39
 
@@ -53,14 +46,13 @@ module DoroParser
53
46
 
54
47
  ircvalues.push "default, currval('dorothy.connections_id_seq'), E'#{Insertdb.escape_bytea(m[0])}', #{direction_bool}"
55
48
  end
56
- return ircvalues
49
+ ircvalues
57
50
  end
58
51
 
59
52
  def analyze_bintraffic(pcaps)
60
53
 
61
54
  dns_list = Hash.new
62
55
  hosts = []
63
- @insertdb.begin_t
64
56
 
65
57
  pcaps.each do |dump|
66
58
  #RETRIVE MALWARE FILE INFO
@@ -92,12 +84,12 @@ module DoroParser
92
84
  #The following section is to avoid a crash while quering such (still-empty instance)
93
85
  #In addition, an added check is inserted, to see if the pcapr instance really match the pcap filename
94
86
  begin
95
- pcapr_query = URI.parse "http://#{DoroSettings.pcapr[:host]}:#{DoroSettings.pcapr[:port]}/pcaps/1/about/#{dump['pcapr_id'].rstrip}"
96
- pcapr_response = Net::HTTP.get_response(pcapr_query)
97
- pcapname = File.basename(JSON.parse(pcapr_response.body)["filename"], ".pcap")
87
+ pcapr_query = URI.parse "http://#{DoroSettings.pcapr[:host]}:#{DoroSettings.pcapr[:port]}/pcaps/1/about/#{dump['pcapr_id'].rstrip}"
88
+ pcapr_response = Net::HTTP.get_response(pcapr_query)
89
+ pcapname = File.basename(JSON.parse(pcapr_response.body)["filename"], ".pcap")
98
90
 
99
- t ||= $1 if pcapname =~ /[0-9]*\-(.*)$/
100
- raise NameError.new if t != dump['sample'].rstrip
91
+ t ||= $1 if pcapname =~ /[0-9]*\-(.*)$/
92
+ raise NameError.new if t != dump['sample'].rstrip
101
93
 
102
94
  rescue NameError
103
95
  LOGGER_PARSER.error "PARSER", "The pcapr filename mismatchs the one present in Dorothive!. Skipping."
@@ -111,6 +103,9 @@ module DoroParser
111
103
 
112
104
  LOGGER_PARSER.info "PARSER", "Scanning network flows and searching for unknown host IPs".yellow
113
105
 
106
+ @insertdb.begin_t
107
+
108
+
114
109
  xtractr.flows.each { |flow|
115
110
 
116
111
  begin
@@ -139,7 +134,7 @@ module DoroParser
139
134
 
140
135
 
141
136
  #insert Geoinfo
142
- unless(localnet.include?(flow.dst.address) || multicast.include?(flow.dst.address))
137
+ unless localnet.include?(flow.dst.address) || multicast.include?(flow.dst.address)
143
138
 
144
139
  geo = Geoinfo.new(flow.dst.address.to_s)
145
140
  geoval = ["default", geo.coord, geo.country, geo.city, geo.updated, geo.asn]
@@ -163,9 +158,9 @@ module DoroParser
163
158
  #Insert host info
164
159
  #ip - geoinfo - sbl - uptime - is_online - whois - zone - last-update - id - dns_name
165
160
  hostname = (dns_list[dest].nil? ? "null" : dns_list[dest])
166
- hostval = [dest, geoval, "null", "null", true, "null", "null", get_time, "default", hostname]
161
+ hostval = [dest, geoval, "null", "null", true, "null", "null", Util.get_time, "default", hostname]
167
162
 
168
- if !@insertdb.insert("host_ips",hostval)
163
+ unless @insertdb.insert("host_ips",hostval)
169
164
  LOGGER_PARSER.debug "DB", " Skipping flow #{flow.id}: #{flow.src.address} > #{flow.dst.address}" if VERBOSE
170
165
  next
171
166
  end
@@ -179,7 +174,7 @@ module DoroParser
179
174
 
180
175
  flowvals = [flow.src.address, flow.dst.address, flow.sport, flow.dport, flow.bytes, dump['sha256'], flow.packets, "default", flow.proto, flow.service.name, title, "null", flow.duration, flow.time, flow.id ]
181
176
 
182
- if !@insertdb.insert("flows",flowvals)
177
+ unless @insertdb.insert("flows",flowvals)
183
178
  LOGGER_PARSER.info "PARSER", "Skipping flow #{flow.id}: #{flow.src.address} > #{flow.dst.address}"
184
179
  next
185
180
  end
@@ -424,72 +419,33 @@ module DoroParser
424
419
 
425
420
  LOGGER_PARSER.info "PARSER", "Started, looking for network dumps into Dorothive.."
426
421
 
427
- if daemon
428
- check_pid_file DoroSettings.env[:pidfile_parser]
429
- puts "[PARSER]".yellow + " Going in backround with pid #{Process.pid}"
430
- puts "[PARSER]".yellow + " Logging on #{DoroSettings.env[:logfile_parser]}"
431
- Process.daemon
432
- create_pid_file DoroSettings.env[:pidfile_parser]
433
- puts "[PARSER]".yellow + " Going in backround with pid #{Process.pid}"
434
- end
435
422
 
436
- @insertdb = Insertdb.new
437
423
  infinite = true
438
424
 
439
- while infinite
440
- pcaps = @insertdb.find_pcap
441
- analyze_bintraffic(pcaps)
442
- infinite = daemon
443
- LOGGER.info "PARSER", "SLEEPING" if daemon
444
- sleep DoroSettings.env[:dtimeout].to_i if daemon # Sleeping a while if -d wasn't set, then quit.
445
- end
446
- LOGGER_PARSER.info "PARSER" , "There are no more pcaps to analyze.".yellow
447
- @insertdb.close
448
- exit(0)
449
- end
425
+ @insertdb = Insertdb.new
450
426
 
451
- def check_pid_file file
452
- if File.exist? file
453
- # If we get Errno::ESRCH then process does not exist and
454
- # we can safely cleanup the pid file.
455
- pid = File.read(file).to_i
456
- begin
457
- Process.kill(0, pid)
458
- rescue Errno::ESRCH
459
- stale_pid = true
460
- end
461
427
 
462
- unless stale_pid
463
- puts "[PARSER]".yellow + " Dorothy is already running (pid=#{pid})"
464
- exit(1)
428
+ begin
429
+ while infinite
430
+ begin
431
+ pcaps = @insertdb.find_pcap
432
+ analyze_bintraffic(pcaps)
433
+ rescue SignalException #, RuntimeError
434
+ LOGGER.warn "PARSER", "SIGINT".red + " Catched [1], exiting gracefully."
435
+ end
436
+ infinite = daemon
437
+ LOGGER.debug "PARSER", "SLEEPING" if daemon && DEBUG
438
+ sleep DoroSettings.env[:sleeptime].to_i if daemon # Sleeping a while if -d wasn't set, then quit.
465
439
  end
466
- end
467
- end
440
+ LOGGER_PARSER.info "PARSER" , "There are no more pcaps to analyze.".yellow if DEBUG
441
+ @insertdb.close
442
+ exit(0)
468
443
 
469
- def create_pid_file file
470
- File.open(file, "w") { |f| f.puts Process.pid }
471
-
472
- # Remove pid file during shutdown
473
- at_exit do
474
- LOGGER_PARSER.info "PARSER", "Shutting down." rescue nil
475
- if File.exist? file
476
- File.unlink file
477
- end
444
+ rescue SignalException #, RuntimeError
445
+ LOGGER.warn "PARSER", "SIGINT".red + " Catched [2], exiting gracefully."
478
446
  end
479
- end
480
447
 
481
- # Sends SIGTERM to process in pidfile. Server should trap this
482
- # and shutdown cleanly.
483
- def self.stop
484
- LOGGER_PARSER.info "PARSER", "Shutting down.."
485
- pid_file = DoroSettings.env[:pidfile_parser]
486
- if pid_file and File.exist? pid_file
487
- pid = Integer(File.read(pid_file))
488
- Process.kill -15, -pid
489
- LOGGER_PARSER.info "PARSER", "Process #{DoroSettings.env[:pidfile_parser]} terminated"
490
- else
491
- LOGGER_PARSER.info "PARSER", "Can't find PID file, is DoroParser really running?"
492
- end
493
448
  end
494
449
 
450
+
495
451
  end