dorothy2 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,156 @@
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
+
6
+ ###########################
7
+ ###BINARY FETCHER MODULE###
8
+ ### ###
9
+ ###########################
10
+
11
+ module Dorothy
12
+
13
+
14
+ class DorothyFetcher
15
+ attr_reader :bins
16
+
17
+
18
+ def initialize(source) #source struct: Hash, {:dir => "#{HOME}/bins/honeypot", :typeid=> 0 ..}
19
+ ndownloaded = 0
20
+
21
+ @bins = []
22
+ #case source.honeypot1[:type]
23
+
24
+ case source["type"]
25
+
26
+ when "ssh" then
27
+ LOGGER.info "BFM", " Fetching trojan from > Honeypot"
28
+ #file = "/opt/dionaea/var/dionaea/binaries/"
29
+
30
+ #puts "Start to download malware"
31
+
32
+ files = []
33
+
34
+ begin
35
+ Net::SSH.start(source["ip"], source["user"], :password => source["pass"], :port => source["port"]) do |ssh|
36
+ ssh.scp.download!(source["remotedir"],source["localdir"], :recursive => true) do |ch, name, sent, total|
37
+ unless files.include? "#{source["localdir"]}/" + File.basename(name)
38
+ ndownloaded += 1
39
+ files.push "#{source["localdir"]}/" + File.basename(name)
40
+ # puts ""
41
+ end
42
+ # print "#{File.basename(name)}: #{sent}/#{total}\r"
43
+ # $stdout.flush
44
+ end
45
+ LOGGER.info "BFM", "#{ndownloaded} files downloaded"
46
+ end
47
+
48
+
49
+ rescue => e
50
+ LOGGER.error "BFM", "An error occurred while downloading malwares from honeypot sensor: " + $!
51
+ LOGGER.error "BFM", "Error: #{$!}, #{e.inspect}, #{e.backtrace}"
52
+ end
53
+
54
+ #DIRTY WORKAROUND for scp-ing only files without directory
55
+
56
+ FileUtils.mv(Dir.glob(source["localdir"] + "/binaries/*"), source["localdir"])
57
+ Dir.rmdir(source["localdir"] + "/binaries")
58
+
59
+
60
+ begin
61
+
62
+ unless DoroSettings.env[:testmode]
63
+ Net::SSH.start(source["ip"], source["user"], :password => source["pass"], :port => source["port"]) do |ssh|
64
+ ssh.exec "mv #{source["remotedir"]}/* #{source["remotedir"]}/../analyzed "
65
+ end
66
+ end
67
+
68
+ rescue
69
+ LOGGER.error "BFM", "An error occurred while erasing parsed malwares in the honeypot sensor: " + $!
70
+ end
71
+
72
+ files.each do |f|
73
+ next unless load_malw(f, source[skey][:typeid])
74
+ end
75
+
76
+ when "system" then
77
+ LOGGER.info "BFM", "Fetching trojan from > filesystem: " + source["localdir"]
78
+ empty = true
79
+ Dir.foreach(source["localdir"]) do |file|
80
+ bin = source["localdir"] + "/" + file
81
+ next if File.directory?(bin) || !load_malw(bin,source["typeid"])
82
+ empty = false
83
+ end
84
+ LOGGER.warn "BFM", "There are no files to analyze in the selected source" if empty
85
+ else
86
+ LOGGER.fatal "BFM", "Source #{skey} is not yet configured"
87
+ end
88
+ end
89
+
90
+
91
+
92
+ private
93
+ def load_malw(f, typeid, sourceinfo = nil)
94
+
95
+ filename = File.basename f
96
+ bin = Loadmalw.new(f)
97
+ if bin.size == 0 || bin.sha.empty?
98
+ LOGGER.warn "BFM", "Warning - Empty file #{filename}, deleting and skipping.."
99
+ FileUtils.rm bin.binpath
100
+ return false
101
+ end
102
+
103
+ samplevalues = [bin.sha, bin.size, bin.dbtype, bin.dir_bin, filename, bin.md5, bin.type ]
104
+ sighvalues = [bin.sha, typeid, bin.ctime, "null"]
105
+
106
+ begin
107
+ updatedb(samplevalues, sighvalues)
108
+ rescue => e
109
+ LOGGER.error "DB", $!
110
+ LOGGER.debug "DB", e.inspect
111
+ return false
112
+ end
113
+
114
+ #FileUtils.rm(bin.binpath)
115
+ @bins.push bin
116
+ end
117
+
118
+
119
+
120
+ def updatedb(samplevalues, sighvalues, airisvalues=nil)
121
+
122
+ db = Insertdb.new
123
+ db.begin_t
124
+
125
+ unless db.select("samples", "hash", samplevalues[0]).one? #is bin.sha already present in my db?
126
+ raise "A DB error occurred" unless db.insert("samples", samplevalues) #no it isn't, insert it
127
+
128
+ else #yes it is, don't insert in sample table
129
+ date = db.select("sightings", "sample", samplevalues[0]).first["date"]
130
+ LOGGER.warn "BFM", " The binary #{samplevalues[0]} has been already added on #{date}"
131
+ end
132
+
133
+ raise "A DB error occurred" unless db.insert("sightings", sighvalues)
134
+
135
+ # explanation: I don't want to insert/analyze the same malware but I do want to
136
+ # insert the sighting value anyway ("the malware X has been downloaded 1 time but
137
+ # has been spoted 32 times")
138
+
139
+ db.commit
140
+ db.close
141
+ true
142
+
143
+ end
144
+
145
+
146
+
147
+ end
148
+
149
+ end
150
+
151
+
152
+
153
+
154
+
155
+
156
+
@@ -0,0 +1,239 @@
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 Doro_VSM
8
+
9
+ #Creates a new instance for communicating with ESX through the vSpere5's API
10
+ class ESX
11
+
12
+ def initialize(server,user,pass,vmname,guestuser,guestpass)
13
+
14
+ begin
15
+ vim = RbVmomi::VIM.connect(:host => server , :user => user, :password=> pass, :insecure => true)
16
+ rescue Timeout::Error
17
+ raise "Fail to connect to the ESXi server #{server} - TimeOut (Are you sure that is the right address?)"
18
+ end
19
+
20
+ @server = server
21
+ dc = vim.serviceInstance.find_datacenter
22
+ @vm = dc.find_vm(vmname)
23
+
24
+ raise "Virtual Machine #{vmname} not present within ESX!!" if @vm.nil?
25
+
26
+ om = vim.serviceContent.guestOperationsManager
27
+ am = om.authManager
28
+ @pm = om.processManager
29
+ @fm = om.fileManager
30
+
31
+ #AUTHENTICATION
32
+ guestauth = {:interactiveSession => false, :username => guestuser, :password => guestpass}
33
+ @auth=RbVmomi::VIM::NamePasswordAuthentication(guestauth)
34
+ abort if am.ValidateCredentialsInGuest(:vm => @vm, :auth => @auth) != nil
35
+ end
36
+
37
+ def revert_vm
38
+ @vm.RevertToCurrentSnapshot_Task
39
+ end
40
+
41
+ def copy_file(filename,file)
42
+ filepath = "C:\\#{filename}" #put md5 hash
43
+
44
+ begin
45
+ url = @fm.InitiateFileTransferToGuest(:vm => @vm, :auth=> @auth, :guestFilePath=> filepath, :fileSize => file.size, :fileAttributes => '', :overwrite => true).sub('*:443', @server)
46
+
47
+ RestClient.put(url, file)
48
+
49
+ rescue RbVmomi::Fault
50
+ LOGGER.error "VSM", "Fail to copy the file #{file} to #{@vm}: #{$!}"
51
+ abort
52
+ end
53
+
54
+ end
55
+
56
+ def exec_file(filename, arguments="")
57
+ filepath = "C:\\#{filename}"
58
+
59
+ if File.extname(filename) == ".dll"
60
+ cmd = { :programPath => "C:\\windows\\system32\\rundll32.exe", :arguments => filepath}
61
+ LOGGER.info "VSM", ".:: Executing dll #{filename}"
62
+
63
+ else
64
+ cmd = { :programPath => filepath, :arguments => arguments }
65
+ end
66
+
67
+ pid = @pm.StartProgramInGuest(:vm => @vm , :auth => @auth, :spec => cmd )
68
+ pid.to_i
69
+ end
70
+
71
+ def check_internet
72
+ exec_file("windows\\system32\\ping.exe", "-n 1 www.google.com") #make www.google.com customizable, move to doroconf
73
+ end
74
+
75
+
76
+ def get_status(pid)
77
+ p = @pm.ListProcessesInGuest(:vm => @vm , :auth => @auth, :pids => Array(pid) ).inspect
78
+ status = (p =~ /exitCode=>([0-9])/ ? $1.to_i : nil )
79
+ return status
80
+ end
81
+
82
+
83
+ def screenshot
84
+ a = @vm.CreateScreenshot_Task.wait_for_completion.split(" ")
85
+ ds = @vm.datastore.find { |ds| ds.name == a[0].delete("[]")}
86
+ screenpath = "/vmfs/volumes/" + a[0].delete("[]") + "/" + a[1]
87
+ return screenpath
88
+ end
89
+ end
90
+
91
+ #TODO. Example of how a new VSM´s structure should look like
92
+ class VirtualBox
93
+ def initialize
94
+
95
+ end
96
+
97
+ def revert_vm
98
+
99
+ end
100
+
101
+ def copy_file
102
+
103
+ end
104
+
105
+ def exec_file
106
+
107
+ end
108
+
109
+ def check_internet
110
+
111
+ end
112
+
113
+ def get_status
114
+
115
+ end
116
+
117
+ def screenshot
118
+
119
+ end
120
+
121
+ end
122
+ end
123
+
124
+ class Loadmalw
125
+ attr_reader :pcaprid
126
+ attr_reader :type
127
+ attr_reader :dbtype
128
+ attr_accessor :sha
129
+ attr_reader :md5
130
+ attr_reader :binpath
131
+ attr_reader :filename
132
+ attr_reader :ctime
133
+ attr_reader :size
134
+ attr_reader :pcapsize
135
+ attr_reader :extension
136
+ attr_accessor :sourceinfo #used for storing info about where the binary come from (if needed)
137
+
138
+ # attr_accessor :dir_home
139
+ attr_accessor :dir_pcap
140
+ attr_accessor :dir_bin
141
+ attr_accessor :dir_screens
142
+ attr_accessor :dir_downloads
143
+
144
+ def initialize(file)
145
+
146
+ fm = FileMagic.new
147
+ sha = Digest::SHA2.new
148
+ md5 = Digest::MD5.new
149
+ @binpath = file
150
+ @filename = File.basename file
151
+ @extension = File.extname file
152
+ @dbtype = "null" #TODO: remove type column in sample table
153
+
154
+ File.open(file, 'rb') do |fh1|
155
+ while buffer1 = fh1.read(1024)
156
+ @sha = sha << buffer1
157
+ @md5 = md5 << buffer1
158
+ end
159
+ end
160
+
161
+ @sha = @sha.to_s
162
+ @md5 = @md5.to_s
163
+ @sourceinfo = nil
164
+
165
+ timetmp = File.ctime(file)
166
+ @ctime= timetmp.strftime("%m/%d/%y %H:%M:%S")
167
+ @type = fm.file(file)
168
+
169
+ if @extension.empty? #no extension, trying to put the right one..
170
+ case @type
171
+ when /^PE32/ then
172
+ @extension = (@type =~ /DLL/ ? ".dll" : ".exe")
173
+ when /^MS-DOS/ then
174
+ @extension = ".bat"
175
+ when /^HTML/ then
176
+ @extension = ".html"
177
+ else
178
+ @extension = nil
179
+ end
180
+ end
181
+
182
+
183
+ @size = File.size(file)
184
+ # @dir_pcap = "#{ANALYSIS_DIR}/#{@md5}/pcap/"
185
+ # @dir_bin = "#{ANALYSIS_DIR}/#{@md5}/bin/"
186
+ # @dir_screens = "#{ANALYSIS_DIR}/#{@md5}/screens/"
187
+ # @dir_downloads = "#{ANALYSIS_DIR}/#{@md5}/downloads/"
188
+ end
189
+
190
+
191
+
192
+ def self.calc_pcaprid(file, size)
193
+ #t = file.split('/')
194
+ #dumpname = t[t.length - 1]
195
+ @pcaprid = Digest::MD5.new
196
+ @pcaprid << "#{file}:#{size}"
197
+ @pcaprid =@pcaprid.dup.to_s
198
+ end
199
+
200
+
201
+ end
202
+
203
+ class Doro_NAM
204
+
205
+ #Create a dotothy user in the NSM machine, and add this line to the sudoers :
206
+ # dorothy ALL = NOPASSWD: /usr/sbin/tcpdump, /bin/kill
207
+ #
208
+
209
+ def initialize(namdata)
210
+ @server = namdata[:host]
211
+ @user= namdata[:user]
212
+ @pass= namdata[:pass]
213
+ @port = namdata[:port]
214
+ end
215
+
216
+ def start_sniffer(vmaddress, interface, name, pcaphome)
217
+ Net::SSH.start(@server, @user, :password => @pass, :port =>@port) do |@ssh|
218
+ # @ssh.exec "nohup sudo tcpdump -i eth0 -s 1514 -w ~/pcaps/#{name}.pcap host #{vmaddress} > blah.log 2>&1 & "
219
+ @ssh.exec "nohup sudo tcpdump -i #{interface} -s 1514 -w #{pcaphome}/#{name}.pcap host #{vmaddress} > log.tmp 2>&1 & "
220
+ t = @ssh.exec!"ps aux |grep #{vmaddress}|grep -v grep|grep -v bash"
221
+ pid = t.split(" ")[1]
222
+ return pid.to_i
223
+ end
224
+ end
225
+
226
+ def stop_sniffer(pid)
227
+ Net::SSH.start(@server, @user, :password => @pass, :port =>@port) do |ssh|
228
+ ssh.exec "sudo kill -2 #{pid}"
229
+ #LOGGER.info "[NAM]".yellow + "Tcpdump instance #{pid} stopped"
230
+ end
231
+ end
232
+
233
+ end
234
+
235
+ end
236
+
237
+
238
+
239
+
@@ -0,0 +1,35 @@
1
+ #Thx to morhekil :
2
+ #http://speakmy.name/2011/05/29/simple-configuration-for-ruby-apps/
3
+ module Dorothy
4
+
5
+ module DoroSettings
6
+
7
+ extend self
8
+
9
+ @_settings = {}
10
+ attr_reader :_settings
11
+
12
+ def load!(filename, options = {})
13
+ newsets = YAML::load_file(filename).deep_symbolize
14
+ newsets = newsets[options[:env].to_sym] if \
15
+ options[:env] && \
16
+ newsets[options[:env].to_sym]
17
+ deep_merge!(@_settings, newsets)
18
+ end
19
+
20
+ # Deep merging of hashes
21
+ # deep_merge by Stefan Rusterholz, see http://www.ruby-forum.com/topic/142809
22
+ def deep_merge!(target, data)
23
+ merger = proc{|key, v1, v2|
24
+ Hash === v1 && Hash === v2 ? v1.merge(v2, &merger) : v2 }
25
+ target.merge! data, &merger
26
+ end
27
+
28
+ def method_missing(name, *args, &block)
29
+ @_settings[name.to_sym] ||
30
+ fail(NoMethodError, "unknown configuration root #{name}", caller)
31
+ end
32
+
33
+ end
34
+
35
+ end
@@ -0,0 +1,67 @@
1
+ # Symbolizes all of hash's keys and subkeys.
2
+ # Also allows for custom pre-processing of keys (e.g. downcasing, etc)
3
+ # if the block is given:
4
+ #
5
+ # somehash.deep_symbolize { |key| key.downcase }
6
+ #
7
+ # Usage: either include it into global Hash class to make it available to
8
+ # to all hashes, or extend only your own hash objects with this
9
+ # module.
10
+ # E.g.:
11
+ # 1) class Hash; include DeepSymbolizable; end
12
+ # 2) myhash.extend DeepSymbolizable
13
+
14
+ module DeepSymbolizable
15
+
16
+ class Hash
17
+ include DeepSymbolizable
18
+ end
19
+
20
+ def deep_symbolize(&block)
21
+ method = self.class.to_s.downcase.to_sym
22
+ syms = DeepSymbolizable::Symbolizers
23
+ syms.respond_to?(method) ? syms.send(method, self, &block) : self
24
+ end
25
+
26
+ module Symbolizers
27
+ extend self
28
+
29
+ # the primary method - symbolizes keys of the given hash,
30
+ # preprocessing them with a block if one was given, and recursively
31
+ # going into all nested enumerables
32
+ def hash(hash, &block)
33
+ hash.inject({}) do |result, (key, value)|
34
+ # Recursively deep-symbolize subhashes
35
+ value = _recurse_(value, &block)
36
+
37
+ # Pre-process the key with a block if it was given
38
+ key = yield key if block_given?
39
+ # Symbolize the key string if it responds to to_sym
40
+ sym_key = key.to_sym rescue key
41
+
42
+ # write it back into the result and return the updated hash
43
+ result[sym_key] = value
44
+ result
45
+ end
46
+ end
47
+
48
+ # walking over arrays and symbolizing all nested elements
49
+ def array(ary, &block)
50
+ ary.map { |v| _recurse_(v, &block) }
51
+ end
52
+
53
+ # handling recursion - any Enumerable elements (except String)
54
+ # is being extended with the module, and then symbolized
55
+ def _recurse_(value, &block)
56
+ if value.is_a?(Enumerable) && !value.is_a?(String)
57
+ # support for a use case without extended core Hash
58
+ value.extend DeepSymbolizable unless value.class.include?(DeepSymbolizable)
59
+ value = value.deep_symbolize(&block)
60
+ end
61
+ value
62
+ end
63
+ end
64
+
65
+ end
66
+
67
+ class Hash; include DeepSymbolizable; end