mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-16 02:12:46 +00:00
Compare commits
14 Commits
feature/an
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
61947d17af | ||
|
|
7173e02a6f | ||
|
|
8f34902bed | ||
|
|
939bec82ff | ||
|
|
b183ca33b5 | ||
|
|
a2c9e0c6cf | ||
|
|
4bfad1f87d | ||
|
|
c3dc3d96d5 | ||
|
|
afab222f93 | ||
|
|
5a1166c416 | ||
|
|
dd3d665bea | ||
|
|
5c3b92aeee | ||
|
|
d7e058af43 | ||
|
|
cdbaad94cc |
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13']
|
||||
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
7
Makefile
7
Makefile
@@ -1,14 +1,9 @@
|
||||
PWD = $(shell pwd)
|
||||
|
||||
autofix:
|
||||
ruff format .
|
||||
ruff check --fix .
|
||||
|
||||
check: ruff mypy
|
||||
|
||||
ruff:
|
||||
ruff format --check .
|
||||
ruff check -q .
|
||||
ruff check .
|
||||
|
||||
mypy:
|
||||
mypy
|
||||
|
||||
59
SECURITY.md
59
SECURITY.md
@@ -2,4 +2,61 @@
|
||||
|
||||
Thank you for your interest in reporting security issues and vulnerabilities! Security research is of utmost importance and we take all reports seriously. If you discover an issue please report it to us right away!
|
||||
|
||||
Please DO NOT file a public issue, instead send your report privately to *nex [at] nex [dot] sx*. You can also write PGP-encrypted emails to [this key](https://keybase.io/nex/pgp_keys.asc?fingerprint=05216f3b86848a303c2fe37dd166f1667359d880).
|
||||
Please DO NOT file a public issue, instead send your report privately to the MVT maintainers at Amnesty International via `security [at] amnesty [dot] tech`.
|
||||
|
||||
You can also write PGP-encrypted emails to key `CFBF9698DCA8EB2A80F48ADEA035A030FA04ED13`. The corresponding PGP public key is lited below.
|
||||
|
||||
```
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mQINBGlFPwsBEADQ+d7SeHrFPYv3wPOjWs2oMpp0DPdfIyGbg+iYWOC36FegZhKY
|
||||
+WeK96GqJWt8wD6kwFUVwQI795WZrjSd1q4a7wR+kj/h7xlRB6ZfVICA6O5DOOm6
|
||||
GNMvqy7ESm8g1XZDpb2u1BXmSS9X8f6rjB0e86kYsF1mB5/2USTM63jgDs0GGTkZ
|
||||
Q1z4Mq4gYyqH32b3gvXkbb68LeQmONUIM3cgmec9q8/pNc1l7fcoLWhOVADRj17Q
|
||||
plisa/EUf/SYqdtk9w7EHGggNenKNwVM235mkPcMqmE72bTpjT6XCxvZY3ByG5yi
|
||||
7L+tHJU45ZuXtt62EvX03azxThVfSmH/WbRk8lH8+CW8XMmiWZphG4ydPWqgVKCB
|
||||
2UOXm+6CQnKA+7Dt1AeK2t5ciATrv9LvwgSxk5WKc3288XFLA6eGMrTdQygYlLjJ
|
||||
+42RSdK/7fCt/qk4q13oUw8ZTVcCia98uZFi704XuuYTH6NrntIB7j/0oucIS4Y9
|
||||
cTWNO5LBerez4v8VI4YHcYESPeIWGFkXhvJzo0VMg1zidBLtiPoGF2JKZGwaK7/p
|
||||
yY1xALskLp4H+5OY4eB1kf8kl4vGsEK8xA/NNzOiapVmwBXpvVvmXIQJE2k+olNf
|
||||
sAuyB8+aO1Ws7tFYt3D+olC7iaprOdK7uA4GCgmYYhq6QQPg+cxfczgHfwARAQAB
|
||||
tD1TZWN1cml0eSBMYWIgYXQgQW1uZXN0eSBJbnRlcm5hdGlvbmFsIDxzZWN1cml0
|
||||
eUBhbW5lc3R5LnRlY2g+iQJRBBMBCAA7FiEEz7+WmNyo6yqA9IreoDWgMPoE7RMF
|
||||
AmlFPwsCGwMFCwkIBwICIgIGFQoJCAsCBBYCAwECHgcCF4AACgkQoDWgMPoE7RNr
|
||||
2w//a88uP90uSN6lgeIwKsHr1ri27QIBbzCV6hLN/gZBFR2uaiOn/xfFDbnR0Cjo
|
||||
5nMCJCT1k4nrPbMTlfmWLCD+YKELBzVqWlw4J2SOg3nznPl2JrL8QBKjwts0sF+h
|
||||
QbRWDsT54wBZnl6ZJJ79eLShNTokBbKnQ7071dMrENr5e2P2sClQXyiIc51ga4FM
|
||||
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
|
||||
8ZDExMYM4WVpVCOj+frdsaBvzItHacRWuijtkw==
|
||||
=JAXX
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
```
|
||||
|
||||
@@ -16,6 +16,12 @@ Now you can try launching MVT with:
|
||||
mvt-android check-adb --output /path/to/results
|
||||
```
|
||||
|
||||
!!! warning
|
||||
The `check-adb` command is deprecated and will be removed in a future release.
|
||||
Whenever possible, prefer acquiring device data using the AndroidQF project (https://github.com/mvt-project/androidqf/) and then analyze those acquisitions with MVT.
|
||||
|
||||
Running `mvt-android check-adb` will also emit a runtime deprecation warning advising you to migrate to AndroidQF.
|
||||
|
||||
If you have previously started an adb daemon MVT will alert you and require you to kill it with `adb kill-server` and relaunch the command.
|
||||
|
||||
!!! warning
|
||||
@@ -37,6 +43,14 @@ mvt-android check-adb --serial 192.168.1.20:5555 --output /path/to/results
|
||||
|
||||
Where `192.168.1.20` is the correct IP address of your device.
|
||||
|
||||
!!! warning
|
||||
The `check-adb` workflow shown above is deprecated. If you can acquire an AndroidQF acquisition from the device (recommended), use the AndroidQF project to create that acquisition: https://github.com/mvt-project/androidqf/
|
||||
|
||||
AndroidQF acquisitions provide a more stable, reproducible analysis surface and are the preferred workflow going forward.
|
||||
|
||||
## MVT modules requiring root privileges
|
||||
|
||||
!!! warning
|
||||
Deprecated: many `mvt-android check-adb` workflows are deprecated and will be removed in a future release. Whenever possible, prefer acquiring an AndroidQF acquisition using the AndroidQF project (https://github.com/mvt-project/androidqf/).
|
||||
|
||||
Of the currently available `mvt-android check-adb` modules a handful require root privileges to function correctly. This is because certain files, such as browser history and SMS messages databases are not accessible with user privileges through adb. These modules are to be considered OPTIONALLY available in case the device was already jailbroken. **Do NOT jailbreak your own device unless you are sure of what you are doing!** Jailbreaking your phone exposes it to considerable security risks!
|
||||
|
||||
@@ -17,21 +17,21 @@ classifiers = [
|
||||
"Programming Language :: Python",
|
||||
]
|
||||
dependencies = [
|
||||
"click==8.2.1",
|
||||
"click==8.3.0",
|
||||
"rich==14.1.0",
|
||||
"tld==0.13.1",
|
||||
"requests==2.32.4",
|
||||
"simplejson==3.20.1",
|
||||
"requests==2.32.5",
|
||||
"simplejson==3.20.2",
|
||||
"packaging==25.0",
|
||||
"appdirs==1.4.4",
|
||||
"iOSbackup==0.9.925",
|
||||
"adb-shell[usb]==0.4.4",
|
||||
"libusb1==3.3.1",
|
||||
"cryptography==45.0.6",
|
||||
"cryptography==46.0.3",
|
||||
"PyYAML>=6.0.2",
|
||||
"pyahocorasick==2.2.0",
|
||||
"betterproto==1.2.5",
|
||||
"pydantic==2.11.7",
|
||||
"pydantic==2.12.3",
|
||||
"pydantic-settings==2.10.1",
|
||||
"NSKeyedUnArchiver==1.5.2",
|
||||
"python-dateutil==2.9.0.post0",
|
||||
@@ -80,7 +80,7 @@ packages = "src"
|
||||
addopts = "-ra -q --cov=mvt --cov-report html --junitxml=pytest.xml --cov-report=term-missing:skip-covered"
|
||||
testpaths = ["tests"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
[tool.ruff]
|
||||
select = ["C90", "E", "F", "W"] # flake8 default set
|
||||
ignore = [
|
||||
"E501", # don't enforce line length violations
|
||||
@@ -95,10 +95,10 @@ ignore = [
|
||||
# "E203", # whitespace-before-punctuation
|
||||
]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
[tool.ruff.per-file-ignores]
|
||||
"__init__.py" = ["F401"] # unused-import
|
||||
|
||||
[tool.ruff.lint.mccabe]
|
||||
[tool.ruff.mccabe]
|
||||
max-complexity = 10
|
||||
|
||||
[tool.setuptools]
|
||||
|
||||
@@ -9,34 +9,34 @@ import click
|
||||
|
||||
from mvt.common.cmd_check_iocs import CmdCheckIOCS
|
||||
from mvt.common.help import (
|
||||
HELP_MSG_VERSION,
|
||||
HELP_MSG_OUTPUT,
|
||||
HELP_MSG_SERIAL,
|
||||
HELP_MSG_DOWNLOAD_APKS,
|
||||
HELP_MSG_DOWNLOAD_ALL_APKS,
|
||||
HELP_MSG_VIRUS_TOTAL,
|
||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||
HELP_MSG_APK_OUTPUT,
|
||||
HELP_MSG_APKS_FROM_FILE,
|
||||
HELP_MSG_VERBOSE,
|
||||
HELP_MSG_CHECK_ADB,
|
||||
HELP_MSG_IOC,
|
||||
HELP_MSG_CHECK_ANDROID_BACKUP,
|
||||
HELP_MSG_CHECK_ANDROIDQF,
|
||||
HELP_MSG_CHECK_BUGREPORT,
|
||||
HELP_MSG_CHECK_IOCS,
|
||||
HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK,
|
||||
HELP_MSG_DISABLE_UPDATE_CHECK,
|
||||
HELP_MSG_DOWNLOAD_ALL_APKS,
|
||||
HELP_MSG_DOWNLOAD_APKS,
|
||||
HELP_MSG_FAST,
|
||||
HELP_MSG_HASHES,
|
||||
HELP_MSG_IOC,
|
||||
HELP_MSG_LIST_MODULES,
|
||||
HELP_MSG_MODULE,
|
||||
HELP_MSG_NONINTERACTIVE,
|
||||
HELP_MSG_ANDROID_BACKUP_PASSWORD,
|
||||
HELP_MSG_CHECK_BUGREPORT,
|
||||
HELP_MSG_CHECK_ANDROID_BACKUP,
|
||||
HELP_MSG_CHECK_ANDROIDQF,
|
||||
HELP_MSG_HASHES,
|
||||
HELP_MSG_CHECK_IOCS,
|
||||
HELP_MSG_OUTPUT,
|
||||
HELP_MSG_SERIAL,
|
||||
HELP_MSG_STIX2,
|
||||
HELP_MSG_DISABLE_UPDATE_CHECK,
|
||||
HELP_MSG_DISABLE_INDICATOR_UPDATE_CHECK,
|
||||
HELP_MSG_VERBOSE,
|
||||
HELP_MSG_VERSION,
|
||||
HELP_MSG_VIRUS_TOTAL,
|
||||
)
|
||||
from mvt.common.logo import logo
|
||||
from mvt.common.updates import IndicatorsUpdates
|
||||
from mvt.common.utils import init_logging, set_verbose_logging, CommandWrapperGroup
|
||||
from mvt.common.utils import init_logging, set_verbose_logging
|
||||
|
||||
from .cmd_check_adb import CmdAndroidCheckADB
|
||||
from .cmd_check_androidqf import CmdAndroidCheckAndroidQF
|
||||
@@ -68,7 +68,7 @@ def _get_disable_flags(ctx):
|
||||
# ==============================================================================
|
||||
# Main
|
||||
# ==============================================================================
|
||||
@click.group(invoke_without_command=False, cls=CommandWrapperGroup)
|
||||
@click.group(invoke_without_command=False)
|
||||
@click.option(
|
||||
"--disable-update-check", is_flag=True, help=HELP_MSG_DISABLE_UPDATE_CHECK
|
||||
)
|
||||
@@ -201,6 +201,11 @@ def check_adb(
|
||||
cmd.list_modules()
|
||||
return
|
||||
|
||||
log.warning(
|
||||
"DEPRECATION: The 'check-adb' command is deprecated and may be removed in a future release. "
|
||||
"Prefer acquiring device data using the AndroidQF project (https://github.com/mvt-project/androidqf/) and analyzing that acquisition with MVT."
|
||||
)
|
||||
|
||||
log.info("Checking Android device over debug bridge")
|
||||
|
||||
cmd.run()
|
||||
|
||||
@@ -34,6 +34,20 @@ class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule):
|
||||
|
||||
self.results = results if results else {}
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
for result in self.results:
|
||||
if self.indicators:
|
||||
receiver_name = self.results[result][0]["receiver"]
|
||||
|
||||
# return IoC if the stix2 process name a substring of the receiver name
|
||||
ioc = self.indicators.check_receiver_prefix(receiver_name)
|
||||
if ioc:
|
||||
self.results[result][0]["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
|
||||
|
||||
def run(self) -> None:
|
||||
content = self._get_dumpstate_file()
|
||||
if not content:
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import os
|
||||
import yaml
|
||||
import json
|
||||
import uuid
|
||||
|
||||
from typing import Tuple, Type, Optional
|
||||
from appdirs import user_config_dir
|
||||
from pydantic import AnyHttpUrl, BaseModel, Field
|
||||
from pydantic import AnyHttpUrl, Field
|
||||
from pydantic_settings import (
|
||||
BaseSettings,
|
||||
InitSettingsSource,
|
||||
@@ -18,22 +17,6 @@ MVT_CONFIG_FOLDER = user_config_dir("mvt")
|
||||
MVT_CONFIG_PATH = os.path.join(MVT_CONFIG_FOLDER, "config.yaml")
|
||||
|
||||
|
||||
class TelemetrySettings(BaseModel):
|
||||
"""
|
||||
Settings used by the Telemetry module.
|
||||
"""
|
||||
|
||||
ENABLED: bool = Field(True, description="Flag for telemetry collection")
|
||||
ENDPOINT: AnyHttpUrl = Field(
|
||||
"https://t.mvt.re/events", description="Telemetry collection endpoint"
|
||||
)
|
||||
DEVICE_ID: str | None = Field(
|
||||
default=None,
|
||||
required=True,
|
||||
description="Anonymous Unique ID for use in telemetry",
|
||||
)
|
||||
|
||||
|
||||
class MVTSettings(BaseSettings):
|
||||
model_config = SettingsConfigDict(
|
||||
env_prefix="MVT_",
|
||||
@@ -41,7 +24,7 @@ class MVTSettings(BaseSettings):
|
||||
extra="ignore",
|
||||
nested_model_default_partial_updates=True,
|
||||
)
|
||||
# Flag to enable or disable loading of environment variables.
|
||||
# Allow to decided if want to load environment variables
|
||||
load_env: bool = Field(True, exclude=True)
|
||||
|
||||
# General settings
|
||||
@@ -68,9 +51,6 @@ class MVTSettings(BaseSettings):
|
||||
PROFILE: bool = Field(False, description="Profile the execution of MVT modules")
|
||||
HASH_FILES: bool = Field(False, description="Should MVT hash output files")
|
||||
|
||||
# Telemetry settings
|
||||
TELEMETRY: TelemetrySettings = TelemetrySettings(include=True)
|
||||
|
||||
@classmethod
|
||||
def settings_customise_sources(
|
||||
cls,
|
||||
@@ -115,8 +95,6 @@ class MVTSettings(BaseSettings):
|
||||
"""
|
||||
# Set invalid env prefix to avoid loading env variables.
|
||||
settings = MVTSettings(load_env=False)
|
||||
if not settings.TELEMETRY.DEVICE_ID:
|
||||
settings.TELEMETRY.DEVICE_ID = str(uuid.uuid4())
|
||||
settings.save_settings()
|
||||
|
||||
# Load the settings again with any ENV variables.
|
||||
|
||||
@@ -47,7 +47,7 @@ HELP_MSG_APKS_FROM_FILE = (
|
||||
"Instead of acquiring APKs from a phone, load an existing packages.json file for "
|
||||
"lookups (mainly for debug purposes)"
|
||||
)
|
||||
HELP_MSG_CHECK_ADB = "Check an Android device over ADB"
|
||||
HELP_MSG_CHECK_ADB = "Deprecated: Check an Android device over ADB. Prefer using the external AndroidQF project (https://github.com/mvt-project/androidqf) to acquire AndroidQF images for analysis."
|
||||
HELP_MSG_CHECK_BUGREPORT = "Check an Android Bug Report"
|
||||
HELP_MSG_CHECK_ANDROID_BACKUP = "Check an Android Backup"
|
||||
HELP_MSG_CHECK_ANDROIDQF = "Check data collected with AndroidQF"
|
||||
|
||||
@@ -768,6 +768,30 @@ class Indicators:
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def check_receiver_prefix(self, receiver_name: str) -> Union[dict, None]:
|
||||
"""Check the provided receiver name against the list of indicators.
|
||||
An IoC match is detected when a substring of the receiver matches the indicator
|
||||
:param app_id: App ID to check against the list of indicators
|
||||
:type app_id: str
|
||||
:returns: Indicator details if matched, otherwise None
|
||||
|
||||
"""
|
||||
if not receiver_name:
|
||||
return None
|
||||
|
||||
for ioc in self.get_iocs("app_ids"):
|
||||
if ioc["value"].lower() in receiver_name.lower():
|
||||
self.log.warning(
|
||||
'Found a known suspicious receiver with name "%s" '
|
||||
'matching indicators from "%s"',
|
||||
receiver_name,
|
||||
ioc["name"],
|
||||
)
|
||||
return ioc
|
||||
|
||||
return None
|
||||
|
||||
def check_android_property_name(self, property_name: str) -> Optional[dict]:
|
||||
"""Check the android property name against the list of indicators.
|
||||
|
||||
|
||||
@@ -1,113 +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 sys
|
||||
import platform
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
import threading
|
||||
|
||||
from mvt.common.config import settings
|
||||
from mvt.common.version import MVT_VERSION
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Telemetry(object):
|
||||
"""
|
||||
MVT collects anonymous telemetry to understand how MVT is used.
|
||||
|
||||
This data is helpful to prioritize features, identify platforms and versions. It
|
||||
will also how many users are using custom indicators, modules and packages.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.endpoint = settings.TELEMETRY.ENDPOINT
|
||||
self.device_id = settings.TELEMETRY.DEVICE_ID
|
||||
|
||||
def is_telemetry_enabled(self):
|
||||
return settings.TELEMETRY.ENABLED
|
||||
|
||||
@staticmethod
|
||||
def _installation_type():
|
||||
"""Check if MVT is installed via pip, docker or source."""
|
||||
if "site-packages" in __file__:
|
||||
return "pypi"
|
||||
elif os.environ.get("MVT_DOCKER_IMAGE", None):
|
||||
return "docker"
|
||||
else:
|
||||
return "source"
|
||||
|
||||
def _get_device_properties(self):
|
||||
return {
|
||||
"os_type": platform.system(),
|
||||
"os_version": platform.platform(),
|
||||
"python_version": f"{platform.python_version()}/{platform.python_implementation()}",
|
||||
"mvt_version": MVT_VERSION,
|
||||
"mvt_installation_type": self._installation_type(),
|
||||
"mvt_package_name": __package__,
|
||||
"mvt_command": os.path.basename(sys.argv[0]),
|
||||
"telemetry_version": "0.0.1",
|
||||
}
|
||||
|
||||
def _build_event(self, event_name, event_properties):
|
||||
return {
|
||||
"event": event_name,
|
||||
"distinct_id": self.device_id,
|
||||
"properties": {
|
||||
**self._get_device_properties(),
|
||||
**event_properties,
|
||||
},
|
||||
}
|
||||
|
||||
def _send_event(self, event):
|
||||
if not self.is_telemetry_enabled():
|
||||
# Telemetry is disabled. Do not send any data.
|
||||
return
|
||||
|
||||
event_json = json.dumps(event)
|
||||
|
||||
try:
|
||||
telemetry_thread = threading.Thread(
|
||||
target=self._send_event_thread, args=(event_json,)
|
||||
)
|
||||
telemetry_thread.start()
|
||||
except Exception as e:
|
||||
logger.debug(f"Failed to send telemetry data in a thread: {e}")
|
||||
|
||||
def _send_event_thread(self, event):
|
||||
try:
|
||||
response = requests.post(
|
||||
self.endpoint,
|
||||
data=json.dumps(event),
|
||||
timeout=5,
|
||||
headers={
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": f"mvt/{MVT_VERSION}",
|
||||
},
|
||||
)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
logger.debug(f"Failed to send telemetry data: {e}")
|
||||
|
||||
def send_cli_command_event(self, command_name):
|
||||
event = self._build_event(
|
||||
event_name="run_mvt_cli_command",
|
||||
event_properties={"cli_command_name": command_name},
|
||||
)
|
||||
self._send_event(event)
|
||||
|
||||
def send_module_detections_event(self, module_name, detections):
|
||||
event = self._build_event(
|
||||
event_name="module_detections",
|
||||
event_properties={"module_name": module_name, "detections": detections},
|
||||
)
|
||||
self._send_event(event)
|
||||
|
||||
|
||||
telemetry = Telemetry()
|
||||
@@ -10,34 +10,12 @@ import json
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import click
|
||||
from typing import Any, Iterator, Union
|
||||
|
||||
from rich.logging import RichHandler
|
||||
from mvt.common.telemetry import telemetry
|
||||
from mvt.common.config import settings
|
||||
|
||||
|
||||
class CommandWrapperGroup(click.Group):
|
||||
"""Allow hooks to run before and after MVT CLI commands"""
|
||||
|
||||
def add_command(self, cmd, name=None):
|
||||
click.Group.add_command(self, cmd, name=name)
|
||||
cmd.invoke = self.build_command_invoke(cmd.invoke)
|
||||
|
||||
def build_command_invoke(self, original_invoke):
|
||||
def command_invoke(ctx):
|
||||
"""Invoke the Click command"""
|
||||
|
||||
# Run telemetry before the command
|
||||
telemetry.send_cli_command_event(ctx.command.name)
|
||||
|
||||
# Run the original command
|
||||
original_invoke(ctx)
|
||||
|
||||
return command_invoke
|
||||
|
||||
|
||||
class CustomJSONEncoder(json.JSONEncoder):
|
||||
"""
|
||||
Custom JSON encoder to handle non-standard types.
|
||||
|
||||
@@ -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.6.1"
|
||||
MVT_VERSION = "2.7.0"
|
||||
|
||||
@@ -18,7 +18,6 @@ from mvt.common.utils import (
|
||||
generate_hashes_from_path,
|
||||
init_logging,
|
||||
set_verbose_logging,
|
||||
CommandWrapperGroup,
|
||||
)
|
||||
from mvt.common.help import (
|
||||
HELP_MSG_VERSION,
|
||||
@@ -69,7 +68,7 @@ def _get_disable_flags(ctx):
|
||||
# ==============================================================================
|
||||
# Main
|
||||
# ==============================================================================
|
||||
@click.group(invoke_without_command=False, cls=CommandWrapperGroup)
|
||||
@click.group(invoke_without_command=False)
|
||||
@click.option(
|
||||
"--disable-update-check", is_flag=True, help=HELP_MSG_DISABLE_UPDATE_CHECK
|
||||
)
|
||||
|
||||
@@ -194,5 +194,41 @@
|
||||
{
|
||||
"identifier": "iPhone16,2",
|
||||
"description": "iPhone 15 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone17,1",
|
||||
"description": "iPhone 16 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone17,2",
|
||||
"description": "iPhone 16 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone17,3",
|
||||
"description": "iPhone 16"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone17,4",
|
||||
"description": "iPhone 16 Plus"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone17,5",
|
||||
"description": "iPhone 16e"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone18,1",
|
||||
"description": "iPhone 17 Pro"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone18,2",
|
||||
"description": "iPhone 17 Pro Max"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone18,3",
|
||||
"description": "iPhone 17"
|
||||
},
|
||||
{
|
||||
"identifier": "iPhone18,4",
|
||||
"description": "iPhone Air"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -631,6 +631,10 @@
|
||||
"build": "16H81",
|
||||
"version": "12.5.7"
|
||||
},
|
||||
{
|
||||
"version": "12.5.8",
|
||||
"build": "16H88"
|
||||
},
|
||||
{
|
||||
"build": "17A577",
|
||||
"version": "13.0"
|
||||
@@ -899,6 +903,10 @@
|
||||
"version": "15.8.5",
|
||||
"build": "19H394"
|
||||
},
|
||||
{
|
||||
"version": "15.8.6",
|
||||
"build": "19H402"
|
||||
},
|
||||
{
|
||||
"build": "20A362",
|
||||
"version": "16.0"
|
||||
@@ -1008,6 +1016,10 @@
|
||||
"version": "16.7.12",
|
||||
"build": "20H364"
|
||||
},
|
||||
{
|
||||
"version": "16.7.14",
|
||||
"build": "20H370"
|
||||
},
|
||||
{
|
||||
"version": "17.0",
|
||||
"build": "21A327"
|
||||
@@ -1160,6 +1172,14 @@
|
||||
"version": "18.7.2",
|
||||
"build": "22H124"
|
||||
},
|
||||
{
|
||||
"version": "18.7.3",
|
||||
"build": "22H217"
|
||||
},
|
||||
{
|
||||
"version": "18.7.4",
|
||||
"build": "22H218"
|
||||
},
|
||||
{
|
||||
"version": "26",
|
||||
"build": "23A341"
|
||||
@@ -1171,5 +1191,13 @@
|
||||
{
|
||||
"version": "26.1",
|
||||
"build": "23B85"
|
||||
},
|
||||
{
|
||||
"version": "26.2",
|
||||
"build": "23C55"
|
||||
},
|
||||
{
|
||||
"version": "26.2.1",
|
||||
"build": "23C71"
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user