Class: Uirusu::CLI::Application

Inherits:
Object
  • Object
show all
Defined in:
lib/uirusu/cli/application.rb

Instance Method Summary collapse

Constructor Details

#initializeApplication

Creates a new instance of the [Application] class



35
36
37
38
39
40
41
42
# File 'lib/uirusu/cli/application.rb', line 35

def initialize
	@options = {}
	@config = {}
	@hashes = Array.new
	@files_of_hashes = Array.new
	@sites = Array.new
	@uploads = Array.new
end

Instance Method Details

#load_configObject

Loads the .uirusu config file for the api key



171
172
173
174
175
176
177
178
179
180
# File 'lib/uirusu/cli/application.rb', line 171

def load_config
	if File.exists?(File.expand_path(CONFIG_FILE))
		@config = YAML.load_file File.expand_path(CONFIG_FILE)
	else
		STDERR.puts "[!] #{CONFIG_FILE} does not exist. Please run #{APP_NAME} --create-config, to create it."
		exit
	end

	@options[:timeout] = @config['virustotal']['timeout'] if @config['virustotal']['timeout'] != nil
end

#main(args) ⇒ Object



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/uirusu/cli/application.rb', line 264

def main(args)
	parse_options(args)
	load_config

	if @options['output'] == :stdout
		output_method = :to_stdout
	elsif @options['output'] == :json
		output_method = :to_json
	elsif @options['output'] == :yaml
		output_method = :to_yaml
	elsif @options['output'] == :xml
		output_method = :to_xml
	end

	if @options['proxy'] != nil
		RestClient.proxy = @options['proxy']
	end

	if @options[:directory] != nil
		hashes = Uirusu::Scanner.scan(@options[:directory])

		hashes.each do |hash|
			@hashes.push hash
		end
	end

	if @files_of_hashes != nil
		@files_of_hashes.each do |file|
			f = File.open(file, 'r')

			f.each do |hash|
				hash.chomp!
				@hashes.push hash
			end
		end
	end

	if @hashes != nil
		@hashes.each_with_index do |hash, index|
			if @options['rescan']
				results = scan_and_wait(Uirusu::VTFile, hash, 5)
			else
				results = Uirusu::VTFile.query_report(@config['virustotal']['api-key'], hash)
			end

			result = Uirusu::VTResult.new(hash, results)
			print result.send output_method if result != nil
			sleep @options[:timeout] if index != @hashes.length - 1
		end
	end

	if @sites != nil
		@sites.each_with_index do |url, index|
			results = scan_and_wait(Uirusu::VTUrl, url, 5)
			result = Uirusu::VTResult.new(results[0], results[1])
			print result.send output_method if result != nil
			sleep @options[:timeout] if index != @sites.length - 1
		end
	end

	if @uploads != nil
		@uploads.each_with_index do |upload, index|
			results = scan_and_wait(Uirusu::VTFile, upload, 5)
			result = Uirusu::VTResult.new(results[0], results[1])
			print result.send output_method if result != nil
			sleep @options[:timeout] if index != @uploads.length - 1
		end
	end
end

#parse_options(args) ⇒ Hash

Parses the command the line options and returns the parsed options hash

Returns:

  • (Hash)

    of the parsed options



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/uirusu/cli/application.rb', line 47

