virustotal 2.0.0
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/LICENSE +25 -0
- data/NEWS.markdown +30 -0
- data/README.markdown +42 -0
- data/Rakefile +15 -0
- data/TODO.markdown +8 -0
- data/bin/virustotal +17 -0
- data/lib/virustotal.rb +16 -0
- data/lib/virustotal/application.rb +184 -0
- data/lib/virustotal/virustotal.rb +104 -0
- data/lib/virustotal/virustotalresult.rb +110 -0
- data/virustotal.gemspec +36 -0
- metadata +112 -0
data/LICENSE
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
Copyright (c) 2010-2011 Jacob Hammack, Hammackj LLC
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without
|
5
|
+
modification, are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright
|
8
|
+
notice, this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright
|
10
|
+
notice, this list of conditions and the following disclaimer in the
|
11
|
+
documentation and/or other materials provided with the distribution.
|
12
|
+
* Neither the name of the Jacob Hammack or Hammackj LLC nor the
|
13
|
+
names of its contributors may be used to endorse or promote products
|
14
|
+
derived from this software without specific prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
17
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
19
|
+
DISCLAIMED. IN NO EVENT SHALL JACOB HAMMACK or HAMMACKJ LLC BE LIABLE FOR ANY
|
20
|
+
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
21
|
+
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
22
|
+
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
23
|
+
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
24
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
25
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/NEWS.markdown
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# News
|
2
|
+
|
3
|
+
# 2.0.0 (February 20, 2011)
|
4
|
+
- Converted the entire thing into a rubygem
|
5
|
+
- Cleaned up the code significantly
|
6
|
+
- Added a config file to store the api-key in
|
7
|
+
- fixed up the command line options some
|
8
|
+
- Optimized execution some
|
9
|
+
|
10
|
+
# 1.0.0 (September 27, 2010)
|
11
|
+
- Cleaned up some dead code
|
12
|
+
- Added URL for the report for easier web viewing.
|
13
|
+
|
14
|
+
# 0.0.5 (September 11, 2010)
|
15
|
+
- Added a url scan option -w, so that urls can be scanned using the new api
|
16
|
+
- Added file upload option -u, so that files can be uploaded and waits for results
|
17
|
+
|
18
|
+
# 0.0.4 (September 10, 2010)
|
19
|
+
- Modified to use the new virustotal.com API, the code has been simplified.
|
20
|
+
|
21
|
+
# 0.0.3 (June 10, 2010)
|
22
|
+
- Added a timer between hash lookups from files
|
23
|
+
- Added a check for the invalid hash error that seems to happen on some MD5 hashes
|
24
|
+
- Added debug output
|
25
|
+
|
26
|
+
# 0.0.2 (January 31, 2010)
|
27
|
+
- Updated a output bug on the usage statement. Thanks to smithj for finding it.
|
28
|
+
|
29
|
+
# 0.0.1 (July 11, 2009)
|
30
|
+
- Initial public release (BlackHat)
|
data/README.markdown
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# ruby-virustotal
|
2
|
+
|
3
|
+
ruby-virustotal is [virustotal](http://www.virustotal.com) automation and convenience tool for hash, file and URL submission.
|
4
|
+
|
5
|
+
The current version is 2.0.
|
6
|
+
|
7
|
+
## Requirements
|
8
|
+
|
9
|
+
* ruby
|
10
|
+
* rubygems
|
11
|
+
* json
|
12
|
+
* rest-client
|
13
|
+
|
14
|
+
* public api key from [virustotal.com](http://www.virustotal.com)
|
15
|
+
|
16
|
+
## Installation
|
17
|
+
|
18
|
+
% gem install virustotal
|
19
|
+
% virustotal [options]
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Searching a file of hashes
|
24
|
+
|
25
|
+
% ./virustotal.rb -f <file_with_hashes_one_per_line>
|
26
|
+
|
27
|
+
### Searching a single hash
|
28
|
+
|
29
|
+
% ./virustotal.rb -h FD287794107630FA3116800E617466A9
|
30
|
+
|
31
|
+
### Searching a file of hashes and outputting to XML
|
32
|
+
% ./virustotal.rb -f <file_with_hashes_one_per_line> -x
|
33
|
+
|
34
|
+
### Upload a file to Virustotal and wait for analysis
|
35
|
+
% ./virustotal.rb -u </path/to/file>
|
36
|
+
|
37
|
+
### Search for a single URL
|
38
|
+
% ./virustotal.rb -s "http://www.google.com"
|
39
|
+
|
40
|
+
## Contact
|
41
|
+
|
42
|
+
You can reach me at Jacob[dot]Hammack[at]hammackj[dot]com or http://www.hammackj.com
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
require "virustotal"
|
4
|
+
|
5
|
+
task :build do
|
6
|
+
system "gem build #{VirusTotal::APP_NAME}.gemspec"
|
7
|
+
end
|
8
|
+
|
9
|
+
task :release => :build do
|
10
|
+
system "gem push #{VirusTotal::APP_NAME}-#{VirusTotal::VERSION}.gem"
|
11
|
+
end
|
12
|
+
|
13
|
+
task :clean do
|
14
|
+
system "rm *.gem"
|
15
|
+
end
|
data/TODO.markdown
ADDED
data/bin/virustotal
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# encoding: utf-8
|
3
|
+
|
4
|
+
# virustotal - a command line tool for working with virustotal.com
|
5
|
+
# Jacob Hammack <jacob.hammack@hammackj.com>
|
6
|
+
# http://www.hammackj.com
|
7
|
+
#
|
8
|
+
# hammackj - 01-05-2011 - Version 2.0.0
|
9
|
+
#
|
10
|
+
|
11
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '/../lib'))
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'virustotal'
|
15
|
+
|
16
|
+
app = VirusTotal::Application.new
|
17
|
+
app.run(ARGV)
|
data/lib/virustotal.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VirusTotal
|
4
|
+
APP_NAME = "virustotal"
|
5
|
+
VERSION = "2.0.0"
|
6
|
+
CONFIG_FILE = "~/.virustotal"
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'json'
|
10
|
+
require 'rest_client'
|
11
|
+
require 'optparse'
|
12
|
+
require 'yaml'
|
13
|
+
|
14
|
+
require 'virustotal/application'
|
15
|
+
require 'virustotal/virustotal'
|
16
|
+
require 'virustotal/virustotalresult'
|
@@ -0,0 +1,184 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VirusTotal
|
4
|
+
class Application
|
5
|
+
|
6
|
+
# Creates a new instance of the [Application] class
|
7
|
+
#
|
8
|
+
def initialize
|
9
|
+
@options = {}
|
10
|
+
@config = {}
|
11
|
+
@hashes = Array.new
|
12
|
+
@files_of_hashes = Array.new
|
13
|
+
@sites = Array.new
|
14
|
+
@uploads = Array.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Parses the command the line options and returns the parsed options hash
|
18
|
+
#
|
19
|
+
# @return [Hash] of the parsed options
|
20
|
+
def parse_options(args)
|
21
|
+
begin
|
22
|
+
@options['output'] = :stdout
|
23
|
+
@options['debug'] = false
|
24
|
+
|
25
|
+
opt = OptionParser.new do |opt|
|
26
|
+
opt.banner = "#{APP_NAME} v#{VERSION}\nJacob Hammack\nhttp://www.hammackj.com\n\n"
|
27
|
+
opt.banner << "Usage: #{APP_NAME} <options>"
|
28
|
+
opt.separator('')
|
29
|
+
opt.separator("Search Options")
|
30
|
+
|
31
|
+
opt.on('-h HASH', '--search-hash HASH', 'Searches a single hash on virustotal.com') { |hash|
|
32
|
+
@hashes.push(hash)
|
33
|
+
}
|
34
|
+
|
35
|
+
opt.on('-f FILE', '--search-file FILE', 'Searches a each hash in a file of hashes on virustotal.com') { |file|
|
36
|
+
if File.exists?(file)
|
37
|
+
puts "[+] Adding file #{file}" if @options["debug"]
|
38
|
+
@files_of_hashes.push(file)
|
39
|
+
else
|
40
|
+
puts "[!] #{file} does not exist, please check your input!\n"
|
41
|
+
end
|
42
|
+
}
|
43
|
+
|
44
|
+
opt.on('-u FILE', '--upload-file FILE', 'Uploads a file to virustotal.com for analysis') do |file|
|
45
|
+
if File.exists?(file)
|
46
|
+
puts "[+] Adding file #{file}" if @options["debug"]
|
47
|
+
@uploads.push(file)
|
48
|
+
else
|
49
|
+
puts "[!] #{file} does not exist, please check your input!\n"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
opt.on('-s SITE', '--search-site SITE', 'Searches for a single url on virustotal.com') { |site|
|
54
|
+
@sites.push(site)
|
55
|
+
}
|
56
|
+
|
57
|
+
opt.separator('')
|
58
|
+
opt.separator('Output Options')
|
59
|
+
|
60
|
+
opt.on('-x', '--xml-output', 'Print results as xml to stdout') {
|
61
|
+
@options["output"] = :xml
|
62
|
+
}
|
63
|
+
|
64
|
+
opt.on('-y', '--yaml-output', 'Print results as yaml to stdout') {
|
65
|
+
@options['output'] = :yaml
|
66
|
+
}
|
67
|
+
|
68
|
+
opt.on('--stdout-output', 'Print results as normal text line to stdout, this is default') {
|
69
|
+
@options['output'] = :stdout
|
70
|
+
}
|
71
|
+
|
72
|
+
opt.separator ''
|
73
|
+
opt.separator 'Advanced Options'
|
74
|
+
|
75
|
+
opt.on('-c', '--create-config', 'Creates a skeleton config file to use') do
|
76
|
+
if File.exists?(File.expand_path(CONFIG_FILE)) == false
|
77
|
+
File.open(File.expand_path(CONFIG_FILE), 'w+') do |f|
|
78
|
+
f.write("virustotal: \n api-key: \n timeout: 10\n\n")
|
79
|
+
end
|
80
|
+
|
81
|
+
puts "[*] An empty #{File.expand_path(CONFIG_FILE)} has been created. Please edit and fill in the correct values."
|
82
|
+
exit
|
83
|
+
else
|
84
|
+
puts "[!] #{File.expand_path(CONFIG_FILE)} already exists. Please delete it if you wish to re-create it."
|
85
|
+
exit
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
opt.on('-d', '--debug', 'Print verbose debug information') do |d|
|
90
|
+
@options["debug"] = d
|
91
|
+
end
|
92
|
+
|
93
|
+
opt.separator ''
|
94
|
+
opt.separator 'Other Options'
|
95
|
+
|
96
|
+
opt.on('-v', '--version', "Shows application version information") do
|
97
|
+
puts "#{APP_NAME} - #{VERSION}"
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
|
101
|
+
opt.on_tail("-?", "--help", "Show this message") { |help|
|
102
|
+
puts opt.to_s + "\n"
|
103
|
+
exit
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
if ARGV.length != 0
|
108
|
+
opt.parse!
|
109
|
+
else
|
110
|
+
puts opt.to_s + "\n"
|
111
|
+
exit
|
112
|
+
end
|
113
|
+
rescue OptionParser::MissingArgument => m
|
114
|
+
puts opt.to_s + "\n"
|
115
|
+
exit
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Loads the .virustotal config file for the api key
|
120
|
+
#
|
121
|
+
def load_config
|
122
|
+
if File.exists?(File.expand_path(CONFIG_FILE))
|
123
|
+
@config = YAML.load_file File.expand_path(CONFIG_FILE)
|
124
|
+
else
|
125
|
+
puts "[!] #{CONFIG_FILE} does not exist. Please run virustotal --create-config, to create it."
|
126
|
+
exit
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Processes all of the command line arguments and displays the results
|
131
|
+
#
|
132
|
+
def run(args)
|
133
|
+
parse_options(args)
|
134
|
+
load_config
|
135
|
+
|
136
|
+
vt = VirusTotal.new(@config["virustotal"]["api-key"], @config["virustotal"]["timeout"], @options["debug"])
|
137
|
+
|
138
|
+
if @options['output'] == :stdout
|
139
|
+
output_method = :to_stdout
|
140
|
+
elsif @options['output'] == :yaml
|
141
|
+
output_method = :to_yaml
|
142
|
+
elsif @options['output'] == :xml
|
143
|
+
output_method = :to_xml
|
144
|
+
print "<results>\n"
|
145
|
+
end
|
146
|
+
|
147
|
+
if @files_of_hashes != nil
|
148
|
+
@files_of_hashes.each do |file|
|
149
|
+
f = File.open(file, 'r')
|
150
|
+
|
151
|
+
f.each do |hash|
|
152
|
+
hash.chomp!
|
153
|
+
@hashes.push hash
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
if @hashes != nil
|
159
|
+
@hashes.each do |hash|
|
160
|
+
result = vt.query_hash hash
|
161
|
+
print result.send output_method
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
if @sites != nil
|
166
|
+
@sites.each do |site|
|
167
|
+
result = vt.query_site site
|
168
|
+
print result.send output_method
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
if @uploads != nil
|
173
|
+
@uploads.each do |upload|
|
174
|
+
result = vt.query_upload upload
|
175
|
+
print result.send output_method
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
if @options['output'] == :xml
|
180
|
+
print "</results>\n"
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VirusTotal
|
4
|
+
class VirusTotal
|
5
|
+
|
6
|
+
# Creates a new instance of the [VirusTotal] class
|
7
|
+
#
|
8
|
+
# @return [VirusTotal]
|
9
|
+
def initialize(api_key, timeout = 7, debug = false)
|
10
|
+
@api_key = api_key
|
11
|
+
@timeout = timeout.to_i
|
12
|
+
@debug = debug
|
13
|
+
end
|
14
|
+
|
15
|
+
# Queries a single hash on virustotal.com
|
16
|
+
#
|
17
|
+
# @return [VirusTotalResult] of the results from the query
|
18
|
+
def query_hash hash
|
19
|
+
begin
|
20
|
+
puts "[*] Querying hash #{hash}" if @debug
|
21
|
+
hash.chomp!
|
22
|
+
if hash.include?('-')
|
23
|
+
hash = hash.split('-')[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
response = RestClient.post 'https://www.virustotal.com/api/get_file_report.json', { :resource => hash, :key => @api_key }
|
27
|
+
results = VirusTotalResult.new hash, :hash, JSON.parse(response)
|
28
|
+
|
29
|
+
return results
|
30
|
+
rescue Exception => e
|
31
|
+
puts e.message
|
32
|
+
puts e.backtrace.join("\n")
|
33
|
+
STDERR.puts "[!] An error has occured. Retrying #{hash} in #{@timeout} seconds.\n"
|
34
|
+
sleep @timeout #So we do not DOS virustotal.com we wait at least 5 seconds between each query
|
35
|
+
retry
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Queries a single url on virustotal.com
|
40
|
+
#
|
41
|
+
# @return [VirusTotalResult] of the results from the query
|
42
|
+
def query_site url
|
43
|
+
begin
|
44
|
+
puts "[*] Querying url #{url}" if @debug
|
45
|
+
|
46
|
+
response = RestClient.post 'https://www.virustotal.com/api/get_url_report.json', { :resource => url, :key => @api_key }
|
47
|
+
results = VirusTotalResult.new url, :site, JSON.parse(response)
|
48
|
+
|
49
|
+
return results
|
50
|
+
rescue Exception => e
|
51
|
+
puts e.message
|
52
|
+
puts e.backtrace.join("\n")
|
53
|
+
STDERR.puts "[!] An error has occured. Retrying #{url} in #{@timeout} seconds\n"
|
54
|
+
sleep @timeout #So we do not DOS virustotal.com we wait at least 5 seconds between each query
|
55
|
+
retry
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Fetch results from virustotal using a specific hash
|
60
|
+
#
|
61
|
+
def query_upload file
|
62
|
+
results = Array.new
|
63
|
+
file = file.chomp
|
64
|
+
|
65
|
+
begin
|
66
|
+
puts "[*] Attempting to upload file #{file}" if @debug
|
67
|
+
|
68
|
+
response = RestClient.post 'https://www.virustotal.com/api/scan_file.json', { :key => @api_key, :file => File.new(file, 'rb') }
|
69
|
+
result = JSON.parse(response)
|
70
|
+
|
71
|
+
puts "[*] File #{file} uploaded, waiting for results this could take several minutes..." if @debug
|
72
|
+
|
73
|
+
if result['result'] == 1
|
74
|
+
results = query_hash result['scan_id']
|
75
|
+
|
76
|
+
while results.results[0]['result'] == "Hash Not Found"
|
77
|
+
puts "[*] File has not been analyized yet, waiting 60 seconds to try again" if @debug
|
78
|
+
sleep 60
|
79
|
+
results = query_hash result['scan_id']
|
80
|
+
end
|
81
|
+
elsif result['result'] == -2
|
82
|
+
puts "[!] Virustotal limits exceeded, ***do not edit the time out values.***"
|
83
|
+
else
|
84
|
+
fres = Hash.new
|
85
|
+
fres['hash'] = file
|
86
|
+
fres['scanner'] = '-'
|
87
|
+
fres['version'] = '-'
|
88
|
+
fres['date'] = '-'
|
89
|
+
fres['result'] = "File failed to upload"
|
90
|
+
|
91
|
+
results.push fres
|
92
|
+
end
|
93
|
+
rescue Exception => e
|
94
|
+
puts e.message
|
95
|
+
puts e.backtrace.join("\n")
|
96
|
+
STDERR.puts "[!] An error has occured. Retrying #{file} in #{@timeout} seconds\n"
|
97
|
+
sleep @timeout #So we do not DOS virustotal.com we wait at least 5 seconds between each query
|
98
|
+
retry
|
99
|
+
end
|
100
|
+
|
101
|
+
return results
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module VirusTotal
|
4
|
+
|
5
|
+
# A Wrapper class for the results from a virustotal.com
|
6
|
+
#
|
7
|
+
# @author Jacob Hammack <jacob.hammack@hammackj.com>
|
8
|
+
class VirusTotalResult
|
9
|
+
attr_accessor :results
|
10
|
+
|
11
|
+
# Creates a new instance of the [VirusTotalResult] class
|
12
|
+
#
|
13
|
+
def initialize hash, type, result
|
14
|
+
@type = type
|
15
|
+
@results = Array.new
|
16
|
+
fres = Hash.new
|
17
|
+
|
18
|
+
if result["result"] == 0
|
19
|
+
|
20
|
+
fres = Hash.new
|
21
|
+
fres['hash'] = hash
|
22
|
+
fres['scanner'] = '-'
|
23
|
+
fres['date'] = '-'
|
24
|
+
fres['permalink'] = '-'
|
25
|
+
|
26
|
+
if @type == :hash
|
27
|
+
fres['result'] = "Hash Not Found"
|
28
|
+
elsif @type == :site
|
29
|
+
fres['result'] = "Site Not Found"
|
30
|
+
end
|
31
|
+
|
32
|
+
@results.push fres
|
33
|
+
elsif result["result"] == -1
|
34
|
+
puts "[!] Invalid API KEY! Please correct this!"
|
35
|
+
exit
|
36
|
+
else
|
37
|
+
permalink = result["permalink"]
|
38
|
+
date = result["report"][0]
|
39
|
+
result["report"][1].each do |scanner, res|
|
40
|
+
if res != ''
|
41
|
+
fres = Hash.new
|
42
|
+
fres['hash'] = hash
|
43
|
+
fres['scanner'] = scanner
|
44
|
+
fres['date'] = date
|
45
|
+
fres['permalink'] = permalink unless permalink == nil
|
46
|
+
fres['result'] = res
|
47
|
+
|
48
|
+
@results.push fres
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
#if we didn't have any results let create a fake not found
|
54
|
+
if @results.size == 0
|
55
|
+
fres = Hash.new
|
56
|
+
fres['hash'] = hash
|
57
|
+
fres['scanner'] = '-'
|
58
|
+
fres['date'] = '-'
|
59
|
+
fres['permalink'] = '-'
|
60
|
+
if @type == :hash
|
61
|
+
fres['result'] = "No Results for Hash"
|
62
|
+
elsif @type == :site
|
63
|
+
fres['result'] = "No Results for Site"
|
64
|
+
end
|
65
|
+
@results.push fres
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Prints the [VirusTotalResult] object to screen
|
70
|
+
#
|
71
|
+
def to_stdout
|
72
|
+
result_string = String.new
|
73
|
+
@results.each do |result|
|
74
|
+
result_string << "#{result['hash']}: Scanner: #{result['scanner']} Result: #{result['result']}\n"
|
75
|
+
end
|
76
|
+
print result_string
|
77
|
+
end
|
78
|
+
|
79
|
+
# Prints the [VirusTotalResult] object as a xml string to the screen
|
80
|
+
#
|
81
|
+
def to_xml
|
82
|
+
result_string = String.new
|
83
|
+
@results.each do |result|
|
84
|
+
result_string << "\t<vtresult>\n"
|
85
|
+
result_string << "\t\t<hash>#{result['hash']}</hash>\n"
|
86
|
+
result_string << "\t\t<scanner>#{result['scanner']}</scanner>\n"
|
87
|
+
result_string << "\t\t<date>#{result['date']}</date>\n"
|
88
|
+
result_string << "\t\t<permalink>#{result['permalink']}</permalink>\n" unless result['permalink'] == nil
|
89
|
+
result_string << "\t\t<result>#{result['result']}</result>\n"
|
90
|
+
result_string << "\t</vtresult>\n"
|
91
|
+
end
|
92
|
+
print result_string
|
93
|
+
end
|
94
|
+
|
95
|
+
# Prints the [VirusTotalResult] object as a yaml string to the screen
|
96
|
+
#
|
97
|
+
def to_yaml
|
98
|
+
result_string = String.new
|
99
|
+
@results.each do |result|
|
100
|
+
result_string << "vt-result:\n"
|
101
|
+
result_string << " hash: #{result['hash']}\n"
|
102
|
+
result_string << " scanner: #{result['scanner']}\n"
|
103
|
+
result_string << " date: #{result['date']}\n"
|
104
|
+
result_string << " permalink: #{result['permalink']}\n" unless result['permalink'] == nil
|
105
|
+
result_string << " result: #{result['result']}\n\n"
|
106
|
+
end
|
107
|
+
print result_string
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/virustotal.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
base = __FILE__
|
4
|
+
$:.unshift(File.join(File.dirname(base), 'lib'))
|
5
|
+
|
6
|
+
require 'virustotal'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.name = 'virustotal'
|
10
|
+
s.version = VirusTotal::VERSION
|
11
|
+
s.homepage = "http://github.com/hammackj/ruby-virustotal/"
|
12
|
+
s.summary = "virustotal"
|
13
|
+
s.description = "virustotal is a script for automating virustotal.com queries"
|
14
|
+
s.license = "BSD"
|
15
|
+
|
16
|
+
s.author = "Jacob Hammack"
|
17
|
+
s.email = "jacob.hammack@hammackj.com"
|
18
|
+
|
19
|
+
s.files = Dir['[A-Z]*'] + Dir['lib/**/*'] + ['virustotal.gemspec']
|
20
|
+
s.default_executable = 'virustotal'
|
21
|
+
s.executables = ['virustotal']
|
22
|
+
s.require_paths = ["lib"]
|
23
|
+
|
24
|
+
s.required_rubygems_version = ">= 1.3.6"
|
25
|
+
s.rubyforge_project = "virustotal"
|
26
|
+
|
27
|
+
s.add_development_dependency("rspec", ">= 2.4.0")
|
28
|
+
s.add_development_dependency("yard", ">= 0.6.4")
|
29
|
+
|
30
|
+
s.has_rdoc = 'yard'
|
31
|
+
s.extra_rdoc_files = ["README.markdown", "LICENSE", "NEWS.markdown", "TODO.markdown"]
|
32
|
+
|
33
|
+
s.add_dependency('json', '>= 1.5.1')
|
34
|
+
s.add_dependency('rest-client', '>= 1.6.1')
|
35
|
+
|
36
|
+
end
|
metadata
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: virustotal
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 2.0.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Jacob Hammack
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-18 00:00:00 -06:00
|
14
|
+
default_executable: virustotal
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rspec
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 2.4.0
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: yard
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ">="
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 0.6.4
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
- !ruby/object:Gem::Dependency
|
39
|
+
name: json
|
40
|
+
prerelease: false
|
41
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 1.5.1
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id003
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: rest-client
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.6.1
|
58
|
+
type: :runtime
|
59
|
+
version_requirements: *id004
|
60
|
+
description: virustotal is a script for automating virustotal.com queries
|
61
|
+
email: jacob.hammack@hammackj.com
|
62
|
+
executables:
|
63
|
+
- virustotal
|
64
|
+
extensions: []
|
65
|
+
|
66
|
+
extra_rdoc_files:
|
67
|
+
- README.markdown
|
68
|
+
- LICENSE
|
69
|
+
- NEWS.markdown
|
70
|
+
- TODO.markdown
|
71
|
+
files:
|
72
|
+
- LICENSE
|
73
|
+
- NEWS.markdown
|
74
|
+
- Rakefile
|
75
|
+
- README.markdown
|
76
|
+
- TODO.markdown
|
77
|
+
- lib/virustotal/application.rb
|
78
|
+
- lib/virustotal/virustotal.rb
|
79
|
+
- lib/virustotal/virustotalresult.rb
|
80
|
+
- lib/virustotal.rb
|
81
|
+
- virustotal.gemspec
|
82
|
+
- bin/virustotal
|
83
|
+
has_rdoc: yard
|
84
|
+
homepage: http://github.com/hammackj/ruby-virustotal/
|
85
|
+
licenses:
|
86
|
+
- BSD
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
|
90
|
+
require_paths:
|
91
|
+
- lib
|
92
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.3.6
|
104
|
+
requirements: []
|
105
|
+
|
106
|
+
rubyforge_project: virustotal
|
107
|
+
rubygems_version: 1.5.2
|
108
|
+
signing_key:
|
109
|
+
specification_version: 3
|
110
|
+
summary: virustotal
|
111
|
+
test_files: []
|
112
|
+
|