Fix merge conflicts

This commit is contained in:
Hamza Z authored and Hamza Z committed 2021-08-08 20:05:50 +02:00
commit 15c0d71933
82 files changed
+1261 -579

No files matched your search

+3 -3
View File
@@ -1,4 +1,4 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
+3 -3
View File
@@ -1,6 +1,6 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .cli import cli
+33 -14
View File
@@ -1,17 +1,19 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import sys
import click
import argparse
import logging
import os
import sys
import click
from rich.logging import RichHandler
from mvt.common.module import run_module, save_timeline
from mvt.common.indicators import Indicators
from mvt.common.module import run_module, save_timeline
from .download_apks import DownloadAPKs
from .lookups.koodous import koodous_lookup
from .lookups.virustotal import virustotal_lookup
@@ -45,8 +47,8 @@ def cli():
@click.option("--virustotal", "-v", is_flag=True, help="Check packages on VirusTotal")
@click.option("--koodous", "-k", is_flag=True, help="Check packages on Koodous")
@click.option("--all-checks", "-A", is_flag=True, help="Run all available checks")
@click.option("--output", "-o", type=click.Path(exists=True),
help="Specify a path to a folder where you want to store JSON results")
@click.option("--output", "-o", type=click.Path(exists=False),
help="Specify a path to a folder where you want to store the APKs")
@click.option("--from-file", "-f", type=click.Path(exists=True),
help="Instead of acquiring from phone, load an existing packages.json file for lookups (mainly for debug purposes)")
@click.option("--serial", "-s", type=str, help="Use the Android device with a given serial number")
@@ -55,9 +57,12 @@ def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file,
if from_file:
download = DownloadAPKs.from_json(from_file)
else:
if not output:
log.critical("You need to specify an output folder (with --output, -o) when extracting APKs from a device")
sys.exit(-1)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
download = DownloadAPKs(output_folder=output, all_apks=all_apks, serial=serial)
download.run()
@@ -82,7 +87,7 @@ def download_apks(all_apks, virustotal, koodous, all_checks, output, from_file,
#==============================================================================
@cli.command("check-adb", help="Check an Android device over adb")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True),
@click.option("--output", "-o", type=click.Path(exists=False),
help="Specify a path to a folder where you want to store JSON results")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -97,6 +102,13 @@ def check_adb(iocs, output, list_modules, module, serial):
log.info("Checking Android through adb bridge")
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at %s", iocs)
@@ -129,12 +141,19 @@ def check_adb(iocs, output, list_modules, module, serial):
#==============================================================================
@cli.command("check-backup", help="Check an Android Backup")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.option("--serial", "-s", type=str, help="Use the Android device with a given serial")
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def check_backup(iocs, output, backup_path, serial):
log.info("Checking ADB backup located at: %s", backup_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at %s", iocs)
+8 -6
View File
@@ -1,22 +1,24 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import json
import logging
import os
import pkg_resources
from tqdm import tqdm
from mvt.common.utils import get_sha256_from_file_path
from .modules.adb.base import AndroidExtraction
log = logging.getLogger(__name__)
# TODO: Would be better to replace tqdm with rich.progress to reduce
# the number of dependencies. Need to investigate whether
# it's possible to have a simialr callback system.
# it's possible to have a similar callback system.
class PullProgress(tqdm):
"""PullProgress is a tqdm update system for APK downloads."""
@@ -42,7 +44,7 @@ class DownloadAPKs(AndroidExtraction):
"""Initialize module.
:param output_folder: Path to the folder where data should be stored
:param all_apks: Boolean indicating whether to download all packages
or filter known-goods
or filter known-goods
:param packages: Provided list of packages, typically for JSON checks
:param serial: The USB device serial ID
"""
+3 -3
View File
@@ -1,4 +1,4 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
+7 -7
View File
@@ -1,15 +1,15 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 requests
import logging
from rich.text import Text
from rich.table import Table
from rich.progress import track
import requests
from rich.console import Console
from rich.progress import track
from rich.table import Table
from rich.text import Text
log = logging.getLogger(__name__)
+8 -7
View File
@@ -1,14 +1,15 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 logging
import requests
import logging
from rich.text import Text
from rich.table import Table
from rich.progress import track
from rich.console import Console
from rich.progress import track
from rich.table import Table
from rich.text import Text
log = logging.getLogger(__name__)
+3 -3
View File
@@ -1,4 +1,4 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
+5 -5
View File
@@ -1,17 +1,17 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .chrome_history import ChromeHistory
from .dumpsys_batterystats import DumpsysBatterystats
from .dumpsys_packages import DumpsysPackages
from .dumpsys_procstats import DumpsysProcstats
from .packages import Packages
from .processes import Processes
from .rootbinaries import RootBinaries
from .sms import SMS
from .whatsapp import Whatsapp
from .packages import Packages
from .rootbinaries import RootBinaries
ADB_MODULES = [ChromeHistory, SMS, Whatsapp, Processes,
DumpsysBatterystats, DumpsysProcstats,
+60 -12
View File
@@ -1,20 +1,22 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import sys
import time
import logging
import os
import random
import string
import sys
import tempfile
import time
from adb_shell.adb_device import AdbDeviceUsb, AdbDeviceTcp
from adb_shell.auth.keygen import keygen, write_public_keyfile
from adb_shell.auth.sign_pythonrsa import PythonRSASigner
from adb_shell.exceptions import DeviceAuthError, AdbCommandFailureException
from usb1 import USBErrorBusy, USBErrorAccess
from adb_shell.exceptions import AdbCommandFailureException, DeviceAuthError
from usb1 import USBErrorAccess, USBErrorBusy
from mvt.common.module import MVTModule
from mvt.common.module import MVTModule, InsufficientPrivileges
log = logging.getLogger(__name__)
@@ -106,13 +108,13 @@ class AndroidExtraction(MVTModule):
"""Check if we have a `su` binary on the Android device.
:returns: Boolean indicating whether a `su` binary is present or not
"""
return bool(self._adb_command("[ ! -f /sbin/su ] || echo 1"))
return bool(self._adb_command("command -v su"))
def _adb_root_or_die(self):
"""Check if we have a `su` binary, otherwise raise an Exception.
"""
if not self._adb_check_if_root():
raise Exception("The Android device does not seem to have a `su` binary. Cannot run this module.")
raise InsufficientPrivileges("The Android device does not seem to have a `su` binary. Cannot run this module.")
def _adb_command_as_root(self, command):
"""Execute an adb shell command.
@@ -120,8 +122,23 @@ class AndroidExtraction(MVTModule):
:returns: Output of command
"""
return self._adb_command(f"su -c {command}")
def _adb_check_file_exists(self, file):
"""Verify that a file exists.
:param file: Path of the file
:returns: Boolean indicating whether the file exists or not
"""
def _adb_download(self, remote_path, local_path, progress_callback=None):
# TODO: Need to support checking files without root privileges as well.
# Connect to the device over adb.
self._adb_connect()
# Check if we have root, if not raise an Exception.
self._adb_root_or_die()
return bool(self._adb_command_as_root(f"[ ! -f {file} ] || echo 1"))
def _adb_download(self, remote_path, local_path, progress_callback=None, retry_root=True):
"""Download a file form the device.
:param remote_path: Path to download from the device
:param local_path: Path to where to locally store the copy of the file
@@ -129,6 +146,37 @@ class AndroidExtraction(MVTModule):
"""
try:
self.device.pull(remote_path, local_path, progress_callback)
except AdbCommandFailureException as e:
if retry_root:
self._adb_download_root(remote_path, local_path, progress_callback)
else:
raise Exception(f"Unable to download file {remote_path}: {e}")
def _adb_download_root(self, remote_path, local_path, progress_callback=None):
try:
# Check if we have root, if not raise an Exception.
self._adb_root_or_die()
# We generate a random temporary filename.
tmp_filename = "tmp_" + ''.join(random.choices(string.ascii_uppercase + string.ascii_lowercase + string.digits, k=10))
# We create a temporary local file.
new_remote_path = f"/sdcard/{tmp_filename}"
# We copy the file from the data folder to /sdcard/.
cp = self._adb_command_as_root(f"cp {remote_path} {new_remote_path}")
if cp.startswith("cp: ") and "No such file or directory" in cp:
raise Exception(f"Unable to process file {remote_path}: File not found")
elif cp.startswith("cp: ") and "Permission denied" in cp:
raise Exception(f"Unable to process file {remote_path}: Permission denied")
# We download from /sdcard/ to the local temporary file.
# If it doesn't work now, don't try again (retry_root=False)
self._adb_download(new_remote_path, local_path, retry_root=False)
# Delete the copy on /sdcard/.
self._adb_command(f"rm -rf {new_remote_path}")
except AdbCommandFailureException as e:
raise Exception(f"Unable to download file {remote_path}: {e}")
+6 -5
View File
@@ -1,13 +1,14 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 logging
import os
import sqlite3
import logging
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import AndroidExtraction
@@ -1,10 +1,10 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import logging
import os
from .base import AndroidExtraction
+4 -4
View File
@@ -1,10 +1,10 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import logging
import os
from .base import AndroidExtraction
+4 -4
View File
@@ -1,10 +1,10 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import logging
import os
from .base import AndroidExtraction
+5 -4
View File
@@ -1,10 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import logging
import os
import pkg_resources
from .base import AndroidExtraction
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 logging
+5 -4
View File
@@ -1,10 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import logging
import os
import pkg_resources
from .base import AndroidExtraction
+46 -21
View File
@@ -1,18 +1,43 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 logging
import os
import sqlite3
import logging
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
from .base import AndroidExtraction
from mvt.common.utils import convert_timestamp_to_iso, check_for_links
log = logging.getLogger(__name__)
SMS_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_PATH = "data/data/com.google.android.apps.messaging/databases/bugle_db"
SMS_BUGLE_QUERY = """
SELECT
ppl.normalized_destination AS number,
p.timestamp AS timestamp,
CASE WHEN m.sender_id IN
(SELECT _id FROM participants WHERE contact_id=-1)
THEN 2 ELSE 1 END incoming, p.text AS text
FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id)
AND (m._id = p.message_id)
AND (cp.conversation_id = c._id)
AND (cp.participant_id = ppl._id);
"""
SMS_MMSSMS_PATH = "data/data/com.android.providers.telephony/databases/mmssms.db"
SMS_MMSMS_QUERY = """
SELECT
address AS number,
date_sent AS timestamp,
type as incoming,
body AS text
FROM sms;
"""
class SMS(AndroidExtraction):
"""This module extracts all SMS messages containing links."""
@@ -50,20 +75,12 @@ class SMS(AndroidExtraction):
"""
conn = sqlite3.connect(db_path)
cur = conn.cursor()
cur.execute("""
SELECT
ppl.normalized_destination AS number,
p.timestamp AS timestamp,
CASE WHEN m.sender_id IN
(SELECT _id FROM participants WHERE contact_id=-1)
THEN 2 ELSE 1 END incoming, p.text AS text
FROM messages m, conversations c, parts p,
participants ppl, conversation_participants cp
WHERE (m.conversation_id = c._id)
AND (m._id = p.message_id)
AND (cp.conversation_id = c._id)
AND (cp.participant_id = ppl._id);
""")
if (self.SMS_DB_TYPE == 1):
cur.execute(SMS_BUGLE_QUERY)
elif (self.SMS_DB_TYPE == 2):
cur.execute(SMS_MMSMS_QUERY)
names = [description[0] for description in cur.description]
for item in cur:
@@ -85,7 +102,15 @@ class SMS(AndroidExtraction):
log.info("Extracted a total of %d SMS messages containing links", len(self.results))
def run(self):
# Checking the SMS database path
try:
self._adb_process_file(os.path.join("/", SMS_PATH), self._parse_db)
if (self._adb_check_file_exists(os.path.join("/", SMS_BUGLE_PATH))):
self.SMS_DB_TYPE = 1
self._adb_process_file(os.path.join("/", SMS_BUGLE_PATH), self._parse_db)
elif (self._adb_check_file_exists(os.path.join("/", SMS_MMSSMS_PATH))):
self.SMS_DB_TYPE = 2
self._adb_process_file(os.path.join("/", SMS_MMSSMS_PATH), self._parse_db)
else:
self.log.error("No SMS database found")
except Exception as e:
self.log.error(e)
+9 -5
View File
@@ -1,14 +1,16 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 base64
import logging
import os
import sqlite3
import logging
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
from .base import AndroidExtraction
from mvt.common.utils import convert_timestamp_to_iso, check_for_links
log = logging.getLogger(__name__)
@@ -69,6 +71,8 @@ class Whatsapp(AndroidExtraction):
# If we find links in the messages or if they are empty we add them to the list.
if check_for_links(message["data"]) or message["data"].strip() == "":
if (message.get('thumb_image') is not None):
message['thumb_image'] = base64.b64encode(message['thumb_image'])
messages.append(message)
cur.close()
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .sms import SMS
+6 -6
View File
@@ -1,15 +1,15 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import json
import os
import zlib
from mvt.common.module import MVTModule
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_timestamp_to_iso
from mvt.common.utils import check_for_links, convert_timestamp_to_iso
class SMS(MVTModule):
+3 -3
View File
@@ -1,4 +1,4 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
+5 -4
View File
@@ -1,13 +1,14 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import json
import os
from .url import URL
class Indicators:
"""This class is used to parse indicators from a STIX2 file and provide
functions to compare extracted artifacts to the indicators.
+41 -19
View File
@@ -1,18 +1,29 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 io
import os
import re
import csv
import glob
import io
import logging
import os
import re
import simplejson as json
from .indicators import Indicators
class DatabaseNotFoundError(Exception):
pass
class DatabaseCorruptedError(Exception):
pass
class InsufficientPrivileges(Exception):
pass
class MVTModule(object):
"""This class provides a base for all extraction modules."""
@@ -69,7 +80,7 @@ class MVTModule(object):
self.indicators = Indicators(file_path, self.log)
def check_indicators(self):
"""Check the results of this module againt a provided list of
"""Check the results of this module against a provided list of
indicators."""
raise NotImplementedError
@@ -85,7 +96,11 @@ class MVTModule(object):
results_file_name = f"{name}.json"
results_json_path = os.path.join(self.output_folder, results_file_name)
with open(results_json_path, "w") as handle:
json.dump(self.results, handle, indent=4)
try:
json.dump(self.results, handle, indent=4)
except Exception as e:
self.log.error("Unable to store results of module %s to file %s: %s",
self.__class__.__name__, results_file_name, e)
if self.detected:
detected_file_name = f"{name}_detected.json"
@@ -101,19 +116,21 @@ class MVTModule(object):
"""
for result in self.results:
record = self.serialize(result)
if type(record) == list:
self.timeline.extend(record)
else:
self.timeline.append(record)
if record:
if type(record) == list:
self.timeline.extend(record)
else:
self.timeline.append(record)
for detected in self.detected:
record = self.serialize(detected)
if type(record) == list:
self.timeline_detected.extend(record)
else:
self.timeline_detected.append(record)
if record:
if type(record) == list:
self.timeline_detected.extend(record)
else:
self.timeline_detected.append(record)
# De-duplicate timeline entries
# De-duplicate timeline entries.
self.timeline = self.timeline_deduplicate(self.timeline)
self.timeline_detected = self.timeline_deduplicate(self.timeline_detected)
@@ -138,8 +155,13 @@ def run_module(module):
except NotImplementedError:
module.log.exception("The run() procedure of module %s was not implemented yet!",
module.__class__.__name__)
except FileNotFoundError as e:
module.log.error("There might be no data to extract by module %s: %s",
except InsufficientPrivileges as e:
module.log.info("Insufficient privileges for module %s: %s", module.__class__.__name__, e)
except DatabaseNotFoundError as e:
module.log.info("There might be no data to extract by module %s: %s",
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:
module.log.exception("Error in running extraction from module %s: %s",
+5 -4
View File
@@ -1,11 +1,12 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
# From: https://gist.github.com/stanchan/bce1c2d030c76fe9223b5ff6ad0f03db
from click import command, option, Option, UsageError
from click import Option, UsageError, command, option
class MutuallyExclusiveOption(Option):
"""This class extends click to support mutually exclusive options.
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 requests
from tld import get_tld
+6 -5
View File
@@ -1,12 +1,13 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import re
import datetime
import hashlib
import os
import re
def convert_mactime_to_unix(timestamp, from_2001=True):
"""Converts Mac Standard Time to a Unix timestamp.
+3 -3
View File
@@ -1,6 +1,6 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .cli import cli
+79 -16
View File
@@ -1,18 +1,20 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 logging
import os
import sys
import click
import tarfile
import logging
from rich.logging import RichHandler
import click
from rich.logging import RichHandler
from rich.prompt import Prompt
from mvt.common.indicators import Indicators
from mvt.common.module import run_module, save_timeline
from mvt.common.options import MutuallyExclusiveOption
from mvt.common.indicators import Indicators
from .decrypt import DecryptBackup
from .modules.fs import BACKUP_MODULES, FS_MODULES
@@ -26,6 +28,8 @@ log = logging.getLogger(__name__)
# Help messages of repeating options.
OUTPUT_HELP_MESSAGE = "Specify a path to a folder where you want to store JSON results"
# Set this environment variable to a password if needed.
PASSWD_ENV = "MVT_IOS_BACKUP_PASSWORD"
#==============================================================================
# Main
@@ -42,8 +46,7 @@ def cli():
@click.option("--destination", "-d", required=True,
help="Path to the folder where to store the decrypted backup")
@click.option("--password", "-p", cls=MutuallyExclusiveOption,
help="Password to use to decrypt the backup",
prompt="Enter backup password", hide_input=True, prompt_required=False,
help=f"Password to use to decrypt the backup (or, set {PASSWD_ENV} environment variable)",
mutually_exclusive=["key_file"])
@click.option("--key-file", "-k", cls=MutuallyExclusiveOption,
type=click.Path(exists=True),
@@ -52,13 +55,59 @@ def cli():
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def decrypt_backup(destination, password, key_file, backup_path):
backup = DecryptBackup(backup_path, destination)
if password:
backup.decrypt_with_password(password)
elif key_file:
if key_file:
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --key-file '{key_file}' instead")
backup.decrypt_with_key_file(key_file)
elif password:
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --password argument instead")
backup.decrypt_with_password(password)
elif PASSWD_ENV in os.environ:
log.info(f"Using password from {PASSWD_ENV} environment variable")
backup.decrypt_with_password(os.environ[PASSWD_ENV])
else:
raise click.ClickException("Missing required option. Specify either "
"--password or --key-file.")
sekrit = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(sekrit)
backup.process_backup()
#==============================================================================
# Command: extract-key
#==============================================================================
@cli.command("extract-key", help="Extract decryption key from an iTunes backup")
@click.option("--password", "-p",
help=f"Password to use to decrypt the backup (or, set {PASSWD_ENV} environment variable)")
@click.option("--key-file", "-k",
help="Key file to be written (if unset, will print to STDOUT)",
required=False,
type=click.Path(exists=False, file_okay=True, dir_okay=False, writable=True))
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
def extract_key(password, backup_path, key_file):
backup = DecryptBackup(backup_path)
if password:
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if PASSWD_ENV in os.environ:
log.info(f"Ignoring {PASSWD_ENV} environment variable, using --password argument instead")
elif PASSWD_ENV in os.environ:
log.info(f"Using password from {PASSWD_ENV} environment variable")
password = os.environ[PASSWD_ENV]
else:
password = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(password)
backup.get_key()
if key_file:
backup.write_key(key_file)
#==============================================================================
@@ -66,7 +115,7 @@ def decrypt_backup(destination, password, key_file, backup_path):
#==============================================================================
@cli.command("check-backup", help="Extract artifacts from an iTunes backup")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.option("--fast", "-f", is_flag=True, help="Avoid running time/resource consuming features")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -81,6 +130,13 @@ def check_backup(iocs, output, fast, backup_path, list_modules, module):
log.info("Checking iTunes backup located at: %s", backup_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at: %s", iocs)
@@ -116,7 +172,7 @@ def check_backup(iocs, output, fast, backup_path, list_modules, module):
#==============================================================================
@cli.command("check-fs", help="Extract artifacts from a full filesystem dump")
@click.option("--iocs", "-i", type=click.Path(exists=True), help="Path to indicators file")
@click.option("--output", "-o", type=click.Path(exists=True), help=OUTPUT_HELP_MESSAGE)
@click.option("--output", "-o", type=click.Path(exists=False), help=OUTPUT_HELP_MESSAGE)
@click.option("--fast", "-f", is_flag=True, help="Avoid running time/resource consuming features")
@click.option("--list-modules", "-l", is_flag=True, help="Print list of available modules and exit")
@click.option("--module", "-m", help="Name of a single module you would like to run instead of all")
@@ -131,6 +187,13 @@ def check_fs(iocs, output, fast, dump_path, list_modules, module):
log.info("Checking filesystem dump located at: %s", dump_path)
if output and not os.path.exists(output):
try:
os.makedirs(output)
except Exception as e:
log.critical("Unable to create output folder %s: %s", output, e)
sys.exit(-1)
if iocs:
# Pre-load indicators for performance reasons.
log.info("Loading indicators from provided file at: %s", iocs)
+45 -19
View File
@@ -1,13 +1,14 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 binascii
import logging
import os
import shutil
import sqlite3
import logging
import binascii
from iOSbackup import iOSbackup
log = logging.getLogger(__name__)
@@ -17,7 +18,7 @@ class DecryptBackup:
using either a password or a key file.
"""
def __init__(self, backup_path, dest_path):
def __init__(self, backup_path, dest_path=None):
"""Decrypts an encrypted iOS backup.
:param backup_path: Path to the encrypted backup folder
:param dest_path: Path to the folder where to store the decrypted backup
@@ -25,8 +26,12 @@ class DecryptBackup:
self.backup_path = backup_path
self.dest_path = dest_path
self._backup = None
self._decryption_key = None
def process_backup(self):
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
def _process_backup(self):
manifest_path = os.path.join(self.dest_path, "Manifest.db")
# We extract a decrypted Manifest.db.
self._backup.getManifestDB()
@@ -68,19 +73,18 @@ class DecryptBackup:
"""
log.info("Decrypting iOS backup at path %s with password", self.backup_path)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
try:
self._backup = iOSbackup(udid=os.path.basename(self.backup_path),
cleartextpassword=password,
backuproot=os.path.dirname(self.backup_path))
except Exception as e:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct password?")
return
else:
self._process_backup()
if isinstance(e, KeyError) and len(e.args) > 0 and e.args[0] == b"KEY":
log.critical("Failed to decrypt backup. Password is probably wrong.")
elif isinstance(e, FileNotFoundError) and os.path.basename(e.filename) == "Manifest.plist":
log.critical(f"Failed to find a valid backup at {self.backup_path}. Did you point to the right backup path?")
else:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct password? Did you point to the right backup path?")
def decrypt_with_key_file(self, key_file):
"""Decrypts an encrypted iOS backup using a key file.
@@ -89,9 +93,6 @@ class DecryptBackup:
log.info("Decrypting iOS backup at path %s with key file %s",
self.backup_path, key_file)
if not os.path.exists(self.dest_path):
os.makedirs(self.dest_path)
with open(key_file, "rb") as handle:
key_bytes = handle.read()
@@ -108,6 +109,31 @@ class DecryptBackup:
except Exception as e:
log.exception(e)
log.critical("Failed to decrypt backup. Did you provide the correct key file?")
def get_key(self):
"""Retrieve and prints the encryption key.
"""
if not self._backup:
return
self._decryption_key = self._backup.getDecryptionKey()
log.info("Derived decryption key for backup at path %s is: \"%s\"",
self.backup_path, self._decryption_key)
def write_key(self, key_path):
"""Save extracted key to file.
:param key_path: Path to the file where to write the derived decryption key.
"""
if not self._decryption_key:
return
try:
with open(key_path, 'w') as handle:
handle.write(self._decryption_key)
except Exception as e:
log.exception(e)
log.critical("Failed to write key to file: %s", key_path)
return
else:
self._process_backup()
log.info("Wrote decryption key to file: %s. This file is equivalent to a plaintext password. Keep it safe!",
key_path)
+3 -3
View File
@@ -1,4 +1,4 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
+26 -24
View File
@@ -1,42 +1,44 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .manifest import Manifest
from .contacts import Contacts
from .net_netusage import Netusage
from .net_datausage import Datausage
from .safari_history import SafariHistory
from .safari_favicon import SafariFavicon
from .safari_browserstate import SafariBrowserState
from .webkit_indexeddb import WebkitIndexedDB
from .webkit_localstorage import WebkitLocalStorage
from .webkit_safariviewservice import WebkitSafariViewService
from .webkit_session_resource_log import WebkitSessionResourceLog
from .chrome_history import ChromeHistory
from .cache_files import CacheFiles
from .calls import Calls
from .chrome_favicon import ChromeFavicon
from .firefox_history import FirefoxHistory
from .chrome_history import ChromeHistory
from .contacts import Contacts
from .filesystem import Filesystem
from .firefox_favicon import FirefoxFavicon
from .version_history import IOSVersionHistory
from .firefox_history import FirefoxHistory
from .idstatuscache import IDStatusCache
from .locationd import LocationdClients
from .interactionc import InteractionC
from .locationd import LocationdClients
from .manifest import Manifest
from .net_datausage import Datausage
from .net_netusage import Netusage
from .safari_browserstate import SafariBrowserState
from .safari_favicon import SafariFavicon
from .safari_history import SafariHistory
from .sms import SMS
from .sms_attachments import SMSAttachments
from .calls import Calls
from .version_history import IOSVersionHistory
from .webkit_indexeddb import WebkitIndexedDB
from .webkit_localstorage import WebkitLocalStorage
from .webkit_resource_load_statistics import WebkitResourceLoadStatistics
from .webkit_safariviewservice import WebkitSafariViewService
from .webkit_session_resource_log import WebkitSessionResourceLog
from .whatsapp import Whatsapp
from .cache_files import CacheFiles
from .filesystem import Filesystem
BACKUP_MODULES = [SafariBrowserState, SafariHistory, Datausage, SMS, SMSAttachments,
ChromeHistory, ChromeFavicon, WebkitSessionResourceLog,
Calls, IDStatusCache, LocationdClients, InteractionC,
FirefoxHistory, FirefoxFavicon, Contacts, Manifest, Whatsapp]
WebkitResourceLoadStatistics, Calls, IDStatusCache, LocationdClients,
InteractionC, FirefoxHistory, FirefoxFavicon, Contacts, Manifest, Whatsapp]
FS_MODULES = [IOSVersionHistory, SafariHistory, SafariFavicon, SafariBrowserState,
WebkitIndexedDB, WebkitLocalStorage, WebkitSafariViewService,
WebkitSessionResourceLog, Datausage, Netusage, ChromeHistory,
WebkitResourceLoadStatistics, WebkitSessionResourceLog,
Datausage, Netusage, ChromeHistory,
ChromeFavicon, Calls, IDStatusCache, SMS, SMSAttachments,
LocationdClients, InteractionC, FirefoxHistory, FirefoxFavicon,
Contacts, CacheFiles, Whatsapp, Filesystem]
+58 -6
View File
@@ -1,12 +1,18 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import glob
import io
import os
import shutil
import sqlite3
import subprocess
from mvt.common.module import (DatabaseCorruptedError, DatabaseNotFoundError,
MVTModule)
from mvt.common.module import MVTModule
class IOSExtraction(MVTModule):
"""This class provides a base for all iOS filesystem/backup extraction modules."""
@@ -15,10 +21,53 @@ class IOSExtraction(MVTModule):
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.
@@ -52,4 +101,7 @@ class IOSExtraction(MVTModule):
if file_path:
self.file_path = file_path
else:
raise FileNotFoundError("Unable to find the module's database file")
raise DatabaseNotFoundError("Unable to find the module's database file")
if self._is_database_malformed(self.file_path):
self._recover_database(self.file_path)
+4 -3
View File
@@ -1,13 +1,14 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import sqlite3
from .base import IOSExtraction
class CacheFiles(IOSExtraction):
def __init__(self, file_path=None, base_folder=None, output_folder=None,
+4 -3
View File
@@ -1,11 +1,12 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
CALLS_BACKUP_IDS = [
+5 -4
View File
@@ -1,11 +1,12 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
+13 -4
View File
@@ -1,11 +1,12 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from mvt.common.utils import convert_chrometime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (convert_chrometime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
@@ -35,6 +36,14 @@ class ChromeHistory(IOSExtraction):
"data": f"{record['id']} - {record['url']} (visit ID: {record['visit_id']}, redirect source: {record['redirect_source']})"
}
def check_indicators(self):
if not self.indicators:
return
for result in self.results:
if self.indicators.check_domain(result["url"]):
self.detected.append(result)
def run(self):
self._find_ios_database(backup_ids=CHROME_HISTORY_BACKUP_IDS, root_paths=CHROME_HISTORY_ROOT_PATHS)
self.log.info("Found Chrome history database at path: %s", self.file_path)
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
+5 -4
View File
@@ -1,15 +1,16 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import datetime
import os
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
class Filesystem(IOSExtraction):
"""This module extracts creation and modification date of files from a
full file-system dump."""
+4 -4
View File
@@ -1,11 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from datetime import datetime
from mvt.common.url import URL
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
+4 -4
View File
@@ -1,11 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from datetime import datetime
from mvt.common.url import URL
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
+7 -6
View File
@@ -1,12 +1,13 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import glob
import biplist
import collections
import glob
import os
import biplist
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from base64 import b64encode
+8 -7
View File
@@ -1,10 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import glob
import os
import biplist
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
@@ -43,10 +44,10 @@ class LocationdClients(IOSExtraction):
for ts in self.timestamps:
if ts in record.keys():
records.append({
"timestamp": entry[ts],
"timestamp": record[ts],
"module": self.__class__.__name__,
"event": ts,
"data": f"{ts} from {entry['package']}"
"data": f"{ts} from {record['package']}"
})
return records
@@ -62,7 +63,7 @@ class LocationdClients(IOSExtraction):
result["package"] = app
for ts in self.timestamps:
if ts in result.keys():
result[ts] = convert_timestamp_to_iso(convert_mactime_to_unix(result[date]))
result[ts] = convert_timestamp_to_iso(convert_mactime_to_unix(result[ts]))
self.results.append(result)
+18 -10
View File
@@ -1,18 +1,21 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 datetime
import io
import os
import biplist
import sqlite3
import datetime
import biplist
from mvt.common.module import DatabaseNotFoundError
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
class Manifest(IOSExtraction):
"""This module extracts information from a backup Manifest.db file."""
@@ -40,6 +43,8 @@ class Manifest(IOSExtraction):
def serialize(self, record):
records = []
if "modified" not in record or "statusChanged" not in record:
return
for ts in set([record["created"], record["modified"], record["statusChanged"]]):
macb = ""
macb += "M" if ts == record["modified"] else "-"
@@ -63,12 +68,15 @@ class Manifest(IOSExtraction):
for result in self.results:
if not "relativePath" in result:
continue
if os.path.basename(result["relativePath"]) == "com.apple.CrashReporter.plist" and result["domain"] == "RootDomain":
self.log.warning("Found a potentially suspicious \"com.apple.CrashReporter.plist\" file created in RootDomain")
self.detected.append(result)
if not result["relativePath"]:
continue
if result["domain"]:
if os.path.basename(result["relativePath"]) == "com.apple.CrashReporter.plist" and result["domain"] == "RootDomain":
self.log.warning("Found a potentially suspicious \"com.apple.CrashReporter.plist\" file created in RootDomain")
self.detected.append(result)
continue
if self.indicators.check_file(result["relativePath"]):
self.log.warning("Found a known malicious file at path: %s", result["relativePath"])
self.detected.append(result)
@@ -84,7 +92,7 @@ class Manifest(IOSExtraction):
def run(self):
manifest_db_path = os.path.join(self.base_folder, "Manifest.db")
if not os.path.isfile(manifest_db_path):
raise FileNotFoundError("Impossible to find the module's database file")
raise DatabaseNotFoundError("Impossible to find the module's database file")
self.log.info("Found Manifest.db database at path: %s", manifest_db_path)
+9 -4
View File
@@ -1,16 +1,17 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
import operator
import sqlite3
from pathlib import Path
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
class NetBase(IOSExtraction):
"""This class provides a base for DataUsage and NetUsage extraction modules."""
@@ -167,6 +168,10 @@ class NetBase(IOSExtraction):
results_by_proc = {proc["proc_id"]: proc for proc in self.results if proc["proc_id"]}
all_proc_id = sorted(results_by_proc.keys())
# Fix issue #108
if not all_proc_id:
return
missing_procs, last_proc_id = {}, None
for proc_id in range(min(all_proc_id), max(all_proc_id)):
if proc_id not in all_proc_id:
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .net_base import NetBase
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .net_base import NetBase
+7 -6
View File
@@ -1,14 +1,15 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 io
import biplist
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import keys_bytes_to_string
import biplist
from mvt.common.utils import (convert_mactime_to_unix,
convert_timestamp_to_iso, keys_bytes_to_string)
from .base import IOSExtraction
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
+5 -5
View File
@@ -1,13 +1,13 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from base64 import b64encode
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
+5 -5
View File
@@ -1,13 +1,13 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
from base64 import b64encode
from mvt.common.utils import check_for_links
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
+4 -4
View File
@@ -1,10 +1,10 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 json
import datetime
import json
from mvt.common.utils import convert_timestamp_to_iso
+6 -5
View File
@@ -1,14 +1,15 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import datetime
import os
from mvt.common.utils import convert_timestamp_to_iso
from .base import IOSExtraction
from mvt.common.utils import convert_timestamp_to_iso
class WebkitBase(IOSExtraction):
"""This class is a base for other WebKit-related modules."""
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .webkit_base import WebkitBase
+3 -3
View File
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .webkit_base import WebkitBase
@@ -0,0 +1,98 @@
# 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 datetime
import os
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso
from .base import IOSExtraction
WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH = "Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db"
WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS = [
"private/var/mobile/Containers/Data/Application/*/Library/WebKit/WebsiteData/ResourceLoadStatistics/observations.db",
"private/var/mobile/Containers/Data/Application/*/SystemData/com.apple.SafariViewService/Library/WebKit/WebsiteData/observations.db",
]
class WebkitResourceLoadStatistics(IOSExtraction):
"""This module extracts records from WebKit ResourceLoadStatistics observations.db.
"""
# TODO: Add serialize().
def __init__(self, file_path=None, base_folder=None, output_folder=None,
fast_mode=False, log=None, results=[]):
super().__init__(file_path=file_path, base_folder=base_folder,
output_folder=output_folder, fast_mode=fast_mode,
log=log, results=results)
def check_indicators(self):
if not self.indicators:
return
self.detected = {}
for key, items in self.results.items():
for item in items:
if self.indicators.check_domain(item["registrable_domain"]):
if key not in self.detected:
self.detected[key] = [item,]
else:
self.detected[key].append(item)
def _process_observations_db(self, db_path, key):
self.log.info("Found WebKit ResourceLoadStatistics observations.db file at path %s", db_path)
if self._is_database_malformed(db_path):
self._recover_database(db_path)
conn = sqlite3.connect(db_path)
cur = conn.cursor()
try:
cur.execute("SELECT * from ObservedDomains;")
except sqlite3.OperationalError:
return
if not key in self.results:
self.results[key] = []
for row in cur:
self.results[key].append(dict(
domain_id=row[0],
registrable_domain=row[1],
last_seen=row[2],
had_user_interaction=bool(row[3]),
# TODO: Fix isodate.
last_seen_isodate=convert_timestamp_to_iso(datetime.datetime.utcfromtimestamp(int(row[2]))),
))
if len(self.results[key]) > 0:
self.log.info("Extracted a total of %d records from %s", len(self.results[key]), db_path)
def run(self):
self.results = {}
if self.is_backup:
manifest_db_path = os.path.join(self.base_folder, "Manifest.db")
if not os.path.exists(manifest_db_path):
self.log.info("Unable to search for WebKit observations.db files in backup because of missing Manifest.db")
return
try:
conn = sqlite3.connect(manifest_db_path)
cur = conn.cursor()
cur.execute("SELECT fileID, domain FROM Files WHERE relativePath = ?;", (WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH,))
except Exception as e:
self.log.error("Unable to search for WebKit observations.db files in backup because of failed query to Manifest.db: %s", e)
for row in cur:
file_id = row[0]
domain = row[1]
db_path = os.path.join(self.base_folder, file_id[0:2], file_id)
if os.path.exists(db_path):
self._process_observations_db(db_path=db_path, key=f"{domain}/{WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH}")
elif self.is_fs_dump:
for db_path in self._find_paths(WEBKIT_RESOURCELOADSTATICS_ROOT_PATHS):
self._process_observations_db(db_path=db_path, key=os.path.relpath(db_path, self.base_folder))
@@ -1,7 +1,7 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
from .webkit_base import WebkitBase
@@ -1,10 +1,11 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 os
import glob
import os
import biplist
from mvt.common.utils import convert_timestamp_to_iso
@@ -70,6 +71,9 @@ class WebkitSessionResourceLog(IOSExtraction):
return domains
def check_indicators(self):
if not self.indicators:
return
for key, entries in self.results.items():
for entry in entries:
source_domains = self._extract_domains(entry["redirect_source"])
+7 -6
View File
@@ -1,12 +1,13 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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 sqlite3
import logging
import sqlite3
from mvt.common.utils import convert_mactime_to_unix, convert_timestamp_to_iso, check_for_links
from mvt.common.utils import (check_for_links, convert_mactime_to_unix,
convert_timestamp_to_iso)
from .base import IOSExtraction
@@ -73,7 +74,7 @@ class Whatsapp(IOSExtraction):
# Extract links from the WhatsApp message.
message_links = check_for_links(new_message["ZTEXT"])
# If we find mesages, or if there's an empty message we add it to the list.
# If we find messages, or if there's an empty message we add it to the list.
if new_message["ZTEXT"] and (message_links or new_message["ZTEXT"].strip() == ""):
self.results.append(new_message)
+213 -169
View File
@@ -1,190 +1,234 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021 MVT Project Developers.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE
# 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/
IPHONE_MODELS = [
{"description": "iPhone 4S", "identifier": "iPhone4,1"},
{"description": "iPhone 5", "identifier": "iPhone5,1"},
{"description": "iPhone 5", "identifier": "iPhone5,2"},
{"description": "iPhone 5c", "identifier": "iPhone5,3"},
{"description": "iPhone 5c", "identifier": "iPhone5,4"},
{"description": "iPhone 5s", "identifier": "iPhone6,1"},
{"description": "iPhone 5s", "identifier": "iPhone6,2"},
{"description": "iPhone 6 Plus", "identifier": "iPhone7,1"},
{"description": "iPhone 6", "identifier": "iPhone7,2"},
{"description": "iPhone 6s", "identifier": "iPhone8,1"},
{"description": "iPhone 6s Plus", "identifier": "iPhone8,2"},
{"description": "iPhone SE (1st generation)", "identifier": "iPhone8,4"},
{"description": "iPhone 7", "identifier": "iPhone9,1"},
{"description": "iPhone 7 Plus", "identifier": "iPhone9,2"},
{"description": "iPhone 7", "identifier": "iPhone9,3"},
{"description": "iPhone 7 Plus", "identifier": "iPhone9,4"},
{"description": "iPhone 8", "identifier": "iPhone10,1"},
{"description": "iPhone 8 Plus", "identifier": "iPhone10,2"},
{"description": "iPhone X", "identifier": "iPhone10,3"},
{"description": "iPhone 8", "identifier": "iPhone10,4"},
{"description": "iPhone 8 Plus", "identifier": "iPhone10,5"},
{"description": "iPhone X", "identifier": "iPhone10,6"},
{"description": "iPhone XS", "identifier": "iPhone11,2"},
{"description": "iPhone XS Max", "identifier": "iPhone11,4"},
{"description": "iPhone XS Max", "identifier": "iPhone11,6"},
{"description": "iPhone XR", "identifier": "iPhone11,8"},
{"description": "iPhone 11", "identifier": "iPhone12,1"},
{"description": "iPhone 11 Pro", "identifier": "iPhone12,3"},
{"description": "iPhone 11 Pro Max", "identifier": "iPhone12,5"},
{"description": "iPhone SE (2nd generation)", "identifier": "iPhone12,8"},
{"description": "iPhone 12 mini", "identifier": "iPhone13,1"},
{"description": "iPhone 12", "identifier": "iPhone13,2"},
{"description": "iPhone 12 Pro", "identifier": "iPhone13,3"},
{"description": "iPhone 12 Pro Max", "identifier": "iPhone13,4"},
]
IPHONE_IOS_VERSIONS = [
{"build": "7E18", "version": "3.1.3"},
{"build": "7D11", "version": "3.1.2"},
{"build": "7C144", "version": "3.1"},
{"build": "7A400", "version": "3.0.1"},
{"build": "7A341", "version": "3.0"},
{"build": "5H11", "version": "2.2.1"},
{"build": "5G77", "version": "2.2"},
{"build": "5F136", "version": "2.1"},
{"build": "5C1", "version": "2.0.2"},
{"build": "5B108", "version": "2.0.1"},
{"build": "5A347", "version": "2.0"},
{"build": "4A102", "version": "1.1.4"},
{"build": "4A93", "version": "1.1.3"},
{"build": "3B48b", "version": "1.1.2"},
{"build": "3A109a", "version": "1.1.1"},
{"build": "1C28", "version": "1.0.2"},
{"build": "1C25", "version": "1.0.1"},
{"build": "1A543a", "version": "1.0"},
{"build": "8C148", "version": "4.2"},
{"build": "8B117", "version": "4.1"},
{"build": "8A306", "version": "4.0.1"},
{"build": "1C25", "version": "1.0.1"},
{"build": "1C28", "version": "1.0.2"},
{"build": "3A109a", "version": "1.1.1"},
{"build": "3B48b", "version": "1.1.2"},
{"build": "4A93", "version": "1.1.3"},
{"build": "4A102", "version": "1.1.4"},
{"build": "5A347", "version": "2.0"},
{"build": "5B108", "version": "2.0.1"},
{"build": "5C1", "version": "2.0.2"},
{"build": "5F136", "version": "2.1"},
{"build": "5G77", "version": "2.2"},
{"build": "5H11", "version": "2.2.1"},
{"build": "7A341", "version": "3.0"},
{"build": "7A400", "version": "3.0.1"},
{"build": "7C144", "version": "3.1"},
{"build": "7D11", "version": "3.1.2"},
{"build": "7E18", "version": "3.1.3"},
{"build": "8A293", "version": "4.0"},
{"build": "10B500", "version": "6.1.6"},
{"build": "10B329", "version": "6.1.3"},
{"build": "10B146", "version": "6.1.2"},
{"build": "10B141", "version": "6.1"},
{"build": "10A523", "version": "6.0.1"},
{"build": "10A403", "version": "6.0"},
{"build": "9B206", "version": "5.1.1"},
{"build": "9B176", "version": "5.1"},
{"build": "9A405", "version": "5.0.1"},
{"build": "9A334", "version": "5.0"},
{"build": "8L1", "version": "4.3.5"},
{"build": "8K2", "version": "4.3.4"},
{"build": "8J2", "version": "4.3.3"},
{"build": "8F190", "version": "4.3"},
{"build": "8A306", "version": "4.0.1"},
{"build": "8B117", "version": "4.1"},
{"build": "8C148", "version": "4.2"},
{"build": "8C148a", "version": "4.2.1"},
{"build": "11D257", "version": "7.1.2"},
{"build": "11D201", "version": "7.1.1"},
{"build": "11D169", "version": "7.1"},
{"build": "11B651", "version": "7.0.6"},
{"build": "11B554a", "version": "7.0.4"},
{"build": "11B511", "version": "7.0.3"},
{"build": "10B144", "version": "6.1"},
{"build": "9B208", "version": "5.1.1"},
{"build": "8C148", "version": "4.2.1"},
{"build": "11D167", "version": "7.1"},
{"build": "8E600", "version": "4.2.10"},
{"build": "8E501", "version": "4.2.9"},
{"build": "8E401", "version": "4.2.8"},
{"build": "13G37", "version": "9.3.6"},
{"build": "13G36", "version": "9.3.5"},
{"build": "13G35", "version": "9.3.4"},
{"build": "13G34", "version": "9.3.3"},
{"build": "13F69", "version": "9.3.2"},
{"build": "13E238", "version": "9.3.1"},
{"build": "8E501", "version": "4.2.9"},
{"build": "8F190", "version": "4.3"},
{"build": "8J2", "version": "4.3.3"},
{"build": "8K2", "version": "4.3.4"},
{"build": "8L1", "version": "4.3.5"},
{"build": "9A334", "version": "5.0"},
{"build": "9A405", "version": "5.0.1"},
{"build": "9A406", "version": "5.0.1"},
{"build": "9B176", "version": "5.1"},
{"build": "9B179", "version": "5.1"},
{"build": "9B206", "version": "5.1.1"},
{"build": "9B208", "version": "5.1.1"},
{"build": "10A403", "version": "6.0"},
{"build": "10A405", "version": "6.0"},
{"build": "10A523", "version": "6.0.1"},
{"build": "10A525", "version": "6.0.1"},
{"build": "10A551", "version": "6.0.2"},
{"build": "10B141", "version": "6.1"},
{"build": "10B144", "version": "6.1"},
{"build": "10B142", "version": "6.1"},
{"build": "10B143", "version": "6.1"},
{"build": "10B145", "version": "6.1.1"},
{"build": "10B146", "version": "6.1.2"},
{"build": "10B329", "version": "6.1.3"},
{"build": "10B350", "version": "6.1.4"},
{"build": "10B500", "version": "6.1.6"},
{"build": "11B511", "version": "7.0.3"},
{"build": "11B554a", "version": "7.0.4"},
{"build": "11B601", "version": "7.0.5"},
{"build": "11B651", "version": "7.0.6"},
{"build": "11D169", "version": "7.1"},
{"build": "11D167", "version": "7.1"},
{"build": "11D201", "version": "7.1.1"},
{"build": "11D257", "version": "7.1.2"},
{"build": "12A365", "version": "8.0"},
{"build": "12A366", "version": "8.0"},
{"build": "12A402", "version": "8.0.1"},
{"build": "12A405", "version": "8.0.2"},
{"build": "12B411", "version": "8.1"},
{"build": "12B435", "version": "8.1.1"},
{"build": "12B436", "version": "8.1.1"},
{"build": "12B440", "version": "8.1.2"},
{"build": "12B466", "version": "8.1.3"},
{"build": "12D508", "version": "8.2"},
{"build": "12F70", "version": "8.3"},
{"build": "12H143", "version": "8.4"},
{"build": "12H321", "version": "8.4.1"},
{"build": "13A344", "version": "9.0"},
{"build": "13A342", "version": "9.0"},
{"build": "13A343", "version": "9.0"},
{"build": "13A404", "version": "9.0.1"},
{"build": "13A405", "version": "9.0.1"},
{"build": "13A452", "version": "9.0.2"},
{"build": "13B143", "version": "9.1"},
{"build": "13C75", "version": "9.2"},
{"build": "13D15", "version": "9.2.1"},
{"build": "13D20", "version": "9.2.1"},
{"build": "13E237", "version": "9.3"},
{"build": "13E233", "version": "9.3"},
{"build": "13D15", "version": "9.2.1"},
{"build": "13C75", "version": "9.2"},
{"build": "13B143", "version": "9.1"},
{"build": "13A452", "version": "9.0.2"},
{"build": "13A404", "version": "9.0.1"},
{"build": "13A344", "version": "9.0"},
{"build": "12H321", "version": "8.4.1"},
{"build": "12H143", "version": "8.4"},
{"build": "12F70", "version": "8.3"},
{"build": "12D508", "version": "8.2"},
{"build": "12B466", "version": "8.1.3"},
{"build": "12B440", "version": "8.1.2"},
{"build": "12B435", "version": "8.1.1"},
{"build": "12B411", "version": "8.1"},
{"build": "12A405", "version": "8.0.2"},
{"build": "12A402", "version": "8.0.1"},
{"build": "12A365", "version": "8.0"},
{"build": "10B145", "version": "6.1.1"},
{"build": "10B142", "version": "6.1"},
{"build": "9B179", "version": "5.1"},
{"build": "9A406", "version": "5.0.1"},
{"build": "14G61", "version": "10.3.4"},
{"build": "14G60", "version": "10.3.3"},
{"build": "14F89", "version": "10.3.2"},
{"build": "14E304", "version": "10.3.1"},
{"build": "14E277", "version": "10.3"},
{"build": "14D27", "version": "10.2.1"},
{"build": "14C92", "version": "10.2"},
{"build": "13E234", "version": "9.3"},
{"build": "13E238", "version": "9.3.1"},
{"build": "13F69", "version": "9.3.2"},
{"build": "13G34", "version": "9.3.3"},
{"build": "13G35", "version": "9.3.4"},
{"build": "13G36", "version": "9.3.5"},
{"build": "13G37", "version": "9.3.6"},
{"build": "14A403", "version": "10.0.1"},
{"build": "14A456", "version": "10.0.2"},
{"build": "14A551", "version": "10.0.3"},
{"build": "14B72", "version": "10.1"},
{"build": "14B72c", "version": "10.1"},
{"build": "14B150", "version": "10.1.1"},
{"build": "14B100", "version": "10.1.1"},
{"build": "14B72", "version": "10.1"},
{"build": "14A456", "version": "10.0.2"},
{"build": "14A403", "version": "10.0.1"},
{"build": "10B350", "version": "6.1.4"},
{"build": "10B143", "version": "6.1"},
{"build": "10A551", "version": "6.0.2"},
{"build": "10A525", "version": "6.0.1"},
{"build": "10A405", "version": "6.0"},
{"build": "11B601", "version": "7.0.5"},
{"build": "18F72", "version": "14.6"},
{"build": "18E199", "version": "14.5"},
{"build": "18E212", "version": "14.5.1"},
{"build": "14C92", "version": "10.2"},
{"build": "14D27", "version": "10.2.1"},
{"build": "14E277", "version": "10.3"},
{"build": "14E304", "version": "10.3.1"},
{"build": "14F89", "version": "10.3.2"},
{"build": "14G60", "version": "10.3.3"},
{"build": "14G61", "version": "10.3.4"},
{"build": "15A372", "version": "11.0"},
{"build": "15A402", "version": "11.0.1"},
{"build": "15A421", "version": "11.0.2"},
{"build": "15A432", "version": "11.0.3"},
{"build": "15B93", "version": "11.1"},
{"build": "15B150", "version": "11.1.1"},
{"build": "15B202", "version": "11.1.2"},
{"build": "15C114", "version": "11.2"},
{"build": "15C153", "version": "11.2.1"},
{"build": "15C202", "version": "11.2.2"},
{"build": "15D60", "version": "11.2.5"},
{"build": "15D100", "version": "11.2.6"},
{"build": "15E216", "version": "11.3"},
{"build": "15E302", "version": "11.3.1"},
{"build": "15F79", "version": "11.4"},
{"build": "15G77", "version": "11.4.1"},
{"build": "16A366", "version": "12.0"},
{"build": "16A404", "version": "12.0.1"},
{"build": "16A405", "version": "12.0.1"},
{"build": "16B92", "version": "12.1"},
{"build": "16B94", "version": "12.1"},
{"build": "16B93", "version": "12.1"},
{"build": "16C50", "version": "12.1.1"},
{"build": "16C104", "version": "12.1.2"},
{"build": "16C101", "version": "12.1.2"},
{"build": "16D39", "version": "12.1.3"},
{"build": "16D40", "version": "12.1.3"},
{"build": "16D57", "version": "12.1.4"},
{"build": "16E227", "version": "12.2"},
{"build": "16F156", "version": "12.3"},
{"build": "16F203", "version": "12.3.1"},
{"build": "16F250", "version": "12.3.2"},
{"build": "16G77", "version": "12.4"},
{"build": "16G102", "version": "12.4.1"},
{"build": "16G114", "version": "12.4.2"},
{"build": "16G130", "version": "12.4.3"},
{"build": "16G161", "version": "12.4.5"},
{"build": "16G183", "version": "12.4.6"},
{"build": "16G192", "version": "12.4.7"},
{"build": "16G201", "version": "12.4.8"},
{"build": "16H5", "version": "12.4.9"},
{"build": "16H20", "version": "12.5"},
{"build": "16H22", "version": "12.5.1"},
{"build": "17A577", "version": "13.0"},
{"build": "17A844", "version": "13.1"},
{"build": "17A854", "version": "13.1.1"},
{"build": "17A860", "version": "13.1.2"},
{"build": "17A861", "version": "13.1.2"},
{"build": "17A878", "version": "13.1.3"},
{"build": "17B84", "version": "13.2"},
{"build": "17B102", "version": "13.2.2"},
{"build": "17B111", "version": "13.2.3"},
{"build": "17C54", "version": "13.3"},
{"build": "17D50", "version": "13.3.1"},
{"build": "17E255", "version": "13.4"},
{"build": "17E262", "version": "13.4.1"},
{"build": "17E8258", "version": "13.4.1"},
{"build": "17F75", "version": "13.5"},
{"build": "17F80", "version": "13.5.1"},
{"build": "17G68", "version": "13.6"},
{"build": "17G80", "version": "13.6.1"},
{"build": "17H35", "version": "13.7"},
{"build": "18A373", "version": "14.0"},
{"build": "18A393", "version": "14.0.1"},
{"build": "18A8395", "version": "14.1"},
{"build": "18B92", "version": "14.2"},
{"build": "18C66", "version": "14.3"},
{"build": "18D52", "version": "14.4"},
{"build": "18D61", "version": "14.4.1"},
{"build": "18D70", "version": "14.4.2"},
{"build": "18C66", "version": "14.3"},
{"build": "18B92", "version": "14.2"},
{"build": "18A8395", "version": "14.1"},
{"build": "18A393", "version": "14.0.1"},
{"build": "18A373", "version": "14.0"},
{"build": "17H35", "version": "13.7"},
{"build": "17G80", "version": "13.6.1"},
{"build": "17G68", "version": "13.6"},
{"build": "17F80", "version": "13.5.1"},
{"build": "17F75", "version": "13.5"},
{"build": "17E262", "version": "13.4.1"},
{"build": "17E255", "version": "13.4"},
{"build": "17C54", "version": "13.3"},
{"build": "17D50", "version": "13.3.1"},
{"build": "17B111", "version": "13.2.3"},
{"build": "17B102", "version": "13.2.2"},
{"build": "17B84", "version": "13.2"},
{"build": "17A878", "version": "13.1.3"},
{"build": "17A860", "version": "13.1.2"},
{"build": "17A854", "version": "13.1.1"},
{"build": "17A844", "version": "13.1"},
{"build": "17A577", "version": "13.0"},
{"build": "16H22", "version": "12.5.1"},
{"build": "16H20", "version": "12.5"},
{"build": "16H5", "version": "12.4.9"},
{"build": "16G201", "version": "12.4.8"},
{"build": "16G192", "version": "12.4.7"},
{"build": "16G183", "version": "12.4.6"},
{"build": "16G161", "version": "12.4.5"},
{"build": "16G130", "version": "12.4.3"},
{"build": "16G114", "version": "12.4.2"},
{"build": "16G102", "version": "12.4.1"},
{"build": "16G77", "version": "12.4"},
{"build": "16F203", "version": "12.3.1"},
{"build": "16F156", "version": "12.3"},
{"build": "16E227", "version": "12.2"},
{"build": "16D57", "version": "12.1.4"},
{"build": "16D39", "version": "12.1.3"},
{"build": "16C104", "version": "12.1.2"},
{"build": "16C101", "version": "12.1.2"},
{"build": "16C50", "version": "12.1.1"},
{"build": "16B92", "version": "12.1"},
{"build": "16A404", "version": "12.0.1"},
{"build": "16A366", "version": "12.0"},
{"build": "15G77", "version": "11.4.1"},
{"build": "15F79", "version": "11.4"},
{"build": "15E302", "version": "11.3.1"},
{"build": "15E216", "version": "11.3"},
{"build": "15D100", "version": "11.2.6"},
{"build": "15D60", "version": "11.2.5"},
{"build": "15C202", "version": "11.2.2"},
{"build": "15C153", "version": "11.2.1"},
{"build": "15C114", "version": "11.2"},
{"build": "15B202", "version": "11.1.2"},
{"build": "15B150", "version": "11.1.1"},
{"build": "15B93", "version": "11.1"},
{"build": "15A432", "version": "11.0.3"},
{"build": "15A421", "version": "11.0.2"},
{"build": "15A402", "version": "11.0.1"},
{"build": "15A372", "version": "11.0"},
{"build": "13D20", "version": "9.2.1"},
{"build": "12B436", "version": "8.1.1"},
{"build": "12A366", "version": "8.0"},
{"build": "13E234", "version": "9.3"},
{"build": "13A405", "version": "9.0.1"},
{"build": "13A342", "version": "9.0"},
{"build": "13A343", "version": "9.0"},
{"build": "14B72c", "version": "10.1"},
{"build": "14A551", "version": "10.0.3"},
{"build": "16F250", "version": "12.3.2"},
{"build": "17A861", "version": "13.1.2"},
{"build": "16D40", "version": "12.1.3"},
{"build": "16A405", "version": "12.0.1"},
{"build": "16B94", "version": "12.1"},
{"build": "16B93", "version": "12.1"},
{"build": "17E8258", "version": "13.4.1"}
{"build": "18E199", "version": "14.5"},
{"build": "18E212", "version": "14.5.1"},
{"build": "18F72", "version": "14.6"},
{"build": "18G69", "version": "14.7"},
{"build": "18G82", "version": "14.7.1"},
]
def get_device_desc_from_id(identifier, devices_list=IPHONE_MODELS):
for model in IPHONE_MODELS:
if identifier == model["identifier"]:
return model["description"]
def find_version_by_build(build):
build = build.upper()
for version in IPHONE_IOS_VERSIONS: