mvt/mvt/ios/cli.py

250 lines
10 KiB
Python
Raw Normal View History

2021-07-16 06:05:01 +00:00
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2022 Claudio Guarnieri.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
2021-07-16 06:05:01 +00:00
2021-07-30 09:40:09 +00:00
import logging
2021-07-16 06:05:01 +00:00
import os
2021-07-30 09:40:09 +00:00
import click
2021-07-16 06:05:01 +00:00
from rich.logging import RichHandler
2021-07-31 08:13:18 +00:00
from rich.prompt import Prompt
2021-07-16 06:05:01 +00:00
from mvt.common.cmd_check_iocs import CmdCheckIOCS
2022-01-11 15:02:44 +00:00
from mvt.common.help import (HELP_MSG_FAST, HELP_MSG_IOC,
HELP_MSG_LIST_MODULES, HELP_MSG_MODULE,
2022-06-28 18:35:52 +00:00
HELP_MSG_OUTPUT, HELP_MSG_SERIAL)
2021-08-26 10:40:45 +00:00
from mvt.common.logo import logo
2021-07-16 06:05:01 +00:00
from mvt.common.options import MutuallyExclusiveOption
from mvt.common.updates import IndicatorsUpdates
2021-07-16 06:05:01 +00:00
2022-06-16 13:18:50 +00:00
from .cmd_check_backup import CmdIOSCheckBackup
from .cmd_check_fs import CmdIOSCheckFS
2022-06-28 18:35:52 +00:00
from .cmd_check_usb import CmdIOSCheckUSB
2021-07-16 06:05:01 +00:00
from .decrypt import DecryptBackup
2021-08-15 11:14:18 +00:00
from .modules.backup import BACKUP_MODULES
from .modules.fs import FS_MODULES
from .modules.mixed import MIXED_MODULES
2021-07-16 06:05:01 +00:00
# Setup logging using Rich.
LOG_FORMAT = "[%(name)s] %(message)s"
logging.basicConfig(level="INFO", format=LOG_FORMAT, handlers=[
RichHandler(show_path=False, log_time_format="%X")])
log = logging.getLogger(__name__)
2021-07-31 08:13:18 +00:00
# Set this environment variable to a password if needed.
MVT_IOS_BACKUP_PASSWORD = "MVT_IOS_BACKUP_PASSWORD"
2021-07-16 06:05:01 +00:00
2021-11-19 14:27:51 +00:00
2021-07-16 06:05:01 +00:00
#==============================================================================
# Main
#==============================================================================
@click.group(invoke_without_command=False)
def cli():
2021-08-26 10:40:45 +00:00
logo()
2021-07-16 06:05:01 +00:00
2021-09-17 12:19:03 +00:00
#==============================================================================
# Command: version
#==============================================================================
@cli.command("version", help="Show the currently installed version of MVT")
def version():
return
2021-07-16 06:05:01 +00:00
#==============================================================================
# Command: decrypt-backup
#==============================================================================
@cli.command("decrypt-backup", help="Decrypt an encrypted iTunes backup")
@click.option("--destination", "-d", required=True,
help="Path to the folder where to store the decrypted backup")
@click.option("--password", "-p", cls=MutuallyExclusiveOption,
help=f"Password to use to decrypt the backup (or, set {MVT_IOS_BACKUP_PASSWORD} environment variable)",
2021-07-16 06:05:01 +00:00
mutually_exclusive=["key_file"])
@click.option("--key-file", "-k", cls=MutuallyExclusiveOption,
type=click.Path(exists=True),
help="File containing raw encryption key to use to decrypt the backup",
mutually_exclusive=["password"])
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
2021-08-11 01:55:36 +00:00
@click.pass_context
def decrypt_backup(ctx, destination, password, key_file, backup_path):
2021-07-16 06:05:01 +00:00
backup = DecryptBackup(backup_path, destination)
if key_file:
if MVT_IOS_BACKUP_PASSWORD in os.environ:
2021-08-12 10:48:29 +00:00
log.info("Ignoring environment variable, using --key-file '%s' instead",
MVT_IOS_BACKUP_PASSWORD, key_file)
2021-07-31 08:13:18 +00:00
2021-07-16 06:05:01 +00:00
backup.decrypt_with_key_file(key_file)
elif password:
2021-07-31 08:13:18 +00:00
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if MVT_IOS_BACKUP_PASSWORD in os.environ:
2021-08-12 10:48:29 +00:00
log.info("Ignoring %s environment variable, using --password argument instead",
MVT_IOS_BACKUP_PASSWORD)
2021-07-31 08:13:18 +00:00
backup.decrypt_with_password(password)
elif MVT_IOS_BACKUP_PASSWORD in os.environ:
log.info("Using password from %s environment variable", MVT_IOS_BACKUP_PASSWORD)
backup.decrypt_with_password(os.environ[MVT_IOS_BACKUP_PASSWORD])
2021-07-16 06:05:01 +00:00
else:
2021-07-31 08:27:44 +00:00
sekrit = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(sekrit)
2021-07-16 06:05:01 +00:00
2021-08-11 01:55:36 +00:00
if not backup.can_process():
ctx.exit(1)
backup.process_backup()
2021-07-23 06:52:52 +00:00
#==============================================================================
# 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 {MVT_IOS_BACKUP_PASSWORD} environment variable)")
2021-07-23 06:52:52 +00:00
@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))
2022-06-17 15:16:20 +00:00
def extract_key(password, key_file, backup_path):
backup = DecryptBackup(backup_path)
if password:
2021-07-31 08:13:18 +00:00
log.info("Your password may be visible in the process table because it was supplied on the command line!")
if MVT_IOS_BACKUP_PASSWORD in os.environ:
2021-08-12 10:48:29 +00:00
log.info("Ignoring %s environment variable, using --password argument instead",
MVT_IOS_BACKUP_PASSWORD)
elif MVT_IOS_BACKUP_PASSWORD in os.environ:
log.info("Using password from %s environment variable", MVT_IOS_BACKUP_PASSWORD)
password = os.environ[MVT_IOS_BACKUP_PASSWORD]
else:
2021-07-31 08:27:44 +00:00
password = Prompt.ask("Enter backup password", password=True)
backup.decrypt_with_password(password)
backup.get_key()
2021-07-23 06:52:52 +00:00
if key_file:
backup.write_key(key_file)
2021-07-23 06:52:52 +00:00
2021-07-16 06:05:01 +00:00
#==============================================================================
# Command: check-backup
#==============================================================================
@cli.command("check-backup", help="Extract artifacts from an iTunes backup")
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
2021-08-21 13:48:52 +00:00
default=[], help=HELP_MSG_IOC)
@click.option("--output", "-o", type=click.Path(exists=False), help=HELP_MSG_OUTPUT)
@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST)
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
2021-07-16 06:05:01 +00:00
@click.argument("BACKUP_PATH", type=click.Path(exists=True))
2021-08-12 10:48:29 +00:00
@click.pass_context
2022-06-17 15:16:20 +00:00
def check_backup(ctx, iocs, output, fast, list_modules, module, backup_path):
2022-06-16 13:18:50 +00:00
cmd = CmdIOSCheckBackup(target_path=backup_path, results_path=output,
ioc_files=iocs, module_name=module, fast_mode=fast)
2021-07-16 06:05:01 +00:00
2022-06-16 13:18:50 +00:00
if list_modules:
cmd.list_modules()
2021-07-16 06:05:01 +00:00
return
log.info("Checking iTunes backup located at: %s", backup_path)
2022-06-16 13:18:50 +00:00
cmd.run()
if len(cmd.timeline_detected) > 0:
log.warning("The analysis of the backup produced %d detections!",
2022-06-16 13:18:50 +00:00
len(cmd.timeline_detected))
2021-07-16 06:05:01 +00:00
#==============================================================================
# Command: check-fs
#==============================================================================
@cli.command("check-fs", help="Extract artifacts from a full filesystem dump")
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
2021-08-21 13:48:52 +00:00
default=[], help=HELP_MSG_IOC)
@click.option("--output", "-o", type=click.Path(exists=False), help=HELP_MSG_OUTPUT)
@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST)
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
2021-07-16 06:05:01 +00:00
@click.argument("DUMP_PATH", type=click.Path(exists=True))
2021-08-12 10:48:29 +00:00
@click.pass_context
2022-06-17 15:16:20 +00:00
def check_fs(ctx, iocs, output, fast, list_modules, module, dump_path):
2022-06-16 13:18:50 +00:00
cmd = CmdIOSCheckFS(target_path=dump_path, results_path=output,
ioc_files=iocs, module_name=module, fast_mode=fast)
2021-07-16 06:05:01 +00:00
2022-06-16 13:18:50 +00:00
if list_modules:
cmd.list_modules()
2021-07-16 06:05:01 +00:00
return
2022-06-16 13:18:50 +00:00
log.info("Checking iOS filesystem located at: %s", dump_path)
cmd.run()
if len(cmd.timeline_detected) > 0:
log.warning("The analysis of the iOS filesystem produced %d detections!",
len(cmd.timeline_detected))
2021-07-16 06:05:01 +00:00
2022-01-29 14:13:35 +00:00
2021-07-16 06:05:01 +00:00
#==============================================================================
# Command: check-iocs
#==============================================================================
@cli.command("check-iocs", help="Compare stored JSON results to provided indicators")
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
default=[], help=HELP_MSG_IOC)
2021-08-21 13:48:52 +00:00
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
2021-07-16 06:05:01 +00:00
@click.argument("FOLDER", type=click.Path(exists=True))
@click.pass_context
def check_iocs(ctx, iocs, list_modules, module, folder):
cmd = CmdCheckIOCS(target_path=folder, ioc_files=iocs, module_name=module)
cmd.modules = BACKUP_MODULES + FS_MODULES + MIXED_MODULES
2021-07-16 06:05:01 +00:00
if list_modules:
cmd.list_modules()
2021-07-16 06:05:01 +00:00
return
cmd.run()
#==============================================================================
2022-01-11 14:59:01 +00:00
# Command: download-iocs
#==============================================================================
2022-01-11 14:59:01 +00:00
@cli.command("download-iocs", help="Download public STIX2 indicators")
2022-01-14 00:52:57 +00:00
def download_iocs():
ioc_updates = IndicatorsUpdates()
ioc_updates.update()
2022-06-28 18:35:52 +00:00
2022-06-28 18:35:52 +00:00
#==============================================================================
# Command: check-usb
#==============================================================================
@cli.command("check-usb", help="Extract artifacts from a live iPhone through USB / lockdown")
@click.option("--serial", "-s", type=str, help=HELP_MSG_SERIAL)
@click.option("--iocs", "-i", type=click.Path(exists=True), multiple=True,
default=[], help=HELP_MSG_IOC)
@click.option("--output", "-o", type=click.Path(exists=False), help=HELP_MSG_OUTPUT)
@click.option("--fast", "-f", is_flag=True, help=HELP_MSG_FAST)
@click.option("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
# TODO: serial
# @click.argument("BACKUP_PATH", type=click.Path(exists=True))
@click.pass_context
def check_usb(ctx, serial, iocs, output, fast, list_modules, module):
cmd = CmdIOSCheckUSB(results_path=output, ioc_files=iocs,
module_name=module, fast_mode=fast,
serial=serial)
2022-06-28 18:35:52 +00:00
if list_modules:
cmd.list_modules()
return
log.info("Checking iPhone through USB, this may take a while")
cmd.run()
if len(cmd.timeline_detected) > 0:
log.warning("The analysis of the data produced %d detections!",
len(cmd.timeline_detected))