def parse_options(args)
	begin
		@options['output']  = :stdout
		@options['verbose'] = false
		@options['rescan']  = false
		@options[:timeout]  = 25
		@options[:directory] = nil

		opt = OptionParser.new do |opt|
			opt.banner = "#{APP_NAME} v#{VERSION}\nJacob Hammack\nhttp://www.arxopia.com\n\n"
			opt.banner << "Usage: #{APP_NAME} <options>"
			opt.separator('')
			opt.separator('File Options')

			opt.on('-h HASH', '--search-hash HASH', 'Searches a single hash on virustotal.com') do |hash|
				@hashes.push(hash)
			end

			opt.on('-r HASH[,HASH]', '--rescan-hash HASH[,HASH]', 'Requests a rescan of a single hash, or multiple hashes (comma separated), by virustotal.com') do |hash|
				@options['rescan'] = true
				@hashes.push(hash)
			end

			opt.on('-f FILE', '--search-hash-file FILE', 'Searches a each hash in a file of hashes on virustotal.com') do |file|
				if File.exists?(file)
					puts "[+] Adding file #{file}" if @options['verbose']
					@files_of_hashes.push(file)
				else
					puts "[!] #{file} does not exist, please check your input!\n"
				end
			end

			opt.on('-u FILE', '--upload-file FILE', 'Uploads a file to virustotal.com for analysis') do |file|
				if File.exists?(file)
					puts "[+] Adding file #{file}" if @options['verbose']
					@uploads.push(file)
				else
					puts "[!] #{file} does not exist, please check your input!\n"
				end
			end

			opt.separator('')
			opt.separator("Url Options")

			opt.on('-s SITE', '--search-site SITE', 'Searches for a single url on virustotal.com') { |site|
				@sites.push(site)
			}

			opt.separator('')
			opt.separator('Output Options')

			opt.on('-j', '--json-output', 'Print results as json to stdout') do
				@options['output'] = :json
			end

			opt.on('-x', '--xml-output', 'Print results as xml to stdout') do
				@options['output'] = :xml
			end

			opt.on('-y', '--yaml-output', 'Print results as yaml to stdout') do
				@options['output'] = :yaml
			end

			opt.on('--stdout-output', 'Print results as normal text line to stdout, this is default') do
				@options['output'] = :stdout
			end

			opt.separator ''
			opt.separator 'Advanced Options'

			opt.on('-c', '--create-config', 'Creates a skeleton config file to use') do
				if File.exists?(File.expand_path(CONFIG_FILE)) == false
					File.open(File.expand_path(CONFIG_FILE), 'w+') do |f|
						f.write("virustotal: \n  api-key: \n  timeout: 25\n\n")
					end

					puts "[*] An empty #{File.expand_path(CONFIG_FILE)} has been created. Please edit and fill in the correct values."
					exit
				else
					puts "[!]  #{File.expand_path(CONFIG_FILE)} already exists. Please delete it if you wish to re-create it."
					exit
				end
			end

			opt.on('-d DIRECTORY', '--directory', 'Scans a directory recursively for files and submits the hashes') do |directory|
				@options[:directory] = directory
			end

			opt.on('-p PROXY', '--proxy-server', 'Uses a specified proxy server') do |proxy|
				@options['proxy'] = proxy
			end

			opt.on('--[no-]verbose', 'Print verbose information') do |v|
				@options['verbose'] = v
			end

			opt.separator ''
			opt.separator 'Other Options'

			opt.on('-v', '--version', 'Shows application version information') do
				puts "#{APP_NAME} - #{VERSION}"
				exit
			end

			opt.on_tail('-?', '--help', 'Show this message') { |help|
				puts opt.to_s + "\n"
				exit
			}
		end

	  if ARGV.length != 0
	    opt.parse!
	  else
	    puts opt.to_s + "\n"
		  exit
		end
	rescue OptionParser::MissingArgument => m
		puts opt.to_s + "\n"
		exit
	end
end

#scan_and_wait(mod, resource, attempts) ⇒ Object

Submits a file/url and waits for analysis to be complete and returns the results.

Parameters:

  • mod
  • resource
  • attempts


188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
# File 'lib/uirusu/cli/application.rb', line 188

def scan_and_wait(mod, resource, attempts)
	method = nil
	retries = attempts

	if mod.name == "Uirusu::VTFile"
		STDERR.puts "[*] Attempting to rescan #{resource}" if  @options['verbose']
		method = @options['rescan'] ? mod.method(:rescan_file) : mod.method(:scan_file)
	else
		STDERR.puts "[*] Attempting to upload file #{resource}" if  @options['verbose']
		method = mod.method :scan_url
	end

	begin
		result = method.call(@config['virustotal']['api-key'], resource)
	rescue => e
		if @options['rescan']
			STDERR.puts "[!] An error has occurred with the rescan request.  Retrying 60 seconds up #{retries} retries: #{e.message}\n" if  @options['verbose']
		else
			STDERR.puts "[!] An error has occurred uploading the file. Retrying 60 seconds up #{retries} retries.\n" if  @options['verbose']
		end

		if retries >= 0
			sleep 60
			retry
			retries = retries - 1
		end
	end

	begin

		# Convert all single result replies to an array.  This is because
		# rescan_file returns an array of results if more than one hash
		# is requested to be rescanned.
		result_array = result.is_a?(Array) ? result : [ result ]

		result_array.collect do |result|
			if result['response_code'] == 1
				STDERR.puts "[*] Attempting to parse the results for: #{result['resource']}" if @options['verbose']
				results = mod.query_report(@config['virustotal']['api-key'], result['resource'])

				while results['response_code'] != 1
					STDERR.puts "[*] File has not been analyized yet, waiting 60 seconds to try again" if  @options['verbose']
					sleep 60
					results = mod.query_report(@config['virustotal']['api-key'], result['resource'])
				end

				[result['resource'], results]
				#return [result['resource'], results]

			elsif result['response_code'] == 0 and @options['rescan']
				STDERR.puts "[!] Unknown Virustotal error for rescan of #{result['resource']}." if @options['verbose']
				next

			elsif result['response_code'] == -1 and @options['rescan']
				STDERR.puts "[!] Virustotal does not have a sample of #{result['resource']}." if @options['verbose']
				next

			elsif result['response_code'] == -2
				STDERR.puts "[!] Virustotal limits exceeded, ***do not edit the timeout values.***"
				exit(1)
			else
				nil
			end
		end
	rescue => e
		STDERR.puts "[!] An error has occurred retrieving the report. Retrying 60 seconds up #{retries} retries. #{e.message}\n" if  @options['verbose']
		if retries >= 0
			sleep 60
			retry
			retries = retries - 1
		end
	end
end