From c41885131f50b2c5005b2789e23f896324a8643f Mon Sep 17 00:00:00 2001 From: Florian Roth Date: Wed, 25 Apr 2018 13:37:33 +0200 Subject: [PATCH] VirusTotal retrohunt verification mode --- README.md | 31 +++++++++++++++++-------- munin.py | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 7bb5787..dd02080 100644 --- a/README.md +++ b/README.md @@ -25,20 +25,31 @@ Note: Munin is based on the script "VT-Checker", which has been maintained in th # Usage usage: munin.py [-h] [-f path] [-c cache-db] [-i ini-file] [-s sample-folder] - [--nocache] [--nocsv] [--debug] + [--comment] [-p vt-comment-prefix] [--nocache] [--intense] + [--retroverify] [-r num-results] [--nocsv] [--sort] [--debug] Online Hash Checker optional arguments: - -h, --help show this help message and exit - -f path File to process (hash line by line OR csv with hash in - each line - auto-detects position and comment) - -c cache-db Name of the cache database file (default: vt-hash-db.pkl) - -i ini-file Name of the ini file that holds the API keys - -s sample-folder Folder with samples to process - --nocache Do not use cache database file - --nocsv Do not write a CSV with the results - --debug Debug output + -h, --help show this help message and exit + -f path File to process (hash line by line OR csv with hash in + each line - auto-detects position and comment) + -c cache-db Name of the cache database file (default: vt-hash- + db.pkl) + -i ini-file Name of the ini file that holds the API keys + -s sample-folder Folder with samples to process + --comment Posts a comment for the analysed hash which contains + the comment from the log line + -p vt-comment-prefix Virustotal comment prefix + --nocache Do not use cache database file + --intense Do use PhantomJS to parse the permalink (used to + extract user comments on samples) + --retroverify Check only 40 entries with the same comment and + therest at the end of the run (retrohunt verification) + -r num-results Number of results to take as verification + --nocsv Do not write a CSV with the results + --sort Sort the input lines (useful for VT retrohunt results) + --debug Debug output # Features diff --git a/munin.py b/munin.py index 99f09d1..fef12af 100644 --- a/munin.py +++ b/munin.py @@ -1,7 +1,7 @@ #!/usr/bin/env python2.7 __AUTHOR__ = 'Florian Roth' -__VERSION__ = "0.5.0 April 2018" +__VERSION__ = "0.6.0 April 2018" """ Install dependencies with: @@ -98,6 +98,11 @@ def processLines(lines, resultFile, nocsv=False, debug=False): if args.sort: lines = sorted(lines) + # Retrohunt Verification + if args.retroverify: + print("[+] Virustotal Retrohunt verification mode (using '%d' as sample size)" % int(args.r)) + verifiedSigs = {} + for i, line in enumerate(lines): # Remove line break line = line.rstrip("\n").rstrip("\r") @@ -111,6 +116,16 @@ def processLines(lines, resultFile, nocsv=False, debug=False): # If no hash found if hashVal == '': continue + + # Retrohunt Verification - Skip + if args.retroverify: + sigName = comment.rstrip(" /subfile") + if sigName in verifiedSigs: + if verifiedSigs[sigName]['count'] >= int(args.r): + if debug: + print("[D] Skipping entry because this sig has already been verified '%s'" % sigName) + continue + # Info dictionary info = None info = {'hash': hashVal, hashType: hashVal, 'comment': comment} @@ -152,6 +167,23 @@ def processLines(lines, resultFile, nocsv=False, debug=False): # Comparison checks extraChecks(info, infos, cache) + # Retrohunt Verification - Log + if args.retroverify: + sigName = comment.rstrip(" /subfile") + rating = info['rating'] + if sigName not in verifiedSigs: + verifiedSigs[sigName] = {'positives': [], + 'malicious': 0, + 'suspicious': 0, + 'clean': 0, + 'unknown': 0, + 'count': 0} + verifiedSigs[sigName][rating] += 1 + verifiedSigs[sigName]['positives'].append(int(info['positives'])) + verifiedSigs[sigName]['count'] += 1 + if verifiedSigs[sigName]['count'] >= int(args.r): + printVerificationResult(sigName, verifiedSigs[sigName]) + # Print to CSV if not nocsv: writeCSV(info, resultFile) @@ -579,7 +611,9 @@ def extraChecks(info, infos, cache): def printResult(info, count, total): """ prints the result block - :param info: + :param info: all collected info + :param count: counter (number of samples checked) + :param total: total number of lines to check :return: """ # Rating and Color @@ -630,6 +664,31 @@ def printResult(info, count, total): printHighlighted("RESULT: %s%s" % (info["result"], tags), hl_color=info["res_color"]) +def printVerificationResult(sigName, vResults): + """ + prints the result of a retrohunt verification + :param sigName: signature name + :param vResults: dictionary with verification results + :return: + """ + # Color + res_color = Back.CYAN + # Average positives + avgPositives = sum(vResults['positives']) / float(len(vResults['positives'])) + + if avgPositives > 10: + res_color = Back.RED + if avgPositives > 10: + res_color = Back.YELLOW + if vResults['clean'] > 0: + res_color = Back.YELLOW + if vResults['suspicious'] == 0 and vResults['malicious'] == 0: + res_color = Back.GREEN + + # Print the highlighted result line + printHighlighted("VERIFIED_SIG: %s AVG_POS: %.2f" % (sigName, avgPositives), hl_color=res_color) + + def printHighlighted(line, hl_color=Back.WHITE): """ Print a highlighted line @@ -896,6 +955,11 @@ def signal_handler(signal, frame): parser.add_argument('--intense', action='store_true', help='Do use PhantomJS to parse the permalink ' '(used to extract user comments on samples)', default=False) + parser.add_argument('--retroverify', action='store_true', help='Check only 40 entries with the same comment and the' + 'rest at the end of the run (retrohunt verification)', + default=False) + parser.add_argument('-r', help='Number of results to take as verification', metavar='num-results', + default=40) parser.add_argument('--nocsv', action='store_true', help='Do not write a CSV with the results', default=False) parser.add_argument('--sort', action='store_true', help='Sort the input lines (useful for VT retrohunt results)', default=False) parser.add_argument('--debug', action='store_true', default=False, help='Debug output')