dorothy2 0.0.1
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.
- data/.gitignore +21 -0
- data/Gemfile +4 -0
- data/LICENSE +644 -0
- data/README.md +231 -0
- data/Rakefile +1 -0
- data/bin/dorothy_start +176 -0
- data/bin/dorothy_stop +28 -0
- data/bin/dparser_start +66 -0
- data/bin/dparser_stop +23 -0
- data/dorothy2.gemspec +30 -0
- data/etc/ddl/dorothive.ddl +1803 -0
- data/etc/dorothy copy.yml.example +39 -0
- data/etc/sandboxes.yml.example +20 -0
- data/etc/sources.yml.example +32 -0
- data/lib/doroParser.rb +518 -0
- data/lib/dorothy2/BFM.rb +156 -0
- data/lib/dorothy2/MAM.rb +239 -0
- data/lib/dorothy2/Settings.rb +35 -0
- data/lib/dorothy2/deep_symbolize.rb +67 -0
- data/lib/dorothy2/do-init.rb +296 -0
- data/lib/dorothy2/do-logger.rb +43 -0
- data/lib/dorothy2/do-parsers.rb +468 -0
- data/lib/dorothy2/do-utils.rb +223 -0
- data/lib/dorothy2/environment.rb +29 -0
- data/lib/dorothy2/version.rb +3 -0
- data/lib/dorothy2/vtotal.rb +84 -0
- data/lib/dorothy2.rb +470 -0
- data/share/img/Dorothy-Basic.pdf +0 -0
- data/share/img/Setup-Advanced.pdf +0 -0
- data/share/img/The_big_picture.pdf +0 -0
- data/test/tc_dorothy_full.rb +95 -0
- data/var/log/parser.log +0 -0
- metadata +260 -0
@@ -0,0 +1,84 @@
|
|
1
|
+
# Copyright (C) 2010-2013 marco riccardi.
|
2
|
+
# This file is part of Dorothy - http://www.honeynet.it/dorothy
|
3
|
+
# See the file 'LICENSE' for copying permission.
|
4
|
+
|
5
|
+
module Dorothy
|
6
|
+
|
7
|
+
class Vtotal < VirusTotal::VirusTotal
|
8
|
+
attr_writer :api_key
|
9
|
+
attr_reader :rate
|
10
|
+
attr_reader :filehash
|
11
|
+
attr_reader :scanid
|
12
|
+
attr_reader :family
|
13
|
+
attr_reader :permalink
|
14
|
+
attr_reader :updated
|
15
|
+
attr_reader :version
|
16
|
+
attr_reader :vendor
|
17
|
+
attr_reader :detected
|
18
|
+
|
19
|
+
|
20
|
+
def initialize()
|
21
|
+
@api_key = VTAPIKEY
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def analyze_file(file)
|
26
|
+
f = File.open(file, 'r')
|
27
|
+
begin
|
28
|
+
results = RestClient.post 'https://www.virustotal.com/vtapi/v2/file/scan' , { :key => @api_key, :file => f}
|
29
|
+
parsed = JSON.parse(results)
|
30
|
+
LOGGER.info "VTOTAL]", " Ok, received with scan id " + parsed["scan_id"] if parsed["response_code"]
|
31
|
+
#puts "[VTOTAL] ".yellow + parsed["verbose_msg"]
|
32
|
+
@scanid = parsed["scan_id"]
|
33
|
+
rescue
|
34
|
+
LOGGER.error "VTOTAL", "An error accurred while quering Virustotal"
|
35
|
+
LOGGER.debug "DEBUG", "#{$!}"
|
36
|
+
end
|
37
|
+
return @scanid
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
def get_report(id)
|
42
|
+
begin
|
43
|
+
report = RestClient.post 'https://www.virustotal.com/vtapi/v2/file/report' , { :resource => id.to_s, :key => @api_key }
|
44
|
+
rescue
|
45
|
+
LOGGER.error "VTOTAL", "An error accurred while quering Virustotal"
|
46
|
+
LOGGER.debug "DEBUG", "#{$!}"
|
47
|
+
end
|
48
|
+
|
49
|
+
if !report.empty?
|
50
|
+
|
51
|
+
parsed = JSON.parse(report)
|
52
|
+
|
53
|
+
if (parsed["response_code"] == 1 )
|
54
|
+
if (parsed["scans"]["McAfee"]["detected"] == true )
|
55
|
+
@rate = parsed["positives"].to_s + "/" + parsed["total"].to_s
|
56
|
+
@family = parsed["scans"]["McAfee"]["result"]
|
57
|
+
@permalink = (parsed["permalink"] != "-" ? parsed["permalink"] : "null")
|
58
|
+
@vendor = "McAfee" #TODO Move to config file!
|
59
|
+
@updated = (parsed["scans"]["McAfee"]["update"] != "-" ? parsed["scans"]["McAfee"]["update"] : "null")
|
60
|
+
@version = (parsed["scans"]["McAfee"]["version"] != "-" ? parsed["scans"]["McAfee"]["version"] : "null")
|
61
|
+
@detected = true
|
62
|
+
else #not detected by McAfee
|
63
|
+
@rate = parsed["positives"].to_s + "/" + parsed["total"].to_s
|
64
|
+
@family = "Unknown"
|
65
|
+
@permalink = "null"
|
66
|
+
@vendor = "McAfee" #TODO Move to config file!
|
67
|
+
@updated = "null"
|
68
|
+
@version = "null"
|
69
|
+
@detected = false
|
70
|
+
end
|
71
|
+
else
|
72
|
+
LOGGER.error "VTOTAL", parsed["verbose_msg"]
|
73
|
+
return false
|
74
|
+
end
|
75
|
+
else
|
76
|
+
LOGGER.error "VTOTAL", "No data received "
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
return parsed
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
data/lib/dorothy2.rb
ADDED
@@ -0,0 +1,470 @@
|
|
1
|
+
# Copyright (C) 2010-2013 marco riccardi.
|
2
|
+
# This file is part of Dorothy - http://www.honeynet.it/dorothy
|
3
|
+
# See the file 'LICENSE' for copying permission.
|
4
|
+
|
5
|
+
##for irb debug:
|
6
|
+
##from $home, irb and :
|
7
|
+
##load 'lib/dorothy2.rb'; include Dorothy; LOGGER = DoroLogger.new(STDOUT, "weekly"); DoroSettings.load!('etc/dorothy.yml')
|
8
|
+
#$LOAD_PATH.unshift '/opt/local/lib/ruby/gems/1.8/gems/ruby-filemagic-0.4.2/lib'
|
9
|
+
|
10
|
+
require 'net/ssh'
|
11
|
+
require 'net/scp'
|
12
|
+
require 'trollop'
|
13
|
+
require 'fileutils'
|
14
|
+
require 'rest_client'
|
15
|
+
require 'mime/types'
|
16
|
+
require 'colored'
|
17
|
+
require 'logger'
|
18
|
+
require 'pg'
|
19
|
+
require 'filemagic'
|
20
|
+
require 'rbvmomi'
|
21
|
+
require 'timeout'
|
22
|
+
require 'virustotal'
|
23
|
+
require 'ftools' #deprecated at ruby 1.9 !!!
|
24
|
+
require 'filemagic'
|
25
|
+
require 'md5'
|
26
|
+
|
27
|
+
require File.dirname(__FILE__) + '/dorothy2/do-init'
|
28
|
+
require File.dirname(__FILE__) + '/dorothy2/Settings'
|
29
|
+
require File.dirname(__FILE__) + '/dorothy2/deep_symbolize'
|
30
|
+
|
31
|
+
require File.dirname(__FILE__) + '/dorothy2/environment'
|
32
|
+
|
33
|
+
require File.dirname(__FILE__) + '/dorothy2/vtotal'
|
34
|
+
require File.dirname(__FILE__) + '/dorothy2/MAM'
|
35
|
+
require File.dirname(__FILE__) + '/dorothy2/BFM'
|
36
|
+
require File.dirname(__FILE__) + '/dorothy2/do-utils'
|
37
|
+
require File.dirname(__FILE__) + '/dorothy2/do-logger'
|
38
|
+
|
39
|
+
module Dorothy
|
40
|
+
|
41
|
+
def get_time
|
42
|
+
time = Time.new
|
43
|
+
time.utc.strftime("%Y-%m-%d %H:%M:%S")
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def start_analysis(bins)
|
48
|
+
bins.each do |bin|
|
49
|
+
next unless check_support(bin)
|
50
|
+
scan(bin) unless DoroSettings.env[:testmode] #avoid to stress VT if we are just testing
|
51
|
+
@analysis_threads << Thread.new(bin.filename){
|
52
|
+
db = Insertdb.new
|
53
|
+
sleep 30 while !(guestvm = db.find_vm) #guestvm struct: array ["sandbox id", "sandbox name", "ipaddress", "user", "password"]
|
54
|
+
analyze(bin, guestvm)
|
55
|
+
db.free_vm(guestvm[0])
|
56
|
+
db.close
|
57
|
+
}
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
def check_support(bin)
|
63
|
+
if bin.extension == ".exe" || bin.extension == ".bat"
|
64
|
+
true
|
65
|
+
else
|
66
|
+
LOGGER.warn("SANDBOX", "File #{bin.filename} actually not supported, skipping\n" + " Filtype: #{bin.type}") # if VERBOSE
|
67
|
+
dir_not_supported = File.dirname(bin.binpath) + "/not_supported"
|
68
|
+
Dir.mkdir(dir_not_supported) unless Utils.exists?(dir_not_supported)
|
69
|
+
FileUtils.cp(bin.binpath,dir_not_supported) #mv?
|
70
|
+
FileUtils.rm(bin.binpath) ## mv?
|
71
|
+
return false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
###ANALYZE THE SOURCE
|
76
|
+
def analyze(bin, guestvm)
|
77
|
+
|
78
|
+
#RESERVING AN ANALYSIS ID
|
79
|
+
db = Insertdb.new
|
80
|
+
anal_id = db.get_anal_id
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
#source.each do |sname, sinfo|
|
85
|
+
|
86
|
+
#Dir.chdir(sinfo[:dir])
|
87
|
+
|
88
|
+
#set home vars
|
89
|
+
sample_home = DoroSettings.env[:analysis_dir] + "/#{anal_id}"
|
90
|
+
bin.dir_bin = "#{sample_home}/bin/"
|
91
|
+
bin.dir_pcap = "#{sample_home}/pcap/"
|
92
|
+
bin.dir_screens = "#{sample_home}/screens/"
|
93
|
+
bin.dir_downloads = "#{sample_home}/downloads/"
|
94
|
+
|
95
|
+
|
96
|
+
LOGGER.info "SANDBOX", "VM#{guestvm[0]} ".yellow + "[" + "#{anal_id}".red + "]" + " Analyzing binary #{bin.filename}"
|
97
|
+
|
98
|
+
begin
|
99
|
+
#crate dir structure in analisys home
|
100
|
+
unless File.directory?(sample_home)
|
101
|
+
LOGGER.info "VSM","VM#{guestvm[0]} ".yellow + "Creating DIRS"
|
102
|
+
Dir.mkdir sample_home
|
103
|
+
Dir.mkdir bin.dir_bin
|
104
|
+
Dir.mkdir bin.dir_pcap
|
105
|
+
Dir.mkdir bin.dir_screens
|
106
|
+
Dir.mkdir bin.dir_downloads
|
107
|
+
|
108
|
+
if VERBOSE
|
109
|
+
LOGGER.debug "VSM", sample_home
|
110
|
+
LOGGER.debug "VSM",bin.dir_bin
|
111
|
+
LOGGER.debug "VSM",bin.dir_pcap
|
112
|
+
LOGGER.debug "VSM",bin.dir_screens
|
113
|
+
end
|
114
|
+
|
115
|
+
else
|
116
|
+
LOGGER.warn "SANDBOX","Malware #{bin.md5} sample_home already present, WTF!? Skipping.."
|
117
|
+
#print "\n"
|
118
|
+
return false
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
|
123
|
+
FileUtils.cp(bin.binpath,bin.dir_bin) # mv?
|
124
|
+
|
125
|
+
|
126
|
+
#Creating a new VSM object for managing the SandBox VM
|
127
|
+
LOGGER.info "VSM","VM#{guestvm[0]} ".yellow + "Connecting to ESX Server #{DoroSettings.esx[:host]}"
|
128
|
+
|
129
|
+
vsm = Doro_VSM::ESX.new(DoroSettings.esx[:host],DoroSettings.esx[:user],DoroSettings.esx[:pass],guestvm[1], guestvm[3], guestvm[4])
|
130
|
+
|
131
|
+
#Copy File to VM
|
132
|
+
r = 0
|
133
|
+
|
134
|
+
begin
|
135
|
+
vsm.check_internet
|
136
|
+
rescue
|
137
|
+
if r <= 2
|
138
|
+
r = r+1
|
139
|
+
LOGGER.warn "SANDBOX","VM#{guestvm[0]}".yellow + " GUESTOS Connection problem to Internet, retry n. #{r}/3"
|
140
|
+
sleep 20
|
141
|
+
retry
|
142
|
+
end
|
143
|
+
LOGGER.error "SANDBOX", "VM#{guestvm[0]}".yellow + " Guest system is not able to connect to internet"
|
144
|
+
r = 0
|
145
|
+
retry
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
|
150
|
+
LOGGER.info "VSM","VM#{guestvm[0]} ".yellow + "Copying #{bin.md5} to VM"
|
151
|
+
|
152
|
+
filecontent = File.open(bin.binpath, "rb") { |byte| byte.read } #load filebinary
|
153
|
+
vsm.copy_file("#{bin.md5}#{bin.extension}",filecontent)
|
154
|
+
|
155
|
+
#Start Sniffer
|
156
|
+
dumpname = bin.md5
|
157
|
+
pid = @nam.start_sniffer(guestvm[2],DoroSettings.nam[:interface], dumpname, DoroSettings.nam[:pcaphome]) #dumpname = vmfile.pcap
|
158
|
+
LOGGER.info "NAM","VM#{guestvm[0]} ".yellow + "Start sniffing module"
|
159
|
+
LOGGER.debug "NAM","VM#{guestvm[0]} ".yellow + "Tcpdump instance #{pid} started" if VERBOSE
|
160
|
+
|
161
|
+
sleep 5
|
162
|
+
|
163
|
+
begin
|
164
|
+
#Execute File into VM
|
165
|
+
LOGGER.info "VSM","VM#{guestvm[0]} ".yellow + "Executing #{bin.md5} File into VM"
|
166
|
+
|
167
|
+
guestpid = vsm.exec_file("#{bin.md5}#{bin.extension}")
|
168
|
+
|
169
|
+
LOGGER.debug "VSM","VM#{guestvm[0]} ".yellow + "Program executed with PID #{guestpid}" if VERBOSE
|
170
|
+
|
171
|
+
|
172
|
+
LOGGER.info "VSM","VM#{guestvm[0]}".yellow + " Sleeping #{DoroSettings.sandbox[:sleeptime]} seconds".yellow
|
173
|
+
|
174
|
+
#wait n seconds
|
175
|
+
|
176
|
+
(1..DoroSettings.sandbox[:sleeptime]).each do |i|
|
177
|
+
@screenshot1 = vsm.screenshot if i == DoroSettings.sandbox[:screen1time]
|
178
|
+
@screenshot2 = vsm.screenshot if i == DoroSettings.sandbox[:screen2time]
|
179
|
+
#t = "."*i
|
180
|
+
#print "VM#{guestvm[0]}Sleeping #{SLEEPTIME} seconds".yellow + " #{t}\r"
|
181
|
+
#print "VM#{guestvm[0]}Sleeping #{SLEEPTIME} seconds".yellow + " #{t}" + " [Done]\n".green if i == SLEEPTIME
|
182
|
+
sleep 1
|
183
|
+
$stdout.flush
|
184
|
+
end
|
185
|
+
|
186
|
+
|
187
|
+
|
188
|
+
#Stopt Sniffer
|
189
|
+
LOGGER.info "NAM", "VM#{guestvm[0]} ".yellow + "Stopping sniffing module " + pid.to_s
|
190
|
+
@nam.stop_sniffer(pid)
|
191
|
+
|
192
|
+
#Stop/Revert VM
|
193
|
+
LOGGER.info "VSM","VM#{guestvm[0]} ".yellow + "Reverting VM"
|
194
|
+
vsm.revert_vm
|
195
|
+
|
196
|
+
sleep 5
|
197
|
+
|
198
|
+
rescue => e
|
199
|
+
|
200
|
+
LOGGER.error "SANDBOX", "VM#{guestvm[0]} - An error occourred while executing the file into the vm:\n #{$!}"
|
201
|
+
|
202
|
+
LOGGER.debug "SANDBOX" , "#{$!}\n #{e.inspect} \n #{e.backtrace}" if VERBOSE
|
203
|
+
|
204
|
+
LOGGER.warn "SANDBOX", "VM#{guestvm[0]} ".red + "[RECOVER] Stopping sniffing module ".yellow + pid.to_s
|
205
|
+
@nam.stop_sniffer(pid)
|
206
|
+
|
207
|
+
LOGGER.warn "SANDBOX", "VM#{guestvm[0]} ".red + "[RECOVER] Reverting VM".yellow
|
208
|
+
vsm.revert_vm
|
209
|
+
sleep 5
|
210
|
+
|
211
|
+
LOGGER.warn "SANDBOX", "VM#{guestvm[0]} ".red + "[RECOVER] Recovering finished, skipping to next binaries".yellow
|
212
|
+
FileUtils.rm_r(sample_home)
|
213
|
+
return false
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
#Downloading PCAP
|
219
|
+
LOGGER.info "NAM", "VM#{guestvm[0]} ".yellow + "Downloading #{dumpname}.pcap to #{bin.dir_pcap}"
|
220
|
+
#t = DoroSettings.nam[:pcaphome] + "/" + dumpname + ".pcap"
|
221
|
+
Ssh.download(DoroSettings.nam[:host], DoroSettings.nam[:user],DoroSettings.nam[:pass], DoroSettings.nam[:pcaphome] + "/" + dumpname + ".pcap", bin.dir_pcap)
|
222
|
+
|
223
|
+
#Downloading Screenshots from esx
|
224
|
+
LOGGER.info "NAM", "VM#{guestvm[0]} ".yellow + "Downloading Screenshots"
|
225
|
+
Ssh.download(DoroSettings.esx[:host],DoroSettings.esx[:user], DoroSettings.esx[:pass], @screenshot1, bin.dir_screens)
|
226
|
+
Ssh.download(DoroSettings.esx[:host],DoroSettings.esx[:user], DoroSettings.esx[:pass], @screenshot2, bin.dir_screens)
|
227
|
+
|
228
|
+
#Put them to 644
|
229
|
+
File.chmod(0644, bin.dir_screens + File.basename(@screenshot1), bin.dir_screens + File.basename(@screenshot2) )
|
230
|
+
|
231
|
+
#####################
|
232
|
+
#UPDATE DOROTHIBE DB#
|
233
|
+
#####################
|
234
|
+
|
235
|
+
pcapfile = bin.dir_pcap + dumpname + ".pcap"
|
236
|
+
dump = Loadmalw.new(pcapfile)
|
237
|
+
|
238
|
+
pcaprpath = bin.md5 + "/pcap/" + dump.filename
|
239
|
+
pcaprid = Loadmalw.calc_pcaprid(pcaprpath, dump.size)
|
240
|
+
|
241
|
+
LOGGER.debug "NAM", "VM#{guestvm[0]} ".yellow + "Pcaprid: " + pcaprid if VERBOSE
|
242
|
+
|
243
|
+
empty_pcap = false
|
244
|
+
|
245
|
+
if dump.size <= 30
|
246
|
+
LOGGER.warn "NAM", "VM#{guestvm[0]} WARNING - EMPTY PCAP FILE!!!! ::.."
|
247
|
+
#FileUtils.rm_r(sample_home)
|
248
|
+
empty_pcap = true
|
249
|
+
end
|
250
|
+
|
251
|
+
dumpvalues = [dump.sha, dump.size, pcaprid, pcapfile, 'false']
|
252
|
+
dump.sha = "EMPTYPCAP" if empty_pcap
|
253
|
+
analysis_values = [anal_id, bin.sha, guestvm[0], dump.sha, get_time]
|
254
|
+
|
255
|
+
if pcaprid.nil? || bin.dir_pcap.nil? || bin.sha.nil? || bin.md5.nil?
|
256
|
+
LOGGER.error "SANDBOX", "VM#{guestvm[0]} Can't retrieve the required information"
|
257
|
+
FileUtils.rm_r(sample_home)
|
258
|
+
return false
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
LOGGER.debug "DB", "VM#{guestvm[0]} Database insert phase" if VERBOSE
|
263
|
+
|
264
|
+
db = Insertdb.new
|
265
|
+
db.begin_t #needed for rollbacks
|
266
|
+
|
267
|
+
unless empty_pcap
|
268
|
+
unless db.insert("traffic_dumps", dumpvalues)
|
269
|
+
LOGGER.fatal "DB", "VM#{guestvm[0]} Error while inserting data into table traffic_dumps. Skipping binary #{bin.md5}"
|
270
|
+
FileUtils.rm_r(sample_home)
|
271
|
+
return false
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
|
276
|
+
|
277
|
+
unless db.insert("analyses", analysis_values)
|
278
|
+
LOGGER.fatal "DB", "VM#{guestvm[0]} Error while inserting data into table analyses. Skipping binary #{bin.md5}"
|
279
|
+
FileUtils.rm_r(sample_home)
|
280
|
+
return false
|
281
|
+
end
|
282
|
+
|
283
|
+
#TODO ADD RT CODE
|
284
|
+
|
285
|
+
db.commit
|
286
|
+
db.close
|
287
|
+
|
288
|
+
LOGGER.info "VSM", "VM#{guestvm[0]} ".yellow + "Removing file from /bins directory"
|
289
|
+
FileUtils.rm(bin.binpath)
|
290
|
+
LOGGER.info "VSM", "VM#{guestvm[0]} ".yellow + "Process compleated successfully"
|
291
|
+
|
292
|
+
rescue => e
|
293
|
+
|
294
|
+
LOGGER.error "SANDBOX", "VM#{guestvm[0]} An error occurred while analyzing #{bin.filename}, skipping\n"
|
295
|
+
LOGGER.debug "Dorothy" , "#{$!}\n #{e.inspect} \n #{e.backtrace}" if VERBOSE
|
296
|
+
|
297
|
+
FileUtils.rm_r(sample_home)
|
298
|
+
db.rollback unless db.nil? #rollback in case there is a transaction on going
|
299
|
+
return false
|
300
|
+
end
|
301
|
+
|
302
|
+
|
303
|
+
|
304
|
+
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
########################
|
309
|
+
## VTOTAL SCAN ####
|
310
|
+
########################
|
311
|
+
private
|
312
|
+
def scan(bin)
|
313
|
+
#puts "TOTAL", "Forking for VTOTAL"
|
314
|
+
@vtotal_threads << Thread.new(bin.sha) {
|
315
|
+
LOGGER.info "VTOTAL", "Scanning file #{bin.md5}".yellow
|
316
|
+
|
317
|
+
vt = Vtotal.new
|
318
|
+
id = vt.analyze_file(bin.binpath)
|
319
|
+
|
320
|
+
LOGGER.debug "VTOTAL", "Sleeping"
|
321
|
+
|
322
|
+
sleep 15
|
323
|
+
|
324
|
+
until vt.get_report(id)
|
325
|
+
LOGGER.info "VTOTAL", "Waiting a while and keep retring..."
|
326
|
+
sleep 30
|
327
|
+
end
|
328
|
+
|
329
|
+
LOGGER.info("VTOTAL", "#{bin.md5} Detection Rate: #{vt.rate}")
|
330
|
+
LOGGER.info("VTOTAL", "#{bin.md5} Family by McAfee: #{vt.family}")
|
331
|
+
|
332
|
+
LOGGER.info "VTOTAL", "Updating DB"
|
333
|
+
vtvalues = [bin.sha, vt.family, vt.vendor, vt.version, vt.rate, vt.updated, vt.detected]
|
334
|
+
db = Insertdb.new
|
335
|
+
db.begin
|
336
|
+
begin
|
337
|
+
db.insert("malwares", vtvalues)
|
338
|
+
db.close
|
339
|
+
rescue
|
340
|
+
db.rollback
|
341
|
+
LOGGER.error "VTOTAL", "Error while inserting values in malware table"
|
342
|
+
end
|
343
|
+
|
344
|
+
#TODO upload evidence to RT
|
345
|
+
}
|
346
|
+
|
347
|
+
end
|
348
|
+
|
349
|
+
|
350
|
+
|
351
|
+
#########################
|
352
|
+
## MAIN #
|
353
|
+
#########################
|
354
|
+
|
355
|
+
def self.start(source=nil, daemon=nil)
|
356
|
+
|
357
|
+
@db = Insertdb.new
|
358
|
+
daemon ||= false
|
359
|
+
|
360
|
+
puts "[Dorothy]".yellow + " Process Started"
|
361
|
+
|
362
|
+
|
363
|
+
LOGGER.info "Dorothy", "Started".yellow
|
364
|
+
|
365
|
+
if daemon
|
366
|
+
check_pid_file DoroSettings.env[:pidfile]
|
367
|
+
puts "[Dorothy]".yellow + " Going in backround with pid #{Process.pid}"
|
368
|
+
puts "[Dorothy]".yellow + " Logging on #{DoroSettings.env[:logfile]}"
|
369
|
+
Process.daemon
|
370
|
+
create_pid_file DoroSettings.env[:pidfile]
|
371
|
+
LOGGER.info "Dorothy", "Going in backround with pid #{Process.pid}"
|
372
|
+
end
|
373
|
+
|
374
|
+
#Creating a new NAM object for managing the sniffer
|
375
|
+
@nam = Doro_NAM.new(DoroSettings.nam)
|
376
|
+
|
377
|
+
@vtotal_threads = []
|
378
|
+
@vtotal_threads = []
|
379
|
+
@analysis_threads = []
|
380
|
+
|
381
|
+
infinite = true
|
382
|
+
|
383
|
+
#be sure that all the vm are available by forcing their release
|
384
|
+
@db.vm_init
|
385
|
+
|
386
|
+
if source # a source has been specified
|
387
|
+
while infinite #infinite loop
|
388
|
+
dfm = DorothyFetcher.new(source)
|
389
|
+
start_analysis(dfm.bins)
|
390
|
+
infinite = daemon #exit if wasn't set
|
391
|
+
wait_end
|
392
|
+
LOGGER.info "Dorothy", "SLEEPING" if daemon
|
393
|
+
sleep DoroSettings.env[:dtimeout] if daemon # Sleeping a while if -d wasn't set, then quit.
|
394
|
+
end
|
395
|
+
else # no sources specified, analyze all of them
|
396
|
+
while infinite #infinite loop
|
397
|
+
sources = YAML.load_file(DoroSettings.env[:home] + '/etc/sources.yml')
|
398
|
+
sources.keys.each do |sname|
|
399
|
+
dfm = DorothyFetcher.new(sources[sname])
|
400
|
+
start_analysis(dfm.bins)
|
401
|
+
end
|
402
|
+
infinite = daemon #exit if wasn't set
|
403
|
+
wait_end
|
404
|
+
LOGGER.info "Dorothy", "SLEEPING" if daemon
|
405
|
+
sleep DoroSettings.env[:dtimeout] if daemon # Sleeping a while if -d wasn't set, then quit.
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
@db.close
|
410
|
+
|
411
|
+
end
|
412
|
+
|
413
|
+
def wait_end
|
414
|
+
|
415
|
+
unless @vtotal_threads.empty?
|
416
|
+
@vtotal_threads.each { |aThread| aThread.join}
|
417
|
+
LOGGER.info "VTOTAL","Process compleated successfully"
|
418
|
+
end
|
419
|
+
|
420
|
+
@analysis_threads.each { |aThread| aThread.join }
|
421
|
+
LOGGER.info "Dorothy", "Process finished"
|
422
|
+
|
423
|
+
end
|
424
|
+
|
425
|
+
def check_pid_file file
|
426
|
+
if File.exist? file
|
427
|
+
# If we get Errno::ESRCH then process does not exist and
|
428
|
+
# we can safely cleanup the pid file.
|
429
|
+
pid = File.read(file).to_i
|
430
|
+
begin
|
431
|
+
Process.kill(0, pid)
|
432
|
+
rescue Errno::ESRCH
|
433
|
+
stale_pid = true
|
434
|
+
end
|
435
|
+
|
436
|
+
unless stale_pid
|
437
|
+
puts "[Dorothy]".yellow + " Dorothy is already running (pid=#{pid})"
|
438
|
+
exit(1)
|
439
|
+
end
|
440
|
+
end
|
441
|
+
end
|
442
|
+
|
443
|
+
def create_pid_file file
|
444
|
+
File.open(file, "w") { |f| f.puts Process.pid }
|
445
|
+
|
446
|
+
# Remove pid file during shutdown
|
447
|
+
at_exit do
|
448
|
+
Logger.info "Dorothy", "Shutting down." rescue nil
|
449
|
+
if File.exist? file
|
450
|
+
File.unlink file
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
|
455
|
+
## Sends SIGTERM to process in pidfile. Server should trap this
|
456
|
+
# and shutdown cleanly.
|
457
|
+
def self.stop
|
458
|
+
LOGGER.info "Dorothy", "Shutting down."
|
459
|
+
pid_file = DoroSettings.env[:pidfile]
|
460
|
+
if pid_file and File.exist? pid_file
|
461
|
+
pid = Integer(File.read(pid_file))
|
462
|
+
Process.kill(-15, -pid)
|
463
|
+
puts "[Dorothy]".yellow + " Process #{pid} terminated"
|
464
|
+
LOGGER.info "Dorothy", "Process #{pid} terminated"
|
465
|
+
else
|
466
|
+
puts "[Dorothy]".yellow + " Can't find PID file, is Dorothy really running?"
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
470
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require "test/unit"
|
3
|
+
require 'dorothy2' #comment for testing/developmnet
|
4
|
+
|
5
|
+
#load '../lib/dorothy2.rb'
|
6
|
+
|
7
|
+
include Dorothy
|
8
|
+
|
9
|
+
LOGGER = DoroLogger.new(STDOUT, "weekly")
|
10
|
+
|
11
|
+
CONF = "#{File.expand_path("~")}/.dorothy.yml"
|
12
|
+
|
13
|
+
#LOAD ENV
|
14
|
+
if Util.exists?(CONF)
|
15
|
+
DoroSettings.load!(CONF)
|
16
|
+
else
|
17
|
+
DoroConfig.create
|
18
|
+
exit(0)
|
19
|
+
end
|
20
|
+
|
21
|
+
class DoroTest < Test::Unit::TestCase
|
22
|
+
|
23
|
+
# Called before every test method runs. Can be used
|
24
|
+
# to set up fixture information.
|
25
|
+
def setup
|
26
|
+
DoroSettings.load!(CONF)
|
27
|
+
@db = Insertdb.new
|
28
|
+
guestvm = @db.find_vm
|
29
|
+
assert_nothing_raised { @vsm = Doro_VSM::ESX.new(DoroSettings.esx[:host],DoroSettings.esx[:user],DoroSettings.esx[:pass],guestvm[1], guestvm[3], guestvm[4]) }
|
30
|
+
@nam = Doro_NAM.new(DoroSettings.nam)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Called after every test method runs. Can be used to tear
|
34
|
+
# down fixture information.
|
35
|
+
|
36
|
+
def teardown
|
37
|
+
@db.vm_init
|
38
|
+
@db.close
|
39
|
+
end
|
40
|
+
|
41
|
+
# Fake test
|
42
|
+
def test_db_A_connection
|
43
|
+
assert_kind_of(Dorothy::Insertdb, @db, "Problem, can't connect to DB")
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_db_B_insert
|
47
|
+
randstring = (0...8).map{(65+rand(26)).chr}.join
|
48
|
+
values = [randstring, 16, "pe", "", "test.exe", "testtest", "test"]
|
49
|
+
assert_kind_of(PG::Result, @db.insert("samples", values), "Problem, can't insert data into the DB")
|
50
|
+
end
|
51
|
+
|
52
|
+
# def test_vsm
|
53
|
+
# guestvm = @db.find_vm
|
54
|
+
# assert_nothing_raised { @vsm = Doro_VSM::ESX.new(DoroSettings.esx[:host],DoroSettings.esx[:user],DoroSettings.esx[:pass],guestvm[1], guestvm[3], guestvm[4]) }
|
55
|
+
# end
|
56
|
+
|
57
|
+
def test_vsm_A_execute
|
58
|
+
assert_nothing_raised {@vsm.exec_file("windows\\system32\\calc.exe")}
|
59
|
+
assert_kind_of(Fixnum, @vsm.exec_file("windows\\system32\\calc.exe"))
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_vsm_B_chk_internet
|
63
|
+
assert_nothing_raised {@vsm.check_internet}
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_vsm_C_screenshot
|
67
|
+
assert_nothing_raised {@vsm.screenshot}
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_vsm_D_copy_screenshot
|
71
|
+
screen = @vsm.screenshot
|
72
|
+
assert_nothing_raised {Ssh.download(DoroSettings.esx[:host],DoroSettings.esx[:user], DoroSettings.esx[:pass], screen, Dir.pwd)}
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_vsm_E_revertvm
|
76
|
+
assert_nothing_raised {@vsm.revert_vm}
|
77
|
+
end
|
78
|
+
|
79
|
+
#NAM
|
80
|
+
|
81
|
+
def test_nam_A_start_stop
|
82
|
+
puts "NAM".yellow + " Starting sniffer on NAM"
|
83
|
+
assert_nothing_raised { @nampid = @nam.start_sniffer("localhost",DoroSettings.nam[:interface], "testpcap", DoroSettings.nam[:pcaphome])}
|
84
|
+
assert_kind_of(Fixnum, @nampid)
|
85
|
+
sleep 3
|
86
|
+
puts "NAM".yellow + " Stopping sniffer on NAM"
|
87
|
+
assert_nothing_raised {@nam.stop_sniffer(@nampid)}
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_nam_C_copydump
|
91
|
+
assert_nothing_raised {Ssh.download(DoroSettings.nam[:host], DoroSettings.nam[:user],DoroSettings.nam[:pass], DoroSettings.nam[:pcaphome] + "/" + "testpcap.pcap", Dir.pwd)}
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
end
|
data/var/log/parser.log
ADDED
File without changes
|