mirror of https://github.com/mvt-project/mvt.git
108 lines
4.1 KiB
Python
108 lines
4.1 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# Copyright (c) 2021 The MVT Project Authors.
|
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
|
# https://license.mvt.re/1.1/
|
|
|
|
import glob
|
|
import io
|
|
import os
|
|
import shutil
|
|
import sqlite3
|
|
import subprocess
|
|
|
|
from mvt.common.module import (DatabaseCorruptedError, DatabaseNotFoundError,
|
|
MVTModule)
|
|
|
|
|
|
class IOSExtraction(MVTModule):
|
|
"""This class provides a base for all iOS filesystem/backup extraction modules."""
|
|
|
|
is_backup = False
|
|
is_fs_dump = False
|
|
is_sysdiagnose = False
|
|
|
|
def _is_database_malformed(self, file_path):
|
|
# Check if the database is malformed.
|
|
conn = sqlite3.connect(file_path)
|
|
cur = conn.cursor()
|
|
|
|
try:
|
|
recover = False
|
|
cur.execute("SELECT name FROM sqlite_master WHERE type='table';")
|
|
except sqlite3.DatabaseError as e:
|
|
if "database disk image is malformed" in str(e):
|
|
recover = True
|
|
finally:
|
|
conn.close()
|
|
|
|
return recover
|
|
|
|
def _recover_database(self, file_path):
|
|
"""Tries to recover a malformed database by running a .clone command.
|
|
:param file_path: Path to the malformed database file.
|
|
"""
|
|
# TODO: Find a better solution.
|
|
|
|
self.log.info("Database at path %s is malformed. Trying to recover...", file_path)
|
|
|
|
if not os.path.exists(file_path):
|
|
return
|
|
|
|
if not shutil.which("sqlite3"):
|
|
raise DatabaseCorruptedError("Unable to recover without sqlite3 binary. Please install sqlite3!")
|
|
if '"' in file_path:
|
|
raise DatabaseCorruptedError(f"Database at path '{file_path}' is corrupted. unable to recover because it has a quotation mark (\") in its name.")
|
|
|
|
bak_path = f"{file_path}.bak"
|
|
shutil.move(file_path, bak_path)
|
|
|
|
ret = subprocess.call(["sqlite3", bak_path, f".clone '{file_path}'"],
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
if ret != 0:
|
|
raise DatabaseCorruptedError("Recovery of database failed")
|
|
|
|
self.log.info("Database at path %s recovered successfully!", file_path)
|
|
|
|
def _find_ios_database(self, backup_ids=None, root_paths=[]):
|
|
"""Try to locate the module's database file from either an iTunes
|
|
backup or a full filesystem dump.
|
|
:param backup_id: iTunes backup database file's ID (or hash).
|
|
:param root_paths: Glob patterns for files to seek in filesystem dump.
|
|
"""
|
|
file_path = None
|
|
# First we check if the was an explicit file path specified.
|
|
if not self.file_path:
|
|
# If not, we first try with backups.
|
|
# We construct the path to the file according to the iTunes backup
|
|
# folder structure, if we have a valid ID.
|
|
if backup_ids:
|
|
for backup_id in backup_ids:
|
|
file_path = os.path.join(self.base_folder, backup_id[0:2], backup_id)
|
|
# If we found the correct backup file, then we stop searching.
|
|
if os.path.exists(file_path):
|
|
break
|
|
|
|
# If this file does not exist we might be processing a full
|
|
# filesystem dump (checkra1n all the things!).
|
|
if not file_path or not os.path.exists(file_path):
|
|
# We reset the file_path.
|
|
file_path = None
|
|
for root_path in root_paths:
|
|
for found_path in glob.glob(os.path.join(self.base_folder, root_path)):
|
|
# If we find a valid path, we set file_path.
|
|
if os.path.exists(found_path):
|
|
file_path = found_path
|
|
break
|
|
|
|
# Otherwise, we reset the file_path again.
|
|
file_path = None
|
|
|
|
# If we do not find any, we fail.
|
|
if file_path:
|
|
self.file_path = file_path
|
|
else:
|
|
raise DatabaseNotFoundError("Unable to find the module's database file")
|
|
|
|
if self._is_database_malformed(self.file_path):
|
|
self._recover_database(self.file_path)
|