Automatically recover malformed sqlite3 databases (closes: #25 #37)

This commit is contained in:
Nex 2021-07-25 11:47:05 +02:00
parent 9d9b77e02e
commit 7fffef77ce
3 changed files with 59 additions and 4 deletions

View File

@ -7,7 +7,7 @@ Before proceeding, please note that mvt requires Python 3.6+ to run. While it sh
First install some basic dependencies that will be necessary to build all required tools: First install some basic dependencies that will be necessary to build all required tools:
```bash ```bash
sudo apt install python3 python3-pip libusb-1.0-0 sudo apt install python3 python3-pip libusb-1.0-0 sqlite3
``` ```
*libusb-1.0-0* is not required if you intend to only use `mvt-ios` and not `mvt-android`. *libusb-1.0-0* is not required if you intend to only use `mvt-ios` and not `mvt-android`.
@ -19,7 +19,7 @@ Running MVT on Mac requires Xcode and [homebrew](https://brew.sh) to be installe
In order to install dependencies use: In order to install dependencies use:
```bash ```bash
brew install python3 libusb brew install python3 libusb sqlite3
``` ```
*libusb* is not required if you intend to only use `mvt-ios` and not `mvt-android`. *libusb* is not required if you intend to only use `mvt-ios` and not `mvt-android`.

View File

@ -13,6 +13,12 @@ import simplejson as json
from .indicators import Indicators from .indicators import Indicators
class DatabaseNotFoundError(Exception):
pass
class DatabaseCorruptedError(Exception):
pass
class MVTModule(object): class MVTModule(object):
"""This class provides a base for all extraction modules.""" """This class provides a base for all extraction modules."""
@ -136,9 +142,12 @@ def run_module(module):
except NotImplementedError: except NotImplementedError:
module.log.exception("The run() procedure of module %s was not implemented yet!", module.log.exception("The run() procedure of module %s was not implemented yet!",
module.__class__.__name__) module.__class__.__name__)
except FileNotFoundError as e: except DatabaseNotFoundError as e:
module.log.info("There might be no data to extract by module %s: %s", module.log.info("There might be no data to extract by module %s: %s",
module.__class__.__name__, e) module.__class__.__name__, e)
except DatabaseCorruptedError as e:
module.log.error("The %s module database seems to be corrupted and recovery failed: %s",
module.__class__.__name__, e)
except Exception as e: except Exception as e:
module.log.exception("Error in running extraction from module %s: %s", module.log.exception("Error in running extraction from module %s: %s",
module.__class__.__name__, e) module.__class__.__name__, e)

View File

@ -3,10 +3,15 @@
# See the file 'LICENSE' for usage and copying permissions, or find a copy at # See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE # https://github.com/mvt-project/mvt/blob/main/LICENSE
import io
import os import os
import glob import glob
import shutil
import sqlite3
import subprocess
from mvt.common.module import MVTModule from mvt.common.module import MVTModule
from mvt.common.module import DatabaseNotFoundError, DatabaseCorruptedError
class IOSExtraction(MVTModule): class IOSExtraction(MVTModule):
"""This class provides a base for all iOS filesystem/backup extraction modules.""" """This class provides a base for all iOS filesystem/backup extraction modules."""
@ -15,6 +20,31 @@ class IOSExtraction(MVTModule):
is_fs_dump = False is_fs_dump = False
is_sysdiagnose = False is_sysdiagnose = False
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!")
bak_path = f"{file_path}.bak"
shutil.move(file_path, bak_path)
cmd = f"sqlite3 {bak_path} '.clone {file_path}'"
ret = subprocess.call(cmd, shell=True, 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=[]): def _find_ios_database(self, backup_ids=None, root_paths=[]):
"""Try to locate the module's database file from either an iTunes """Try to locate the module's database file from either an iTunes
backup or a full filesystem dump. backup or a full filesystem dump.
@ -52,4 +82,20 @@ class IOSExtraction(MVTModule):
if file_path: if file_path:
self.file_path = file_path self.file_path = file_path
else: else:
raise FileNotFoundError("Unable to find the module's database file") raise DatabaseNotFoundError("Unable to find the module's database file")
# Check if the database is corrupted.
conn = sqlite3.connect(self.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()
if recover:
self._recover_database(self.file_path)