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.
@@ -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