1
mirror of https://github.com/mvt-project/mvt synced 2025-10-21 22:42:15 +02:00

Compare commits

..

35 Commits

Author SHA1 Message Date
Nex
99e14ad8b0 Bumped version 2022-11-13 01:11:52 +01:00
tek
deaa68a2e0 Adds iOS 16.1.1 in iOS versions 2022-11-11 12:11:46 +01:00
tek
07f819bf5f Adds new iPhone hardware 2022-11-02 10:41:33 +01:00
tek
51fdfce7f4 Adds iOS 16.1 to iOS versions 2022-10-31 11:17:25 +01:00
Nex
41e05a107e Merge branch 'main' of github.com:mvt-project/mvt 2022-10-15 11:26:55 +02:00
Nex
e559fb223b Upgraded dependencies 2022-10-15 11:26:40 +02:00
Nex
b69bb92f3d Merge pull request #279 from Niek/main
Dockerfile improvements, support arm64 builds
2022-10-15 11:14:40 +02:00
Nex
42e8e41b7d Merge branch 'besendorf-patch-1' 2022-10-15 11:11:57 +02:00
Nex
00b7314395 Added quotes 2022-10-15 11:11:47 +02:00
Nex
39a8bf236d Merge branch 'patch-1' of github.com:besendorf/mvt into besendorf-patch-1 2022-10-15 11:11:29 +02:00
tek
d268b17284 Adds missing module in androidqf module list 2022-10-14 15:01:08 +02:00
tek
66c015bc23 Improves check-androidqf tests 2022-10-11 13:07:24 +02:00
tek
ba0106c476 Adds SMS androidqf module and improves tests 2022-10-11 12:41:42 +02:00
tek
41826d7951 Fixes PEP8 syntax issue 2022-10-05 15:30:39 +02:00
Nex
4e0a393a02 Bumped version 2022-10-01 12:40:04 +02:00
Tek
c3dc4174fc Adds detection for disabled security packages in Android (#306)
* Adds detection for disabled security packages in Android

* Update detection of disabled security packages
2022-09-26 12:17:09 +02:00
tek
e1d1b6c5de Fixes a minor issue in the iOS manifest module 2022-09-26 12:07:52 +02:00
tek
d0a893841b Adds new iOS versions 2022-09-12 23:49:33 +02:00
Nex
d4e99661c7 Merge pull request #300 from andefined/fix-idstatuscache-error
Fixed missing root_paths check for ios/idstatuscache module
2022-09-07 09:29:09 +02:00
Nex
6a00d3a14d Closing handle to ZipFile 2022-09-05 12:21:11 +02:00
Nex
a863209abb Added check-androidqf command 2022-09-05 12:12:36 +02:00
Nex
4c7db02da4 Bumped version 2022-09-01 09:42:03 +02:00
Nex
92dfefbdeb Added some support for patterns in backups' relative paths 2022-08-31 19:34:59 +02:00
Nex
8988adcf77 Warnings should be reserved for detections ideally 2022-08-25 17:22:24 +02:00
andefined
91667b0ded Fixed missing root_paths check for ios/idstatuscache module 2022-08-24 18:54:45 +03:00
tek
2365175dbd Adds check of process name in paths in indicators 2022-08-23 13:18:42 +02:00
Nex
528d43b914 Merge branch 'main' of github.com:mvt-project/mvt 2022-08-22 21:13:22 +02:00
Nex
f952ba5119 Removed comment with odd char 2022-08-22 21:12:59 +02:00
besendorf
d61b2751f1 Add pip command for update
Adds the pip comman for updating mvt. I think this would be helpfull for novice users as it already has been asked here: https://github.com/mvt-project/mvt/discussions/261
Also I sometimes forget the command too ;)
2022-08-22 12:20:56 +02:00
Nex
b4ed2c6ed4 Added commented backup ID 2022-08-22 10:40:36 +02:00
Nex
3eed1d6edf Sorted imports 2022-08-22 10:30:58 +02:00
Nex
83ef545cd1 Merge pull request #298 from jons44/patch-1
Fixed idevicebackup2 syntax
2022-08-20 16:29:57 +02:00
jons44
5d4fbec62b Fixed idevicebackup2 syntax 2022-08-19 19:34:12 +02:00
Nex
fa7d6166f4 Removed legacy print 2022-08-19 15:19:46 +02:00
Niek van der Maas
067402831a Dockerfile improvements, support arm64 builds 2022-06-02 09:22:07 +02:00
53 changed files with 1491 additions and 67 deletions

