Compare commits

...

4 Commits

Author SHA1 Message Date
Janik Besendorf
6342e3261b Fix ruff F821: use self.log instead of undefined log 2026-04-12 10:05:43 +02:00
Janik Besendorf
642add21b0 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
2026-04-12 10:03:57 +02:00
besendorf
f26303c930 Update README with warning about v3 breaking changes (#771)
Added important note about upcoming breaking changes in v3.
2026-04-12 09:54:29 +02:00
besendorf
4edab3c4f8 handle empty sms databases (#770)
Co-authored-by: Janik Besendorf <janik.besendorf@reporter-ohne-grenzen.de>
2026-04-08 18:40:56 +02:00
6 changed files with 56 additions and 18 deletions

View File

@@ -4,6 +4,9 @@
# Mobile Verification Toolkit # Mobile Verification Toolkit
> [!IMPORTANT]
> Soon we will merge the v3 pull request which will result in breaking changes. If you rely on mvt output in other script make sure to the the branch before we merge. More details: https://github.com/mvt-project/mvt/issues/757
[![](https://img.shields.io/pypi/v/mvt)](https://pypi.org/project/mvt/) [![](https://img.shields.io/pypi/v/mvt)](https://pypi.org/project/mvt/)
[![Documentation Status](https://readthedocs.org/projects/mvt/badge/?version=latest)](https://docs.mvt.re/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/mvt/badge/?version=latest)](https://docs.mvt.re/en/latest/?badge=latest)
[![CI](https://github.com/mvt-project/mvt/actions/workflows/tests.yml/badge.svg)](https://github.com/mvt-project/mvt/actions/workflows/tests.yml) [![CI](https://github.com/mvt-project/mvt/actions/workflows/tests.yml/badge.svg)](https://github.com/mvt-project/mvt/actions/workflows/tests.yml)

View File

@@ -11,7 +11,7 @@ from datetime import datetime
from typing import Optional from typing import Optional
from mvt.common.indicators import Indicators 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 ( from mvt.common.utils import (
convert_datetime_to_iso, convert_datetime_to_iso,
generate_hashes_from_path, generate_hashes_from_path,
@@ -244,7 +244,14 @@ class Command:
except NotImplementedError: except NotImplementedError:
pass pass
run_module(m) try:
run_module(m)
except EncryptedBackupError:
self.log.critical(
"The backup appears to be encrypted. "
"Please decrypt it first using `mvt-ios decrypt-backup`."
)
return
self.executed.append(m) self.executed.append(m)

View File

@@ -21,6 +21,10 @@ class DatabaseCorruptedError(Exception):
pass pass
class EncryptedBackupError(Exception):
pass
class InsufficientPrivileges(Exception): class InsufficientPrivileges(Exception):
pass pass
@@ -169,6 +173,8 @@ def run_module(module: MVTModule) -> None:
try: try:
exec_or_profile("module.run()", globals(), locals()) exec_or_profile("module.run()", globals(), locals())
except EncryptedBackupError:
raise
except NotImplementedError: except NotImplementedError:
module.log.exception( module.log.exception(
"The run() procedure of module %s was not implemented yet!", "The run() procedure of module %s was not implemented yet!",

View File

@@ -8,9 +8,10 @@ import io
import logging import logging
import os import os
import plistlib import plistlib
import sqlite3
from typing import Optional 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.url import URL
from mvt.common.utils import convert_datetime_to_iso, convert_unix_to_iso 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) conn = self._open_sqlite_db(manifest_db_path)
cur = conn.cursor() 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] names = [description[0] for description in cur.description]
for file_entry in cur: for file_entry in cur:

View File

@@ -123,6 +123,11 @@ class SMS(IOSExtraction):
""" """
) )
items = list(cur) items = list(cur)
elif "no such table" in str(exc):
self.log.info(
"No SMS tables found in the database, skipping: %s", exc
)
return
else: else:
raise exc raise exc
names = [description[0] for description in cur.description] names = [description[0] for description in cur.description]

View File

@@ -4,6 +4,7 @@
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
import logging import logging
import sqlite3
from base64 import b64encode from base64 import b64encode
from typing import Optional, Union from typing import Optional, Union
@@ -79,21 +80,29 @@ class SMSAttachments(IOSExtraction):
conn = self._open_sqlite_db(self.file_path) conn = self._open_sqlite_db(self.file_path)
cur = conn.cursor() cur = conn.cursor()
cur.execute( try:
cur.execute(
"""
SELECT
attachment.ROWID as "attachment_id",
attachment.*,
message.service as "service",
handle.id as "phone_number"
FROM attachment
LEFT JOIN message_attachment_join ON
message_attachment_join.attachment_id = attachment.ROWID
LEFT JOIN message ON
message.ROWID = message_attachment_join.message_id
LEFT JOIN handle ON handle.ROWID = message.handle_id;
""" """
SELECT )
attachment.ROWID as "attachment_id", except sqlite3.OperationalError as exc:
attachment.*, self.log.info(
message.service as "service", "No SMS attachment tables found in the database, skipping: %s", exc
handle.id as "phone_number" )
FROM attachment cur.close()
LEFT JOIN message_attachment_join ON conn.close()
message_attachment_join.attachment_id = attachment.ROWID return
LEFT JOIN message ON
message.ROWID = message_attachment_join.message_id
LEFT JOIN handle ON handle.ROWID = message.handle_id;
"""
)
names = [description[0] for description in cur.description] names = [description[0] for description in cur.description]
for item in cur: for item in cur: