Created
July 27, 2017 21:19
-
-
Save carter-yagemann/856bfd03e3c2112dfaf22a790da5f369 to your computer and use it in GitHub Desktop.
Takes two VirusTotal analysis pages and diffs them.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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