2017-01-10 13:24:21 +00:00
|
|
|
#!/usr/bin/env python
|
|
|
|
# encoding: utf-8
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
import codecs
|
|
|
|
import time
|
2017-02-17 06:28:30 +00:00
|
|
|
import hashlib
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
from virustotal_api import PublicApi as VirusTotalPublicApi
|
2017-12-21 07:08:52 +00:00
|
|
|
#from virus_total_apis import PublicApi as VirusTotalPublicApi
|
2017-01-10 13:24:21 +00:00
|
|
|
from cortexutils.analyzer import Analyzer
|
|
|
|
|
|
|
|
|
|
|
|
class VirusTotalAnalyzer(Analyzer):
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
Analyzer.__init__(self)
|
2017-04-21 15:38:30 +00:00
|
|
|
self.service = self.getParam('config.service', None, 'Service parameter is missing')
|
|
|
|
self.virustotal_key = self.getParam('config.key', None, 'Missing VirusTotal API key')
|
|
|
|
self.polling_interval = self.getParam('config.polling_interval', 60)
|
2017-12-21 07:08:52 +00:00
|
|
|
self.proxies = self.getParam('config.proxy', None)
|
|
|
|
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
def wait_file_report(self, id):
|
|
|
|
results = self.check_response(self.vt.get_file_report(id))
|
|
|
|
code = results.get('response_code', None)
|
|
|
|
if code == 1:
|
|
|
|
self.report(results)
|
|
|
|
else:
|
2017-04-21 15:38:30 +00:00
|
|
|
time.sleep(self.polling_interval)
|
2017-01-10 13:24:21 +00:00
|
|
|
self.wait_file_report(id)
|
|
|
|
|
|
|
|
def wait_url_report(self, id):
|
|
|
|
results = self.check_response(self.vt.get_url_report(id))
|
|
|
|
code = results.get('response_code', None)
|
2017-10-26 16:14:40 +00:00
|
|
|
if code == 1 and (results.get('scan_id') == id):
|
2017-01-10 13:24:21 +00:00
|
|
|
self.report(results)
|
|
|
|
else:
|
2017-04-21 15:38:30 +00:00
|
|
|
time.sleep(self.polling_interval)
|
2017-01-10 13:24:21 +00:00
|
|
|
self.wait_url_report(id)
|
|
|
|
|
|
|
|
def check_response(self, response):
|
|
|
|
if type(response) is not dict:
|
|
|
|
self.error('Bad response : ' + str(response))
|
|
|
|
status = response.get('response_code', -1)
|
2017-04-13 12:09:29 +00:00
|
|
|
if status == 204:
|
|
|
|
self.error('VirusTotal api rate limit exceeded (Status 204).')
|
2017-01-10 13:24:21 +00:00
|
|
|
if status != 200:
|
|
|
|
self.error('Bad status : ' + str(status))
|
|
|
|
results = response.get('results', {})
|
|
|
|
if 'verbose_msg' in results:
|
|
|
|
print >> sys.stderr, str(results.get('verbose_msg'))
|
|
|
|
return results
|
|
|
|
|
|
|
|
# 0 => not found
|
|
|
|
# -2 => in queue
|
|
|
|
# 1 => ready
|
|
|
|
|
|
|
|
def read_scan_response(self, response, func):
|
|
|
|
results = self.check_response(response)
|
|
|
|
code = results.get('response_code', None)
|
|
|
|
scan_id = results.get('scan_id', None)
|
|
|
|
if code == 1 and scan_id is not None:
|
|
|
|
func(scan_id)
|
|
|
|
else:
|
|
|
|
self.error('Scan not found')
|
|
|
|
|
|
|
|
def summary(self, raw):
|
2017-06-20 16:52:07 +00:00
|
|
|
taxonomies = []
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "info"
|
|
|
|
namespace = "VT"
|
|
|
|
predicate = "Score"
|
|
|
|
value = "\"0\""
|
2017-06-20 16:52:07 +00:00
|
|
|
|
2017-01-10 13:24:21 +00:00
|
|
|
result = {
|
|
|
|
"has_result": True
|
|
|
|
}
|
|
|
|
|
|
|
|
if(raw["response_code"] != 1):
|
|
|
|
result["has_result"] = False
|
|
|
|
|
|
|
|
result["positives"] = raw.get("positives", 0)
|
|
|
|
result["total"] = raw.get("total", 0)
|
|
|
|
|
|
|
|
if("scan_date" in raw):
|
|
|
|
result["scan_date"] = raw["scan_date"]
|
|
|
|
|
|
|
|
if self.service == "get":
|
|
|
|
if("scans" in raw):
|
|
|
|
result["scans"] = len(raw["scans"])
|
2017-06-27 12:05:56 +00:00
|
|
|
value = "\"{}/{}\"".format(result["positives"], result["total"])
|
2017-06-23 14:37:07 +00:00
|
|
|
if result["positives"] == 0:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "safe"
|
2017-06-23 14:37:07 +00:00
|
|
|
elif result["positives"] < 5:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "suspicious"
|
2017-06-23 14:37:07 +00:00
|
|
|
else:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "malicious"
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
if("resolutions" in raw):
|
|
|
|
result["resolutions"] = len(raw["resolutions"])
|
2017-06-27 12:05:56 +00:00
|
|
|
value = "\"{} resolution(s)\"".format(result["resolutions"])
|
2017-06-23 14:37:07 +00:00
|
|
|
if result["resolutions"] == 0:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "safe"
|
2017-06-23 14:37:07 +00:00
|
|
|
elif result["resolutions"] < 5:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "suspicious"
|
2017-06-23 14:37:07 +00:00
|
|
|
else:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "malicious"
|
2017-01-10 13:24:21 +00:00
|
|
|
if("detected_urls" in raw):
|
|
|
|
result["detected_urls"] = len(raw["detected_urls"])
|
2017-06-27 12:05:56 +00:00
|
|
|
value = "\"{} detected_url(s)\"".format(result["detected_urls"])
|
2017-06-23 14:37:07 +00:00
|
|
|
if result["detected_urls"] == 0:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "safe"
|
2017-06-23 14:37:07 +00:00
|
|
|
elif result["detected_urls"] < 5:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "suspicious"
|
2017-06-23 14:37:07 +00:00
|
|
|
else:
|
2017-06-27 12:05:56 +00:00
|
|
|
level = "malicious"
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
if("detected_downloaded_samples" in raw):
|
|
|
|
result["detected_downloaded_samples"] = len(
|
|
|
|
raw["detected_downloaded_samples"])
|
|
|
|
|
2017-07-13 07:01:52 +00:00
|
|
|
if self.service == "scan":
|
|
|
|
if("scans" in raw):
|
|
|
|
result["scans"] = len(raw["scans"])
|
|
|
|
value = "\"{}/{}\"".format(result["positives"], result["total"])
|
|
|
|
if result["positives"] == 0:
|
|
|
|
level = "safe"
|
|
|
|
elif result["positives"] < 5:
|
|
|
|
level = "suspicious"
|
|
|
|
else:
|
|
|
|
level = "malicious"
|
2017-06-20 16:52:07 +00:00
|
|
|
|
2017-06-27 12:05:56 +00:00
|
|
|
taxonomies.append(self.build_taxonomy(level, namespace, predicate, value))
|
|
|
|
return {"taxonomies": taxonomies}
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
def run(self):
|
|
|
|
Analyzer.run(self)
|
2017-12-21 07:08:52 +00:00
|
|
|
self.vt = VirusTotalPublicApi(self.virustotal_key, self.proxies)
|
2017-01-10 13:24:21 +00:00
|
|
|
|
|
|
|
if self.service == 'scan':
|
|
|
|
if self.data_type == 'file':
|
2017-12-13 12:33:07 +00:00
|
|
|
filename = self.getParam('filename', 'noname.ext')
|
2017-01-10 13:24:21 +00:00
|
|
|
filepath = self.getParam('file', None, 'File is missing')
|
|
|
|
self.read_scan_response(self.vt.scan_file(
|
|
|
|
(filename, open(filepath, 'rb'))), self.wait_file_report)
|
|
|
|
elif self.data_type == 'url':
|
|
|
|
data = self.getParam('data', None, 'Data is missing')
|
|
|
|
self.read_scan_response(
|
|
|
|
self.vt.scan_url(data), self.wait_url_report)
|
|
|
|
else:
|
|
|
|
self.error('Invalid data type')
|
|
|
|
elif self.service == 'get':
|
|
|
|
if self.data_type == 'domain':
|
|
|
|
data = self.getParam('data', None, 'Data is missing')
|
|
|
|
self.report(self.check_response(
|
|
|
|
self.vt.get_domain_report(data)))
|
|
|
|
elif self.data_type == 'ip':
|
|
|
|
data = self.getParam('data', None, 'Data is missing')
|
|
|
|
self.report(self.check_response(self.vt.get_ip_report(data)))
|
|
|
|
elif self.data_type == 'file':
|
2017-02-17 06:28:30 +00:00
|
|
|
|
2017-01-10 13:24:21 +00:00
|
|
|
hashes = self.getParam('attachment.hashes',
|
2017-02-17 06:28:30 +00:00
|
|
|
None)
|
|
|
|
if hashes is None:
|
|
|
|
filepath = self.getParam('file', None, 'File is missing')
|
|
|
|
hash = hashlib.sha256(open(filepath, 'r').read()).hexdigest();
|
|
|
|
else:
|
2017-01-10 13:24:21 +00:00
|
|
|
# find SHA256 hash
|
2017-02-17 06:28:30 +00:00
|
|
|
hash = next(h for h in hashes if len(h) == 64)
|
|
|
|
|
2017-01-10 13:24:21 +00:00
|
|
|
self.report(self.check_response(self.vt.get_file_report(hash)))
|
2017-02-17 06:28:30 +00:00
|
|
|
|
2017-01-10 13:24:21 +00:00
|
|
|
elif self.data_type == 'hash':
|
|
|
|
data = self.getParam('data', None, 'Data is missing')
|
|
|
|
self.report(self.check_response(self.vt.get_file_report(data)))
|
|
|
|
else:
|
|
|
|
self.error('Invalid data type')
|
|
|
|
else:
|
|
|
|
self.error('Invalid service')
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
VirusTotalAnalyzer().run()
|