Bryce Boe

The Adventures of a UCSB Computer Science Ph.D. Candidate and reddit Open Sorcerer

Skip to: Content | Sidebar | Footer

Submitting Binaries to VirusTotal

1 September, 2010 (01:12) | General | By: Bryce Boe

VirusTotal is a web service that essentially performs a virus scan of an uploaded file, or url against many of the top virus scanners (see full list). I recently needed to submit over 100 binaries to VirusTotal, and being a computer scientist I knew this task, like many other things I do, could be perfectly automated. I was thrilled to see that VirusTotal provides both a simple API as well as some python code examples demonstrating the file submission and report checking process.

Two days ago (2010/09/30) I attempted to run their file upload and scan example when I encountered some server errors. I quickly contacted VirusTotal via email to which I received a reply from Emiliano who informed me he corrected the issues on their end. Thanks Emiliano! Meanwhile I wrote a fairly simple, self contained python script which retrieves a VirusTotal report for a given binary, uploading the file if necessary. The following script is loosely based off the API examples, and contains modified code from this snippet that was already required to run the scan file example.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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
#!/usr/bin/env python
import hashlib, httplib, mimetypes, os, pprint, simplejson, sys, urlparse
 
DEFAULT_TYPE = 'application/octet-stream'
 
REPORT_URL = 'https://www.virustotal.com/api/get_file_report.json'
SCAN_URL = 'https://www.virustotal.com/api/scan_file.json'
 
API_KEY = 'YOUR KEY HERE'
 
# 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 scan_file(filename):
    files = [('file', filename, open(filename, 'rb').read())]
    json = post_multipart(SCAN_URL, {'key':API_KEY}, files)
    return simplejson.loads(json)
 
def get_report(filename):
    md5sum = hashlib.md5(open(filename, 'rb').read()).hexdigest()
    json = post_multipart(REPORT_URL, {'resource':md5sum, 'key':API_KEY})
    data = simplejson.loads(json)
    if data['result'] != 1:
        print 'Result not found, submitting file.'
        data = scan_file(filename)
        if data['result'] == 1:
            print 'Submit successful.'
            print 'Please wait a few minutes and try again to receive report.'
        else:
            print 'Submit failed.'
            pprint.pprint(data)
    else:
        pprint.pprint(data['report'])
 
 
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)
 
    get_report(filename)

Be sure to change the API_KEY value on line 8 to reflect your VirusTotal API key. The entire script can be downloaded here to save you a copy and paste.

Comments

Pingback from Tweets that mention Bryce Boe » Submitting Binaries to VirusTotal — Topsy.com
Time 2010/09/01 at 2:17 AM

[...] This post was mentioned on Twitter by Gadix, vierito5 and Sofian Brabez, Bryce Boe. Bryce Boe said: New Blog Post: Submitting Binaries to VirusTotal — http://goo.gl/oJhu [...]

Comment from Darrell
Time 2010/09/15 at 11:53 PM

How about an example .php with the submit form linking to the py for us python noobs?

thanx

Comment from Bryce Boe
Time 2010/09/16 at 9:36 AM

Darrell- that sort of defeats the purpose as you can use the submit form on the VirusTotal website.

Comment from Darrell
Time 2010/09/25 at 1:45 PM

Defeats the purpose? How?… Does an .php example defeat the purpose. As if the python example dont…. If no one is gonna show examples for the API, in .php, .html etc…..then releasing the API in the first place, defeats the purpose. Dont be lazy, or just admit. You havent been able to succeed a .php API example yourself either…

Comment from Darrell
Time 2010/09/25 at 1:49 PM

Some people might rather use the .php example rather than the python for many reasons. Such as building plugins for sites such as phpbb, seditio etc…

Comment from Peter
Time 2010/10/28 at 2:34 AM

There is a PHP example on the virustotal website.

Comment from Bryce Boe
Time 2010/10/28 at 10:45 AM

Cool, thanks Peter.

Comment from Bo
Time 2010/12/01 at 7:52 AM

How to find my own API key Bryce?
I appreciate your response..

Comment from Bryce Boe
Time 2010/12/01 at 8:35 AM

Bo, you need to sign up to the VT Community to get an API key.

Comment from Bo
Time 2010/12/01 at 12:54 PM

Bryce, thank you for your response.
I also appreciate your contributions..
You are a computer genius..

Comment from Peter
Time 2010/12/03 at 5:57 AM

Official PHP API example is here: http://uploadie.com/d/5tt8qi/PHP_VirusTotal_API_Functions.zip

Pingback from Deweloperka w piątek | Wiadomości o technologiach IT
Time 2011/03/03 at 8:49 PM

[...] do różnych zastosowań (np. obsługa API w połączeniach z serwisami internetowymi) – np. http://www.bryceboe.com/2010/09/01/submitting-binaries-to-virustotal/ zróbił automatyczne wysyłanie z Pythona plików do serwisu http://www.virustotal.com/ w celu [...]

Pingback from VirusTotal plugin for IDA Pro | Hex Blog
Time 2011/04/21 at 8:05 AM

[...] JSON strings. In this article, we are going to borrow some code from Bryce Boe’s blog entry “Submitting Binaries to VirusTotal” and modify it a bit. The resulting changes can be found in the BboeVt module as part of this [...]

Comment from jason404
Time 2011/10/03 at 7:16 AM

This does seem to work from a GUI-less Linux machine. I get a error saying that it cannot open x server and then several other errors which must be related. I’m just wondering why it’s needed?

Comment from Bryce Boe
Time 2011/10/03 at 9:24 AM

jason404- You were trying to run the script as an executable, however, the script previously did not have the shebang line at the top thus you needed to explicitly run it via python. I just updated the script to include the shebang so this will no longer be an issue.

Comment from Oren
Time 2012/02/09 at 1:40 AM

FYI – VirusTotal published a new API (V2.0). The JSON makes more sense than V1. V1 is now marked as deprecated (although it still works). https://www.virustotal.com/documentation/public-api/v2/

Pingback from VirusTotal plugin for IDA Pro
Time 2012/02/15 at 3:51 AM

[...] JSON strings. In this article, we are going to borrow some code from Bryce Boe’s blog entry “Submitting Binaries to VirusTotal” and modify it a bit. The resulting changes can be found in the BboeVt module as part of this [...]

Comment from Madalina
Time 2012/02/17 at 6:45 AM

Thank you very much Bryce!
Cheers!

Write a comment