2015-04-20 14:26:30 +02:00
|
|
|
"""
|
|
|
|
Original code by Bryce Boe: http://www.bryceboe.com/2010/09/01/submitting-binaries-to-virustotal/
|
|
|
|
|
|
|
|
Modified by Elias Bachaalany <elias at hex-rays.com>
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
import hashlib, httplib, mimetypes, os, pprint, simplejson, sys, urlparse
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
DEFAULT_TYPE = 'application/octet-stream'
|
|
|
|
FILE_REPORT_URL = 'https://www.virustotal.com/api/get_file_report.json'
|
|
|
|
SCAN_URL = 'https://www.virustotal.com/api/scan_file.json'
|
|
|
|
API_KEY = "" # Put API key here. Register an account in VT Community
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
# The following function is modified from the snippet at:
|
|
|
|
# http://code.activestate.com/recipes/146306/
|
|
|
|
def _encode_multipart_formdata(fields, files=()):
|
|
|
|
"""
|
|
|
|
fields is a dictionary of name to value for regular form fields.
|
|
|
|
files is a sequence of (name, filename, value) elements for data to be
|
|
|
|
uploaded as files.
|
|
|
|
Return (content_type, body) ready for httplib.HTTP instance
|
|
|
|
"""
|
|
|
|
BOUNDARY = '----------ThIs_Is_tHe_bouNdaRY_$'
|
|
|
|
CRLF = '\r\n'
|
|
|
|
L = []
|
|
|
|
for key, value in fields.items():
|
|
|
|
L.append('--' + BOUNDARY)
|
|
|
|
L.append('Content-Disposition: form-data; name="%s"' % key)
|
|
|
|
L.append('')
|
|
|
|
L.append(value)
|
|
|
|
for (key, filename, value) in files:
|
|
|
|
L.append('--' + BOUNDARY)
|
|
|
|
L.append('Content-Disposition: form-data; name="%s"; filename="%s"' %
|
|
|
|
(key, filename))
|
|
|
|
content_type = mimetypes.guess_type(filename)[0] or DEFAULT_TYPE
|
|
|
|
L.append('Content-Type: %s' % content_type)
|
|
|
|
L.append('')
|
|
|
|
L.append(value)
|
|
|
|
L.append('--' + BOUNDARY + '--')
|
|
|
|
L.append('')
|
|
|
|
body = CRLF.join(L)
|
|
|
|
content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
|
|
|
|
return content_type, body
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def _post_multipart(url, fields, files=()):
|
|
|
|
"""
|
|
|
|
url is the full to send the post request to.
|
|
|
|
fields is a dictionary of name to value for regular form fields.
|
|
|
|
files is a sequence of (name, filename, value) elements for data to be
|
|
|
|
uploaded as files.
|
|
|
|
Return body of http response.
|
|
|
|
"""
|
|
|
|
content_type, data = _encode_multipart_formdata(fields, files)
|
|
|
|
url_parts = urlparse.urlparse(url)
|
|
|
|
if url_parts.scheme == 'http':
|
|
|
|
h = httplib.HTTPConnection(url_parts.netloc)
|
|
|
|
elif url_parts.scheme == 'https':
|
|
|
|
h = httplib.HTTPSConnection(url_parts.netloc)
|
|
|
|
else:
|
|
|
|
raise Exception('Unsupported URL scheme')
|
|
|
|
path = urlparse.urlunparse(('', '') + url_parts[2:])
|
|
|
|
h.request('POST', path, data, {'content-type':content_type})
|
|
|
|
return h.getresponse().read()
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def set_apikey(key, dbg = False):
|
|
|
|
"""
|
|
|
|
Set the VT API key
|
|
|
|
"""
|
|
|
|
global API_KEY
|
|
|
|
API_KEY = key
|
|
|
|
if dbg:
|
|
|
|
httplib.HTTPConnection.debuglevel = 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def scan_file(filename):
|
|
|
|
"""
|
|
|
|
Uploads a file for scanning.
|
|
|
|
|
|
|
|
@param filename: The filename to upload
|
|
|
|
|
|
|
|
@return: - None if upload failed
|
|
|
|
- scan_id value if upload succeeds
|
|
|
|
- raises an exception on IO failures
|
|
|
|
"""
|
|
|
|
files = [('file', filename, open(filename, 'rb').read())]
|
|
|
|
json = _post_multipart(SCAN_URL, {'key':API_KEY}, files)
|
|
|
|
data = simplejson.loads(json)
|
|
|
|
return str(data['scan_id']) if data['result'] == 1 else None
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def get_file_md5_hash(filename):
|
|
|
|
f = open(filename, 'rb')
|
|
|
|
r = hashlib.md5(f.read()).hexdigest()
|
|
|
|
f.close()
|
|
|
|
return r
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def get_file_report(filename=None, md5sum=None):
|
|
|
|
"""
|
|
|
|
Returns an report for a file or md5su.
|
|
|
|
|
|
|
|
@param filename: File name to get report. The file is used just
|
|
|
|
to compute its MD5Sum
|
|
|
|
@param md5sum: MD5sum string (in case filename was not passed)
|
|
|
|
|
|
|
|
@return: - None: if file was not previously analyzed
|
|
|
|
- A dictionary if report exists: key=scanner, value=reported name
|
|
|
|
"""
|
|
|
|
if filename is None and md5sum is None:
|
|
|
|
raise Exception('Either filename or md5sum should be passed!')
|
|
|
|
|
|
|
|
# Filename passed? Compute its MD5
|
|
|
|
if filename:
|
|
|
|
global LAST_FILE_HASH
|
|
|
|
LAST_FILE_HASH = md5sum = get_file_md5_hash(filename)
|
|
|
|
|
|
|
|
# Form the request
|
|
|
|
json = _post_multipart(FILE_REPORT_URL, {'resource':md5sum, 'key':API_KEY})
|
|
|
|
data = simplejson.loads(json)
|
|
|
|
if data['result'] != 1:
|
|
|
|
# No results
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
# date, result_dict = data['report']
|
|
|
|
return data['report'][1]
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
def pretty_print(obj):
|
|
|
|
pprint.pprint(obj)
|
|
|
|
|
|
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
|
|
if __name__ == '__main__':
|
|
|
|
if len(sys.argv) != 2:
|
|
|
|
print('Usage: %s filename' % sys.argv[0])
|
|
|
|
sys.exit(1)
|
|
|
|
|
|
|
|
filename = sys.argv[1]
|
|
|
|
if not os.path.isfile(filename):
|
|
|
|
print('%s is not a valid file' % filename)
|
|
|
|
sys.exit(1)
|
|
|
|
|
2011-05-09 12:21:32 +02:00
|
|
|
get_file_report(filename=filename)
|