View File

@@ -1,4 +1,4 @@
FROM ubuntu:20.04
FROM ubuntu:22.04
# Ref. https://github.com/mvt-project/mvt
@@ -7,13 +7,12 @@ LABEL vcs-url="https://github.com/mvt-project/mvt"
LABEL description="MVT is a forensic tool to look for signs of infection in smartphone devices."
ENV PIP_NO_CACHE_DIR=1
ENV DEBIAN_FRONTEND=noninteractive
# Fixing major OS dependencies
# ----------------------------
RUN apt update \
&& apt install -y python3 python3-pip libusb-1.0-0-dev \
&& apt install -y wget unzip\
&& DEBIAN_FRONTEND=noninteractive apt-get -y install default-jre-headless \
&& apt install -y python3 python3-pip libusb-1.0-0-dev wget unzip default-jre-headless adb \
# Install build tools for libimobiledevice
# ----------------------------------------
@@ -67,18 +66,9 @@ RUN mkdir /opt/abe \
# Create alias for abe
&& echo 'alias abe="java -jar /opt/abe/abe.jar"' >> ~/.bashrc
# Install Android Platform Tools
# ------------------------------
RUN mkdir /opt/android \
&& wget -q https://dl.google.com/android/repository/platform-tools-latest-linux.zip \
&& unzip platform-tools-latest-linux.zip -d /opt/android \
# Create alias for adb
&& echo 'alias adb="/opt/android/platform-tools/adb"' >> ~/.bashrc
# Generate adb key folder
# ------------------------------
RUN mkdir /root/.android && /opt/android/platform-tools/adb keygen /root/.android/adbkey
RUN mkdir /root/.android && adb keygen /root/.android/adbkey
# Setup investigations environment
# --------------------------------

View File

