From 642add21b0e77b6e9fbc9de95026e4713eeb8b33 Mon Sep 17 00:00:00 2001 From: Janik Besendorf Date: Sun, 12 Apr 2026 10:03:57 +0200 Subject: [PATCH] Abort analysis and warn user when backup is encrypted When `check-backup` is run against an encrypted backup, Manifest.db cannot be opened as a plain SQLite database. Previously this caused a flood of confusing "file is not a database" errors across all modules. Now the Manifest module detects the sqlite3.DatabaseError on its first query and raises a new EncryptedBackupError. This exception propagates out of run_module() and is caught in Command.run(), which logs a clear critical message instructing the user to decrypt the backup first with `mvt-ios decrypt-backup`, then stops the analysis immediately. Fixes #769 --- src/mvt/common/command.py | 11 +++++++++-- src/mvt/common/module.py | 6 ++++++ src/mvt/ios/modules/backup/manifest.py | 12 ++++++++++-- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/mvt/common/command.py b/src/mvt/common/command.py index 0eb633a..858f7d8 100644 --- a/src/mvt/common/command.py +++ b/src/mvt/common/command.py @@ -11,7 +11,7 @@ from datetime import datetime from typing import Optional from mvt.common.indicators import Indicators -from mvt.common.module import MVTModule, run_module, save_timeline +from mvt.common.module import EncryptedBackupError, MVTModule, run_module, save_timeline from mvt.common.utils import ( convert_datetime_to_iso, generate_hashes_from_path, @@ -244,7 +244,14 @@ class Command: except NotImplementedError: pass - run_module(m) + try: + run_module(m) + except EncryptedBackupError: + log.critical( + "The backup appears to be encrypted. " + "Please decrypt it first using `mvt-ios decrypt-backup`." + ) + return self.executed.append(m) diff --git a/src/mvt/common/module.py b/src/mvt/common/module.py index 2a52677..9c26064 100644 --- a/src/mvt/common/module.py +++ b/src/mvt/common/module.py @@ -21,6 +21,10 @@ class DatabaseCorruptedError(Exception): pass +class EncryptedBackupError(Exception): + pass + + class InsufficientPrivileges(Exception): pass @@ -169,6 +173,8 @@ def run_module(module: MVTModule) -> None: try: exec_or_profile("module.run()", globals(), locals()) + except EncryptedBackupError: + raise except NotImplementedError: module.log.exception( "The run() procedure of module %s was not implemented yet!", diff --git a/src/mvt/ios/modules/backup/manifest.py b/src/mvt/ios/modules/backup/manifest.py index ccbc459..da5c1a8 100644 --- a/src/mvt/ios/modules/backup/manifest.py +++ b/src/mvt/ios/modules/backup/manifest.py @@ -8,9 +8,10 @@ import io import logging import os import plistlib +import sqlite3 from typing import Optional -from mvt.common.module import DatabaseNotFoundError +from mvt.common.module import DatabaseNotFoundError, EncryptedBackupError from mvt.common.url import URL from mvt.common.utils import convert_datetime_to_iso, convert_unix_to_iso @@ -127,7 +128,14 @@ class Manifest(IOSExtraction): conn = self._open_sqlite_db(manifest_db_path) cur = conn.cursor() - cur.execute("SELECT * FROM Files;") + try: + cur.execute("SELECT * FROM Files;") + except sqlite3.DatabaseError: + conn.close() + raise EncryptedBackupError( + "Manifest.db is not a valid SQLite database. " + "The backup may be encrypted." + ) names = [description[0] for description in cur.description] for file_entry in cur: