1
mirror of https://github.com/mvt-project/mvt synced 2025-11-13 01:37:36 +01:00

Compare commits

..

1 Commits

Author SHA1 Message Date
Tek
b12eaa4007 Reorganize code in iOS app module 2024-12-14 10:03:23 +01:00
76 changed files with 186 additions and 2965 deletions

View File

@@ -1,11 +0,0 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"

View File

@@ -12,7 +12,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
python-version: ['3.10', '3.11', '3.12', '3.13'] python-version: ['3.8', '3.9', '3.10'] # , '3.11']
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4

View File

@@ -21,7 +21,6 @@ jobs:
title: '[auto] Update iOS releases and versions' title: '[auto] Update iOS releases and versions'
commit-message: Add new iOS versions and build numbers commit-message: Add new iOS versions and build numbers
branch: auto/add-new-ios-releases branch: auto/add-new-ios-releases
draft: true
body: | body: |
This is an automated pull request to update the iOS releases and version numbers. This is an automated pull request to update the iOS releases and version numbers.
add-paths: | add-paths: |

View File

@@ -103,7 +103,7 @@ RUN git clone https://github.com/libimobiledevice/usbmuxd && cd usbmuxd \
# Create main image # Create main image
FROM ubuntu:24.04 as main FROM ubuntu:22.04 as main
LABEL org.opencontainers.image.url="https://mvt.re" LABEL org.opencontainers.image.url="https://mvt.re"
LABEL org.opencontainers.image.documentation="https://docs.mvt.re" LABEL org.opencontainers.image.documentation="https://docs.mvt.re"
@@ -135,7 +135,8 @@ COPY --from=build-usbmuxd /build /
COPY . mvt/ COPY . mvt/
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y git python3-pip \ && apt-get install -y git python3-pip \
&& PIP_NO_CACHE_DIR=1 pip3 install --break-system-packages ./mvt \ && PIP_NO_CACHE_DIR=1 pip3 install --upgrade pip \
&& PIP_NO_CACHE_DIR=1 pip3 install ./mvt \
&& apt-get remove -y python3-pip git && apt-get autoremove -y \ && apt-get remove -y python3-pip git && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& rm -rf mvt && rm -rf mvt

View File

@@ -23,12 +23,7 @@ install:
python3 -m pip install --upgrade -e . python3 -m pip install --upgrade -e .
test-requirements: test-requirements:
python3 -m pip install --upgrade --group dev python3 -m pip install --upgrade -r test-requirements.txt
generate-proto-parsers:
# Generate python parsers for protobuf files
PROTO_FILES=$$(find src/mvt/android/parsers/proto/ -iname "*.proto"); \
protoc -Isrc/mvt/android/parsers/proto/ --python_betterproto_out=src/mvt/android/parsers/proto/ $$PROTO_FILES
clean: clean:
rm -rf $(PWD)/build $(PWD)/dist $(PWD)/src/mvt.egg-info rm -rf $(PWD)/build $(PWD)/dist $(PWD)/src/mvt.egg-info

View File