@@ -1,4 +1,4 @@
Using Docker simplifies having all the required dependencies and tools (including most recent versions of [libimobiledevice](https://libimobiledevice.org)) readily installed.
Using Docker simplifies having all the required dependencies and tools (including most recent versions of [libimobiledevice](https://libimobiledevice.org)) readily installed. Note that this requires a Linux host, as Docker for Windows and Mac [doesn't support passing through USB devices](https://docs.docker.com/desktop/faqs/#can-i-pass-through-a-usb-device-to-a-container).
Install Docker following the [official documentation](https://docs.docker.com/get-docker/).
@@ -10,11 +10,6 @@ cd mvt
docker build -t mvt .
```
Optionally, you may need to specify your platform to Docker in order to build successfully (Apple M1)
```bash
docker build --platform amd64 -t mvt .
```
Test if the image was created successfully:
```bash

View File

@@ -3,10 +3,10 @@
If you have correctly [installed libimobiledevice](../install.md) you can easily generate an iTunes backup using the `idevicebackup2` tool included in the suite. First, you might want to ensure that backup encryption is enabled (**note: encrypted backup contain more data than unencrypted backups**):
```bash
idevicebackup2 -i backup encryption on
idevicebackup2 -i encryption on
```
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 -i backup changepw`, or by turning off encryption (`idevicebackup2 -i backup encryption off`) and turning it back on again.
Note that if a backup password was previously set on this device, you might need to use the same or change it. You can try changing password using `idevicebackup2 -i changepw`, or by turning off encryption (`idevicebackup2 -i encryption off`) and turning it back on again.
If you are not able to recover or change the password, you should try to disable encryption and obtain an unencrypted backup.

View File

@@ -16,6 +16,7 @@ from mvt.common.logo import logo
from mvt.common.updates import IndicatorsUpdates
from .cmd_check_adb import CmdAndroidCheckADB
from .cmd_check_androidqf import CmdAndroidCheckAndroidQF
from .cmd_check_backup import CmdAndroidCheckBackup
from .cmd_check_bugreport import CmdAndroidCheckBugreport
from .cmd_download_apks import DownloadAPKs
@@ -121,9 +122,9 @@ def check_adb(ctx, serial, iocs, output, fast, list_modules, module):
cmd.run()
if len(cmd.timeline_detected) > 0:
if cmd.detected_count > 0:
log.warning("The analysis of the Android device produced %d detections!",
len(cmd.timeline_detected))
cmd.detected_count)
#==============================================================================
@@ -151,9 +152,9 @@ def check_bugreport(ctx, iocs, output, list_modules, module, bugreport_path):
cmd.run()
if len(cmd.timeline_detected) > 0:
if cmd.detected_count > 0:
log.warning("The analysis of the Android bug report produced %d detections!",
len(cmd.timeline_detected))
cmd.detected_count)
#==============================================================================
@@ -179,9 +180,39 @@ def check_backup(ctx, iocs, output, list_modules, backup_path):
cmd.run()
if len(cmd.timeline_detected) > 0:
if cmd.detected_count > 0:
log.warning("The analysis of the Android backup produced %d detections!",
len(cmd.timeline_detected))
cmd.detected_count)
#==============================================================================
# Command: check-androidqf
#==============================================================================
@cli.command("check-androidqf", help="Check data collected with AndroidQF")
@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("--list-modules", "-l", is_flag=True, help=HELP_MSG_LIST_MODULES)
@click.option("--module", "-m", help=HELP_MSG_MODULE)
@click.argument("ANDROIDQF_PATH", type=click.Path(exists=True))
@click.pass_context
def check_androidqf(ctx, iocs, output, list_modules, module, androidqf_path):
cmd = CmdAndroidCheckAndroidQF(target_path=androidqf_path,
results_path=output, ioc_files=iocs,
module_name=module)
if list_modules:
cmd.list_modules()
return
log.info("Checking AndroidQF acquisition at path: %s", androidqf_path)
cmd.run()
if cmd.detected_count > 0:
log.warning("The analysis of the AndroidQF acquisition produced %d detections!",
cmd.detected_count)
#==============================================================================

View File

@@ -0,0 +1,32 @@
# 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/
import logging
from typing import Optional
from mvt.common.command import Command
from .modules.androidqf import ANDROIDQF_MODULES
log = logging.getLogger(__name__)
class CmdAndroidCheckAndroidQF(Command):
def __init__(
self,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
ioc_files: Optional[list] = None,
module_name: Optional[str] = None,
serial: Optional[str] = None,
fast_mode: Optional[bool] = False,
) -> None:
super().__init__(target_path=target_path, results_path=results_path,
ioc_files=ioc_files, module_name=module_name,
serial=serial, fast_mode=fast_mode, log=log)
self.name = "check-androidqf"
self.modules = ANDROIDQF_MODULES

View File

@@ -58,3 +58,7 @@ class CmdAndroidCheckBugreport(Command):
module.from_zip(self.bugreport_archive, self.bugreport_files)
else:
module.from_folder(self.target_path, self.bugreport_files)
def finish(self) -> None:
if self.bugreport_archive:
self.bugreport_archive.close()

View File

@@ -272,8 +272,8 @@ class AndroidExtraction(MVTModule):
self._adb_command(f"rm -f {new_remote_path}")
def _generate_backup(self, package_name: str) -> bytes:
self.log.warning("Please check phone and accept Android backup prompt. "
"You may need to set a backup password. \a")
self.log.info("Please check phone and accept Android backup prompt. "
"You may need to set a backup password. \a")
# TODO: Base64 encoding as temporary fix to avoid byte-mangling over
# the shell transport...
@@ -300,7 +300,7 @@ class AndroidExtraction(MVTModule):
except InvalidBackupPassword:
self.log.error("You provided the wrong password! Please try again...")
self.log.warn("All attempts to decrypt backup with password failed!")
self.log.error("All attempts to decrypt backup with password failed!")
return None

View File

@@ -66,6 +66,23 @@ ROOT_PACKAGES = [
"com.kingouser.com",
"com.topjohnwu.magisk",
]
SECURITY_PACKAGES = [
"com.policydm",
"com.samsung.android.app.omcagent",
"com.samsung.android.securitylogagent",
"com.sec.android.soagent",
]
SYSTEM_UPDATE_PACKAGES = [
"com.android.updater",
"com.google.android.gms",
"com.huawei.android.hwouc",
"com.lge.lgdmsclient",
"com.motorola.ccc.ota",
"com.oneplus.opbackup",
"com.oppo.ota",
"com.transsion.systemupdate",
"com.wssyncmldm",
]
class Packages(AndroidExtraction):
@@ -122,6 +139,14 @@ class Packages(AndroidExtraction):
self.detected.append(result)
continue
if result["package_name"] in SECURITY_PACKAGES and result["disabled"]:
self.log.warning("Found a security package disabled: \"%s\"",
result["package_name"])
if result["package_name"] in SYSTEM_UPDATE_PACKAGES and result["disabled"]:
self.log.warning("System OTA update package \"%s\" disabled on the phone",
result["package_name"])
if not self.indicators:
continue
@@ -192,7 +217,6 @@ class Packages(AndroidExtraction):
@staticmethod
def parse_package_for_details(output: str) -> dict:
# Get only the package information
lines = []
in_packages = False
for line in output.splitlines():

View File

@@ -30,7 +30,21 @@ class Processes(AndroidExtraction):
return
for result in self.results:
ioc = self.indicators.check_app_id(result.get("name", ""))
proc_name = result.get("proc_name", "")
if not proc_name:
continue
# Skipping this process because of false positives.
if result["proc_name"] == "gatekeeperd":
continue
ioc = self.indicators.check_app_id(proc_name)
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
ioc = self.indicators.check_process(proc_name)
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
@@ -38,7 +52,7 @@ class Processes(AndroidExtraction):
def run(self) -> None:
self._adb_connect()
output = self._adb_command("ps -e")
output = self._adb_command("ps -A")
for line in output.splitlines()[1:]:
line = line.strip()

View File

@@ -158,7 +158,7 @@ class SMS(AndroidExtraction):
except InsufficientPrivileges:
pass
self.log.warn("No SMS database found. Trying extraction of SMS data "
self.log.info("No SMS database found. Trying extraction of SMS data "
"using Android backup feature.")
self._extract_sms_adb()

View File

@@ -0,0 +1,18 @@
# 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/
from .dumpsys_accessibility import DumpsysAccessibility
from .dumpsys_activities import DumpsysActivities
from .dumpsys_appops import DumpsysAppops
from .dumpsys_packages import DumpsysPackages
from .dumpsys_receivers import DumpsysReceivers
from .getprop import Getprop
from .processes import Processes
from .settings import Settings
from .sms import SMS
ANDROIDQF_MODULES = [DumpsysActivities, DumpsysReceivers, DumpsysAccessibility,
DumpsysAppops, Processes, Getprop, Settings, SMS,
DumpsysPackages]

View File

@@ -0,0 +1,38 @@
# 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/
import fnmatch
import logging
import os
from typing import Optional
from mvt.common.module import MVTModule
class AndroidQFModule(MVTModule):
"""This class provides a base for all Android Data analysis modules."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self._path = target_path
self._files = []
for root, dirs, files in os.walk(target_path):
for name in files:
self._files.append(os.path.join(root, name))
def _get_files_by_pattern(self, pattern):
return fnmatch.filter(self._files, pattern)

View File

@@ -0,0 +1,68 @@
# 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/
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_accessibility
from .base import AndroidQFModule
class DumpsysAccessibility(AndroidQFModule):
"""This module analyse dumpsys accessbility"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_app_id(result["package_name"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_accessibility = False
with open(dumpsys_file[0]) as handle:
for line in handle:
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
in_accessibility = True
continue
if not in_accessibility:
continue
if line.strip().startswith("-------------------------------------------------------------------------------"): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_accessibility("\n".join(lines))
for result in self.results:
self.log.info("Found installed accessibility service \"%s\"",
result.get("service"))
self.log.info("Identified a total of %d accessibility services",
len(self.results))

View File

@@ -0,0 +1,66 @@
# 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/
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
from .base import AndroidQFModule
class DumpsysActivities(AndroidQFModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self.results = results if results else {}
def check_indicators(self) -> None:
if not self.indicators:
return
for intent, activities in self.results.items():
for activity in activities:
ioc = self.indicators.check_app_id(activity["package_name"])
if ioc:
activity["matched_indicator"] = ioc
self.detected.append({intent: activity})
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_package = False
with open(dumpsys_file[0]) as handle:
for line in handle:
if line.strip() == "DUMP OF SERVICE package:":
in_package = True
continue
if not in_package:
continue
if line.strip().startswith("------------------------------------------------------------------------------"): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
self.log.info("Extracted activities for %d intents", len(self.results))

View File

@@ -0,0 +1,83 @@
# 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/
import logging
from typing import Optional, Union
from mvt.android.parsers import parse_dumpsys_appops
from .base import AndroidQFModule
class DumpsysAppops(AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append({
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to "
f"{perm['name']} : {entry['access']}",
})
return records
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if (perm["name"] == "REQUEST_INSTALL_PACKAGES"
and perm["access"] == "allow"):
self.log.info("Package %s with REQUEST_INSTALL_PACKAGES permission",
result["package_name"])
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_package = False
with open(dumpsys_file[0]) as handle:
for line in handle:
if line.startswith("DUMP OF SERVICE appops:"):
in_package = True
continue
if in_package:
if line.startswith("-------------------------------------------------------------------------------"): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_appops("\n".join(lines))
self.log.info("Identified %d applications in AppOps Manager",
len(self.results))

View File

@@ -0,0 +1,108 @@
# 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/
import logging
from datetime import datetime
from typing import Optional, Union
from mvt.android.modules.adb.packages import (DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
ROOT_PACKAGES)
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
from mvt.common.utils import convert_datetime_to_iso
from .base import AndroidQFModule
class DumpsysPackages(AndroidQFModule):
"""This module analyse dumpsys packages"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
def serialize(self, record: dict) -> Union[dict, list]:
entries = []
for entry in ["timestamp", "first_install_time", "last_update_time"]:
if entry in record:
entries.append({
"timestamp": record[entry],
"module": self.__class__.__name__,
"event": entry,
"data": f"Package {record['package_name']} "
f"({record['uid']})",
})
return entries
def check_indicators(self) -> None:
for result in self.results:
if result["package_name"] in ROOT_PACKAGES:
self.log.warning("Found an installed package related to "
"rooting/jailbreaking: \"%s\"",
result["package_name"])
self.detected.append(result)
continue
if not self.indicators:
continue
ioc = self.indicators.check_app_id(result.get("package_name", ""))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if len(dumpsys_file) != 1:
self.log.info("Dumpsys file not found")
return
with open(dumpsys_file[0]) as handle:
data = handle.read().split("\n")
package = []
in_service = False
in_package_list = False
for line in data:
if line.strip().startswith("DUMP OF SERVICE package:"):
in_service = True
continue
if in_service and line.startswith("Packages:"):
in_package_list = True
continue
if not in_service or not in_package_list:
continue
if line.strip() == "":
break
package.append(line)
self.results = parse_dumpsys_packages("\n".join(package))
for result in self.results:
dangerous_permissions_count = 0
for perm in result["permissions"]:
if perm["name"] in DANGEROUS_PERMISSIONS:
dangerous_permissions_count += 1
if dangerous_permissions_count >= DANGEROUS_PERMISSIONS_THRESHOLD:
self.log.info("Found package \"%s\" requested %d potentially dangerous permissions",
result["package_name"],
dangerous_permissions_count)
self.log.info("Extracted details on %d packages", len(self.results))

View File

@@ -0,0 +1,86 @@
# 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/
import logging
from typing import Optional
from mvt.android.modules.adb.dumpsys_receivers import (
INTENT_DATA_SMS_RECEIVED, INTENT_NEW_OUTGOING_CALL,
INTENT_NEW_OUTGOING_SMS, INTENT_PHONE_STATE, INTENT_SMS_RECEIVED)
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
from .base import AndroidQFModule
class DumpsysReceivers(AndroidQFModule):
"""This module analyse dumpsys receivers"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self.results = results if results else {}
def check_indicators(self) -> None:
if not self.indicators:
return
for intent, receivers in self.results.items():
for receiver in receivers:
if intent == INTENT_NEW_OUTGOING_SMS:
self.log.info("Found a receiver to intercept outgoing SMS messages: \"%s\"",
receiver["receiver"])
elif intent == INTENT_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming SMS messages: \"%s\"",
receiver["receiver"])
elif intent == INTENT_DATA_SMS_RECEIVED:
self.log.info("Found a receiver to intercept incoming data SMS message: \"%s\"",
receiver["receiver"])
elif intent == INTENT_PHONE_STATE:
self.log.info("Found a receiver monitoring "
"telephony state/incoming calls: \"%s\"",
receiver["receiver"])
elif intent == INTENT_NEW_OUTGOING_CALL:
self.log.info("Found a receiver monitoring outgoing calls: \"%s\"",
receiver["receiver"])
ioc = self.indicators.check_app_id(receiver["package_name"])
if ioc:
receiver["matched_indicator"] = ioc
self.detected.append({intent: receiver})
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
in_receivers = False
lines = []
with open(dumpsys_file[0]) as handle:
for line in handle:
if line.strip() == "DUMP OF SERVICE package:":
in_receivers = True
continue
if not in_receivers:
continue
if line.strip().startswith("------------------------------------------------------------------------------"): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_receiver_resolver_table("\n".join(lines))
self.log.info("Extracted receivers for %d intents", len(self.results))

View File

@@ -0,0 +1,66 @@
# 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/
import logging
from datetime import datetime, timedelta
from typing import Optional
from mvt.android.parsers import getprop
from .base import AndroidQFModule
INTERESTING_PROPERTIES = [
"gsm.sim.operator.alpha",
"gsm.sim.operator.iso-country",
"persist.sys.timezone",
"ro.boot.serialno",
"ro.build.version.sdk",
"ro.build.version.security_patch",
"ro.product.cpu.abi",
"ro.product.locale",
"ro.product.vendor.manufacturer",
"ro.product.vendor.model",
"ro.product.vendor.name"
]
class Getprop(AndroidQFModule):
"""This module extracts data from get properties."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self.results = {}
def run(self) -> None:
getprop_files = self._get_files_by_pattern("*/getprop.txt")
if not getprop_files:
self.log.info("getprop.txt file not found")
return
with open(getprop_files[0]) as f:
data = f.read()
self.results = getprop.parse_getprop(data)
for entry in self.results:
if entry in INTERESTING_PROPERTIES:
self.log.info("%s: %s", entry, self.results[entry])
if entry == "ro.build.version.security_patch":
last_patch = datetime.strptime(self.results[entry], "%Y-%m-%d")
if (datetime.now() - last_patch) > timedelta(days=6*31):
self.log.warning("This phone has not received security "
"updates for more than six months "
"(last update: %s)", self.results[entry])
self.log.info("Extracted a total of %d properties", len(self.results))

View File

@@ -0,0 +1,92 @@
# 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/
import logging
from typing import Optional
from .base import AndroidQFModule
class Processes(AndroidQFModule):
"""This module analyse running processes"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
proc_name = result.get("proc_name", "")
if not proc_name:
continue
# Skipping this process because of false positives.
if result["proc_name"] == "gatekeeperd":
continue
ioc = self.indicators.check_app_id(proc_name)
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
ioc = self.indicators.check_process(proc_name)
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def _parse_ps(self, data):
for line in data.split("\n")[1:]:
proc = line.split()
# Sometimes WCHAN is empty.
if len(proc) == 8:
proc = proc[:5] + [''] + proc[5:]
# Sometimes there is the security label.
if proc[0].startswith("u:r"):
label = proc[0]
proc = proc[1:]
else:
label = ""
# Sometimes there is no WCHAN.
if len(proc) < 9:
proc = proc[:5] + [""] + proc[5:]
self.results.append({
"user": proc[0],
"pid": int(proc[1]),
"ppid": int(proc[2]),
"virtual_memory_size": int(proc[3]),
"resident_set_size": int(proc[4]),
"wchan": proc[5],
"aprocress": proc[6],
"stat": proc[7],
"proc_name": proc[8].strip("[]"),
"label": label,
})
def run(self) -> None:
ps_files = self._get_files_by_pattern("*/ps.txt")
if not ps_files:
return
with open(ps_files[0]) as handle:
self._parse_ps(handle.read())
self.log.info("Identified %d running processes", len(self.results))

View File

@@ -0,0 +1,58 @@
# 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/
import logging
from typing import Optional
from mvt.android.modules.adb.settings import ANDROID_DANGEROUS_SETTINGS
from .base import AndroidQFModule
class Settings(AndroidQFModule):
"""This module analyse setting files"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
self.results = {}
def run(self) -> None:
for setting_file in self._get_files_by_pattern("*/settings_*.txt"):
namespace = setting_file[setting_file.rfind("_")+1:-4]
self.results[namespace] = {}
with open(setting_file) as handle:
for line in handle:
line = line.strip()
try:
key, value = line.split("=", 1)
except ValueError:
continue
try:
self.results[namespace][key] = value
except IndexError:
continue
for danger in ANDROID_DANGEROUS_SETTINGS:
if (danger["key"] == key
and danger["safe_value"] != value):
self.log.warning("Found suspicious setting \"%s = %s\" (%s)",
key, value, danger["description"])
break
self.log.info("Identified %d settings",
sum([len(val) for val in self.results.values()]))

View File

@@ -0,0 +1,85 @@
# Mobile Verification Toolkit (MVT) - Private
# Copyright (c) 2021-2022 Claudio Guarnieri.
# This file is part of MVT Private and its content is confidential.
# Please refer to the project maintainers before sharing with others.
import getpass
import logging
from typing import Optional
from mvt.android.parsers.backup import (AndroidBackupParsingError,
InvalidBackupPassword, parse_ab_header,
parse_backup_file, parse_tar_for_sms)
from .base import AndroidQFModule
class SMS(AndroidQFModule):
"""This module analyse SMS file in backup"""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
fast_mode: Optional[bool] = False,
log: logging.Logger = logging.getLogger(__name__),
results: Optional[list] = None
) -> None:
super().__init__(file_path=file_path, target_path=target_path,
results_path=results_path, fast_mode=fast_mode,
log=log, results=results)
def check_indicators(self) -> None:
if not self.indicators:
return
for message in self.results:
if "body" not in message:
continue
if self.indicators.check_domains(message["links"]):
self.detected.append(message)
def parse_backup(self, data):
header = parse_ab_header(data)
if not header["backup"]:
self.log.critical("Invalid backup format, backup.ab was not analysed")
return
password = None
if header["encryption"] != "none":
password = getpass.getpass(prompt="Backup Password: ", stream=None)
try:
tardata = parse_backup_file(data, password=password)
except InvalidBackupPassword:
self.log.critical("Invalid backup password")
return
except AndroidBackupParsingError:
self.log.critical("Impossible to parse this backup file, please use"
" Android Backup Extractor instead")
return
if not tardata:
return
try:
self.results = parse_tar_for_sms(tardata)
except AndroidBackupParsingError:
self.log.info("Impossible to read SMS from the Android Backup, "
"please extract the SMS and try extracting it with "
"Android Backup Extractor")
return
def run(self) -> None:
files = self._get_files_by_pattern("*/backup.ab")
if not files:
self.log.info("No backup data found")
return
with open(files[0], "rb") as handle:
data = handle.read()
self.parse_backup(data)
self.log.info("Identified %d SMS in backup data",
len(self.results))

View File

@@ -6,10 +6,10 @@
import logging
from typing import Optional, Union
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
from mvt.android.modules.adb.packages import (DANGEROUS_PERMISSIONS,
DANGEROUS_PERMISSIONS_THRESHOLD,
ROOT_PACKAGES)
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
from .base import BugReportModule

View File

@@ -47,6 +47,8 @@ class Command:
# We can use this to reference e.g. self.executed[0].results.
self.executed = []
self.detected_count = 0
self.timeline = []
self.timeline_detected = []
@@ -196,6 +198,8 @@ class Command:
self.executed.append(m)
self.detected_count += len(m.detected)
self.timeline.extend(m.timeline)
self.timeline_detected.extend(m.timeline_detected)

View File

@@ -44,7 +44,6 @@ class Indicators:
paths = os.environ["MVT_STIX2"].split(":")
for path in paths:
print(path)
if os.path.isfile(path):
self.parse_stix2(path)
else:
@@ -437,6 +436,27 @@ class Indicators:
return None
def check_file_path_process(self, file_path: str) -> Union[dict, None]:
"""Check the provided file path contains a process name from the
list of indicators
:param file_path: File path or file name to check against file
indicators
:type file_path: str
:returns: Indicator details if matched, otherwise None
"""
if not file_path:
return None
for ioc in self.get_iocs("processes"):
parts = file_path.split("/")
if ioc["value"] in parts:
self.log.warning("Found known suspicious process name mentioned in file at "
"path \"%s\" matching indicators from \"%s\"",
file_path, ioc["name"])
return ioc
def check_profile(self, profile_uuid: str) -> Union[dict, None]:
"""Check the provided configuration profile UUID against the list of
indicators.

View File

@@ -19,7 +19,7 @@ def check_updates() -> None:
else:
if latest_version:
rich_print(f"\t\t[bold]Version {latest_version} is available! "
"Upgrade mvt![/bold]")
"Upgrade mvt with `pip3 install -U mvt`[/bold]")
# Then we check for indicators files updates.
ioc_updates = IndicatorsUpdates()

View File

@@ -3,4 +3,4 @@
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
MVT_VERSION = "2.1.5"
MVT_VERSION = "2.2.1"

View File

@@ -162,9 +162,9 @@ def check_backup(ctx, iocs, output, fast, list_modules, module, backup_path):
cmd.run()
if len(cmd.timeline_detected) > 0:
if cmd.detected_count > 0:
log.warning("The analysis of the backup produced %d detections!",
len(cmd.timeline_detected))
cmd.detected_count)
#==============================================================================
@@ -192,9 +192,9 @@ def check_fs(ctx, iocs, output, fast, list_modules, module, dump_path):
cmd.run()
if len(cmd.timeline_detected) > 0:
if cmd.detected_count > 0:
log.warning("The analysis of the iOS filesystem produced %d detections!",
len(cmd.timeline_detected))
cmd.detected_count)
#==============================================================================

View File

@@ -80,9 +80,6 @@ class Manifest(IOSExtraction):
return records
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
if not result.get("relative_path"):
continue
@@ -95,6 +92,9 @@ class Manifest(IOSExtraction):
self.detected.append(result)
continue
if not self.indicators:
continue
if self.indicators.check_file_path("/" + result["relative_path"]):
self.detected.append(result)
continue

View File

@@ -11,6 +11,7 @@ from mvt.common.utils import convert_datetime_to_iso
from ..base import IOSExtraction
# CONF_PROFILES_EVENTS_ID = "aeb25de285ea542f7ac7c2070cddd1961e369df1"
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"

View File

@@ -107,8 +107,12 @@ class IOSExtraction(MVTModule):
(relative_path, domain))
else:
if relative_path:
cur.execute(f"{base_sql} relativePath = ?;",
(relative_path,))
if "*" in relative_path:
cur.execute(f"{base_sql} relativePath LIKE ?;",
(relative_path.replace("*", "%"),))
else:
cur.execute(f"{base_sql} relativePath = ?;",
(relative_path,))
elif domain:
cur.execute(f"{base_sql} domain = ?;", (domain,))
except Exception as exc:
@@ -166,14 +170,15 @@ class IOSExtraction(MVTModule):
if file_path:
break
# If this file does not exist we might be processing a full
# filesystem dump (checkra1n all the things!).
if not file_path or not os.path.exists(file_path):
# We reset the file_path.
file_path = None
for found_path in self._get_fs_files_from_patterns(root_paths):
file_path = found_path
break
if root_paths:
# If this file does not exist we might be processing a full
# filesystem dump (checkra1n all the things!).
if not file_path or not os.path.exists(file_path):
# We reset the file_path.
file_path = None
for found_path in self._get_fs_files_from_patterns(root_paths):
file_path = found_path
break
# If we do not find any, we fail.
if file_path:

View File

@@ -57,14 +57,10 @@ class Filesystem(IOSExtraction):
if self.fast_mode:
continue
for ioc in self.indicators.get_iocs("processes"):
parts = result["path"].split("/")
if ioc["value"] in parts:
self.log.warning("Found known suspicious process name mentioned in file at "
"path \"%s\" matching indicators from \"%s\"",
result["path"], ioc["name"])
result["matched_indicator"] = ioc
self.detected.append(result)
ioc = self.indicators.check_file_path_process(result["path"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
def run(self) -> None:
for root, dirs, files in os.walk(self.target_path):

View File

@@ -42,6 +42,9 @@ IPHONE_MODELS = [
{"identifier": "iPhone14,5", "description": "iPhone 13"},
{"identifier": "iPhone14,2", "description": "iPhone 13 Pro"},
{"identifier": "iPhone14,3", "description": "iPhone 13 Pro Max"},
{"identifier": "iPhone14,8", "decription": "iPhone 14 Plus"},
{"identifier": "iPhone15,2", "description": "iPhone 14 Pro"},
{"identifier": "iPhone15,3", "description": "iPhone 14 Pro Max"}
]
IPHONE_IOS_VERSIONS = [
@@ -243,6 +246,10 @@ IPHONE_IOS_VERSIONS = [
{"build": "19F77", "version": "15.5"},
{"build": "19G71", "version": "15.6"},
{"build": "19G82", "version": "15.6.1"},
{"build": "19H12", "version": "15.7"},
{"build": "20A362", "version": "16.0"},
{"build": "20B82", "version": "16.1"},
{"build": "20B101", "version": "16.1.1"}
]

Some files were not shown because too many files have changed in this diff Show More