Skip to content

Instantly share code, notes, and snippets.

@carter-yagemann
Created July 27, 2017 21:19
Show Gist options
  • Save carter-yagemann/856bfd03e3c2112dfaf22a790da5f369 to your computer and use it in GitHub Desktop.
Save carter-yagemann/856bfd03e3c2112dfaf22a790da5f369 to your computer and use it in GitHub Desktop.
Takes two VirusTotal analysis pages and diffs them.
#!/usr/bin/env python
#
# vt-diff.py - Takes two VirusTotal analysis pages and diffs them.
#
# Copyright 2017 Carter Yagemann
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import requests
from lxml import html
import sys
class bcolors:
OKAY = '\033[92m'
FAIL = '\033[91m'
ENDC = '\033[0m'
clean_str = bcolors.OKAY + 'clean' + bcolors.ENDC
def fetch_and_parse(url):
page = requests.get(url)
if page.status_code != 200:
print 'Unexpected HTML response:', page.status_code
return None
tree = html.fromstring(page.text)
rows = tree.xpath('//table[@id="antivirus-results"]/tbody/tr')
if len(rows) < 1:
print 'No anti-virus results found for', str(url)
return None
results = {}
for row in rows:
if len(row[1].text.strip()) > 0:
results[row[0].text.strip()] = bcolors.FAIL + row[1].text.strip() + bcolors.ENDC
else: # An empty results cell means clean or file type not supported
results[row[0].text.strip()] = clean_str
return results
if __name__ == '__main__':
if len(sys.argv) != 3:
print 'Usage:', sys.argv[0], '<VirusTotal_url_1>', '<VirusTotal_url_2>'
sys.exit()
for url in sys.argv[1:]:
if not 'virustotal.com' in url:
print 'Is this really a VirusTotal URL?', str(url)
sys.exit(1)
res_a = fetch_and_parse(sys.argv[1])
res_b = fetch_and_parse(sys.argv[2])
avs = set(res_a.keys() + res_b.keys())
familes = set(res_a.values() + res_b.values())
av_ljust = max([len(av) for av in avs])
fam_ljust = max([len(fam) for fam in familes])
stats = {'minus': 0, 'plus': 0, 'delta': 0}
for av in avs:
if not av in res_a.keys(): # AV isn't in A's results
print '> ' + av.ljust(av_ljust) + ' '.ljust(fam_ljust + 1) + res_b[av]
elif not av in res_b.keys(): # AV isn't in B's results
print '< ' + av.ljust(av_ljust) + ' ' + res_a[av]
elif res_a[av] != res_b[av]: # AV changed from A to B
print '| ' + av.ljust(av_ljust) + ' ' + res_a[av].ljust(fam_ljust) + ' ' + res_b[av]
if res_b[av] == clean_str: # AV detected A but not B
stats['minus'] += -1
stats['delta'] += -1
elif res_a[av] == clean_str: # AV detected B but not A
stats['plus'] += 1
stats['delta'] += 1
else: # AV detected A and B, but as different types
pass
else: # A and B's results match
print ' ' + av.ljust(av_ljust) + ' ' + res_a[av].ljust(fam_ljust) + ' ' + res_b[av]
print "\nSummary: " + str(stats['minus']) + '/+' + str(stats['plus']) + '/' + str(stats['delta'])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment