dorothy2 1.2.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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