From 247843742781a20492ff62c048fe0ef12552c31c Mon Sep 17 00:00:00 2001 From: Macmod Date: Tue, 12 Nov 2024 03:58:45 -0300 Subject: [PATCH 01/10] Implemented -O flag, -vx and -VX. --- .malwapi.conf | 2 + README.md | 21 +++-- malwoverview/malwoverview.py | 26 ++++++- malwoverview/modules/bazaar.py | 7 +- malwoverview/modules/configvars.py | 1 + malwoverview/modules/hybrid.py | 5 +- malwoverview/modules/inquest.py | 9 ++- malwoverview/modules/malpedia.py | 12 +-- malwoverview/modules/malshare.py | 6 +- malwoverview/modules/polyswarm.py | 6 +- malwoverview/modules/triage.py | 14 ++-- malwoverview/modules/urlhaus.py | 6 +- malwoverview/modules/virusexchange.py | 106 ++++++++++++++++++++++++++ malwoverview/utils/colors.py | 4 +- 14 files changed, 185 insertions(+), 40 deletions(-) create mode 100644 malwoverview/modules/virusexchange.py diff --git a/.malwapi.conf b/.malwapi.conf index bf8a018..4792f71 100644 --- a/.malwapi.conf +++ b/.malwapi.conf @@ -25,3 +25,5 @@ TRIAGEAPI = [INQUEST] INQUESTAPI = +[VIRUSEXCHANGE] +VXAPI = \ No newline at end of file diff --git a/README.md b/README.md index 4039639..a6b2cf8 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Malwoverview -[GitHub release (latest by date)](https://github.com/alexandreborges/malwoverview/releases/tag/v6.0.1) [GitHub last commit](https://github.com/alexandreborges/malwoverview/releases) [GitHub Release Date](https://github.com/alexandreborges/malwoverview/releases) [GitHub](https://github.com/alexandreborges/malwoverview/blob/master/LICENSE) +[GitHub release (latest by date)](https://github.com/alexandreborges/malwoverview/releases/tag/v6.0.2) [GitHub last commit](https://github.com/alexandreborges/malwoverview/releases) [GitHub Release Date](https://github.com/alexandreborges/malwoverview/releases) [GitHub](https://github.com/alexandreborges/malwoverview/blob/master/LICENSE) [GitHub stars](https://github.com/alexandreborges/malwoverview/stargazers) [Twitter Follow](https://twitter.com/ale_sp_brazil) [Downloads/Last Month](https://pypistats.org/packages/malwoverview) @@ -71,7 +71,7 @@ See GNU Public License on . -## Current Version: 6.0.1 +## Current Version: 6.0.2 Important note: Malwoverview does NOT submit samples to any endpoint by default, so it respects possible Non-Disclosure Agreements (NDAs). There're specific options @@ -90,12 +90,12 @@ This tool aims to : 1. Determine similar executable malware samples (PE/PE+) according to the import table (imphash) and group them by different colors (pay attention to the second column from output). Thus, colors matter! 2. Show hash information on Virus Total, Hybrid Analysis, Malshare, Polyswarm, URLhaus, Alien Vault, - Malpedia and ThreatCrowd engines. + Malpedia, ThreatCrowd and VirusExchange engines. 3. Determining whether the malware samples contain overlay and, if you want, extract it. 4. Check suspect files on Virus Total, Hybrid Analysis and Polyswarm. 5. Check URLs on Virus Total, Malshare, Polyswarm, URLhaus engines and Alien Vault. -6. Download malware samples from Hybrid Analysis, Malshare, URLHaus, Polyswarm and Malpedia engines. -7. Submit malware samples to VirusTotal, Hybrid Analysis and Polyswarm. +6. Download malware samples from Hybrid Analysis, Malshare, URLHaus, Polyswarm, Malpedia and VirusExchange engines. +7. Submit malware samples to VirusTotal, Hybrid Analysis, Polyswarm and VirusExchange. 8. List last suspected URLs from URLHaus. 9. List last payloads from URLHaus. 10. Search for specific payloads on the Malshare. @@ -214,6 +214,8 @@ has the following format: [INQUEST] INQUESTAPI = + [VIRUSEXCHANGE] + VXAPI = The APIs can be requested on the respective service websites: @@ -230,6 +232,7 @@ The APIs can be requested on the respective service websites: 09. ThreatFox: It isn't necessary an API. 10. InQuest: https://labs.inquest.net/. 11. Triage: https://tria.ge/signup. +12. VirusExchange: https://virus.exchange/users/register. ---------------------------------------------------- A special note about API requests to the MALPEDIA: @@ -442,11 +445,17 @@ should be executed: ## HISTORY +Version 6.0.2: + This version: + + * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack, sample download and sample submission. + * Adds a new -O flag to set up the output directory for any sample downloads + Version 6.0.1: This version: - * Issue in Malshare's download option has been fixed.. + * Issue in Malshare's download option has been fixed. Version 6.0.0: diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index 6aef531..a5066fd 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -21,7 +21,7 @@ # Christian Clauss (https://github.com/cclauss) # Artur Marzano (https://github.com/Macmod) -# Malwoverview.py: version 6.0.1 +# Malwoverview.py: version 6.0.2 import os import argparse @@ -42,6 +42,7 @@ from malwoverview.modules.triage import TriageExtractor from malwoverview.modules.urlhaus import URLHausExtractor from malwoverview.modules.virustotal import VirusTotalExtractor +from malwoverview.modules.virusexchange import VirusExchangeExtractor from malwoverview.utils.colors import printr from malwoverview.utils.hash import calchash import malwoverview.modules.configvars as cv @@ -52,7 +53,7 @@ __author__ = "Alexandre Borges" __copyright__ = "Copyright 2018-2024, Alexandre Borges" __license__ = "GNU General Public License v3.0" -__version__ = "6.0.1" +__version__ = "6.0.2" __email__ = "reverseexploit at proton.me" def finish_hook(signum, frame): @@ -73,7 +74,7 @@ def main(): USER_HOME_DIR = str(Path.home()) + '/' cv.windows = 0 - parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is 6.0.1", usage="python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I ") + parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I ") parser.add_argument('-c', '--config', dest='config', type=str, metavar="CONFIG FILE", default=(USER_HOME_DIR + '.malwapi.conf'), help='Use a custom config file to specify API\'s.') parser.add_argument('-d', '--directory', dest='direct', type=str, default='', metavar="DIRECTORY", help='Specifies the directory containing malware samples to be checked against VIRUS TOTAL. Use the option -D to decide whether you are being using a public VT API or a Premium VT API.') parser.add_argument('-o', '--background', dest='backg', type=int, default=1, metavar="BACKGROUND", help='Adapts the output colors to a light background color terminal. The default is dark background color terminal.') @@ -100,6 +101,9 @@ def main(): parser.add_argument('-X', '--triagearg', dest='triagearg', type=str, default='', metavar="TRIAGE_ARG", help='Provides argument for options especified by -x option. Pay attention: the format of this argument depends on provided -x value.') parser.add_argument('-i', '--inquest', dest='inquest', type=int, default=0, metavar="INQUEST", help='Retrieves multiple information from INQUEST. The possible values are: 1: Downloads a sample; 2: Retrives information about a sample given a SHA256; 3: Retrieves information about a sample given a MD5 hash; 4: Gets the most recent list of threats. To this option, the -I argument must be "list" (lowercase and without double quotes) ; 5: Retrives threats related to a provided domain; 6. Retrieves a list of samples related to the given IP address; 7. Retrives a list of sample related to the given e-mail address; 8. Retrieves a list of samples related to the given filename; 9. Retrieves a list of samples related to a given URL; 10. Retrieves information about a specified IOC; 11. List a list of IOCs. Note: you must pass "list" (without double quotes) as argument to -I; 12. Check for a given keyword in the reputation database; 13. List artifacts in the reputation dabatabse. Note: you must pass "list" (without double quotes) as argument to -I.') parser.add_argument('-I', '--inquestarg', dest='inquestarg', type=str, metavar="INQUEST_ARG", help='Provides argument to INQUEST -i option.') + parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument; 3: Uploads a sample given a path provided in the -VX argument.') + parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') + parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads') args = parser.parse_args() @@ -115,6 +119,7 @@ def main(): MALPEDIAAPI = config_dict.get('MALPEDIA', 'MALPEDIAAPI') TRIAGEAPI = config_dict.get('TRIAGE', 'TRIAGEAPI') INQUESTAPI = config_dict.get('INQUEST', 'INQUESTAPI') + VXAPI = config_dict.get('VIRUSEXCHANGE', 'VXAPI') optval = range(2) optval1 = range(3) @@ -128,6 +133,7 @@ def main(): optval9 = range(14) optval10 = range(16) repo = args.direct + cv.output_dir = args.output_dir cv.bkg = args.backg virustotaloptionx = args.virustotaloption haoptionx = args.haoption @@ -153,6 +159,8 @@ def main(): virustotalargx = args.virustotalarg inquestx = args.inquest inquestargx = args.inquestarg + vxoptionx = args.vxoption + vxargx = args.vxarg config = args.config ffpname = '' @@ -187,7 +195,7 @@ def main(): haargx, mallist, args.malsharehash, args.hausoption, polyoptionx, polyargx, androidoptionx, androidargx, alienx, alienargsx, malpediaargx, malpediax, bazaarx, bazaarargx, triagex, triageargx, - inquestx, inquestargx + inquestx, inquestargx, vxoptionx, vxargx ] # Show the help message if: @@ -211,6 +219,8 @@ def main(): malshare = MalshareExtractor(MALSHAREAPI) haus = URLHausExtractor(HAUSSUBMITAPI) android = AndroidExtractor(hybrid, virustotal) + vx = VirusExchangeExtractor(VXAPI) + # Special parameters for hybrid analysis module query = haargx @@ -385,6 +395,14 @@ def ha_show_and_down(haargx, xx=0): 4: (android.sendandroidha, [androidargx]), 5: (android.sendandroidvt, [androidargx]) } + }, + { + 'flag': vxoptionx, + 'actions': { + 1: (vx.check_hash, [vxargx]), + 2: (vx.download_sample, [vxargx]), + 3: (vx.upload_sample, [vxargx]) + } } ] diff --git a/malwoverview/modules/bazaar.py b/malwoverview/modules/bazaar.py index 58bb3bd..bc52683 100644 --- a/malwoverview/modules/bazaar.py +++ b/malwoverview/modules/bazaar.py @@ -2,7 +2,7 @@ from malwoverview.utils.colors import mycolors, printr import requests import json - +import os class BazaarExtractor(): urlbazaar = 'https://mb-api.abuse.ch/api/v1/' @@ -596,8 +596,9 @@ def bazaar_download(self, bazaarx): print(mycolors.foreground.red + "\nNo malware samples found for the provided sha256 hash!\n" + mycolors.reset) exit(1) - open(resource + '.zip', 'wb').write(bazaarresponse.content) - final = '\nSAMPLE SAVED!' + outputpath = os.path.join(cv.output_dir, resource + '.zip') + open(outputpath, 'wb').write(bazaarresponse.content) + final = f'\nSample downloaded to: {outputpath}' if (cv.bkg == 1): print((mycolors.foreground.yellow + final + "\n")) diff --git a/malwoverview/modules/configvars.py b/malwoverview/modules/configvars.py index d6fdc2c..3873ea2 100644 --- a/malwoverview/modules/configvars.py +++ b/malwoverview/modules/configvars.py @@ -1,2 +1,3 @@ bkg = 0 windows = 0 +output_dir = '.' \ No newline at end of file diff --git a/malwoverview/modules/hybrid.py b/malwoverview/modules/hybrid.py index 1a0a2df..d9ca484 100644 --- a/malwoverview/modules/hybrid.py +++ b/malwoverview/modules/hybrid.py @@ -54,8 +54,9 @@ def downhash(self, filehash, user_agent='Falcon Sandbox'): print((mycolors.reset)) return final - open(resource + '.gz', 'wb').write(haresponse.content) - final = 'SAMPLE SAVED!' + outputpath = os.path.join(cv.output_dir, f'{resource}.gz') + open(outputpath, 'wb').write(haresponse.content) + final = f'Sample downloaded to: {outputpath}' print((mycolors.reset)) print((final + "\n")) diff --git a/malwoverview/modules/inquest.py b/malwoverview/modules/inquest.py index ef8b022..d17b117 100644 --- a/malwoverview/modules/inquest.py +++ b/malwoverview/modules/inquest.py @@ -3,7 +3,7 @@ import requests import textwrap import json - +import os class InQuestExtractor(): inquesturl = 'https://labs.inquest.net/api/dfi' @@ -63,11 +63,12 @@ def inquest_download(self, inquestx): print(mycolors.foreground.red + "\nThe sample is not available for download!\n" + mycolors.reset) exit(1) - open(inquestx + '.bin', 'wb').write(inquestresponse.content) + outputpath = os.path.join(cv.output_dir, inquestx + '.bin') + open(outputpath, 'wb').write(inquestresponse.content) if (cv.bkg == 1): - print("\n" + mycolors.foreground.yellow + "SAMPLE SAVED as: " + inquestx + ".bin" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.yellow + f"Sample downloaded to: {outputpath}" + mycolors.reset, end=' ') if (cv.bkg == 0): - print("\n" + mycolors.foreground.blue + "SAMPLE SAVED as: " + inquestx + ".bin" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.blue + f"Sample downloaded to: {outputpath}" + mycolors.reset, end=' ') print(mycolors.reset + "\n") exit(0) diff --git a/malwoverview/modules/malpedia.py b/malwoverview/modules/malpedia.py index 61aeab2..9788f90 100644 --- a/malwoverview/modules/malpedia.py +++ b/malwoverview/modules/malpedia.py @@ -4,7 +4,7 @@ import textwrap import base64 import json - +import os class MalpediaExtractor(): malpediaurl = 'https://malpedia.caad.fkie.fraunhofer.de/api' @@ -471,13 +471,13 @@ def malpedia_get_sample(self, arg1): exit(1) if ('200' in str(haresponse)): + outputpath = os.path.join(cv.output_dir, myargs + ".zip") + open(outputpath, 'wb').write(base64.b64decode(hatext['zipped'])) if (cv.bkg == 1): - open(myargs + ".zip", 'wb').write(base64.b64decode(hatext['zipped'])) - print(mycolors.foreground.lightcyan + "\nSample successfuly downloaded from Malpedia!\n", mycolors.reset) + print(mycolors.foreground.lightcyan + f"\nSample downloaded to: {outputpath}\n", mycolors.reset) else: - open(myargs + ".zip", 'wb').write(base64.b64decode(hatext['zipped'])) - print(mycolors.foreground.green + "\nSample successfuly downloaded from Malpedia!\n", mycolors.reset) - exit(0) + print(mycolors.foreground.green + f"\nSample downloaded to: {outputpath}\n", mycolors.reset) + exit(0) except ValueError as e: print(e) diff --git a/malwoverview/modules/malshare.py b/malwoverview/modules/malshare.py index ac64190..513431d 100644 --- a/malwoverview/modules/malshare.py +++ b/malwoverview/modules/malshare.py @@ -3,6 +3,7 @@ import json import requests import sys +import os class MalshareExtractor(): @@ -47,10 +48,11 @@ def malsharedown(self, filehash): print(mycolors.reset) exit(1) else: - open(resource, 'wb').write(malresponse3.content) + outputpath = os.path.join(cv.output_dir, resource) + open(outputpath, 'wb').write(malresponse3.content) print("\n") - print((mycolors.reset + "MALWARE SAMPLE SAVED! ")) + print((mycolors.reset + f"Sample downloaded to: {outputpath}")) printr() except (BrokenPipeError, IOError): print(mycolors.reset, file=sys.stderr) diff --git a/malwoverview/modules/polyswarm.py b/malwoverview/modules/polyswarm.py index 2832e98..a64aa76 100644 --- a/malwoverview/modules/polyswarm.py +++ b/malwoverview/modules/polyswarm.py @@ -244,7 +244,7 @@ def polyhashsearch(self, poly, download): firstseen = '' score = 0 down = download - DOWN_DIR = '.' + DOWN_DIR = cv.output_dir self.requestPOLYAPI() polyswarm = PolyswarmAPI(key=self.POLYAPI) @@ -295,7 +295,7 @@ def polyhashsearch(self, poly, download): print(mycolors.foreground.yellow + "\nPolyscore: \t%f" % score) if (down == 1): polyswarm.download(DOWN_DIR, sha256) - print(mycolors.reset + "\n\nThe sample has been SAVED!") + print(mycolors.reset + f"\n\nSample downloaded to: {DOWN_DIR}") else: if (sha256): print(mycolors.foreground.cyan + "\nSHA256: \t%s" % sha256) @@ -309,7 +309,7 @@ def polyhashsearch(self, poly, download): print(mycolors.foreground.red + "\nPolyscore: \t%f" % score) if (down == 1): polyswarm.download(DOWN_DIR, sha256) - print(mycolors.reset + "\n\nThe sample has been SAVED!") + print(mycolors.reset + f"\n\nSample downloaded to: {DOWN_DIR}") printr() except Exception: if (cv.bkg == 1): diff --git a/malwoverview/modules/triage.py b/malwoverview/modules/triage.py index 519f9f6..0dfd3c2 100644 --- a/malwoverview/modules/triage.py +++ b/malwoverview/modules/triage.py @@ -746,11 +746,12 @@ def triage_download(self, triagex): print("\n" + mycolors.foreground.red + triagetext['message'] + mycolors.reset, end='\n\n') exit(1) - open(triagex + '.bin', 'wb').write(triageresponse.content) + outputpath = os.path.join(cv.output_dir, ) + open(outputpath, 'wb').write(triageresponse.content) if (cv.bkg == 1): - print("\n" + mycolors.foreground.yellow + "SAMPLE SAVED as: " + triagex + ".bin" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.yellow + f"Sample downloaded to: {outputpath}" + mycolors.reset, end=' ') if (cv.bkg == 0): - print("\n" + mycolors.foreground.blue + "SAMPLE SAVED as: " + triagex + ".bin" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.blue + f"Sample downloaded to: {outputpath}" + mycolors.reset, end=' ') print(mycolors.reset + "\n") exit(0) @@ -805,11 +806,12 @@ def triage_download_pcap(self, triagex): print("\n" + mycolors.foreground.red + triagetext['message'] + mycolors.reset, end='\n\n') exit(1) - open(triagex + '.pcapng', 'wb').write(triageresponse.content) + outputpath = os.path.join(cv.output_dir, triagex + '.pcapng') + open(outputpath, 'wb').write(triageresponse.content) if (cv.bkg == 1): - print("\n" + mycolors.foreground.yellow + "PCAP SAVED as: " + triagex + ".pcapng" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.yellow + f"PCAP downloaded to: {outputpath}" + mycolors.reset, end=' ') if (cv.bkg == 0): - print("\n" + mycolors.foreground.blue + "PCAP SAVED as: " + triagex + ".pcapng" + mycolors.reset, end=' ') + print("\n" + mycolors.foreground.blue + f"PCAP downloaded to: {outputpath}" + mycolors.reset, end=' ') print(mycolors.reset + "\n") exit(0) diff --git a/malwoverview/modules/urlhaus.py b/malwoverview/modules/urlhaus.py index f00ee73..5fe3f92 100644 --- a/malwoverview/modules/urlhaus.py +++ b/malwoverview/modules/urlhaus.py @@ -7,6 +7,7 @@ from malwoverview.utils.utils import urltoip import json import sys +import os class URLHausExtractor(): @@ -338,8 +339,9 @@ def haussample(self, hashx): print((mycolors.foreground.red + "\n" + final + "\n" + mycolors.reset)) exit(1) - open(resource + '.zip', 'wb').write(response.content) - final = '\nSAMPLE SAVED!' + outputpath = os.path.join(cv.output_dir, resource + '.zip') + open(outputpath, 'wb').write(response.content) + final = f'\nSample downloaded to: {outputpath}' if (cv.bkg == 1): print((mycolors.foreground.yellow + final + "\n")) diff --git a/malwoverview/modules/virusexchange.py b/malwoverview/modules/virusexchange.py new file mode 100644 index 0000000..d769caf --- /dev/null +++ b/malwoverview/modules/virusexchange.py @@ -0,0 +1,106 @@ +import requests +import os +import malwoverview.modules.configvars as cv +from malwoverview.utils.colors import mycolors, printc + +class VirusExchangeExtractor: + def __init__(self, api_key): + self.VXAPI = api_key + self.base_url = "https://virus.exchange/api" + + def _requireVXAPI(self): + if (self.VXAPI == ''): + printc("\nTo be able to get/submit information from/to Virus Exchange, you must create the .malwapi.conf file under your user home directory (on Linux is $HOME\\.malwapi.conf and on Windows is in C:\\Users\\[username]\\.malwapi.conf) and insert the Virus Exchange API according to the format shown on the Github website.", mycolors.foreground.red) + exit(1) + + def _get_hash_metadata(self, sha256): + url = f'{self.base_url}/samples/{sha256}' + response = requests.get(url, headers={'Authorization': f'Bearer {self.VXAPI}'}) + return response + + def check_hash(self, sha256): + self._requireVXAPI() + + metadata_to_show = [ + {"key": "md5", "name": "MD5", "colors": (mycolors.foreground.cyan, mycolors.foreground.lightcyan), "pad": 12}, + {"key": "sha1", "name": "SHA1", "colors": (mycolors.foreground.cyan, mycolors.foreground.lightcyan), "pad": 12}, + {"key": "sha256", "name": "SHA256", "colors": (mycolors.foreground.cyan, mycolors.foreground.lightcyan), "pad": 12}, + {"key": "size", "name": "Size", "colors": (mycolors.foreground.purple, mycolors.foreground.yellow), "pad": 12}, + { + "key": "tags", "name": "Tags", + "colors": (mycolors.foreground.purple, mycolors.foreground.yellow), "pad": 12, + "fn": lambda x: ', '.join(x) if hasattr(x, '__iter__') else "" + }, + {"key": "type", "name": "Type", "colors": (mycolors.foreground.purple, mycolors.foreground.yellow), "pad": 12}, + {"key": "first_seen", "name": "First Seen", "colors": (mycolors.foreground.purple, mycolors.foreground.yellow), "pad": 12} + ] + + try: + response = self._get_hash_metadata(sha256) + except e: + printc(f"[-] Error checking hash metadata: {str(e)}", mycolors.foreground.red) + return + + metadata = response.json() + + print() + print() + for val in metadata_to_show: + key = val['key'] + color = val["colors"][cv.bkg] + name = val['name'] + pad = val['pad'] + fn = val.get('fn', None) + + if key in metadata: + printc(f'{name}:'.ljust(pad), color, end='') + + val_to_print = metadata[key] + if fn is not None: + val_to_print = fn(metadata[key]) + + print(f'{val_to_print}') + + def download_sample(self, sha256): + self._requireVXAPI() + + try: + response = self._get_hash_metadata(sha256) + metadata = response.json() + + if response.status_code == 200: + download_link = metadata.get('download_link') + if download_link: + # Download the actual sample + sample_response = requests.get(download_link) + if sample_response.status_code == 200: + output_path = os.path.join(cv.output_dir, sha256) + with open(output_path, 'wb') as f: + f.write(sample_response.content) + printc(f"Sample downloaded to: {output_path}", mycolors.foreground.green) + else: + printc(f"Failed to download sample: {sample_response.status_code}", mycolors.foreground.red) + else: + printc("No download link available in metadata", mycolors.foreground.red) + else: + error_detail = metadata.get('errors', {}).get('detail', 'Unknown error') + printc(f"Failed to fetch sample metadata: {error_detail}", mycolors.foreground.red) + except Exception as e: + printc(f"Error downloading sample: {str(e)}", mycolors.foreground.red) + + def upload_sample(self, file_path): + self._requireVXAPI() + + url = f'{self.base_url}/upload' + + try: + with open(file_path, 'rb') as f: + files = {'file': f} + response = requests.post(url, headers={'Authorization': f'Bearer {self.VXAPI}'}, files=files) + + if response.status_code == 200: + printc("Sample uploaded successfully", mycolors.foreground.green) + else: + printc(f"Failed to upload sample: {response.status_code}", mycolors.foreground.red) + except Exception as e: + printc(f"Error uploading sample: {str(e)}", mycolors.foreground.red) \ No newline at end of file diff --git a/malwoverview/utils/colors.py b/malwoverview/utils/colors.py index 016d795..a6f009c 100644 --- a/malwoverview/utils/colors.py +++ b/malwoverview/utils/colors.py @@ -30,8 +30,8 @@ class background: red = '\033[41m' -def printc(self, color, text, **args): - print(f'{color}{text}{mycolors.reset}', *args) +def printc(text, color, *args, **kwargs): + print(f'{color}{text}{mycolors.reset}', *args, **kwargs) def printr(): From 6ca1b0d2adc99bd7016f64a938381eeac0d33d01 Mon Sep 17 00:00:00 2001 From: Macmod Date: Fri, 29 Nov 2024 09:50:59 -0300 Subject: [PATCH 02/10] Adding basic IPInfo module and updating README. --- .malwapi.conf | 5 ++- README.md | 28 +++++++++-------- malwoverview/malwoverview.py | 16 ++++++++-- malwoverview/modules/ipinfo.py | 56 ++++++++++++++++++++++++++++++++++ 4 files changed, 90 insertions(+), 15 deletions(-) create mode 100644 malwoverview/modules/ipinfo.py diff --git a/.malwapi.conf b/.malwapi.conf index 4792f71..0b274d3 100644 --- a/.malwapi.conf +++ b/.malwapi.conf @@ -26,4 +26,7 @@ TRIAGEAPI = INQUESTAPI = [VIRUSEXCHANGE] -VXAPI = \ No newline at end of file +VXAPI = + +[IPINFO] +IPINFOAPI = \ No newline at end of file diff --git a/README.md b/README.md index a6b2cf8..8581329 100755 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ This tool aims to : 1. Determine similar executable malware samples (PE/PE+) according to the import table (imphash) and group them by different colors (pay attention to the second column from output). Thus, colors matter! 2. Show hash information on Virus Total, Hybrid Analysis, Malshare, Polyswarm, URLhaus, Alien Vault, - Malpedia, ThreatCrowd and VirusExchange engines. + Malpedia and VirusExchange engines. 3. Determining whether the malware samples contain overlay and, if you want, extract it. 4. Check suspect files on Virus Total, Hybrid Analysis and Polyswarm. 5. Check URLs on Virus Total, Malshare, Polyswarm, URLhaus engines and Alien Vault. @@ -101,13 +101,12 @@ This tool aims to : 10. Search for specific payloads on the Malshare. 11. Search for similar payloads (PE32/PE32+) on Polyswarm engine. 12. Classify all files in a directory searching information on Virus Total and Hybrid Analysis. -13. Make reports about a suspect domain using different engines such as VirusTotal, Malpedia and - ThreatCrowd. +13. Make reports about a suspect domain using different engines such as VirusTotal & Malpedia. 14. Check APK packages directly from Android devices against Hybrid Analysis and Virus Total. 15. Submit APK packages directly from Android devices to Hybrid Analysis and Virus Total. 16. Show URLs related to an user provided tag from URLHaus. 17. Show payloads related to a tag (signature) from URLHaus. -18. Show information about an IP address from Virus Total, Alien Vault, Malpedia and ThreatCrowd. +18. Show information about an IP address from Virus Total, Alien Vault, Malpedia. 19. Show IP address, domain and URL information from Polyswarm. 21. Perform meta-search on Polyswarm Network using several criteria: imphash, IPv4, domain, URL and malware family. @@ -197,7 +196,7 @@ has the following format: MALSHAREAPI = [HAUSSUBMIT] - HAUSSUBMITAPI = + HAUSSUBMITAPI = [POLYSWARM] POLYAPI = @@ -206,17 +205,20 @@ has the following format: ALIENAPI = [MALPEDIA] - MALPEDIAAPI = + MALPEDIAAPI = [TRIAGE] - TRIAGEAPI = + TRIAGEAPI = [INQUEST] - INQUESTAPI = + INQUESTAPI = [VIRUSEXCHANGE] VXAPI = + [IPINFO] + IPINFOAPI = + The APIs can be requested on the respective service websites: 01. Virus Total (community and paid API): https://www.virustotal.com/gui/join-us @@ -230,9 +232,10 @@ The APIs can be requested on the respective service websites: handle is @malpedia. 08. Malware Bazaar: It isn't necessary an API. 09. ThreatFox: It isn't necessary an API. -10. InQuest: https://labs.inquest.net/. -11. Triage: https://tria.ge/signup. -12. VirusExchange: https://virus.exchange/users/register. +10. InQuest: https://labs.inquest.net/ +11. Triage: https://tria.ge/signup +12. VirusExchange: https://virus.exchange/users/register +13. IPInfo: https://ipinfo.io/signup ---------------------------------------------------- A special note about API requests to the MALPEDIA: @@ -445,11 +448,12 @@ should be executed: ## HISTORY -Version 6.0.2: +Version 6.1.0: This version: * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack, sample download and sample submission. * Adds a new -O flag to set up the output directory for any sample downloads + * Adds -ip and -IP flags to allow querying IPInfo for IP address lookups. Version 6.0.1: diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index a5066fd..1703161 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -43,6 +43,7 @@ from malwoverview.modules.urlhaus import URLHausExtractor from malwoverview.modules.virustotal import VirusTotalExtractor from malwoverview.modules.virusexchange import VirusExchangeExtractor +from malwoverview.modules.ipinfo import IPInfoExtractor from malwoverview.utils.colors import printr from malwoverview.utils.hash import calchash import malwoverview.modules.configvars as cv @@ -104,6 +105,8 @@ def main(): parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument; 3: Uploads a sample given a path provided in the -VX argument.') parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads') + parser.add_argument('-ip', '--ipinfo', dest='ipoption', type=int, default=0, metavar="IPINFO", help='Get IP information from IPInfo.io. Value 1: Get details for an IP address provided with -G') + parser.add_argument('-IP', '--ipinfoarg', dest='iparg', type=str, metavar="IPINFO_ARG", help='Provides argument for IPInfo.io operations specified by -g option') args = parser.parse_args() @@ -120,6 +123,7 @@ def main(): TRIAGEAPI = config_dict.get('TRIAGE', 'TRIAGEAPI') INQUESTAPI = config_dict.get('INQUEST', 'INQUESTAPI') VXAPI = config_dict.get('VIRUSEXCHANGE', 'VXAPI') + IPINFOAPI = config_dict.get('IPINFO', 'IPINFOAPI') optval = range(2) optval1 = range(3) @@ -161,6 +165,8 @@ def main(): inquestargx = args.inquestarg vxoptionx = args.vxoption vxargx = args.vxarg + ipoptionx = args.ipoption + ipargx = args.iparg config = args.config ffpname = '' @@ -195,7 +201,7 @@ def main(): haargx, mallist, args.malsharehash, args.hausoption, polyoptionx, polyargx, androidoptionx, androidargx, alienx, alienargsx, malpediaargx, malpediax, bazaarx, bazaarargx, triagex, triageargx, - inquestx, inquestargx, vxoptionx, vxargx + inquestx, inquestargx, vxoptionx, vxargx, ipoptionx, ipargx ] # Show the help message if: @@ -220,7 +226,7 @@ def main(): haus = URLHausExtractor(HAUSSUBMITAPI) android = AndroidExtractor(hybrid, virustotal) vx = VirusExchangeExtractor(VXAPI) - + ipinfo = IPInfoExtractor(IPINFOAPI) # Special parameters for hybrid analysis module query = haargx @@ -403,6 +409,12 @@ def ha_show_and_down(haargx, xx=0): 2: (vx.download_sample, [vxargx]), 3: (vx.upload_sample, [vxargx]) } + }, + { + 'flag': ipoptionx, + 'actions': { + 1: (ipinfo.get_ip_details, [ipargx]) + } } ] diff --git a/malwoverview/modules/ipinfo.py b/malwoverview/modules/ipinfo.py new file mode 100644 index 0000000..ffef0fc --- /dev/null +++ b/malwoverview/modules/ipinfo.py @@ -0,0 +1,56 @@ +import malwoverview.modules.configvars as cv +from malwoverview.utils.colors import mycolors +import requests + +class IPInfoExtractor: + def __init__(self, IPINFOAPI): + self.IPINFOAPI = IPINFOAPI + + def requestIPINFOAPI(self): + if self.IPINFOAPI == '': + print(mycolors.foreground.red + "\nTo use IPInfo.io services, you must create the .malwapi.conf file under your user home directory (on Linux is $HOME\\.malwapi.conf and on Windows is in C:\\Users\\[username]\\.malwapi.conf) and insert the IPInfo API key according to the format shown on the Github website." + mycolors.reset + "\n") + exit(1) + + def _raw_ip_info(self, ip_address): + url = f"https://ipinfo.io/{ip_address}?token={self.IPINFOAPI}" + + try: + response = requests.get(url) + data = response.json() + except: + return {} + + def get_ip_details(self, ip_address): + self.requestIPINFOAPI() + + data = self._get_info(ip_address) + + try: + print("\n") + print((mycolors.reset + "IPINFO.IO REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + if 'error' in data: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\n{data['error']['message']}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\n{data['error']['message']}\n" + mycolors.reset) + return + + fields = ['ip', 'hostname', 'city', 'region', 'country', 'loc', 'org', 'postal', 'timezone'] + + COLSIZE = max(len(field) for field in fields) + 3 + + for field in fields: + if field in data: + if (cv.bkg == 1): + print(mycolors.foreground.lightcyan + f"{field.title()}: ".ljust(COLSIZE) + mycolors.reset + str(data[field])) + else: + print(mycolors.foreground.cyan + f"{field.title()}: ".ljust(COLSIZE) + mycolors.reset + str(data[field])) + + except Exception as e: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) From a657c9a3c9bd66d9549e6829d31ffe46ef951c68 Mon Sep 17 00:00:00 2001 From: Macmod Date: Fri, 29 Nov 2024 10:04:08 -0300 Subject: [PATCH 03/10] Adding basic gitignore for python. --- .gitignore | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b54a3d --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST \ No newline at end of file From efce2ee233a1292fbbd7eeb7e2434c6fc36b2a85 Mon Sep 17 00:00:00 2001 From: Macmod Date: Fri, 29 Nov 2024 15:04:17 -0300 Subject: [PATCH 04/10] Updating version & README, fixing bug in VT module & improving IP lookup features. --- README.md | 10 +- malwoverview/malwoverview.py | 27 ++++-- malwoverview/modules/alienvault.py | 22 +++-- malwoverview/modules/bgpview.py | 57 +++++++++++ malwoverview/modules/inquest.py | 17 ++-- malwoverview/modules/ipinfo.py | 10 +- malwoverview/modules/multipleip.py | 130 ++++++++++++++++++++++++++ malwoverview/modules/virusexchange.py | 2 +- malwoverview/modules/virustotal.py | 21 +++-- setup.py | 2 +- 10 files changed, 256 insertions(+), 42 deletions(-) create mode 100644 malwoverview/modules/bgpview.py create mode 100644 malwoverview/modules/multipleip.py diff --git a/README.md b/README.md index c0e0a76..b14b1c3 100755 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Malwoverview -[GitHub release (latest by date)](https://github.com/alexandreborges/malwoverview/releases/tag/v6.0.2) [GitHub last commit](https://github.com/alexandreborges/malwoverview/releases) [GitHub Release Date](https://github.com/alexandreborges/malwoverview/releases) [GitHub](https://github.com/alexandreborges/malwoverview/blob/master/LICENSE) +[GitHub release (latest by date)](https://github.com/alexandreborges/malwoverview/releases/tag/v6.1.0) [GitHub last commit](https://github.com/alexandreborges/malwoverview/releases) [GitHub Release Date](https://github.com/alexandreborges/malwoverview/releases) [GitHub](https://github.com/alexandreborges/malwoverview/blob/master/LICENSE) [GitHub stars](https://github.com/alexandreborges/malwoverview/stargazers) [Twitter Follow](https://twitter.com/ale_sp_brazil) [Downloads/Last Month](https://pypistats.org/packages/malwoverview) @@ -71,7 +71,7 @@ See GNU Public License on . -## Current Version: 6.0.2 +## Current Version: 6.1.0 Important note: Malwoverview does NOT submit samples to any endpoint by default, so it respects possible Non-Disclosure Agreements (NDAs). There're specific options @@ -343,7 +343,8 @@ usage: python malwoverview.py -c -d -o <0|1 -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> --I +-I -vx <1-3> -VX -O -ip <1-3> +-IP Malwoverview is a first response tool for threat hunting written by Alexandre Borges. @@ -745,7 +746,8 @@ Version 6.1.0: * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack, sample download and sample submission. * Adds a new -O flag to set up the output directory for any sample downloads - * Adds -ip and -IP flags to allow querying IPInfo for IP address lookups. + * Adds -ip and -IP flags to allow querying IPInfo, BGPView and other already existing sources (currently VirusTotal & AlientVault) for basic IP address lookups. + * Fixes a bug in the VirusTotal output. Version 6.0.1: diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index 1703161..ef4f2ed 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -21,7 +21,7 @@ # Christian Clauss (https://github.com/cclauss) # Artur Marzano (https://github.com/Macmod) -# Malwoverview.py: version 6.0.2 +# Malwoverview.py: version 6.1.0 import os import argparse @@ -44,6 +44,8 @@ from malwoverview.modules.virustotal import VirusTotalExtractor from malwoverview.modules.virusexchange import VirusExchangeExtractor from malwoverview.modules.ipinfo import IPInfoExtractor +from malwoverview.modules.bgpview import BGPViewExtractor +from malwoverview.modules.multipleip import MultipleIPExtractor from malwoverview.utils.colors import printr from malwoverview.utils.hash import calchash import malwoverview.modules.configvars as cv @@ -54,7 +56,7 @@ __author__ = "Alexandre Borges" __copyright__ = "Copyright 2018-2024, Alexandre Borges" __license__ = "GNU General Public License v3.0" -__version__ = "6.0.2" +__version__ = "6.1.0" __email__ = "reverseexploit at proton.me" def finish_hook(signum, frame): @@ -75,7 +77,7 @@ def main(): USER_HOME_DIR = str(Path.home()) + '/' cv.windows = 0 - parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I ") + parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="usage: python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I -vx <1-3> -VX -O -ip <1-3> -IP ") parser.add_argument('-c', '--config', dest='config', type=str, metavar="CONFIG FILE", default=(USER_HOME_DIR + '.malwapi.conf'), help='Use a custom config file to specify API\'s.') parser.add_argument('-d', '--directory', dest='direct', type=str, default='', metavar="DIRECTORY", help='Specifies the directory containing malware samples to be checked against VIRUS TOTAL. Use the option -D to decide whether you are being using a public VT API or a Premium VT API.') parser.add_argument('-o', '--background', dest='backg', type=int, default=1, metavar="BACKGROUND", help='Adapts the output colors to a light background color terminal. The default is dark background color terminal.') @@ -105,8 +107,8 @@ def main(): parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument; 3: Uploads a sample given a path provided in the -VX argument.') parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads') - parser.add_argument('-ip', '--ipinfo', dest='ipoption', type=int, default=0, metavar="IPINFO", help='Get IP information from IPInfo.io. Value 1: Get details for an IP address provided with -G') - parser.add_argument('-IP', '--ipinfoarg', dest='iparg', type=str, metavar="IPINFO_ARG", help='Provides argument for IPInfo.io operations specified by -g option') + parser.add_argument('-ip', '--ip', dest='ipoption', type=int, default=0, metavar="IP", help='Get IP information from various sources. The possible values are: 1: Get details for an IP address provided with -IP from all available sources (IPInfo/BGPView/VirusTotal/Alienvault); 2: Get details for an IP address provided with -IP from IPInfo; 3: Get details for an IP address provided with -IP from BGPView') + parser.add_argument('-IP', '--iparg', dest='iparg', type=str, metavar="IP_ARG", help='Provides argument for IP lookup operations specified by the -ip option') args = parser.parse_args() @@ -227,6 +229,17 @@ def main(): android = AndroidExtractor(hybrid, virustotal) vx = VirusExchangeExtractor(VXAPI) ipinfo = IPInfoExtractor(IPINFOAPI) + bgpview = BGPViewExtractor() + multipleip = MultipleIPExtractor( + { + "IPInfo": ipinfo, + "BGPView": bgpview, + "VirusTotal": virustotal, + "AlienVault": alien, + "InQuest": inquest, + # "PolySwarm": polyswarm, + } + ) # Special parameters for hybrid analysis module query = haargx @@ -413,7 +426,9 @@ def ha_show_and_down(haargx, xx=0): { 'flag': ipoptionx, 'actions': { - 1: (ipinfo.get_ip_details, [ipargx]) + 1: (multipleip.get_multiple_ip_details, [ipargx]), + 2: (ipinfo.get_ip_details, [ipargx]), + 3: (bgpview.get_ip_details, [ipargx]), } } ] diff --git a/malwoverview/modules/alienvault.py b/malwoverview/modules/alienvault.py index 898defb..ff9b916 100644 --- a/malwoverview/modules/alienvault.py +++ b/malwoverview/modules/alienvault.py @@ -159,25 +159,27 @@ def alien_subscribed(self, arg1): print((mycolors.foreground.red + "Error while connecting to Alien Vault!\n")) printr() - def alien_ipv4(self, arg1): + def _raw_ip_info(self, ip): url = AlienVaultExtractor.urlalien - self.requestALIENAPI() hatext = '' haresponse = '' history = '10' headers = {'X-OTX-API-KEY': self.ALIENAPI} search_params = {'limit': history} - myargs = arg1 - try: + resource = url + requestsession = requests.Session() + requestsession.headers.update({'Content-Type': 'application/json'}) + finalurl = '/'.join([resource, 'indicators', 'IPv4', ip]) + haresponse = requestsession.post(url=finalurl, headers=headers, params=search_params) + return haresponse - resource = url - requestsession = requests.Session() - requestsession.headers.update({'Content-Type': 'application/json'}) - finalurl = '/'.join([resource, 'indicators', 'IPv4', myargs]) - haresponse = requestsession.post(url=finalurl, headers=headers, params=search_params) - hatext = json.loads(haresponse.text) + def alien_ipv4(self, arg1): + self.requestALIENAPI() + try: + haresponse = self._raw_ip_info(arg1) + hatext = haresponse.json() if (cv.bkg == 1): if 'sections' in hatext: diff --git a/malwoverview/modules/bgpview.py b/malwoverview/modules/bgpview.py new file mode 100644 index 0000000..8cf9e96 --- /dev/null +++ b/malwoverview/modules/bgpview.py @@ -0,0 +1,57 @@ +import requests +import malwoverview.modules.configvars as cv +from malwoverview.utils.colors import mycolors + +class BGPViewExtractor: + urlbgpview = "https://api.bgpview.io/ip/" + + def _raw_ip_info(self, ip_address): + url = f"{BGPViewExtractor.urlbgpview}{ip_address}" + + try: + response = requests.get(url) + data = response.json() + return data.get('data', {}) if data.get('status') == 'ok' else {} + except: + return {} + + def get_ip_details(self, ip_address): + data = self._raw_ip_info(ip_address) + + try: + print("\n") + print((mycolors.reset + "BGPVIEW.IO REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + if not data: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + "\nNo information available\n" + mycolors.reset) + else: + print(mycolors.foreground.red + "\nNo information available\n" + mycolors.reset) + return + + fields = { + 'IP Address': data.get('ip'), + 'PTR Record': data.get('ptr_record'), + 'Prefix': data.get('prefixes', [{}])[0].get('prefix'), + 'ASN': data.get('prefixes', [{}])[0].get('asn', {}).get('asn'), + 'AS Name': data.get('prefixes', [{}])[0].get('asn', {}).get('name'), + 'AS Description': data.get('prefixes', [{}])[0].get('asn', {}).get('description'), + 'Country Code': data.get('prefixes', [{}])[0].get('asn', {}).get('country_code') + } + + COLSIZE = max(len(field) for field in fields.keys()) + 3 + + for field, value in fields.items(): + if value: + if (cv.bkg == 1): + print(mycolors.foreground.lightcyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + else: + print(mycolors.foreground.cyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + + except Exception as e: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) diff --git a/malwoverview/modules/inquest.py b/malwoverview/modules/inquest.py index d17b117..8cb36bf 100644 --- a/malwoverview/modules/inquest.py +++ b/malwoverview/modules/inquest.py @@ -629,15 +629,21 @@ def inquest_domain(self, inquestx): else: print((mycolors.foreground.lightred + "\nError while connecting to InQuest!\n")) - def inquest_ip(self, inquestx): + def _raw_ip_info(self, ip): inquest = InQuestExtractor.inquesturl + requestsession = requests.Session() + requestsession.headers.update({'Accept': 'application/json'}) + requestsession.headers.update({'Authorization': self.INQUESTAPI}) + inquestresponse = requestsession.get(inquest + '/search/ioc/ip?keyword=' + ip) + return inquestresponse + + def inquest_ip(self, inquestx): inquestresponse = '' self.requestINQUESTAPI() try: - print("\n") print((mycolors.reset + "INQUEST IP ADDRESS SEARCH REPORT".center(110)), end='') print((mycolors.reset + "".center(28)), end='') @@ -650,11 +656,8 @@ def inquest_ip(self, inquestx): print(mycolors.foreground.red + "\nThe -I parameter with the provided IP address is required!\n" + mycolors.reset) exit(1) - requestsession = requests.Session() - requestsession.headers.update({'Accept': 'application/json'}) - requestsession.headers.update({'Authorization': self.INQUESTAPI}) - inquestresponse = requestsession.get(inquest + '/search/ioc/ip?keyword=' + inquestx) - inquesttext = json.loads(inquestresponse.text) + inquestresponse = self._raw_ip_info(inquestx) + inquesttext = inquestresponse.json() if (cv.bkg == 1): for i in inquesttext.keys(): diff --git a/malwoverview/modules/ipinfo.py b/malwoverview/modules/ipinfo.py index ffef0fc..dbb61b6 100644 --- a/malwoverview/modules/ipinfo.py +++ b/malwoverview/modules/ipinfo.py @@ -16,14 +16,14 @@ def _raw_ip_info(self, ip_address): try: response = requests.get(url) - data = response.json() - except: - return {} + return response.json() + except Exception as e: + return {'error': e} def get_ip_details(self, ip_address): self.requestIPINFOAPI() - data = self._get_info(ip_address) + data = self._raw_ip_info(ip_address) try: print("\n") @@ -38,7 +38,7 @@ def get_ip_details(self, ip_address): print(mycolors.foreground.red + f"\n{data['error']['message']}\n" + mycolors.reset) return - fields = ['ip', 'hostname', 'city', 'region', 'country', 'loc', 'org', 'postal', 'timezone'] + fields = ['ip', 'hostname', 'org', 'country', 'region', 'city', 'loc', 'postal', 'timezone'] COLSIZE = max(len(field) for field in fields) + 3 diff --git a/malwoverview/modules/multipleip.py b/malwoverview/modules/multipleip.py new file mode 100644 index 0000000..8cd05ab --- /dev/null +++ b/malwoverview/modules/multipleip.py @@ -0,0 +1,130 @@ +from malwoverview.utils.colors import mycolors, printr +import malwoverview.modules.configvars as cv + +class MultipleIPExtractor: + def __init__(self, extractors): + self.extractors = extractors + + def get_multiple_ip_details(self, ip_address): + # print("\n") + # print((mycolors.reset + "MULTIPLE IP REPORTS".center(100)), end='') + # print((mycolors.reset + "".center(28)), end='') + # print("\n" + (100 * '-').center(50)) + + for extractor in self.extractors: + extractor_obj = self.extractors[extractor] + if extractor == "IPInfo": + extractor_obj.get_ip_details(ip_address) + elif extractor == "BGPView": + extractor_obj.get_ip_details(ip_address) + elif extractor == "VirusTotal": + data = extractor_obj._raw_ip_info(ip_address) + self._get_info_virustotal(data.json()) + elif extractor == "AlienVault": + data = extractor_obj._raw_ip_info(ip_address) + self._get_info_alienvault(data.json()) + # elif extractor == "PolySwarm": + # data = extractor_obj._raw_ip_info(ip_address) + # self._get_info_polyswarm(data) + + def _get_info_virustotal(self, data): + try: + attributes = data.get('data', {}).get('attributes', {}) + + print("\n") + print((mycolors.reset + "VIRUSTOTAL IP REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + fields = { + 'Reputation': attributes.get('reputation'), + 'RIR': attributes.get('regional_internet_registry'), + 'Network': attributes.get('network'), + 'ASN': attributes.get('asn'), + 'AS Owner': attributes.get('as_owner'), + 'Country Code': attributes.get('country'), + 'Continent': attributes.get('continent') + } + + COLSIZE = max(len(field) for field in fields.keys()) + 3 + + for field, value in fields.items(): + if (cv.bkg == 1): + print(mycolors.foreground.lightcyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + else: + print(mycolors.foreground.cyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + + print("\nAnalysis Stats:") + stats = attributes.get('last_analysis_stats', {}) + for stat, count in stats.items(): + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"{stat.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + else: + print(mycolors.foreground.red + f"{stat.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + + print("\nCommunity Votes:") + votes = attributes.get('total_votes', {}) + for vote, count in votes.items(): + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"{vote.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + else: + print(mycolors.foreground.red + f"{vote.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + + except Exception as e: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + + print() + print("(For the full VirusTotal report use the -v and -V options)") + + def _get_info_polyswarm(self, data): + print("\n") + print((mycolors.reset + "POLYSWARM IP REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + print(data) + + print() + print("(For the full PolySwarm report use the -p and -P options)") + + def _get_info_alienvault(self, data): + try: + print("\n") + print((mycolors.reset + "ALIENVAULT IP REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + COLSIZE = 21 + + if (cv.bkg == 1): + print(mycolors.foreground.lightcyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) + print(mycolors.foreground.lightcyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) + print(mycolors.foreground.lightcyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) + print(mycolors.foreground.lightcyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) + print(mycolors.foreground.lightcyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) + print(mycolors.foreground.lightcyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) + print(mycolors.foreground.lightcyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) + print(mycolors.foreground.lightcyan + "Sections Available: ".ljust(COLSIZE) + mycolors.reset + ', '.join(data.get('sections', []))) + print(mycolors.foreground.lightred + "Pulses Found: ".ljust(COLSIZE) + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) + else: + print(mycolors.foreground.cyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) + print(mycolors.foreground.cyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) + print(mycolors.foreground.cyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) + print(mycolors.foreground.cyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) + print(mycolors.foreground.cyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) + print(mycolors.foreground.cyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) + print(mycolors.foreground.cyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) + print(mycolors.foreground.cyan + "Sections Available: ".ljust(COLSIZE) + mycolors.reset + ', '.join(data.get('sections', []))) + print(mycolors.foreground.red + "Pulses Found: ".ljust(COLSIZE) + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) + + except Exception as e: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + + print() + print("(For the full AlienVault report use the -n and -N options)") \ No newline at end of file diff --git a/malwoverview/modules/virusexchange.py b/malwoverview/modules/virusexchange.py index d769caf..deeef51 100644 --- a/malwoverview/modules/virusexchange.py +++ b/malwoverview/modules/virusexchange.py @@ -37,7 +37,7 @@ def check_hash(self, sha256): try: response = self._get_hash_metadata(sha256) - except e: + except Exception as e: printc(f"[-] Error checking hash metadata: {str(e)}", mycolors.foreground.red) return diff --git a/malwoverview/modules/virustotal.py b/malwoverview/modules/virustotal.py index 2d666ec..bd7d411 100644 --- a/malwoverview/modules/virustotal.py +++ b/malwoverview/modules/virustotal.py @@ -524,19 +524,23 @@ def vtdomainwork(self, mydomain): print(mycolors.reset) exit(3) + def _raw_ip_info(self, myip): + url = VirusTotalExtractor.urlipvt3 + + finalurl = ''.join([url, "/", myip]) + requestsession = requests.Session() + requestsession.headers.update({'x-apikey': self.VTAPI}) + requestsession.headers.update({'content-type': 'application/json'}) + response = requestsession.get(finalurl) + return response + def vtipwork(self, myip): if not myip: return False - url = VirusTotalExtractor.urlipvt3 - try: - finalurl = ''.join([url, "/", myip]) - requestsession = requests.Session() - requestsession.headers.update({'x-apikey': self.VTAPI}) - requestsession.headers.update({'content-type': 'application/json'}) - response = requestsession.get(finalurl) - vttext = json.loads(response.text) + response = self._raw_ip_info(myip) + vttext = response.json() if (response.status_code == 404): if (cv.bkg == 1): @@ -637,6 +641,7 @@ def vtipwork(self, myip): if ('last_analysis_stats' in attrs): if ('suspicious' in attrs['last_analysis_stats']): suspicious = attrs['last_analysis_stats']['suspicious'] + print(mycolors.foreground.red + "\nSuspicious: ".ljust(26) + mycolors.reset + str(suspicious), end='') print(mycolors.foreground.red + "\nCity: ".ljust(26) + mycolors.reset + str(geocoder.ip(myip).city), end='') self.vt_url_ip_domain_report_light(vttext) diff --git a/setup.py b/setup.py index 43cc1a2..09a0796 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ setup( name="malwoverview", - version="6.0.1", + version="6.1.0", author="Alexandre Borges", author_email="reverseexploit@proton.me", license="GNU GPL v3.0", From f38788abdec09de956f887a4089566edf2a2fb3b Mon Sep 17 00:00:00 2001 From: Macmod Date: Fri, 29 Nov 2024 15:47:47 -0300 Subject: [PATCH 05/10] Commenting out InQuest code from -ip and minor fixes. --- README.md | 2 +- malwoverview/malwoverview.py | 2 +- malwoverview/modules/multipleip.py | 55 +++++++++++++++++++++--------- 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index b14b1c3..0f1ae74 100755 --- a/README.md +++ b/README.md @@ -747,7 +747,7 @@ Version 6.1.0: * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack, sample download and sample submission. * Adds a new -O flag to set up the output directory for any sample downloads * Adds -ip and -IP flags to allow querying IPInfo, BGPView and other already existing sources (currently VirusTotal & AlientVault) for basic IP address lookups. - * Fixes a bug in the VirusTotal output. + * Fixes a bug in VirusTotal output. Version 6.0.1: diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index ef4f2ed..94a49c7 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -236,7 +236,7 @@ def main(): "BGPView": bgpview, "VirusTotal": virustotal, "AlienVault": alien, - "InQuest": inquest, + #"InQuest": inquest, # "PolySwarm": polyswarm, } ) diff --git a/malwoverview/modules/multipleip.py b/malwoverview/modules/multipleip.py index 8cd05ab..421cd41 100644 --- a/malwoverview/modules/multipleip.py +++ b/malwoverview/modules/multipleip.py @@ -23,6 +23,9 @@ def get_multiple_ip_details(self, ip_address): elif extractor == "AlienVault": data = extractor_obj._raw_ip_info(ip_address) self._get_info_alienvault(data.json()) + #elif extractor == "InQuest": + # data = extractor_obj._raw_ip_info(ip_address) + # self._get_info_inquest(data.json()) # elif extractor == "PolySwarm": # data = extractor_obj._raw_ip_info(ip_address) # self._get_info_polyswarm(data) @@ -79,17 +82,6 @@ def _get_info_virustotal(self, data): print() print("(For the full VirusTotal report use the -v and -V options)") - def _get_info_polyswarm(self, data): - print("\n") - print((mycolors.reset + "POLYSWARM IP REPORT".center(100)), end='') - print((mycolors.reset + "".center(28)), end='') - print("\n" + (100 * '-').center(50)) - - print(data) - - print() - print("(For the full PolySwarm report use the -p and -P options)") - def _get_info_alienvault(self, data): try: print("\n") @@ -101,9 +93,9 @@ def _get_info_alienvault(self, data): if (cv.bkg == 1): print(mycolors.foreground.lightcyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) - print(mycolors.foreground.lightcyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) - print(mycolors.foreground.lightcyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) print(mycolors.foreground.lightcyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) + print(mycolors.foreground.lightcyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) + print(mycolors.foreground.lightcyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) print(mycolors.foreground.lightcyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) print(mycolors.foreground.lightcyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) print(mycolors.foreground.lightcyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) @@ -111,9 +103,9 @@ def _get_info_alienvault(self, data): print(mycolors.foreground.lightred + "Pulses Found: ".ljust(COLSIZE) + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) else: print(mycolors.foreground.cyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) - print(mycolors.foreground.cyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) - print(mycolors.foreground.cyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) print(mycolors.foreground.cyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) + print(mycolors.foreground.cyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) + print(mycolors.foreground.cyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) print(mycolors.foreground.cyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) print(mycolors.foreground.cyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) print(mycolors.foreground.cyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) @@ -127,4 +119,35 @@ def _get_info_alienvault(self, data): print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) print() - print("(For the full AlienVault report use the -n and -N options)") \ No newline at end of file + print("(For the full AlienVault report use the -n and -N options)") + +""" + def _get_info_inquest(self, data): + try: + print("\n") + print((mycolors.reset + "INQUEST IP REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + print(data) + + except Exception as e: + if (cv.bkg == 1): + print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) + else: + print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + + print() + print("(For the full InQuest report use the -i and -I options)") + + def _get_info_polyswarm(self, data): + print("\n") + print((mycolors.reset + "POLYSWARM IP REPORT".center(100)), end='') + print((mycolors.reset + "".center(28)), end='') + print("\n" + (100 * '-').center(50)) + + print(data) + + print() + print("(For the full PolySwarm report use the -p and -P options)") +""" \ No newline at end of file From f8ad71d87decc09b60eb97cb9f1a714340f6aa27 Mon Sep 17 00:00:00 2001 From: Macmod Date: Tue, 10 Dec 2024 00:15:17 -0300 Subject: [PATCH 06/10] Removing VX upload, improving output organization and minor bugfixes. --- malwoverview/malwoverview.py | 22 ++++----- malwoverview/modules/bgpview.py | 32 +++++------- malwoverview/modules/ipinfo.py | 19 ++------ malwoverview/modules/multipleip.py | 70 ++++++++------------------- malwoverview/modules/virusexchange.py | 25 +++++----- malwoverview/utils/colors.py | 21 ++++++++ 6 files changed, 83 insertions(+), 106 deletions(-) diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index 94a49c7..8931828 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -104,11 +104,11 @@ def main(): parser.add_argument('-X', '--triagearg', dest='triagearg', type=str, default='', metavar="TRIAGE_ARG", help='Provides argument for options especified by -x option. Pay attention: the format of this argument depends on provided -x value.') parser.add_argument('-i', '--inquest', dest='inquest', type=int, default=0, metavar="INQUEST", help='Retrieves multiple information from INQUEST. The possible values are: 1: Downloads a sample; 2: Retrives information about a sample given a SHA256; 3: Retrieves information about a sample given a MD5 hash; 4: Gets the most recent list of threats. To this option, the -I argument must be "list" (lowercase and without double quotes) ; 5: Retrives threats related to a provided domain; 6. Retrieves a list of samples related to the given IP address; 7. Retrives a list of sample related to the given e-mail address; 8. Retrieves a list of samples related to the given filename; 9. Retrieves a list of samples related to a given URL; 10. Retrieves information about a specified IOC; 11. List a list of IOCs. Note: you must pass "list" (without double quotes) as argument to -I; 12. Check for a given keyword in the reputation database; 13. List artifacts in the reputation dabatabse. Note: you must pass "list" (without double quotes) as argument to -I.') parser.add_argument('-I', '--inquestarg', dest='inquestarg', type=str, metavar="INQUEST_ARG", help='Provides argument to INQUEST -i option.') - parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument; 3: Uploads a sample given a path provided in the -VX argument.') + parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument.') parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') - parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads') - parser.add_argument('-ip', '--ip', dest='ipoption', type=int, default=0, metavar="IP", help='Get IP information from various sources. The possible values are: 1: Get details for an IP address provided with -IP from all available sources (IPInfo/BGPView/VirusTotal/Alienvault); 2: Get details for an IP address provided with -IP from IPInfo; 3: Get details for an IP address provided with -IP from BGPView') - parser.add_argument('-IP', '--iparg', dest='iparg', type=str, metavar="IP_ARG", help='Provides argument for IP lookup operations specified by the -ip option') + parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads.') + parser.add_argument('-ip', '--ip', dest='ipoption', type=int, default=0, metavar="IP", help='Get IP information from various sources. The possible values are: 1: Get details for an IP address provided with -IP from IPInfo; 2: Get details for an IP address provided with -IP from BGPView; 3: Get details for an IP address provided with -IP from all available intel services (VirusTotal/Alienvault).') + parser.add_argument('-IP', '--iparg', dest='iparg', type=str, metavar="IP_ARG", help='Provides argument for IP lookup operations specified by the -ip option.') args = parser.parse_args() @@ -232,8 +232,8 @@ def main(): bgpview = BGPViewExtractor() multipleip = MultipleIPExtractor( { - "IPInfo": ipinfo, - "BGPView": bgpview, + #"IPInfo": ipinfo, + #"BGPView": bgpview, "VirusTotal": virustotal, "AlienVault": alien, #"InQuest": inquest, @@ -419,16 +419,16 @@ def ha_show_and_down(haargx, xx=0): 'flag': vxoptionx, 'actions': { 1: (vx.check_hash, [vxargx]), - 2: (vx.download_sample, [vxargx]), - 3: (vx.upload_sample, [vxargx]) + 2: (vx.download_sample, [vxargx]) +# 3: (vx.upload_sample, [vxargx]) } }, { 'flag': ipoptionx, 'actions': { - 1: (multipleip.get_multiple_ip_details, [ipargx]), - 2: (ipinfo.get_ip_details, [ipargx]), - 3: (bgpview.get_ip_details, [ipargx]), + 1: (ipinfo.get_ip_details, [ipargx]), + 2: (bgpview.get_ip_details, [ipargx]), + 3: (multipleip.get_multiple_ip_details, [ipargx]) } } ] diff --git a/malwoverview/modules/bgpview.py b/malwoverview/modules/bgpview.py index 8cf9e96..ac68131 100644 --- a/malwoverview/modules/bgpview.py +++ b/malwoverview/modules/bgpview.py @@ -19,39 +19,33 @@ def get_ip_details(self, ip_address): data = self._raw_ip_info(ip_address) try: - print("\n") + print() print((mycolors.reset + "BGPVIEW.IO REPORT".center(100)), end='') print((mycolors.reset + "".center(28)), end='') print("\n" + (100 * '-').center(50)) if not data: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + "\nNo information available\n" + mycolors.reset) - else: - print(mycolors.foreground.red + "\nNo information available\n" + mycolors.reset) + print(mycolors.foreground.error(cv.bkg) + "\nNo information available\n" + mycolors.reset) return + prefixes = data.get('prefixes', [{}]) + if len(prefixes) == 0: + prefixes = [{}] + fields = { 'IP Address': data.get('ip'), 'PTR Record': data.get('ptr_record'), - 'Prefix': data.get('prefixes', [{}])[0].get('prefix'), - 'ASN': data.get('prefixes', [{}])[0].get('asn', {}).get('asn'), - 'AS Name': data.get('prefixes', [{}])[0].get('asn', {}).get('name'), - 'AS Description': data.get('prefixes', [{}])[0].get('asn', {}).get('description'), - 'Country Code': data.get('prefixes', [{}])[0].get('asn', {}).get('country_code') + 'Prefix': prefixes[0].get('prefix'), + 'ASN': prefixes[0].get('asn', {}).get('asn'), + 'AS Name': prefixes[0].get('asn', {}).get('name'), + 'AS Description': prefixes[0].get('asn', {}).get('description'), + 'Country Code': prefixes[0].get('asn', {}).get('country_code') } COLSIZE = max(len(field) for field in fields.keys()) + 3 for field, value in fields.items(): - if value: - if (cv.bkg == 1): - print(mycolors.foreground.lightcyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) - else: - print(mycolors.foreground.cyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + print(mycolors.foreground.info(cv.bkg) + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) except Exception as e: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) - else: - print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + print(mycolors.foreground.error(cv.bkg) + f"\nError: {str(e)}" + mycolors.reset) diff --git a/malwoverview/modules/ipinfo.py b/malwoverview/modules/ipinfo.py index dbb61b6..8f5ffeb 100644 --- a/malwoverview/modules/ipinfo.py +++ b/malwoverview/modules/ipinfo.py @@ -26,16 +26,13 @@ def get_ip_details(self, ip_address): data = self._raw_ip_info(ip_address) try: - print("\n") + print() print((mycolors.reset + "IPINFO.IO REPORT".center(100)), end='') print((mycolors.reset + "".center(28)), end='') print("\n" + (100 * '-').center(50)) if 'error' in data: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"\n{data['error']['message']}\n" + mycolors.reset) - else: - print(mycolors.foreground.red + f"\n{data['error']['message']}\n" + mycolors.reset) + printc(f"\n{data['error']['message']}\n", mycolors.foreground.error(cv.bkg)) return fields = ['ip', 'hostname', 'org', 'country', 'region', 'city', 'loc', 'postal', 'timezone'] @@ -44,13 +41,7 @@ def get_ip_details(self, ip_address): for field in fields: if field in data: - if (cv.bkg == 1): - print(mycolors.foreground.lightcyan + f"{field.title()}: ".ljust(COLSIZE) + mycolors.reset + str(data[field])) - else: - print(mycolors.foreground.cyan + f"{field.title()}: ".ljust(COLSIZE) + mycolors.reset + str(data[field])) - + print(mycolors.foreground.info(cv.bkg) + f"{field.title()}: ".ljust(COLSIZE) + mycolors.reset + str(data[field])) + except Exception as e: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) - else: - print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + printc(f"\nError: {str(e)}\n", mycolors.foreground.error(cv.bkg)) diff --git a/malwoverview/modules/multipleip.py b/malwoverview/modules/multipleip.py index 421cd41..c43e40c 100644 --- a/malwoverview/modules/multipleip.py +++ b/malwoverview/modules/multipleip.py @@ -6,11 +6,6 @@ def __init__(self, extractors): self.extractors = extractors def get_multiple_ip_details(self, ip_address): - # print("\n") - # print((mycolors.reset + "MULTIPLE IP REPORTS".center(100)), end='') - # print((mycolors.reset + "".center(28)), end='') - # print("\n" + (100 * '-').center(50)) - for extractor in self.extractors: extractor_obj = self.extractors[extractor] if extractor == "IPInfo": @@ -34,9 +29,9 @@ def _get_info_virustotal(self, data): try: attributes = data.get('data', {}).get('attributes', {}) - print("\n") + print() print((mycolors.reset + "VIRUSTOTAL IP REPORT".center(100)), end='') - print((mycolors.reset + "".center(28)), end='') + #print((mycolors.reset + "".center(28)), end='') print("\n" + (100 * '-').center(50)) fields = { @@ -52,71 +47,46 @@ def _get_info_virustotal(self, data): COLSIZE = max(len(field) for field in fields.keys()) + 3 for field, value in fields.items(): - if (cv.bkg == 1): - print(mycolors.foreground.lightcyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) - else: - print(mycolors.foreground.cyan + f"{field}: ".ljust(COLSIZE) + mycolors.reset + str(value)) + print(mycolors.foreground.info(cv.bkg) + f"{field}:".ljust(COLSIZE) + "\t" + mycolors.reset + str(value)) print("\nAnalysis Stats:") stats = attributes.get('last_analysis_stats', {}) for stat, count in stats.items(): - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"{stat.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) - else: - print(mycolors.foreground.red + f"{stat.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + print(mycolors.foreground.error(cv.bkg) + f"{stat.title()}:".ljust(COLSIZE) + "\t" + mycolors.reset + str(count)) print("\nCommunity Votes:") votes = attributes.get('total_votes', {}) for vote, count in votes.items(): - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"{vote.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) - else: - print(mycolors.foreground.red + f"{vote.title()}: ".ljust(COLSIZE) + mycolors.reset + str(count)) + print(mycolors.foreground.error(cv.bkg) + f"{vote.title()}:".ljust(COLSIZE) + "\t" + mycolors.reset + str(count)) except Exception as e: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) - else: - print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + print(mycolors.foreground.error(cv.bkg) + f"\nError: {str(e)}\n" + mycolors.reset) print() print("(For the full VirusTotal report use the -v and -V options)") def _get_info_alienvault(self, data): try: - print("\n") + print() print((mycolors.reset + "ALIENVAULT IP REPORT".center(100)), end='') - print((mycolors.reset + "".center(28)), end='') + # print((mycolors.reset + "".center(28)), end='') print("\n" + (100 * '-').center(50)) - COLSIZE = 21 + COLSIZE = 13 - if (cv.bkg == 1): - print(mycolors.foreground.lightcyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) - print(mycolors.foreground.lightcyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) - print(mycolors.foreground.lightcyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) - print(mycolors.foreground.lightcyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) - print(mycolors.foreground.lightcyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) - print(mycolors.foreground.lightcyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) - print(mycolors.foreground.lightcyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) - print(mycolors.foreground.lightcyan + "Sections Available: ".ljust(COLSIZE) + mycolors.reset + ', '.join(data.get('sections', []))) - print(mycolors.foreground.lightred + "Pulses Found: ".ljust(COLSIZE) + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) - else: - print(mycolors.foreground.cyan + "ASN: ".ljust(COLSIZE) + mycolors.reset + str(data.get('asn'))) - print(mycolors.foreground.cyan + "Country: ".ljust(COLSIZE) + mycolors.reset + str(data.get('country_name'))) - print(mycolors.foreground.cyan + "Region: ".ljust(COLSIZE) + mycolors.reset + str(data.get('region'))) - print(mycolors.foreground.cyan + "City: ".ljust(COLSIZE) + mycolors.reset + str(data.get('city'))) - print(mycolors.foreground.cyan + "Continent: ".ljust(COLSIZE) + mycolors.reset + str(data.get('continent_code'))) - print(mycolors.foreground.cyan + "Latitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('latitude'))) - print(mycolors.foreground.cyan + "Longitude: ".ljust(COLSIZE) + mycolors.reset + str(data.get('longitude'))) - print(mycolors.foreground.cyan + "Sections Available: ".ljust(COLSIZE) + mycolors.reset + ', '.join(data.get('sections', []))) - print(mycolors.foreground.red + "Pulses Found: ".ljust(COLSIZE) + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) + infocolor = mycolors.foreground.info(cv.bkg) + print(infocolor + f"ASN:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('asn'))) + print(infocolor + f"Country:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('country_name'))) + print(infocolor + f"Region:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('region'))) + print(infocolor + f"City:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('city'))) + print(infocolor + f"Continent:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('continent_code'))) + print(infocolor + f"Latitude:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('latitude'))) + print(infocolor + f"Longitude:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('longitude'))) + print(infocolor + f"Sections:".ljust(COLSIZE) + "\t" + mycolors.reset + ', '.join(data.get('sections', []))) + print(mycolors.foreground.error(cv.bkg) + f"Pulses Found:".ljust(COLSIZE) + "\t" + mycolors.reset + str(data.get('pulse_info', {}).get('count'))) except Exception as e: - if (cv.bkg == 1): - print(mycolors.foreground.lightred + f"\nError: {str(e)}\n" + mycolors.reset) - else: - print(mycolors.foreground.red + f"\nError: {str(e)}\n" + mycolors.reset) + printc(f"\nError: {str(e)}\n", mycolors.foreground.error(cv.bkg)) print() print("(For the full AlienVault report use the -n and -N options)") diff --git a/malwoverview/modules/virusexchange.py b/malwoverview/modules/virusexchange.py index deeef51..fb04682 100644 --- a/malwoverview/modules/virusexchange.py +++ b/malwoverview/modules/virusexchange.py @@ -10,7 +10,7 @@ def __init__(self, api_key): def _requireVXAPI(self): if (self.VXAPI == ''): - printc("\nTo be able to get/submit information from/to Virus Exchange, you must create the .malwapi.conf file under your user home directory (on Linux is $HOME\\.malwapi.conf and on Windows is in C:\\Users\\[username]\\.malwapi.conf) and insert the Virus Exchange API according to the format shown on the Github website.", mycolors.foreground.red) + printc("\nTo be able to get/submit information from/to Virus Exchange, you must create the .malwapi.conf file under your user home directory (on Linux is $HOME\\.malwapi.conf and on Windows is in C:\\Users\\[username]\\.malwapi.conf) and insert the Virus Exchange API according to the format shown on the Github website.", mycolors.foreground.error(cv.bkg)) exit(1) def _get_hash_metadata(self, sha256): @@ -38,12 +38,11 @@ def check_hash(self, sha256): try: response = self._get_hash_metadata(sha256) except Exception as e: - printc(f"[-] Error checking hash metadata: {str(e)}", mycolors.foreground.red) + printc(f"[-] Error checking hash metadata: {str(e)}", mycolors.foreground.error(cv.bkg)) return metadata = response.json() - print() print() for val in metadata_to_show: key = val['key'] @@ -77,21 +76,23 @@ def download_sample(self, sha256): output_path = os.path.join(cv.output_dir, sha256) with open(output_path, 'wb') as f: f.write(sample_response.content) - printc(f"Sample downloaded to: {output_path}", mycolors.foreground.green) + printc(f"Sample downloaded to: {output_path}", mycolors.foreground.success(cv.bkg)) else: - printc(f"Failed to download sample: {sample_response.status_code}", mycolors.foreground.red) + printc(f"Failed to download sample: {sample_response.status_code}", mycolors.foreground.error(cv.bkg)) else: - printc("No download link available in metadata", mycolors.foreground.red) + printc("No download link available in metadata", mycolors.foreground.error(cv.bkg)) else: error_detail = metadata.get('errors', {}).get('detail', 'Unknown error') - printc(f"Failed to fetch sample metadata: {error_detail}", mycolors.foreground.red) + printc(f"Failed to fetch sample metadata: {error_detail}", mycolors.foreground.error(cv.bkg)) except Exception as e: - printc(f"Error downloading sample: {str(e)}", mycolors.foreground.red) + printc(f"Error downloading sample: {str(e)}", mycolors.foreground.error(cv.bkg)) + # This method is currently shown in the docs, but it's always returning 401 + # so it was removed from the official options. def upload_sample(self, file_path): self._requireVXAPI() - url = f'{self.base_url}/upload' + url = f'{self.base_url}/samples/new' try: with open(file_path, 'rb') as f: @@ -99,8 +100,8 @@ def upload_sample(self, file_path): response = requests.post(url, headers={'Authorization': f'Bearer {self.VXAPI}'}, files=files) if response.status_code == 200: - printc("Sample uploaded successfully", mycolors.foreground.green) + printc("Sample uploaded successfully", mycolors.foreground.success(cv.bkg)) else: - printc(f"Failed to upload sample: {response.status_code}", mycolors.foreground.red) + printc(f"Failed to upload sample: {response.status_code}", mycolors.foreground.error(cv.bkg)) except Exception as e: - printc(f"Error uploading sample: {str(e)}", mycolors.foreground.red) \ No newline at end of file + printc(f"Error uploading sample: {str(e)}", mycolors.foreground.error(cv.bkg)) \ No newline at end of file diff --git a/malwoverview/utils/colors.py b/malwoverview/utils/colors.py index a6f009c..9f499e2 100644 --- a/malwoverview/utils/colors.py +++ b/malwoverview/utils/colors.py @@ -19,6 +19,27 @@ class foreground: lightred = '\033[91m' yellow = '\033[93m' + @staticmethod + def error(bkg): + if bkg == 1: + return mycolors.foreground.lightred + else: + return mycolors.foreground.red + + @staticmethod + def info(bkg): + if bkg == 1: + return mycolors.foreground.lightcyan + else: + return mycolors.foreground.cyan + + @staticmethod + def success(bkg): + if bkg == 1: + return mycolors.foreground.yellow + else: + return mycolors.foreground.blue + class background: black = '\033[40m' blue = '\033[44m' From 73feff02a46653dc277d521a755b1f7036b3bb66 Mon Sep 17 00:00:00 2001 From: Macmod Date: Tue, 10 Dec 2024 00:17:02 -0300 Subject: [PATCH 07/10] README & help update. --- README.md | 4 ++-- malwoverview/malwoverview.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0f1ae74..6e94247 100755 --- a/README.md +++ b/README.md @@ -343,7 +343,7 @@ usage: python malwoverview.py -c -d -o <0|1 -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> --I -vx <1-3> -VX -O -ip <1-3> +-I -vx <1-2> -VX -O -ip <1-3> -IP Malwoverview is a first response tool for threat hunting written by Alexandre Borges. @@ -744,7 +744,7 @@ Malwoverview is a first response tool for threat hunting written by Alexandre Bo Version 6.1.0: This version: - * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack, sample download and sample submission. + * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack & sample download. * Adds a new -O flag to set up the output directory for any sample downloads * Adds -ip and -IP flags to allow querying IPInfo, BGPView and other already existing sources (currently VirusTotal & AlientVault) for basic IP address lookups. * Fixes a bug in VirusTotal output. diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index 8931828..b357627 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -77,7 +77,7 @@ def main(): USER_HOME_DIR = str(Path.home()) + '/' cv.windows = 0 - parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="usage: python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I -vx <1-3> -VX -O -ip <1-3> -IP ") + parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="usage: python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I -vx <1-2> -VX -O -ip <1-3> -IP ") parser.add_argument('-c', '--config', dest='config', type=str, metavar="CONFIG FILE", default=(USER_HOME_DIR + '.malwapi.conf'), help='Use a custom config file to specify API\'s.') parser.add_argument('-d', '--directory', dest='direct', type=str, default='', metavar="DIRECTORY", help='Specifies the directory containing malware samples to be checked against VIRUS TOTAL. Use the option -D to decide whether you are being using a public VT API or a Premium VT API.') parser.add_argument('-o', '--background', dest='backg', type=int, default=1, metavar="BACKGROUND", help='Adapts the output colors to a light background color terminal. The default is dark background color terminal.') From 4d5748e524006eb9c26ac3bf83420e5f2f8fe10d Mon Sep 17 00:00:00 2001 From: Alexandre Borges Date: Thu, 12 Dec 2024 02:35:49 -0300 Subject: [PATCH 08/10] Malwoverview --- README.md | 125 +++++++++++++++++++++++------------ malwoverview/malwoverview.py | 53 ++------------- 2 files changed, 86 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 6e94247..8b75228 100755 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ ![Alt text](pictures/picture_47.jpg?raw=true "Title") ![Alt text](pictures/picture_48.jpg?raw=true "Title") - Copyright (C) 2018-2024 Alexandre Borges (https://exploitreversing.com) + Copyright (C) 2018-2025 Alexandre Borges (https://exploitreversing.com) 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 @@ -90,23 +90,24 @@ This tool aims to : 1. Determine similar executable malware samples (PE/PE+) according to the import table (imphash) and group them by different colors (pay attention to the second column from output). Thus, colors matter! 2. Show hash information on Virus Total, Hybrid Analysis, Malshare, Polyswarm, URLhaus, Alien Vault, - Malpedia and VirusExchange engines. + Malpedia and ThreatCrowd engines. 3. Determining whether the malware samples contain overlay and, if you want, extract it. 4. Check suspect files on Virus Total, Hybrid Analysis and Polyswarm. 5. Check URLs on Virus Total, Malshare, Polyswarm, URLhaus engines and Alien Vault. -6. Download malware samples from Hybrid Analysis, Malshare, URLHaus, Polyswarm, Malpedia and VirusExchange engines. -7. Submit malware samples to VirusTotal, Hybrid Analysis, Polyswarm and VirusExchange. +6. Download malware samples from Hybrid Analysis, Malshare, URLHaus, Polyswarm and Malpedia engines. +7. Submit malware samples to VirusTotal, Hybrid Analysis and Polyswarm. 8. List last suspected URLs from URLHaus. 9. List last payloads from URLHaus. 10. Search for specific payloads on the Malshare. 11. Search for similar payloads (PE32/PE32+) on Polyswarm engine. 12. Classify all files in a directory searching information on Virus Total and Hybrid Analysis. -13. Make reports about a suspect domain using different engines such as VirusTotal & Malpedia. +13. Make reports about a suspect domain using different engines such as VirusTotal, Malpedia and + ThreatCrowd. 14. Check APK packages directly from Android devices against Hybrid Analysis and Virus Total. 15. Submit APK packages directly from Android devices to Hybrid Analysis and Virus Total. 16. Show URLs related to an user provided tag from URLHaus. 17. Show payloads related to a tag (signature) from URLHaus. -18. Show information about an IP address from Virus Total, Alien Vault, Malpedia. +18. Show information about an IP address from Virus Total, Alien Vault, Malpedia and ThreatCrowd. 19. Show IP address, domain and URL information from Polyswarm. 21. Perform meta-search on Polyswarm Network using several criteria: imphash, IPv4, domain, URL and malware family. @@ -119,7 +120,11 @@ This tool aims to : 28. Submit large files (>= 32 MB) to Virus Total. 29. Malwoverview uses Virus Total API v.3, so there isn't longer any option using v.2. 30. Retrieve different information from InQuest Labs and download samples from there. - +31. Retrieve information and download malware samples from Virus Exchange (vxunderground). +32. Retrieve information about a given IP address from IPInfo service. +33. Retrieve information about a given IP address from BGPView service. +34. Retrieve combined information about a given IP address from multiple services. +35. Offer extra option to save any downloaded file to a central location. ## CONTRIBUTORS @@ -196,7 +201,7 @@ has the following format: MALSHAREAPI = [HAUSSUBMIT] - HAUSSUBMITAPI = + HAUSSUBMITAPI = [POLYSWARM] POLYAPI = @@ -205,19 +210,19 @@ has the following format: ALIENAPI = [MALPEDIA] - MALPEDIAAPI = + MALPEDIAAPI = [TRIAGE] - TRIAGEAPI = + TRIAGEAPI = [INQUEST] - INQUESTAPI = + INQUESTAPI = [VIRUSEXCHANGE] - VXAPI = + VXAPI = [IPINFO] - IPINFOAPI = + IPINFOAPI = The APIs can be requested on the respective service websites: @@ -232,11 +237,12 @@ The APIs can be requested on the respective service websites: handle is @malpedia. 08. Malware Bazaar: It isn't necessary an API. 09. ThreatFox: It isn't necessary an API. -10. InQuest: https://labs.inquest.net/ -11. Triage: https://tria.ge/signup -12. VirusExchange: https://virus.exchange/users/register -13. IPInfo: https://ipinfo.io/signup - +10. InQuest: https://labs.inquest.net/. +11. Triage: https://tria.ge/signup. +12. Virus Exchange: https://virus.exchange/ +13. IPInfo: https://ipinfo.io/ +14. BGPView: ihttps://bgpview.docs.apiary.io/ + ---------------------------------------------------- A special note about API requests to the MALPEDIA: ---------------------------------------------------- @@ -340,11 +346,10 @@ should be executed: ## HELP usage: python malwoverview.py -c -d -o <0|1> -v <1-13> --V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> --J -p <1-8> -P -y <1-5> -Y -n <1-5> --N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> --I -vx <1-2> -VX -O -ip <1-3> --IP +-V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> +-J -p <1-8> -P -y <1-5> -Y -n <1-5> +-N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> +-I -vx <1-2> -VX -ip <1-3> -IP -O Malwoverview is a first response tool for threat hunting written by Alexandre Borges. @@ -581,7 +586,7 @@ Malwoverview is a first response tool for threat hunting written by Alexandre Bo -x TRIAGE, --triage TRIAGE + Provides information from TRIAGE according to the specified value: - + <1> this option gets sample's general information by providing an + + 1: this option gets sample's general information by providing an argument with -X option in the following possible formats: - sha256: - sha1: @@ -593,12 +598,12 @@ Malwoverview is a first response tool for threat hunting written by Alexandre Bo - wallet: - ip:; - + <2> Get a sumary report for a given Triage ID (got from option -x 1); - + <3> Submit a sample for analysis; - + <4> Submit a sample through a URL for analysis; - + <5> Download sample specified by the Triage ID; - + <6> Download pcapng file from sample associated to given Triage ID; - + <7> Get a dynamic report for the given Triage ID (got from option -x 1); + + 2: Get a sumary report for a given Triage ID (got from option -x 1); + + 3: Submit a sample for analysis; + + 4: Submit a sample through a URL for analysis; + + 5: Download sample specified by the Triage ID; + + 6: Download pcapng file from sample associated to given Triage ID; + + 7: Get a dynamic report for the given Triage ID (got from option -x 1); -X TRIAGE_ARG, --triagearg TRIAGE_ARG @@ -614,20 +619,45 @@ Malwoverview is a first response tool for threat hunting written by Alexandre Bo + 4: Gets the most recent list of threats. To this option, the -I argument must be "list" (lowercase and without double quotes); + 5: Retrives threats related to a provided domain; - + 6. Retrieves a list of samples related to the given IP address; - + 7. Retrives a list of sample related to the given e-mail address; - + 8. Retrieves a list of samples related to the given filename; - + 9. Retrieves a list of samples related to a given URL; - + 10. Retrieves information about a specified IOC; - + 11. List a list of IOCs. Note: you must pass "list" (without + + 6: Retrieves a list of samples related to the given IP address; + + 7: Retrives a list of sample related to the given e-mail address; + + 8: Retrieves a list of samples related to the given filename; + + 9: Retrieves a list of samples related to a given URL; + + 10: Retrieves information about a specified IOC; + + 11: List a list of IOCs. Note: you must pass "list" (without double quotes) as argument to -I; - + 12. Check for a given keyword in the reputation database; - + 13. List artifacts in the reputation dabatabse. Note: you must + + 12: Check for a given keyword in the reputation database; + + 13: List artifacts in the reputation dabatabse. Note: you must pass "list" (without double quotes) as argument to -I. -I INQUEST_ARG, --inquestarg INQUEST_ARG - + Provides argument to INQUEST -i option. + + Provides argument to INQUEST -i option. + + -vx VXOPTION, --vx VXOPTION + + + 1: Gets basic metadata for a given SHA256 hash; + + 2: Downloads sample given a SHA256 provided in the -VX argument. + + -VX VXARG, --VX VXARG + + + Provides argument to the -vx option from VirusExchange. + + -O OUTPUTDIR, --output-dir OUTPUTDIR + + + Set output directory for all sample downloads. + + -ip IP, --ip IP + + + Get IP information from various sources. The possible values are: + + 1: Get details for an IP address provided with -IP from IPInfo; + + 2: Get details for an IP address provided with -IP from BGPView; + + 3: Get details for an IP address provided with -IP from all + available intel services (VirusTotal/Alienvault). + + -IP IPARG, --iparg IPARG + + + Provides argument for IP lookup operations specified by the -ip option. ## EXAMPLES @@ -737,17 +767,26 @@ Malwoverview is a first response tool for threat hunting written by Alexandre Bo malwoverview -i 11 -I list malwoverview -i 12 -I rebrand.ly malwoverview -i 13 -I list | more + malwoverview -vx 1 -VX c3247ada71931ee267e975cb04160dc8ac611f3b4409f41b595177e124be7c2e + malwoverview -vx 2 -VX c3247ada71931ee267e975cb04160dc8ac611f3b4409f41b595177e124be7c2e + malwoverview -ip 1 -IP 8.8.8.8 + malwoverview -ip 2 -IP 8.8.8.8 + malwoverview -ip 3 -IP 8.8.8.8 + malwoverview -vx 2 -VX -O + malwoverview -b 5 -B -O ## HISTORY + Version 6.1.0: + This version: - * Introduces -vx and -VX flags to allow interaction with VirusExchange's APIs for hash chack & sample download. - * Adds a new -O flag to set up the output directory for any sample downloads - * Adds -ip and -IP flags to allow querying IPInfo, BGPView and other already existing sources (currently VirusTotal & AlientVault) for basic IP address lookups. - * Fixes a bug in VirusTotal output. + * Introduces -vx option for Virus Exchange. + * Introduces -ip option for IPView and BGPView. + * Introduces -O option to save samples in a central directory. + * Fixes multiple other issues. Version 6.0.1: diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index b357627..29f29ec 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -# Copyright (C) 2018-2024 Alexandre Borges +# Copyright (C) 2018-2025 Alexandre Borges # # 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 @@ -42,10 +42,6 @@ from malwoverview.modules.triage import TriageExtractor from malwoverview.modules.urlhaus import URLHausExtractor from malwoverview.modules.virustotal import VirusTotalExtractor -from malwoverview.modules.virusexchange import VirusExchangeExtractor -from malwoverview.modules.ipinfo import IPInfoExtractor -from malwoverview.modules.bgpview import BGPViewExtractor -from malwoverview.modules.multipleip import MultipleIPExtractor from malwoverview.utils.colors import printr from malwoverview.utils.hash import calchash import malwoverview.modules.configvars as cv @@ -54,7 +50,7 @@ # On Windows systems, it is necessary to install python-magic-bin: pip install python-magic-bin __author__ = "Alexandre Borges" -__copyright__ = "Copyright 2018-2024, Alexandre Borges" +__copyright__ = "Copyright 2018-2025, Alexandre Borges" __license__ = "GNU General Public License v3.0" __version__ = "6.1.0" __email__ = "reverseexploit at proton.me" @@ -77,7 +73,7 @@ def main(): USER_HOME_DIR = str(Path.home()) + '/' cv.windows = 0 - parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="usage: python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I -vx <1-2> -VX -O -ip <1-3> -IP ") + parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is 6.1.0", usage="python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I ") parser.add_argument('-c', '--config', dest='config', type=str, metavar="CONFIG FILE", default=(USER_HOME_DIR + '.malwapi.conf'), help='Use a custom config file to specify API\'s.') parser.add_argument('-d', '--directory', dest='direct', type=str, default='', metavar="DIRECTORY", help='Specifies the directory containing malware samples to be checked against VIRUS TOTAL. Use the option -D to decide whether you are being using a public VT API or a Premium VT API.') parser.add_argument('-o', '--background', dest='backg', type=int, default=1, metavar="BACKGROUND", help='Adapts the output colors to a light background color terminal. The default is dark background color terminal.') @@ -104,11 +100,6 @@ def main(): parser.add_argument('-X', '--triagearg', dest='triagearg', type=str, default='', metavar="TRIAGE_ARG", help='Provides argument for options especified by -x option. Pay attention: the format of this argument depends on provided -x value.') parser.add_argument('-i', '--inquest', dest='inquest', type=int, default=0, metavar="INQUEST", help='Retrieves multiple information from INQUEST. The possible values are: 1: Downloads a sample; 2: Retrives information about a sample given a SHA256; 3: Retrieves information about a sample given a MD5 hash; 4: Gets the most recent list of threats. To this option, the -I argument must be "list" (lowercase and without double quotes) ; 5: Retrives threats related to a provided domain; 6. Retrieves a list of samples related to the given IP address; 7. Retrives a list of sample related to the given e-mail address; 8. Retrieves a list of samples related to the given filename; 9. Retrieves a list of samples related to a given URL; 10. Retrieves information about a specified IOC; 11. List a list of IOCs. Note: you must pass "list" (without double quotes) as argument to -I; 12. Check for a given keyword in the reputation database; 13. List artifacts in the reputation dabatabse. Note: you must pass "list" (without double quotes) as argument to -I.') parser.add_argument('-I', '--inquestarg', dest='inquestarg', type=str, metavar="INQUEST_ARG", help='Provides argument to INQUEST -i option.') - parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument.') - parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') - parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads.') - parser.add_argument('-ip', '--ip', dest='ipoption', type=int, default=0, metavar="IP", help='Get IP information from various sources. The possible values are: 1: Get details for an IP address provided with -IP from IPInfo; 2: Get details for an IP address provided with -IP from BGPView; 3: Get details for an IP address provided with -IP from all available intel services (VirusTotal/Alienvault).') - parser.add_argument('-IP', '--iparg', dest='iparg', type=str, metavar="IP_ARG", help='Provides argument for IP lookup operations specified by the -ip option.') args = parser.parse_args() @@ -124,8 +115,6 @@ def main(): MALPEDIAAPI = config_dict.get('MALPEDIA', 'MALPEDIAAPI') TRIAGEAPI = config_dict.get('TRIAGE', 'TRIAGEAPI') INQUESTAPI = config_dict.get('INQUEST', 'INQUESTAPI') - VXAPI = config_dict.get('VIRUSEXCHANGE', 'VXAPI') - IPINFOAPI = config_dict.get('IPINFO', 'IPINFOAPI') optval = range(2) optval1 = range(3) @@ -139,7 +128,6 @@ def main(): optval9 = range(14) optval10 = range(16) repo = args.direct - cv.output_dir = args.output_dir cv.bkg = args.backg virustotaloptionx = args.virustotaloption haoptionx = args.haoption @@ -165,10 +153,6 @@ def main(): virustotalargx = args.virustotalarg inquestx = args.inquest inquestargx = args.inquestarg - vxoptionx = args.vxoption - vxargx = args.vxarg - ipoptionx = args.ipoption - ipargx = args.iparg config = args.config ffpname = '' @@ -203,7 +187,7 @@ def main(): haargx, mallist, args.malsharehash, args.hausoption, polyoptionx, polyargx, androidoptionx, androidargx, alienx, alienargsx, malpediaargx, malpediax, bazaarx, bazaarargx, triagex, triageargx, - inquestx, inquestargx, vxoptionx, vxargx, ipoptionx, ipargx + inquestx, inquestargx ] # Show the help message if: @@ -227,19 +211,6 @@ def main(): malshare = MalshareExtractor(MALSHAREAPI) haus = URLHausExtractor(HAUSSUBMITAPI) android = AndroidExtractor(hybrid, virustotal) - vx = VirusExchangeExtractor(VXAPI) - ipinfo = IPInfoExtractor(IPINFOAPI) - bgpview = BGPViewExtractor() - multipleip = MultipleIPExtractor( - { - #"IPInfo": ipinfo, - #"BGPView": bgpview, - "VirusTotal": virustotal, - "AlienVault": alien, - #"InQuest": inquest, - # "PolySwarm": polyswarm, - } - ) # Special parameters for hybrid analysis module query = haargx @@ -414,22 +385,6 @@ def ha_show_and_down(haargx, xx=0): 4: (android.sendandroidha, [androidargx]), 5: (android.sendandroidvt, [androidargx]) } - }, - { - 'flag': vxoptionx, - 'actions': { - 1: (vx.check_hash, [vxargx]), - 2: (vx.download_sample, [vxargx]) -# 3: (vx.upload_sample, [vxargx]) - } - }, - { - 'flag': ipoptionx, - 'actions': { - 1: (ipinfo.get_ip_details, [ipargx]), - 2: (bgpview.get_ip_details, [ipargx]), - 3: (multipleip.get_multiple_ip_details, [ipargx]) - } } ] From a350e6f1b84bac009527f44e4779712ff3378225 Mon Sep 17 00:00:00 2001 From: Macmod Date: Thu, 12 Dec 2024 10:34:05 -0300 Subject: [PATCH 09/10] Reintroducing changes to malwoverview.py and implementing -O in overlay extraction and Malpedia YARA rules download. --- malwoverview/malwoverview.py | 49 ++++++++++++++++++++++++++++++-- malwoverview/modules/malpedia.py | 5 ++-- malwoverview/utils/peinfo.py | 9 ++++-- 3 files changed, 56 insertions(+), 7 deletions(-) diff --git a/malwoverview/malwoverview.py b/malwoverview/malwoverview.py index 29f29ec..0a24ea0 100755 --- a/malwoverview/malwoverview.py +++ b/malwoverview/malwoverview.py @@ -42,6 +42,10 @@ from malwoverview.modules.triage import TriageExtractor from malwoverview.modules.urlhaus import URLHausExtractor from malwoverview.modules.virustotal import VirusTotalExtractor +from malwoverview.modules.virusexchange import VirusExchangeExtractor +from malwoverview.modules.ipinfo import IPInfoExtractor +from malwoverview.modules.bgpview import BGPViewExtractor +from malwoverview.modules.multipleip import MultipleIPExtractor from malwoverview.utils.colors import printr from malwoverview.utils.hash import calchash import malwoverview.modules.configvars as cv @@ -73,7 +77,7 @@ def main(): USER_HOME_DIR = str(Path.home()) + '/' cv.windows = 0 - parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is 6.1.0", usage="python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I ") + parser = argparse.ArgumentParser(prog=None, description="Malwoverview is a first response tool for threat hunting written by Alexandre Borges. This version is " + __version__, usage="usage: python malwoverview.py -c -d -o <0|1> -v <1-13> -V -a <1-15> -w <0|1> -A -l <1-7> -L -j <1-7> -J -p <1-8> -P -y <1-5> -Y -n <1-5> -N -m <1-8> -M -b <1-10> -B -x <1-7> -X -i <1-13> -I -vx <1-2> -VX -O -ip <1-3> -IP ") parser.add_argument('-c', '--config', dest='config', type=str, metavar="CONFIG FILE", default=(USER_HOME_DIR + '.malwapi.conf'), help='Use a custom config file to specify API\'s.') parser.add_argument('-d', '--directory', dest='direct', type=str, default='', metavar="DIRECTORY", help='Specifies the directory containing malware samples to be checked against VIRUS TOTAL. Use the option -D to decide whether you are being using a public VT API or a Premium VT API.') parser.add_argument('-o', '--background', dest='backg', type=int, default=1, metavar="BACKGROUND", help='Adapts the output colors to a light background color terminal. The default is dark background color terminal.') @@ -100,6 +104,11 @@ def main(): parser.add_argument('-X', '--triagearg', dest='triagearg', type=str, default='', metavar="TRIAGE_ARG", help='Provides argument for options especified by -x option. Pay attention: the format of this argument depends on provided -x value.') parser.add_argument('-i', '--inquest', dest='inquest', type=int, default=0, metavar="INQUEST", help='Retrieves multiple information from INQUEST. The possible values are: 1: Downloads a sample; 2: Retrives information about a sample given a SHA256; 3: Retrieves information about a sample given a MD5 hash; 4: Gets the most recent list of threats. To this option, the -I argument must be "list" (lowercase and without double quotes) ; 5: Retrives threats related to a provided domain; 6. Retrieves a list of samples related to the given IP address; 7. Retrives a list of sample related to the given e-mail address; 8. Retrieves a list of samples related to the given filename; 9. Retrieves a list of samples related to a given URL; 10. Retrieves information about a specified IOC; 11. List a list of IOCs. Note: you must pass "list" (without double quotes) as argument to -I; 12. Check for a given keyword in the reputation database; 13. List artifacts in the reputation dabatabse. Note: you must pass "list" (without double quotes) as argument to -I.') parser.add_argument('-I', '--inquestarg', dest='inquestarg', type=str, metavar="INQUEST_ARG", help='Provides argument to INQUEST -i option.') + parser.add_argument('-vx', '--vx', dest='vxoption', type=int, default=0, help='VirusExchange operations. The possible values are: 1: Gets basic metadata for a given SHA256 hash; 2: Downloads sample given a SHA256 provided in the -VX argument.') + parser.add_argument('-VX', '--VX', dest='vxarg', type=str, help='Provides argument to the -vx option from VirusExchange.') + parser.add_argument('-O', '--output-dir', dest='output_dir', type=str, default='.', help='Set output directory for all sample downloads.') + parser.add_argument('-ip', '--ip', dest='ipoption', type=int, default=0, metavar="IP", help='Get IP information from various sources. The possible values are: 1: Get details for an IP address provided with -IP from IPInfo; 2: Get details for an IP address provided with -IP from BGPView; 3: Get details for an IP address provided with -IP from all available intel services (VirusTotal/Alienvault).') + parser.add_argument('-IP', '--iparg', dest='iparg', type=str, metavar="IP_ARG", help='Provides argument for IP lookup operations specified by the -ip option.') args = parser.parse_args() @@ -115,6 +124,8 @@ def main(): MALPEDIAAPI = config_dict.get('MALPEDIA', 'MALPEDIAAPI') TRIAGEAPI = config_dict.get('TRIAGE', 'TRIAGEAPI') INQUESTAPI = config_dict.get('INQUEST', 'INQUESTAPI') + VXAPI = config_dict.get('VIRUSEXCHANGE', 'VXAPI') + IPINFOAPI = config_dict.get('IPINFO', 'IPINFOAPI') optval = range(2) optval1 = range(3) @@ -128,6 +139,7 @@ def main(): optval9 = range(14) optval10 = range(16) repo = args.direct + cv.output_dir = args.output_dir cv.bkg = args.backg virustotaloptionx = args.virustotaloption haoptionx = args.haoption @@ -153,6 +165,10 @@ def main(): virustotalargx = args.virustotalarg inquestx = args.inquest inquestargx = args.inquestarg + vxoptionx = args.vxoption + vxargx = args.vxarg + ipoptionx = args.ipoption + ipargx = args.iparg config = args.config ffpname = '' @@ -187,7 +203,7 @@ def main(): haargx, mallist, args.malsharehash, args.hausoption, polyoptionx, polyargx, androidoptionx, androidargx, alienx, alienargsx, malpediaargx, malpediax, bazaarx, bazaarargx, triagex, triageargx, - inquestx, inquestargx + inquestx, inquestargx, vxoptionx, vxargx, ipoptionx, ipargx ] # Show the help message if: @@ -211,6 +227,19 @@ def main(): malshare = MalshareExtractor(MALSHAREAPI) haus = URLHausExtractor(HAUSSUBMITAPI) android = AndroidExtractor(hybrid, virustotal) + vx = VirusExchangeExtractor(VXAPI) + ipinfo = IPInfoExtractor(IPINFOAPI) + bgpview = BGPViewExtractor() + multipleip = MultipleIPExtractor( + { + #"IPInfo": ipinfo, + #"BGPView": bgpview, + "VirusTotal": virustotal, + "AlienVault": alien, + #"InQuest": inquest, + # "PolySwarm": polyswarm, + } + ) # Special parameters for hybrid analysis module query = haargx @@ -385,6 +414,22 @@ def ha_show_and_down(haargx, xx=0): 4: (android.sendandroidha, [androidargx]), 5: (android.sendandroidvt, [androidargx]) } + }, + { + 'flag': vxoptionx, + 'actions': { + 1: (vx.check_hash, [vxargx]), + 2: (vx.download_sample, [vxargx]) +# 3: (vx.upload_sample, [vxargx]) + } + }, + { + 'flag': ipoptionx, + 'actions': { + 1: (ipinfo.get_ip_details, [ipargx]), + 2: (bgpview.get_ip_details, [ipargx]), + 3: (multipleip.get_multiple_ip_details, [ipargx]) + } } ] diff --git a/malwoverview/modules/malpedia.py b/malwoverview/modules/malpedia.py index 9788f90..4e1c778 100644 --- a/malwoverview/modules/malpedia.py +++ b/malwoverview/modules/malpedia.py @@ -519,11 +519,12 @@ def malpedia_get_yara(self, arg1): exit(1) if ('200' in str(haresponse)): + outputpath = os.path.join(cv.output_dir, myargs + ".zip") if (cv.bkg == 1): - open(myargs + ".zip", 'wb').write(haresponse.content) + open(outputpath, 'wb').write(haresponse.content) print(mycolors.foreground.lightcyan + "\nA zip file named %s.zip containing Yara rules has been SUCCESSFULLY downloaded from Malpedia!\n" % myargs, mycolors.reset) else: - open(myargs + ".zip", 'wb').write(haresponse.content) + open(outputpath, 'wb').write(haresponse.content) print(mycolors.foreground.green + "\nA zip file named %s.zip containing Yara rules has been SUCCESSFULLY downloaded from Malpedia!\n" % myargs, mycolors.reset) exit(0) except ValueError as e: diff --git a/malwoverview/utils/peinfo.py b/malwoverview/utils/peinfo.py index 2aa4ae8..145b2cc 100644 --- a/malwoverview/utils/peinfo.py +++ b/malwoverview/utils/peinfo.py @@ -2,6 +2,7 @@ from malwoverview.utils.colors import mycolors, printr import malwoverview.modules.configvars as cv import magic +import os def ftype(filename): @@ -26,12 +27,14 @@ def overextract(fname): offset = pe.get_overlay_data_start_offset() if offset is None: exit(0) - with open(fname + ".overlay", "wb") as t: + + outputpath = os.path.join(cv.output_dir, fname + '.overlay') + with open(outputpath, "wb") as t: t.write(r[offset:]) if (cv.bkg == 1): - print((mycolors.foreground.yellow + "\n\nOverlay extracted: " + mycolors.reset + "%s.overlay" % fname)) + print((mycolors.foreground.yellow + "\n\nOverlay extracted: " + mycolors.reset + outputpath)) else: - print((mycolors.foreground.green + "\n\nOverlay extracted: " + mycolors.reset + "%s.overlay" % fname)) + print((mycolors.foreground.green + "\n\nOverlay extracted: " + mycolors.reset + outputpath)) printr() From 2810f15437a9536997ca97a50469553c8471f8d7 Mon Sep 17 00:00:00 2001 From: Alexandre Borges Date: Thu, 12 Dec 2024 11:00:27 -0300 Subject: [PATCH 10/10] Malwoverview --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b75228..e101015 100755 --- a/README.md +++ b/README.md @@ -129,9 +129,9 @@ This tool aims to : ## CONTRIBUTORS Alexandre Borges (project owner) + Artur Marzano (https://github.com/Macmod) Corey Forman (https://github.com/digitalsleuth) Christian Clauss (https://github.com/cclauss) - Artur Marzano (https://github.com/Macmod) ## HOW TO CONTRIBUTE TO THIS PROJECT