@@ -1,43 +0,0 @@
# Command Completion
MVT utilizes the [Click](https://click.palletsprojects.com/en/stable/) library for creating its command line interface.
Click provides tab completion support for Bash (version 4.4 and up), Zsh, and Fish.
To enable it, you need to manually register a special function with your shell, which varies depending on the shell you are using.
The following describes how to generate the command completion scripts and add them to your shell configuration.
> **Note: You will need to start a new shell for the changes to take effect.**
### For Bash
```bash
# Generates bash completion scripts
echo "$(_MVT_IOS_COMPLETE=bash_source mvt-ios)" > ~/.mvt-ios-complete.bash &&
echo "$(_MVT_ANDROID_COMPLETE=bash_source mvt-android)" > ~/.mvt-android-complete.bash
```
Add the following to `~/.bashrc`:
```bash
# source mvt completion scripts
. ~/.mvt-ios-complete.bash && . ~/.mvt-android-complete.bash
```
### For Zsh
```bash
# Generates zsh completion scripts
echo "$(_MVT_IOS_COMPLETE=zsh_source mvt-ios)" > ~/.mvt-ios-complete.zsh &&
echo "$(_MVT_ANDROID_COMPLETE=zsh_source mvt-android)" > ~/.mvt-android-complete.zsh
```
Add the following to `~/.zshrc`:
```bash
# source mvt completion scripts
. ~/.mvt-ios-complete.zsh && . ~/.mvt-android-complete.zsh
```
For more information, visit the official [Click Docs](https://click.palletsprojects.com/en/stable/shell-completion/#enabling-completion).

View File

@@ -98,7 +98,3 @@ You now should have the `mvt-ios` and `mvt-android` utilities installed.
**Notes:** **Notes:**
1. The `--force` flag is necessary to force the reinstallation of the package. 1. The `--force` flag is necessary to force the reinstallation of the package.
2. To revert to using a PyPI version, it will be necessary to `pipx uninstall mvt` first. 2. To revert to using a PyPI version, it will be necessary to `pipx uninstall mvt` first.
## Setting up command completions
See ["Command completions"](command_completion.md)

View File

@@ -1,5 +1,5 @@
mkdocs==1.6.1 mkdocs==1.6.1
mkdocs-autorefs==1.4.3 mkdocs-autorefs==1.2.0
mkdocs-material==9.6.20 mkdocs-material==9.5.42
mkdocs-material-extensions==1.3.1 mkdocs-material-extensions==1.3.1
mkdocstrings==0.30.0 mkdocstrings==0.23.0

View File

@@ -1,11 +1,13 @@
[project] [project]
name = "mvt" name = "mvt"
dynamic = ["version"] dynamic = ["version"]
authors = [{ name = "Claudio Guarnieri", email = "nex@nex.sx" }] authors = [
{name = "Claudio Guarnieri", email = "nex@nex.sx"}
]
maintainers = [ maintainers = [
{ name = "Etienne Maynier", email = "tek@randhome.io" }, {name = "Etienne Maynier", email = "tek@randhome.io"},
{ name = "Donncha Ó Cearbhaill", email = "donncha.ocearbhaill@amnesty.org" }, {name = "Donncha Ó Cearbhaill", email = "donncha.ocearbhaill@amnesty.org"},
{ name = "Rory Flynn", email = "rory.flynn@amnesty.org" }, {name = "Rory Flynn", email = "rory.flynn@amnesty.org"}
] ]
description = "Mobile Verification Toolkit" description = "Mobile Verification Toolkit"
readme = "README.md" readme = "README.md"
@@ -14,60 +16,44 @@ classifiers = [
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Intended Audience :: Information Technology", "Intended Audience :: Information Technology",
"Operating System :: OS Independent", "Operating System :: OS Independent",
"Programming Language :: Python", "Programming Language :: Python"
] ]
dependencies = [ dependencies = [
"click==8.2.1", "click >=8.1.3",
"rich==14.1.0", "rich >=12.6.0",
"tld==0.13.1", "tld >=0.12.6",
"requests==2.32.5", "requests >=2.28.1",
"simplejson==3.20.1", "simplejson >=3.17.6",
"packaging==25.0", "packaging >=21.3",
"appdirs==1.4.4", "appdirs >=1.4.4",
"iOSbackup==0.9.925", "iOSbackup >=0.9.923",
"adb-shell[usb]==0.4.4", "adb-shell[usb] >=0.4.3",
"libusb1==3.3.1", "libusb1 >=3.0.0",
"cryptography==45.0.6", "cryptography >=42.0.5",
"PyYAML>=6.0.2", "pyyaml >=6.0",
"pyahocorasick==2.2.0", "pyahocorasick >= 2.0.0",
"betterproto==1.2.5",
"pydantic==2.11.7",
"pydantic-settings==2.10.1",
"NSKeyedUnArchiver==1.5.2",
"python-dateutil==2.9.0.post0",
] ]
requires-python = ">= 3.10" requires-python = ">= 3.8"
[project.urls] [project.urls]
homepage = "https://docs.mvt.re/en/latest/" homepage = "https://docs.mvt.re/en/latest/"
repository = "https://github.com/mvt-project/mvt" repository = "https://github.com/mvt-project/mvt"
[project.scripts] [project.scripts]
mvt-ios = "mvt.ios:cli" mvt-ios = "mvt.ios:cli"
mvt-android = "mvt.android:cli" mvt-android = "mvt.android:cli"
[dependency-groups]
dev = [
"requests>=2.31.0",
"pytest>=7.4.3",
"pytest-cov>=4.1.0",
"pytest-github-actions-annotate-failures>=0.2.0",
"pytest-mock>=3.14.0",
"stix2>=3.0.1",
"ruff>=0.1.6",
"mypy>=1.7.1",
"betterproto[compiler]",
]
[build-system] [build-system]
requires = ["setuptools>=61.0"] requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[tool.coverage.run] [tool.coverage.run]
omit = ["tests/*"] omit = [
"tests/*",
]
[tool.coverage.html] [tool.coverage.html]
directory = "htmlcov" directory= "htmlcov"
[tool.mypy] [tool.mypy]
install_types = true install_types = true
@@ -77,7 +63,9 @@ packages = "src"
[tool.pytest.ini_options] [tool.pytest.ini_options]
addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered" addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered"
testpaths = ["tests"] testpaths = [
"tests"
]
[tool.ruff.lint] [tool.ruff.lint]
select = ["C90", "E", "F", "W"] # flake8 default set select = ["C90", "E", "F", "W"] # flake8 default set
@@ -102,7 +90,7 @@ max-complexity = 10
[tool.setuptools] [tool.setuptools]
include-package-data = true include-package-data = true
package-dir = { "" = "src" } package-dir = {"" = "src"}
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
where = ["src"] where = ["src"]
@@ -111,4 +99,4 @@ where = ["src"]
mvt = ["ios/data/*.json"] mvt = ["ios/data/*.json"]
[tool.setuptools.dynamic] [tool.setuptools.dynamic]
version = { attr = "mvt.common.version.MVT_VERSION" } version = {attr = "mvt.common.version.MVT_VERSION"}

View File

@@ -4,14 +4,13 @@
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
import base64 import base64
import binascii
import hashlib import hashlib
from .artifact import AndroidArtifact from .artifact import AndroidArtifact
class DumpsysADBArtifact(AndroidArtifact): class DumpsysADBArtifact(AndroidArtifact):
multiline_fields = ["user_keys", "keystore"] multiline_fields = ["user_keys"]
def indented_dump_parser(self, dump_data): def indented_dump_parser(self, dump_data):
""" """
@@ -68,38 +67,14 @@ class DumpsysADBArtifact(AndroidArtifact):
return res return res
def parse_xml(self, xml_data):
"""
Parse XML data from dumpsys ADB output
"""
import xml.etree.ElementTree as ET
keystore = []
keystore_root = ET.fromstring(xml_data)
for adb_key in keystore_root.findall("adbKey"):
key_info = self.calculate_key_info(adb_key.get("key").encode("utf-8"))
key_info["last_connected"] = adb_key.get("lastConnection")
keystore.append(key_info)
return keystore
@staticmethod @staticmethod
def calculate_key_info(user_key: bytes) -> str: def calculate_key_info(user_key: bytes) -> str:
if b" " in user_key:
key_base64, user = user_key.split(b" ", 1) key_base64, user = user_key.split(b" ", 1)
else:
key_base64, user = user_key, b""
try:
key_raw = base64.b64decode(key_base64) key_raw = base64.b64decode(key_base64)
key_fingerprint = hashlib.md5(key_raw).hexdigest().upper() key_fingerprint = hashlib.md5(key_raw).hexdigest().upper()
key_fingerprint_colon = ":".join( key_fingerprint_colon = ":".join(
[key_fingerprint[i : i + 2] for i in range(0, len(key_fingerprint), 2)] [key_fingerprint[i : i + 2] for i in range(0, len(key_fingerprint), 2)]
) )
except binascii.Error:
# Impossible to parse base64
key_fingerprint_colon = ""
return { return {
"user": user.decode("utf-8"), "user": user.decode("utf-8"),
"fingerprint": key_fingerprint_colon, "fingerprint": key_fingerprint_colon,
@@ -140,23 +115,7 @@ class DumpsysADBArtifact(AndroidArtifact):
if parsed.get("debugging_manager") is None: if parsed.get("debugging_manager") is None:
self.log.error("Unable to find expected ADB entries in dumpsys output") # noqa self.log.error("Unable to find expected ADB entries in dumpsys output") # noqa
return return
# Keystore can be in different levels, as the basic parser
# is not always consistent due to different dumpsys formats.
if parsed.get("keystore"):
keystore_data = b"\n".join(parsed["keystore"])
elif parsed["debugging_manager"].get("keystore"):
keystore_data = b"\n".join(parsed["debugging_manager"]["keystore"])
else: else:
keystore_data = None
# Keystore is in XML format on some devices and we need to parse it
if keystore_data and keystore_data.startswith(b"<?xml"):
parsed["debugging_manager"]["keystore"] = self.parse_xml(keystore_data)
else:
# Keystore is not XML format
parsed["debugging_manager"]["keystore"] = keystore_data
parsed = parsed["debugging_manager"] parsed = parsed["debugging_manager"]
# Calculate key fingerprints for better readability # Calculate key fingerprints for better readability

View File

@@ -11,10 +11,6 @@ from mvt.common.utils import convert_datetime_to_iso
from .artifact import AndroidArtifact from .artifact import AndroidArtifact
RISKY_PERMISSIONS = ["REQUEST_INSTALL_PACKAGES"]
RISKY_PACKAGES = ["com.android.shell"]
class DumpsysAppopsArtifact(AndroidArtifact): class DumpsysAppopsArtifact(AndroidArtifact):
""" """
Parser for dumpsys app ops info Parser for dumpsys app ops info
@@ -49,40 +45,16 @@ class DumpsysAppopsArtifact(AndroidArtifact):
self.detected.append(result) self.detected.append(result)
continue continue
detected_permissions = []
for perm in result["permissions"]: for perm in result["permissions"]:
if ( if (
perm["name"] in RISKY_PERMISSIONS perm["name"] == "REQUEST_INSTALL_PACKAGES"
# and perm["access"] == "allow" and perm["access"] == "allow"
): ):
detected_permissions.append(perm) self.log.info(
for entry in sorted(perm["entries"], key=lambda x: x["timestamp"]): "Package %s with REQUEST_INSTALL_PACKAGES " "permission",
self.log.warning(
"Package '%s' had risky permission '%s' set to '%s' at %s",
result["package_name"], result["package_name"],
perm["name"],
entry["access"],
entry["timestamp"],
) )
elif result["package_name"] in RISKY_PACKAGES:
detected_permissions.append(perm)
for entry in sorted(perm["entries"], key=lambda x: x["timestamp"]):
self.log.warning(
"Risky package '%s' had '%s' permission set to '%s' at %s",
result["package_name"],
perm["name"],
entry["access"],
entry["timestamp"],
)
if detected_permissions:
# We clean the result to only include the risky permission, otherwise the timeline
# will be polluted with all the other irrelevant permissions
cleaned_result = result.copy()
cleaned_result["permissions"] = detected_permissions
self.detected.append(cleaned_result)
def parse(self, output: str) -> None: def parse(self, output: str) -> None:
self.results: List[Dict[str, Any]] = [] self.results: List[Dict[str, Any]] = []
perm = {} perm = {}
@@ -149,16 +121,11 @@ class DumpsysAppopsArtifact(AndroidArtifact):
if line.startswith(" "): if line.startswith(" "):
# Permission entry like: # Permission entry like:
# Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms) # Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
access_type = line.split(":")[0].strip()
if access_type not in ["Access", "Reject"]:
# Skipping invalid access type. Some entries are not in the format we expect
continue
if entry: if entry:
perm["entries"].append(entry) perm["entries"].append(entry)
entry = {} entry = {}
entry["access"] = access_type entry["access"] = line.split(":")[0].strip()
entry["type"] = line[line.find("[") + 1 : line.find("]")] entry["type"] = line[line.find("[") + 1 : line.find("]")]
try: try:

View File

@@ -16,7 +16,8 @@ class DumpsysPackagesArtifact(AndroidArtifact):
for result in self.results: for result in self.results:
if result["package_name"] in ROOT_PACKAGES: if result["package_name"] in ROOT_PACKAGES:
self.log.warning( self.log.warning(
'Found an installed package related to rooting/jailbreaking: "%s"', "Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["package_name"], result["package_name"],
) )
self.detected.append(result) self.detected.append(result)

View File

@@ -1,42 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 .artifact import AndroidArtifact
class DumpsysPlatformCompatArtifact(AndroidArtifact):
"""
Parser for uninstalled apps listed in platform_compat section.
"""
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)
continue
def parse(self, data: str) -> None:
for line in data.splitlines():
if not line.startswith("ChangeId(168419799; name=DOWNSCALED;"):
continue
if line.strip() == "":
break
# Look for rawOverrides field
if "rawOverrides={" in line:
# Extract the content inside the braces for rawOverrides
overrides_field = line.split("rawOverrides={", 1)[1].split("};", 1)[0]
for entry in overrides_field.split(", "):
# Extract app name
uninstall_app = entry.split("=")[0].strip()
self.results.append({"package_name": uninstall_app})

View File

@@ -1,43 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 typing import Union
from .artifact import AndroidArtifact
class FileTimestampsArtifact(AndroidArtifact):
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for ts in set(
[
record.get("access_time"),
record.get("changed_time"),
record.get("modified_time"),
]
):
if not ts:
continue
macb = ""
macb += "M" if ts == record.get("modified_time") else "-"
macb += "A" if ts == record.get("access_time") else "-"
macb += "C" if ts == record.get("changed_time") else "-"
macb += "-"
msg = record["path"]
if record.get("context"):
msg += f" ({record['context']})"
records.append(
{
"timestamp": ts,
"module": self.__class__.__name__,
"event": macb,
"data": msg,
}
)
return records

View File

@@ -42,17 +42,6 @@ class GetProp(AndroidArtifact):
entry = {"name": matches[0][0], "value": matches[0][1]} entry = {"name": matches[0][0], "value": matches[0][1]}
self.results.append(entry) self.results.append(entry)
def get_device_timezone(self) -> str:
"""
Get the device timezone from the getprop results
Used in other moduels to calculate the timezone offset
"""
for entry in self.results:
if entry["name"] == "persist.sys.timezone":
return entry["value"]
return None
def check_indicators(self) -> None: def check_indicators(self) -> None:
for entry in self.results: for entry in self.results:
if entry["name"] in INTERESTING_PROPERTIES: if entry["name"] in INTERESTING_PROPERTIES:

View File

@@ -16,11 +16,6 @@ ANDROID_DANGEROUS_SETTINGS = [
"key": "package_verifier_enable", "key": "package_verifier_enable",
"safe_value": "1", "safe_value": "1",
}, },
{
"description": "disabled APK package verification",
"key": "package_verifier_state",
"safe_value": "1",
},
{ {
"description": "disabled Google Play Protect", "description": "disabled Google Play Protect",
"key": "package_verifier_user_consent", "key": "package_verifier_user_consent",
@@ -51,6 +46,11 @@ ANDROID_DANGEROUS_SETTINGS = [
"key": "send_action_app_error", "key": "send_action_app_error",
"safe_value": "1", "safe_value": "1",
}, },
{
"description": "enabled installation of non Google Play apps",
"key": "install_non_market_apps",
"safe_value": "0",
},
{ {
"description": "enabled accessibility services", "description": "enabled accessibility services",
"key": "accessibility_enabled", "key": "accessibility_enabled",

File diff suppressed because it is too large Load Diff

View File

@@ -326,7 +326,8 @@ class AndroidExtraction(MVTModule):
if not header["backup"]: if not header["backup"]:
self.log.error( self.log.error(
"Extracting SMS via Android backup failed. No valid backup data found." "Extracting SMS via Android backup failed. "
"No valid backup data found."
) )
return None return None

View File

@@ -75,7 +75,8 @@ class Packages(AndroidExtraction):
for result in self.results: for result in self.results:
if result["package_name"] in ROOT_PACKAGES: if result["package_name"] in ROOT_PACKAGES:
self.log.warning( self.log.warning(
'Found an installed package related to rooting/jailbreaking: "%s"', "Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["package_name"], result["package_name"],
) )
self.detected.append(result) self.detected.append(result)

View File

@@ -70,7 +70,7 @@ class SMS(AndroidExtraction):
"timestamp": record["isodate"], "timestamp": record["isodate"],
"module": self.__class__.__name__, "module": self.__class__.__name__,
"event": f"sms_{record['direction']}", "event": f"sms_{record['direction']}",
"data": f'{record.get("address", "unknown source")}: "{body}"', "data": f"{record.get('address', 'unknown source')}: \"{body}\"",
} }
def check_indicators(self) -> None: def check_indicators(self) -> None:

View File

@@ -14,7 +14,6 @@ from .dumpsys_receivers import DumpsysReceivers
from .dumpsys_adb import DumpsysADBState from .dumpsys_adb import DumpsysADBState
from .getprop import Getprop from .getprop import Getprop
from .packages import Packages from .packages import Packages
from .dumpsys_platform_compat import DumpsysPlatformCompat
from .processes import Processes from .processes import Processes
from .settings import Settings from .settings import Settings
from .sms import SMS from .sms import SMS
@@ -30,7 +29,6 @@ ANDROIDQF_MODULES = [
DumpsysBatteryHistory, DumpsysBatteryHistory,
DumpsysADBState, DumpsysADBState,
Packages, Packages,
DumpsysPlatformCompat,
Processes, Processes,
Getprop, Getprop,
Settings, Settings,

View File

@@ -48,37 +48,6 @@ class AndroidQFModule(MVTModule):
def _get_files_by_pattern(self, pattern: str): def _get_files_by_pattern(self, pattern: str):
return fnmatch.filter(self.files, pattern) return fnmatch.filter(self.files, pattern)
def _get_device_timezone(self):
"""
Get the device timezone from the getprop.txt file.
This is needed to map local timestamps stored in some
Android log files to UTC/timezone-aware timestamps.
"""
get_prop_files = self._get_files_by_pattern("*/getprop.txt")
if not get_prop_files:
self.log.warning(
"Could not find getprop.txt file. "
"Some timestamps and timeline data may be incorrect."
)
return None
from mvt.android.artifacts.getprop import GetProp
properties_artifact = GetProp()
prop_data = self._get_file_content(get_prop_files[0]).decode("utf-8")
properties_artifact.parse(prop_data)
timezone = properties_artifact.get_device_timezone()
if timezone:
self.log.debug("Identified local phone timezone: %s", timezone)
return timezone
self.log.warning(
"Could not find or determine local device timezone. "
"Some timestamps and timeline data may be incorrect."
)
return None
def _get_file_content(self, file_path): def _get_file_content(self, file_path):
if self.archive: if self.archive:
handle = self.archive.open(file_path) handle = self.archive.open(file_path)

View File

@@ -1,44 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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
from typing import Optional
from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact
from .base import AndroidQFModule
class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, AndroidQFModule):
"""This module extracts details on uninstalled apps."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
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,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
self.parse(content)
self.log.info("Found %d uninstalled apps", len(self.results))

View File

@@ -6,11 +6,6 @@
import datetime import datetime
import json import json
import logging import logging
try:
import zoneinfo
except ImportError:
from backports import zoneinfo
from typing import Optional, Union from typing import Optional, Union
from mvt.android.modules.androidqf.base import AndroidQFModule from mvt.android.modules.androidqf.base import AndroidQFModule
@@ -111,12 +106,6 @@ class Files(AndroidQFModule):
# TODO: adds SHA1 and MD5 when available in MVT # TODO: adds SHA1 and MD5 when available in MVT
def run(self) -> None: def run(self) -> None:
if timezone := self._get_device_timezone():
device_timezone = zoneinfo.ZoneInfo(timezone)
else:
self.log.warning("Unable to determine device timezone, using UTC")
device_timezone = zoneinfo.ZoneInfo("UTC")
for file in self._get_files_by_pattern("*/files.json"): for file in self._get_files_by_pattern("*/files.json"):
rawdata = self._get_file_content(file).decode("utf-8", errors="ignore") rawdata = self._get_file_content(file).decode("utf-8", errors="ignore")
try: try:
@@ -131,18 +120,11 @@ class Files(AndroidQFModule):
for file_data in data: for file_data in data:
for ts in ["access_time", "changed_time", "modified_time"]: for ts in ["access_time", "changed_time", "modified_time"]:
if ts in file_data: if ts in file_data:
utc_timestamp = datetime.datetime.fromtimestamp( file_data[ts] = convert_datetime_to_iso(
datetime.datetime.fromtimestamp(
file_data[ts], tz=datetime.timezone.utc file_data[ts], tz=datetime.timezone.utc
) )
# Convert the UTC timestamp to local tiem on Android device's local timezone
local_timestamp = utc_timestamp.astimezone(device_timezone)
# HACK: We only output the UTC timestamp in convert_datetime_to_iso, we
# set the timestamp timezone to UTC, to avoid the timezone conversion again.
local_timestamp = local_timestamp.replace(
tzinfo=datetime.timezone.utc
) )
file_data[ts] = convert_datetime_to_iso(local_timestamp)
self.results.append(file_data) self.results.append(file_data)

View File

@@ -1,65 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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 logging
from typing import Optional
from mvt.common.utils import convert_datetime_to_iso
from .base import AndroidQFModule
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
class LogsFileTimestamps(FileTimestampsArtifact, AndroidQFModule):
"""This module extracts records from battery daily updates."""
slug = "logfile_timestamps"
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
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,
module_options=module_options,
log=log,
results=results,
)
def _get_file_modification_time(self, file_path: str) -> dict:
if self.archive:
file_timetuple = self.archive.getinfo(file_path).date_time
return datetime.datetime(*file_timetuple)
else:
file_stat = os.stat(os.path.join(self.parent_path, file_path))
return datetime.datetime.fromtimestamp(file_stat.st_mtime)
def run(self) -> None:
filesystem_files = self._get_files_by_pattern("*/logs/*")
self.results = []
for file in filesystem_files:
# Only the modification time is available in the zip file metadata.
# The timezone is the local timezone of the machine the phone.
modification_time = self._get_file_modification_time(file)
self.results.append(
{
"path": file,
"modified_time": convert_datetime_to_iso(modification_time),
}
)
self.log.info(
"Extracted a total of %d filesystem timestamps from AndroidQF logs directory.",
len(self.results),
)

View File

@@ -44,7 +44,8 @@ class Packages(AndroidQFModule):
for result in self.results: for result in self.results:
if result["name"] in ROOT_PACKAGES: if result["name"] in ROOT_PACKAGES:
self.log.warning( self.log.warning(
'Found an installed package related to rooting/jailbreaking: "%s"', "Found an installed package related to "
'rooting/jailbreaking: "%s"',
result["name"], result["name"],
) )
self.detected.append(result) self.detected.append(result)

View File

@@ -3,11 +3,10 @@
# Use of this software is governed by the MVT License 1.1 that can be found at # Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/ # https://license.mvt.re/1.1/
import os
from rich.prompt import Prompt from rich.prompt import Prompt
from mvt.common.config import settings
MVT_ANDROID_BACKUP_PASSWORD = "MVT_ANDROID_BACKUP_PASSWORD" MVT_ANDROID_BACKUP_PASSWORD = "MVT_ANDROID_BACKUP_PASSWORD"
@@ -17,24 +16,24 @@ def cli_load_android_backup_password(log, backup_password):
Used in MVT CLI command parsers. Used in MVT CLI command parsers.
""" """
password_from_env_or_config = settings.ANDROID_BACKUP_PASSWORD password_from_env = os.environ.get(MVT_ANDROID_BACKUP_PASSWORD, None)
if backup_password: if backup_password:
log.info( log.info(
"Your password may be visible in the process table because it " "Your password may be visible in the process table because it "
"was supplied on the command line!" "was supplied on the command line!"
) )
if password_from_env_or_config: if password_from_env:
log.info( log.info(
"Ignoring %s environment variable, using --backup-password argument instead", "Ignoring %s environment variable, using --backup-password argument instead",
"MVT_ANDROID_BACKUP_PASSWORD", MVT_ANDROID_BACKUP_PASSWORD,
) )
return backup_password return backup_password
elif password_from_env_or_config: elif password_from_env:
log.info( log.info(
"Using backup password from %s environment variable or config file", "Using backup password from %s environment variable",
"MVT_ANDROID_BACKUP_PASSWORD", MVT_ANDROID_BACKUP_PASSWORD,
) )
return password_from_env_or_config return password_from_env
def prompt_or_load_android_backup_password(log, module_options): def prompt_or_load_android_backup_password(log, module_options):

View File

@@ -11,11 +11,8 @@ from .battery_history import BatteryHistory
from .dbinfo import DBInfo from .dbinfo import DBInfo
from .getprop import Getprop from .getprop import Getprop
from .packages import Packages from .packages import Packages
from .platform_compat import PlatformCompat
from .receivers import Receivers from .receivers import Receivers
from .adb_state import DumpsysADBState from .adb_state import DumpsysADBState
from .fs_timestamps import BugReportTimestamps
from .tombstones import Tombstones
BUGREPORT_MODULES = [ BUGREPORT_MODULES = [
Accessibility, Accessibility,
@@ -26,9 +23,6 @@ BUGREPORT_MODULES = [
DBInfo, DBInfo,
Getprop, Getprop,
Packages, Packages,
PlatformCompat,
Receivers, Receivers,
DumpsysADBState, DumpsysADBState,
BugReportTimestamps,
Tombstones,
] ]

View File

@@ -2,11 +2,10 @@
# Copyright (c) 2021-2023 The MVT Authors. # Copyright (c) 2021-2023 The MVT Authors.
# See the file 'LICENSE' for usage and copying permissions, or find a copy at # See the file 'LICENSE' for usage and copying permissions, or find a copy at
# https://github.com/mvt-project/mvt/blob/main/LICENSE # https://github.com/mvt-project/mvt/blob/main/LICENSE
import datetime
import fnmatch import fnmatch
import logging import logging
import os import os
from typing import List, Optional from typing import List, Optional
from zipfile import ZipFile from zipfile import ZipFile
@@ -92,11 +91,3 @@ class BugReportModule(MVTModule):
return None return None
return self._get_file_content(dumpstate_logs[0]) return self._get_file_content(dumpstate_logs[0])
def _get_file_modification_time(self, file_path: str) -> dict:
if self.zip_archive:
file_timetuple = self.zip_archive.getinfo(file_path).date_time
return datetime.datetime(*file_timetuple)
else:
file_stat = os.stat(os.path.join(self.extract_path, file_path))
return datetime.datetime.fromtimestamp(file_stat.st_mtime)

View File

@@ -1,55 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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
from typing import Optional
from mvt.common.utils import convert_datetime_to_iso
from .base import BugReportModule
from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact
class BugReportTimestamps(FileTimestampsArtifact, BugReportModule):
"""This module extracts records from battery daily updates."""
slug = "bugreport_timestamps"
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
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,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
filesystem_files = self._get_files_by_pattern("FS/*")
self.results = []
for file in filesystem_files:
# Only the modification time is available in the zip file metadata.
# The timezone is the local timezone of the machine the phone.
modification_time = self._get_file_modification_time(file)
self.results.append(
{
"path": file,
"modified_time": convert_datetime_to_iso(modification_time),
}
)
self.log.info(
"Extracted a total of %d filesystem timestamps from bugreport.",
len(self.results),
)

View File

@@ -1,48 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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
from typing import Optional
from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact
from mvt.android.modules.bugreport.base import BugReportModule
class PlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule):
"""This module extracts details on uninstalled apps."""
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
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,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
data = self._get_dumpstate_file()
if not data:
self.log.error(
"Unable to find dumpstate file. "
"Did you provide a valid bug report archive?"
)
return
data = data.decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
self.parse(content)
self.log.info("Found %d uninstalled apps", len(self.results))

View File

@@ -1,64 +0,0 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT 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
from typing import Optional
from mvt.android.artifacts.tombstone_crashes import TombstoneCrashArtifact
from .base import BugReportModule
class Tombstones(TombstoneCrashArtifact, BugReportModule):
"""This module extracts records from battery daily updates."""
slug = "tombstones"
def __init__(
self,
file_path: Optional[str] = None,
target_path: Optional[str] = None,
results_path: Optional[str] = None,
module_options: Optional[dict] = None,
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,
module_options=module_options,
log=log,
results=results,
)
def run(self) -> None:
tombstone_files = self._get_files_by_pattern("*/tombstone_*")
if not tombstone_files:
self.log.error(
"Unable to find any tombstone files. "
"Did you provide a valid bugreport archive?"
)
return
for tombstone_file in sorted(tombstone_files):
tombstone_filename = tombstone_file.split("/")[-1]
modification_time = self._get_file_modification_time(tombstone_file)
tombstone_data = self._get_file_content(tombstone_file)
try:
if tombstone_file.endswith(".pb"):
self.parse_protobuf(
tombstone_filename, modification_time, tombstone_data
)
else:
self.parse(tombstone_filename, modification_time, tombstone_data)
except ValueError as e:
# Catch any exceptions raised during parsing or validation.
self.log.error(f"Error parsing tombstone file {tombstone_file}: {e}")
self.log.info(
"Extracted a total of %d tombstone files",
len(self.results),
)

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