BMMVTU.py - Batch/Mass/Multiple VirusTotal.com Uploader

A python script automate uploading files & getting the results from VirusTotal.com with their API system.

Links

Download (bmmvtu.py): Coming soon

How do I use it?

  1. The first thing is to sign up to VirusTotal.com and collect your unique API key.
  2. Afterwords edit the script (line 14) with your API key.
  3. Make sure you have Python 2.6 & simplejson installed.
  4. Run (See examples)

Examples

1
2
3
python bmmvtu.py
python bmmvtu.py --output report.csv evil1.exe evil2.exe evil3.exe
python bmmvtu.py -o report.txt files/*

Code

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
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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
#!/usr/bin/python
#----------------------------------------------------------------------#
# Batch/Mass/Multiple VirusTotal.com Uploader (BMMVTU.py) v0.1 (2011-09-30)
#---Important----------------------------------------------------------#
# Python 2.6+ & simplejson needs to be installed before hand           #
# Left a few debug commands & incomplete features in comments          #
#                                                                      #
#         *** Do NOT use this for illegal or malicious use ***         #
#             YOU are using this script at YOUR OWN RISK.              #
#This software is provided "as is" WITHOUT ANY guarantees OR warranty. #
#----------------------------------------------------------------------#
import getopt,hashlib,httplib,itertools,mimetypes,os,pprint,simplejson,sys,time,urlparse
#-----------------------------------------------------------------------
key = "def22a11de7bbff674094b877c1c1bab21fb3424b85720a70653958dcb7e8c2e"   # Make sure to set this to your VirusTotal.com public API key.
sleepTime = 30                                                             # VirusTotal.com only allows 20 requests every 5 minitues.
retry = 3                                                                  # The number of times to retry when something fails/Times to wait in the queue.
separator = "\t"                                                           # "," is commonly used for CSV files. TAB (\t) works well for pasting into spreadsheets.
#-----------------------------------------------------------------------
# Is it a valid file (Is it a file & its size)
def check_file(filename):
   if not os.path.isfile(filename):
      print "[-] Error: '%s' is not a valid file" % filename
      fout.write("Error!" + separator + "not a valid file" + separator + filename + "\n")
      return False

   filesize = os.path.getsize(filename)
   if filesize < 1 or filesize > 20971519:
      print "[-] Error: Filesize (%s bytes)" % filesize
      fout.write("Error!" + separator + "Filesize is wrong" + separator + filename + "\n")
      return False
   return True

# Get the results from VirusTotal.com
def get_report(resource):
   json = post_multipart("https://www.virustotal.com/api/get_file_report.json", {'resource':resource, 'key':key})
   return simplejson.loads(json)

# Checks the server response for the API (blocked or wrong key)
def result_status(result,resource):
   while result == -2:                                                     # Exceeded the public API request rate
      print "[-] Error: Exceeded the public API request rate (Waiting 60 second)"
      time.sleep(60)                                                       # Wait a bit before re-trying
      data = get_report(resource)                                          # Check to see if MD5 is in the database
      result = data['result']

   if result == -1:                                                        # API key provided is incorrect
      print "[-] Error: The API key provided is incorrect"
      help()
      sys.exit(1)                                                          # Quit, because we can't go further
   return

# Request the file to VirusTotal.com
def send_file(filename):
   files = [('file', filename, open(filename, 'rb').read())]
   json = post_multipart("https://www.virustotal.com/api/scan_file.json", {'key':key}, files)
   return simplejson.loads(json)

# The magic/behind the scene stuff/Under the bonnet
def do_files(filenames):
   numFiles = len(filenames)
   count = 0
   for filename in filenames:                                              # Do every file
      try:                                                                 # Keep going even if we get an error
         count += 1
         print "[>] Scanning %s/%s (%s)" % (str(count),str(numFiles),filename)

         if check_file(filename) != True:
            continue

         md5sum = hashlib.md5(open(filename, 'rb').read()).hexdigest()     # Find the file's MD5 value
         data = get_report(md5sum)                                         # Check to see if MD5 is in the database
         result_status(data['result'],md5sum)                              # Check server response
         if data['result'] != 1:                                           # Not known to VirusTotal.com
            for _ in itertools.repeat(None, retry):                        # Try xxx times to upload
               print "[>] File not found. Submitting (%s)" % filename
               data = send_file(filename)                                  # Send the file to be scanned
               if data['result'] == 1:                                     # Have we successfully uploaded it?
                  break                                                    # Yes!
               else:                                                       # No!   Other? Fallback/Safey net
                  print "[-] Error: Submit failed (%s)" % filename   # + str(pprint.pprint(data))
                  time.sleep(sleepTime)                                    # Wait a bit before re-trying

            if data['result'] != 1:                                        # Result != 1 if upload wasn't successful
               print "[-] Failed: Didn't submit (%s)" % filename
               fout.write("Failed!" + separator + "Didn't submit" + separator + filename + "\n")
               continue                                                    # Move on to the next file

            for _ in itertools.repeat(None, retry):                        # Try xxx times to check
               for o in data:                                              # Read all the JSON objects
                  if o == "report":                                        # Does VirusTotal.com have a report..
                     break                                                 # ...Yes! So quit
                  elif o == "scan_id":                                     # ...No. Still scanning
                     scan_id = data['scan_id']                             # Use the new scan ID value, rather than the MD5
                     print "[>] Waiting 60 seconds for VirusTotal.com to finish scanning (%s)" % scan_id
                     time.sleep(60)                                        # Wait a bit before re-trying
                  elif data['result'] == 0:                                # ...No. Does VirusTotal.com know of it yet?
                     print "[>] Waiting in the queue"
                     time.sleep(sleepTime)                                 # Wait a bit before re-trying
               data = get_report(scan_id)                                  # Check to see if MD5 is in the database
               result_status(data['result'],scan_id)                       # Check server response

            if data['result'] != 1:                                        # Result != 1 if upload wasn't successful
               print "[-] Failed: VirusTotal.com is still scanning or a large queue. Try again later or increase 'retry' (%s)" % filename
               fout.write("Failed!" + separator + "still scanning or a large queue" + separator + filename + "\n")
               #retry_files.append(scan_id)
               continue

         if count < numFiles:                                              # If we are not using the last file...
            time.sleep(sleepTime)                                          # Sleep between requests so as not to overload VirusTotal.com

         report = data['report']
         permalink = data['permalink']
         #scan_id = permalink.split('=')[1]

         timeStamp = report[0]
         reportEntries = report[1]
         numEntries = len(reportEntries)

         numDetects = 0
         entryValues = dict.values(reportEntries)
         for v in entryValues:
            if v != u'':
               numDetects += 1

         output_string = md5sum + separator + timeStamp + separator + filename + separator + str(numEntries) + separator +str(numDetects) + separator
         for k,v in sorted(reportEntries.iteritems()):
            k = k.encode("ascii")
            v = v.encode("ascii")
            if v == "":
               v = "-"
            output_string += k + separator + v + separator

         output_string += permalink

         fout.write(output_string + "\n")
         #pprint.pprint(data['report'])

      except Exception as e:
         print "[-] Error [1]: ", e
         fout.write("Error!" + separator + str(e) + separator + filename + "\n")

# Perform an HTTP POST request
def post_multipart(url, fields, files=()):
   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)
   path = urlparse.urlunparse(('', '') + url_parts[2:])
   h.request('POST', path, data, {'content-type':content_type})
   return h.getresponse().read()

# Encoding the request
def encode_multipart_formdata(fields, files=()):
   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 "application/octet-stream"
      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

# Help screen
def help():
   print "\n\nAbout:"
   print "  This is a Python script which makes use of VirusTotal.com's public API, to automate scanning multiple files."
   print "  To use this you will need a \"API key\" from VirusTotal.com (Free signup).\n\n"
   print "bmmvtu.py --output <outputFile> <evil1 evil2 evil3...>\n"
   print "Arguments:"
   print "  -o --output      Path of the file to write output to"
   print "  -h --help        Prints this help message\n\n"
   print "Example:"
   print "  bmmvtu.py -o results.csv evil1.exe evil2.exe"
   print "  bmmvtu.py --output results.txt folder/*\n"
#-----------------------------------------------------------------------
print "[*] Batch/Mass/Multiple VirusTotal.com Uploader (BMMVTU) v0.1 (2011-09-30)"

# Process arguments
opts, args = getopt.getopt(sys.argv[1:], "o:h", ["output=", "help"])
for o, a in opts:
   if o in ('-o', '--output'):
      outputFileName = a
   elif o in ('-h', '--help'):
      help()
      sys.exit(1)
   else:
      pass

# Check for valid number of arguments
if len(sys.argv) < 4:
   print "[-] Error: Invalid number of arguments"
   help()
   sys.exit(1)

# Check for API key
if len(key) != 64:
   print "[-] Error: Please provide a valid API key"
   help()
   sys.exit(1)

# Check the wait time
if sleepTime < 15:
   print "[!] Warning: Its recommended to wait at least 15 seconds between requests"

   if len(args) > 20:
      print "[!] Warning: You will quicky max out your API request rate"

try:
   fout = open(outputFileName, "w")
   fout.write("md5sum" + separator + "timeStamp" + separator + "filename" + separator + "NumberOfAVs" + separator + "NumberOfDetects" + separator + ("<AV>" + separator + "<result> " + separator)* 44 + "permalink\n")

   #retry_files = list()                                                    # Retry these files (e.g. still waiting to be scanned)
   do_files(args)
   #do_files(retry_files)

   fout.close()

except Exception as e:
   print "[-] Error [0]: ", e

print "[*] Done!"

References