diff --git a/src/mvt/android/cli.py b/src/mvt/android/cli.py index 8e9086f..ae225d9 100644 --- a/src/mvt/android/cli.py +++ b/src/mvt/android/cli.py @@ -31,6 +31,8 @@ from mvt.common.help import ( HELP_MSG_HASHES, HELP_MSG_CHECK_IOCS, HELP_MSG_STIX2, + HELP_MSG_DISABLE_UPDATE_CHECK, + HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK, ) from mvt.common.logo import logo from mvt.common.updates import IndicatorsUpdates @@ -53,12 +55,37 @@ log = logging.getLogger("mvt") CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) +def _get_disable_flags(ctx): + """Helper function to safely get disable flags from context.""" + if ctx.obj is None: + return False, False + return ( + ctx.obj.get("disable_version_check", False), + ctx.obj.get("disable_indicator_check", False), + ) + + # ============================================================================== # Main # ============================================================================== @click.group(invoke_without_command=False) -def cli(): - logo() +@click.option( + "--disable-update-check", is_flag=True, help=HELP_MSG_DISABLE_UPDATE_CHECK +) +@click.option( + "--disable-indicator-update-check", + is_flag=True, + help=HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK, +) +@click.pass_context +def cli(ctx, disable_update_check, disable_indicator_update_check): + ctx.ensure_object(dict) + ctx.obj["disable_version_check"] = disable_update_check + ctx.obj["disable_indicator_check"] = disable_indicator_update_check + logo( + disable_version_check=disable_update_check, + disable_indicator_check=disable_indicator_update_check, + ) # ============================================================================== @@ -166,6 +193,8 @@ def check_adb( module_name=module, serial=serial, module_options=module_options, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -212,6 +241,8 @@ def check_bugreport(ctx, iocs, output, list_modules, module, verbose, bugreport_ ioc_files=iocs, module_name=module, hashes=True, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -274,6 +305,8 @@ def check_backup( "interactive": not non_interactive, "backup_password": cli_load_android_backup_password(log, backup_password), }, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -338,6 +371,8 @@ def check_androidqf( "interactive": not non_interactive, "backup_password": cli_load_android_backup_password(log, backup_password), }, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -372,7 +407,13 @@ def check_androidqf( @click.argument("FOLDER", type=click.Path(exists=True)) @click.pass_context def check_iocs(ctx, iocs, list_modules, module, folder): - cmd = CmdCheckIOCS(target_path=folder, ioc_files=iocs, module_name=module) + cmd = CmdCheckIOCS( + target_path=folder, + ioc_files=iocs, + module_name=module, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], + ) cmd.modules = BACKUP_MODULES + ADB_MODULES + BUGREPORT_MODULES if list_modules: diff --git a/src/mvt/android/cmd_check_adb.py b/src/mvt/android/cmd_check_adb.py index e274040..c3c0522 100644 --- a/src/mvt/android/cmd_check_adb.py +++ b/src/mvt/android/cmd_check_adb.py @@ -22,6 +22,8 @@ class CmdAndroidCheckADB(Command): module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -31,6 +33,8 @@ class CmdAndroidCheckADB(Command): serial=serial, module_options=module_options, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-adb" diff --git a/src/mvt/android/cmd_check_androidqf.py b/src/mvt/android/cmd_check_androidqf.py index e079807..d821867 100644 --- a/src/mvt/android/cmd_check_androidqf.py +++ b/src/mvt/android/cmd_check_androidqf.py @@ -26,6 +26,8 @@ class CmdAndroidCheckAndroidQF(Command): serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -36,6 +38,8 @@ class CmdAndroidCheckAndroidQF(Command): module_options=module_options, hashes=hashes, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-androidqf" diff --git a/src/mvt/android/cmd_check_backup.py b/src/mvt/android/cmd_check_backup.py index 2a68900..483298e 100644 --- a/src/mvt/android/cmd_check_backup.py +++ b/src/mvt/android/cmd_check_backup.py @@ -36,6 +36,8 @@ class CmdAndroidCheckBackup(Command): serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -46,6 +48,8 @@ class CmdAndroidCheckBackup(Command): module_options=module_options, hashes=hashes, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-backup" diff --git a/src/mvt/android/cmd_check_bugreport.py b/src/mvt/android/cmd_check_bugreport.py index 08a266f..b46023e 100644 --- a/src/mvt/android/cmd_check_bugreport.py +++ b/src/mvt/android/cmd_check_bugreport.py @@ -27,6 +27,8 @@ class CmdAndroidCheckBugreport(Command): serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -37,6 +39,8 @@ class CmdAndroidCheckBugreport(Command): module_options=module_options, hashes=hashes, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-bugreport" diff --git a/src/mvt/common/cmd_check_iocs.py b/src/mvt/common/cmd_check_iocs.py index 2db6edb..0663296 100644 --- a/src/mvt/common/cmd_check_iocs.py +++ b/src/mvt/common/cmd_check_iocs.py @@ -22,6 +22,8 @@ class CmdCheckIOCS(Command): module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -31,6 +33,8 @@ class CmdCheckIOCS(Command): serial=serial, module_options=module_options, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-iocs" diff --git a/src/mvt/common/command.py b/src/mvt/common/command.py index 7f65843..036de7c 100644 --- a/src/mvt/common/command.py +++ b/src/mvt/common/command.py @@ -32,6 +32,8 @@ class Command: module_options: Optional[dict] = None, hashes: bool = False, log: logging.Logger = logging.getLogger(__name__), + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: self.name = "" self.modules = [] @@ -42,6 +44,8 @@ class Command: self.module_name = module_name self.serial = serial self.log = log + self.disable_version_check = disable_version_check + self.disable_indicator_check = disable_indicator_check # This dictionary can contain options that will be passed down from # the Command to all modules. This can for example be used to pass diff --git a/src/mvt/common/help.py b/src/mvt/common/help.py index 0cca7ab..9695e57 100644 --- a/src/mvt/common/help.py +++ b/src/mvt/common/help.py @@ -15,6 +15,8 @@ HELP_MSG_HASHES = "Generate hashes of all the files analyzed" HELP_MSG_VERBOSE = "Verbose mode" HELP_MSG_CHECK_IOCS = "Compare stored JSON results to provided indicators" HELP_MSG_STIX2 = "Download public STIX2 indicators" +HELP_MSG_DISABLE_UPDATE_CHECK = "Disable MVT version update check" +HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK = "Disable indicators update check" # IOS Specific HELP_MSG_DECRYPT_BACKUP = "Decrypt an encrypted iTunes backup" diff --git a/src/mvt/common/logo.py b/src/mvt/common/logo.py index 04071c8..048ee22 100644 --- a/src/mvt/common/logo.py +++ b/src/mvt/common/logo.py @@ -12,74 +12,85 @@ from .updates import IndicatorsUpdates, MVTUpdates from .version import MVT_VERSION -def check_updates() -> None: +def check_updates( + disable_version_check: bool = False, disable_indicator_check: bool = False +) -> None: log = logging.getLogger("mvt") + # First we check for MVT version updates. - try: - mvt_updates = MVTUpdates() - latest_version = mvt_updates.check() - except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): - rich_print( - "\t\t[bold]Note: Could not check for MVT updates.[/bold] " - "You may be working offline. Please update MVT regularly." - ) - except Exception as e: - log.error("Error encountered when trying to check latest MVT version: %s", e) - else: - if latest_version: + if not disable_version_check: + try: + mvt_updates = MVTUpdates() + latest_version = mvt_updates.check() + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): rich_print( - f"\t\t[bold]Version {latest_version} is available! " - "Upgrade mvt with `pip3 install -U mvt` or with `pipx upgrade mvt`[/bold]" + "\t\t[bold]Note: Could not check for MVT updates.[/bold] " + "You may be working offline. Please update MVT regularly." ) - - # Then we check for indicators files updates. - ioc_updates = IndicatorsUpdates() - - # Before proceeding, we check if we have downloaded an indicators index. - # If not, there's no point in proceeding with the updates check. - if ioc_updates.get_latest_update() == 0: - rich_print( - "\t\t[bold]You have not yet downloaded any indicators, check " - "the `download-iocs` command![/bold]" - ) - return - - # We only perform this check at a fixed frequency, in order to not - # overburden the user with too many lookups if the command is being run - # multiple times. - should_check, hours = ioc_updates.should_check() - if not should_check: - rich_print( - f"\t\tIndicators updates checked recently, next automatic check " - f"in {int(hours)} hours" - ) - return - - try: - ioc_to_update = ioc_updates.check() - except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): - rich_print( - "\t\t[bold]Note: Could not check for indicator updates.[/bold] " - "You may be working offline. Please update MVT indicators regularly." - ) - except Exception as e: - log.error("Error encountered when trying to check latest MVT indicators: %s", e) - else: - if ioc_to_update: - rich_print( - "\t\t[bold]There are updates to your indicators files! " - "Run the `download-iocs` command to update![/bold]" + except Exception as e: + log.error( + "Error encountered when trying to check latest MVT version: %s", e ) else: - rich_print("\t\tYour indicators files seem to be up to date.") + if latest_version: + rich_print( + f"\t\t[bold]Version {latest_version} is available! " + "Upgrade mvt with `pip3 install -U mvt` or with `pipx upgrade mvt`[/bold]" + ) + + # Then we check for indicators files updates. + if not disable_indicator_check: + ioc_updates = IndicatorsUpdates() + + # Before proceeding, we check if we have downloaded an indicators index. + # If not, there's no point in proceeding with the updates check. + if ioc_updates.get_latest_update() == 0: + rich_print( + "\t\t[bold]You have not yet downloaded any indicators, check " + "the `download-iocs` command![/bold]" + ) + return + + # We only perform this check at a fixed frequency, in order to not + # overburden the user with too many lookups if the command is being run + # multiple times. + should_check, hours = ioc_updates.should_check() + if not should_check: + rich_print( + f"\t\tIndicators updates checked recently, next automatic check " + f"in {int(hours)} hours" + ) + return + + try: + ioc_to_update = ioc_updates.check() + except (requests.exceptions.ConnectionError, requests.exceptions.Timeout): + rich_print( + "\t\t[bold]Note: Could not check for indicator updates.[/bold] " + "You may be working offline. Please update MVT indicators regularly." + ) + except Exception as e: + log.error( + "Error encountered when trying to check latest MVT indicators: %s", e + ) + else: + if ioc_to_update: + rich_print( + "\t\t[bold]There are updates to your indicators files! " + "Run the `download-iocs` command to update![/bold]" + ) + else: + rich_print("\t\tYour indicators files seem to be up to date.") -def logo() -> None: +def logo( + disable_version_check: bool = False, disable_indicator_check: bool = False +) -> None: rich_print("\n") rich_print("\t[bold]MVT[/bold] - Mobile Verification Toolkit") rich_print("\t\thttps://mvt.re") rich_print(f"\t\tVersion: {MVT_VERSION}") - check_updates() + check_updates(disable_version_check, disable_indicator_check) rich_print("\n") diff --git a/src/mvt/common/updates.py b/src/mvt/common/updates.py index e782b91..c9c380b 100644 --- a/src/mvt/common/updates.py +++ b/src/mvt/common/updates.py @@ -24,7 +24,11 @@ INDICATORS_CHECK_FREQUENCY = 12 class MVTUpdates: def check(self) -> str: - res = requests.get(settings.PYPI_UPDATE_URL, timeout=15) + try: + res = requests.get(settings.PYPI_UPDATE_URL, timeout=5) + except requests.exceptions.RequestException as e: + log.error("Failed to check for updates, skipping updates: %s", e) + return "" data = res.json() latest_version = data.get("info", {}).get("version", "") @@ -93,7 +97,12 @@ class IndicatorsUpdates: url = self.github_raw_url.format( self.index_owner, self.index_repo, self.index_branch, self.index_path ) - res = requests.get(url, timeout=15) + try: + res = requests.get(url, timeout=5) + except requests.exceptions.RequestException as e: + log.error("Failed to retrieve indicators index from %s: %s", url, e) + return None + if res.status_code != 200: log.error( "Failed to retrieve indicators index located at %s (error %d)", @@ -105,7 +114,12 @@ class IndicatorsUpdates: return yaml.safe_load(res.content) def download_remote_ioc(self, ioc_url: str) -> Optional[str]: - res = requests.get(ioc_url, timeout=15) + try: + res = requests.get(ioc_url, timeout=15) + except requests.exceptions.RequestException as e: + log.error("Failed to download indicators file from %s: %s", ioc_url, e) + return None + if res.status_code != 200: log.error( "Failed to download indicators file from %s (error %d)", @@ -171,7 +185,12 @@ class IndicatorsUpdates: file_commit_url = ( f"https://api.github.com/repos/{owner}/{repo}/commits?path={path}" ) - res = requests.get(file_commit_url, timeout=15) + try: + res = requests.get(file_commit_url, timeout=5) + except requests.exceptions.RequestException as e: + log.error("Failed to get details about file %s: %s", file_commit_url, e) + return -1 + if res.status_code != 200: log.error( "Failed to get details about file %s (error %d)", diff --git a/src/mvt/ios/cli.py b/src/mvt/ios/cli.py index 1d06c96..3cb3421 100644 --- a/src/mvt/ios/cli.py +++ b/src/mvt/ios/cli.py @@ -37,6 +37,8 @@ from mvt.common.help import ( HELP_MSG_CHECK_IOCS, HELP_MSG_STIX2, HELP_MSG_CHECK_IOS_BACKUP, + HELP_MSG_DISABLE_UPDATE_CHECK, + HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK, ) from .cmd_check_backup import CmdIOSCheckBackup from .cmd_check_fs import CmdIOSCheckFS @@ -53,12 +55,37 @@ MVT_IOS_BACKUP_PASSWORD = "MVT_IOS_BACKUP_PASSWORD" CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) +def _get_disable_flags(ctx): + """Helper function to safely get disable flags from context.""" + if ctx.obj is None: + return False, False + return ( + ctx.obj.get("disable_version_check", False), + ctx.obj.get("disable_indicator_check", False), + ) + + # ============================================================================== # Main # ============================================================================== @click.group(invoke_without_command=False) -def cli(): - logo() +@click.option( + "--disable-update-check", is_flag=True, help=HELP_MSG_DISABLE_UPDATE_CHECK +) +@click.option( + "--disable-indicator-update-check", + is_flag=True, + help=HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK, +) +@click.pass_context +def cli(ctx, disable_update_check, disable_indicator_update_check): + ctx.ensure_object(dict) + ctx.obj["disable_version_check"] = disable_update_check + ctx.obj["disable_indicator_check"] = disable_indicator_update_check + logo( + disable_version_check=disable_update_check, + disable_indicator_check=disable_indicator_update_check, + ) # ============================================================================== @@ -219,6 +246,8 @@ def check_backup( module_name=module, module_options=module_options, hashes=hashes, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -266,6 +295,8 @@ def check_fs(ctx, iocs, output, fast, list_modules, module, hashes, verbose, dum module_name=module, module_options=module_options, hashes=hashes, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], ) if list_modules: @@ -300,7 +331,13 @@ def check_fs(ctx, iocs, output, fast, list_modules, module, hashes, verbose, dum @click.argument("FOLDER", type=click.Path(exists=True)) @click.pass_context def check_iocs(ctx, iocs, list_modules, module, folder): - cmd = CmdCheckIOCS(target_path=folder, ioc_files=iocs, module_name=module) + cmd = CmdCheckIOCS( + target_path=folder, + ioc_files=iocs, + module_name=module, + disable_version_check=_get_disable_flags(ctx)[0], + disable_indicator_check=_get_disable_flags(ctx)[1], + ) cmd.modules = BACKUP_MODULES + FS_MODULES + MIXED_MODULES if list_modules: diff --git a/src/mvt/ios/cmd_check_backup.py b/src/mvt/ios/cmd_check_backup.py index 66dfc8e..b1a373a 100644 --- a/src/mvt/ios/cmd_check_backup.py +++ b/src/mvt/ios/cmd_check_backup.py @@ -24,6 +24,8 @@ class CmdIOSCheckBackup(Command): serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, @@ -34,6 +36,8 @@ class CmdIOSCheckBackup(Command): module_options=module_options, hashes=hashes, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-backup" diff --git a/src/mvt/ios/cmd_check_fs.py b/src/mvt/ios/cmd_check_fs.py index 3484138..b09fba0 100644 --- a/src/mvt/ios/cmd_check_fs.py +++ b/src/mvt/ios/cmd_check_fs.py @@ -24,16 +24,19 @@ class CmdIOSCheckFS(Command): serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + disable_version_check: bool = False, + disable_indicator_check: bool = False, ) -> None: super().__init__( target_path=target_path, results_path=results_path, ioc_files=ioc_files, module_name=module_name, - serial=serial, module_options=module_options, hashes=hashes, log=log, + disable_version_check=disable_version_check, + disable_indicator_check=disable_indicator_check, ) self.name = "check-fs"