mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-16 18:32:46 +00:00
Compare commits
7 Commits
feature/io
...
feature/co
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
06f65dbbcf | ||
|
|
1c78874b82 | ||
|
|
0c73e3e8fa | ||
|
|
9b5f2d89d5 | ||
|
|
3da61c8da8 | ||
|
|
5b2fe3baec | ||
|
|
9d81b5bfa8 |
37
docs/command_completion.md
Normal file
37
docs/command_completion.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
`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
|
||||||
|
|
||||||
|
# Sources the scripts in ~/.bashrc.
|
||||||
|
. ~/.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
|
||||||
|
|
||||||
|
# Sources the scripts in ~/.zshrc.
|
||||||
|
. ~/.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).
|
||||||
|
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ class DumpsysAppopsArtifact(AndroidArtifact):
|
|||||||
and perm["access"] == "allow"
|
and perm["access"] == "allow"
|
||||||
):
|
):
|
||||||
self.log.info(
|
self.log.info(
|
||||||
"Package %s with REQUEST_INSTALL_PACKAGES " "permission",
|
"Package %s with REQUEST_INSTALL_PACKAGES permission",
|
||||||
result["package_name"],
|
result["package_name"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,7 @@ 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 "
|
'Found an installed package related to rooting/jailbreaking: "%s"',
|
||||||
'rooting/jailbreaking: "%s"',
|
|
||||||
result["package_name"],
|
result["package_name"],
|
||||||
)
|
)
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|||||||
42
src/mvt/android/artifacts/dumpsys_platform_compat.py
Normal file
42
src/mvt/android/artifacts/dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# 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})
|
||||||
@@ -326,8 +326,7 @@ class AndroidExtraction(MVTModule):
|
|||||||
|
|
||||||
if not header["backup"]:
|
if not header["backup"]:
|
||||||
self.log.error(
|
self.log.error(
|
||||||
"Extracting SMS via Android backup failed. "
|
"Extracting SMS via Android backup failed. No valid backup data found."
|
||||||
"No valid backup data found."
|
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,7 @@ 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 "
|
'Found an installed package related to rooting/jailbreaking: "%s"',
|
||||||
'rooting/jailbreaking: "%s"',
|
|
||||||
result["package_name"],
|
result["package_name"],
|
||||||
)
|
)
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ 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
|
||||||
@@ -29,6 +30,7 @@ ANDROIDQF_MODULES = [
|
|||||||
DumpsysBatteryHistory,
|
DumpsysBatteryHistory,
|
||||||
DumpsysADBState,
|
DumpsysADBState,
|
||||||
Packages,
|
Packages,
|
||||||
|
DumpsysPlatformCompat,
|
||||||
Processes,
|
Processes,
|
||||||
Getprop,
|
Getprop,
|
||||||
Settings,
|
Settings,
|
||||||
|
|||||||
44
src/mvt/android/modules/androidqf/dumpsys_platform_compat.py
Normal file
44
src/mvt/android/modules/androidqf/dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# 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))
|
||||||
@@ -44,8 +44,7 @@ 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 "
|
'Found an installed package related to rooting/jailbreaking: "%s"',
|
||||||
'rooting/jailbreaking: "%s"',
|
|
||||||
result["name"],
|
result["name"],
|
||||||
)
|
)
|
||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ 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
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ BUGREPORT_MODULES = [
|
|||||||
DBInfo,
|
DBInfo,
|
||||||
Getprop,
|
Getprop,
|
||||||
Packages,
|
Packages,
|
||||||
|
PlatformCompat,
|
||||||
Receivers,
|
Receivers,
|
||||||
DumpsysADBState,
|
DumpsysADBState,
|
||||||
]
|
]
|
||||||
|
|||||||
48
src/mvt/android/modules/bugreport/platform_compat.py
Normal file
48
src/mvt/android/modules/bugreport/platform_compat.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# 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))
|
||||||
@@ -81,7 +81,7 @@ class Command:
|
|||||||
os.path.join(self.results_path, "command.log")
|
os.path.join(self.results_path, "command.log")
|
||||||
)
|
)
|
||||||
formatter = logging.Formatter(
|
formatter = logging.Formatter(
|
||||||
"%(asctime)s - %(name)s - " "%(levelname)s - %(message)s"
|
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||||
)
|
)
|
||||||
file_handler.setLevel(logging.DEBUG)
|
file_handler.setLevel(logging.DEBUG)
|
||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
|
|||||||
@@ -383,8 +383,7 @@ class Indicators:
|
|||||||
for ioc in self.get_iocs("urls"):
|
for ioc in self.get_iocs("urls"):
|
||||||
if ioc["value"] == url:
|
if ioc["value"] == url:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"Found a known suspicious URL %s "
|
'Found a known suspicious URL %s matching indicator "%s" from "%s"',
|
||||||
'matching indicator "%s" from "%s"',
|
|
||||||
url,
|
url,
|
||||||
ioc["value"],
|
ioc["value"],
|
||||||
ioc["name"],
|
ioc["name"],
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ def decrypt_backup(ctx, destination, password, key_file, hashes, backup_path):
|
|||||||
if key_file:
|
if key_file:
|
||||||
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
||||||
log.info(
|
log.info(
|
||||||
"Ignoring %s environment variable, using --key-file" "'%s' instead",
|
"Ignoring %s environment variable, using --key-file'%s' instead",
|
||||||
MVT_IOS_BACKUP_PASSWORD,
|
MVT_IOS_BACKUP_PASSWORD,
|
||||||
key_file,
|
key_file,
|
||||||
)
|
)
|
||||||
@@ -114,7 +114,7 @@ def decrypt_backup(ctx, destination, password, key_file, hashes, backup_path):
|
|||||||
|
|
||||||
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
||||||
log.info(
|
log.info(
|
||||||
"Ignoring %s environment variable, using --password" "argument instead",
|
"Ignoring %s environment variable, using --passwordargument instead",
|
||||||
MVT_IOS_BACKUP_PASSWORD,
|
MVT_IOS_BACKUP_PASSWORD,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -168,8 +168,7 @@ def extract_key(password, key_file, backup_path):
|
|||||||
|
|
||||||
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
if MVT_IOS_BACKUP_PASSWORD in os.environ:
|
||||||
log.info(
|
log.info(
|
||||||
"Ignoring %s environment variable, using --password "
|
"Ignoring %s environment variable, using --password argument instead",
|
||||||
"argument instead",
|
|
||||||
MVT_IOS_BACKUP_PASSWORD,
|
MVT_IOS_BACKUP_PASSWORD,
|
||||||
)
|
)
|
||||||
elif MVT_IOS_BACKUP_PASSWORD in os.environ:
|
elif MVT_IOS_BACKUP_PASSWORD in os.environ:
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ class BackupInfo(IOSExtraction):
|
|||||||
info_path = os.path.join(self.target_path, "Info.plist")
|
info_path = os.path.join(self.target_path, "Info.plist")
|
||||||
if not os.path.exists(info_path):
|
if not os.path.exists(info_path):
|
||||||
raise DatabaseNotFoundError(
|
raise DatabaseNotFoundError(
|
||||||
"No Info.plist at backup path, unable to extract device " "information"
|
"No Info.plist at backup path, unable to extract device information"
|
||||||
)
|
)
|
||||||
|
|
||||||
with open(info_path, "rb") as handle:
|
with open(info_path, "rb") as handle:
|
||||||
|
|||||||
@@ -110,8 +110,7 @@ class Manifest(IOSExtraction):
|
|||||||
ioc = self.indicators.check_url(part)
|
ioc = self.indicators.check_url(part)
|
||||||
if ioc:
|
if ioc:
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
'Found mention of domain "%s" in a backup file with '
|
'Found mention of domain "%s" in a backup file with path: %s',
|
||||||
"path: %s",
|
|
||||||
ioc["value"],
|
ioc["value"],
|
||||||
rel_path,
|
rel_path,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class IOSExtraction(MVTModule):
|
|||||||
|
|
||||||
if not shutil.which("sqlite3"):
|
if not shutil.which("sqlite3"):
|
||||||
raise DatabaseCorruptedError(
|
raise DatabaseCorruptedError(
|
||||||
"failed to recover without sqlite3 binary: please install " "sqlite3!"
|
"failed to recover without sqlite3 binary: please install sqlite3!"
|
||||||
)
|
)
|
||||||
if '"' in file_path:
|
if '"' in file_path:
|
||||||
raise DatabaseCorruptedError(
|
raise DatabaseCorruptedError(
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ from mvt.ios.modules.base import IOSExtraction
|
|||||||
APPLICATIONS_DB_PATH = [
|
APPLICATIONS_DB_PATH = [
|
||||||
"private/var/containers/Bundle/Application/*/iTunesMetadata.plist"
|
"private/var/containers/Bundle/Application/*/iTunesMetadata.plist"
|
||||||
]
|
]
|
||||||
|
KNOWN_APP_INSTALLERS = [
|
||||||
|
"com.apple.AppStore",
|
||||||
|
"com.apple.AppStore.ProductPageExtension",
|
||||||
|
"com.apple.dmd",
|
||||||
|
"dmd",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class Applications(IOSExtraction):
|
class Applications(IOSExtraction):
|
||||||
@@ -80,12 +86,10 @@ class Applications(IOSExtraction):
|
|||||||
self.detected.append(result)
|
self.detected.append(result)
|
||||||
continue
|
continue
|
||||||
# Some apps installed from apple store with sourceApp "com.apple.AppStore.ProductPageExtension"
|
# Some apps installed from apple store with sourceApp "com.apple.AppStore.ProductPageExtension"
|
||||||
if result.get("sourceApp", "com.apple.AppStore") not in [
|
if (
|
||||||
"com.apple.AppStore",
|
result.get("sourceApp", "com.apple.AppStore")
|
||||||
"com.apple.AppStore.ProductPageExtension",
|
not in KNOWN_APP_INSTALLERS
|
||||||
"com.apple.dmd",
|
):
|
||||||
"dmd",
|
|
||||||
]:
|
|
||||||
self.log.warning(
|
self.log.warning(
|
||||||
"Suspicious app not installed from the App Store or MDM: %s",
|
"Suspicious app not installed from the App Store or MDM: %s",
|
||||||
result["softwareVersionBundleId"],
|
result["softwareVersionBundleId"],
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ class SMS(IOSExtraction):
|
|||||||
|
|
||||||
def serialize(self, record: dict) -> Union[dict, list]:
|
def serialize(self, record: dict) -> Union[dict, list]:
|
||||||
text = record["text"].replace("\n", "\\n")
|
text = record["text"].replace("\n", "\\n")
|
||||||
sms_data = f"{record['service']}: {record['guid']} \"{text}\" from {record['phone_number']} ({record['account']})"
|
sms_data = f'{record["service"]}: {record["guid"]} "{text}" from {record["phone_number"]} ({record["account"]})'
|
||||||
records = [
|
records = [
|
||||||
{
|
{
|
||||||
"timestamp": record["isodate"],
|
"timestamp": record["isodate"],
|
||||||
|
|||||||
@@ -100,7 +100,7 @@ class WebkitSessionResourceLog(IOSExtraction):
|
|||||||
redirect_path += ", ".join(source_domains)
|
redirect_path += ", ".join(source_domains)
|
||||||
redirect_path += " -> "
|
redirect_path += " -> "
|
||||||
|
|
||||||
redirect_path += f"ORIGIN: \"{entry['origin']}\""
|
redirect_path += f'ORIGIN: "{entry["origin"]}"'
|
||||||
|
|
||||||
if len(destination_domains) > 0:
|
if len(destination_domains) > 0:
|
||||||
redirect_path += " -> "
|
redirect_path += " -> "
|
||||||
|
|||||||
40
tests/android/test_artifact_dumpsys_platform_compat.py
Normal file
40
tests/android/test_artifact_dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# 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 mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact
|
||||||
|
from mvt.common.indicators import Indicators
|
||||||
|
|
||||||
|
from ..utils import get_artifact
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPlatformCompatArtifact:
|
||||||
|
def test_parsing(self):
|
||||||
|
dbi = DumpsysPlatformCompatArtifact()
|
||||||
|
file = get_artifact("android_data/dumpsys_platform_compat.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
assert len(dbi.results) == 0
|
||||||
|
dbi.parse(data)
|
||||||
|
assert len(dbi.results) == 2
|
||||||
|
assert dbi.results[0]["package_name"] == "org.torproject.torbrowser"
|
||||||
|
assert dbi.results[1]["package_name"] == "org.article19.circulo.next"
|
||||||
|
|
||||||
|
def test_ioc_check(self, indicator_file):
|
||||||
|
dbi = DumpsysPlatformCompatArtifact()
|
||||||
|
file = get_artifact("android_data/dumpsys_platform_compat.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
dbi.parse(data)
|
||||||
|
|
||||||
|
ind = Indicators(log=logging.getLogger())
|
||||||
|
ind.parse_stix2(indicator_file)
|
||||||
|
ind.ioc_collections[0]["app_ids"].append("org.torproject.torbrowser")
|
||||||
|
ind.ioc_collections[0]["app_ids"].append("org.article19.circulo.next")
|
||||||
|
dbi.indicators = ind
|
||||||
|
assert len(dbi.detected) == 0
|
||||||
|
dbi.check_indicators()
|
||||||
|
assert len(dbi.detected) == 2
|
||||||
23
tests/android_androidqf/test_dumpsys_platform_compat.py
Normal file
23
tests/android_androidqf/test_dumpsys_platform_compat.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 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 pathlib import Path
|
||||||
|
|
||||||
|
from mvt.android.modules.androidqf.dumpsys_platform_compat import DumpsysPlatformCompat
|
||||||
|
from mvt.common.module import run_module
|
||||||
|
|
||||||
|
from ..utils import get_android_androidqf, list_files
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPlatformCompatModule:
|
||||||
|
def test_parsing(self):
|
||||||
|
data_path = get_android_androidqf()
|
||||||
|
m = DumpsysPlatformCompat(target_path=data_path)
|
||||||
|
files = list_files(data_path)
|
||||||
|
parent_path = Path(data_path).absolute().parent.as_posix()
|
||||||
|
m.from_folder(parent_path, files)
|
||||||
|
run_module(m)
|
||||||
|
assert len(m.results) == 2
|
||||||
|
assert len(m.detected) == 0
|
||||||
@@ -246,6 +246,23 @@ Packages:
|
|||||||
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivity
|
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivity
|
||||||
com.instagram.share.handleractivity.ClipsShareHandlerActivity
|
com.instagram.share.handleractivity.ClipsShareHandlerActivity
|
||||||
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivityInterop
|
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivityInterop
|
||||||
|
--------- 0.053s was the duration of dumpsys appops, ending at: 2022-03-29 23:14:27
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
DUMP OF SERVICE platform_compat:
|
||||||
|
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
|
||||||
|
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
|
||||||
|
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
|
||||||
|
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
|
||||||
|
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
|
||||||
|
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
|
||||||
|
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
|
||||||
|
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
|
||||||
|
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
16
tests/artifacts/android_data/dumpsys_platform_compat.txt
Normal file
16
tests/artifacts/android_data/dumpsys_platform_compat.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
DUMP OF SERVICE platform_compat:
|
||||||
|
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
|
||||||
|
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
|
||||||
|
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
|
||||||
|
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
|
||||||
|
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
|
||||||
|
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
|
||||||
|
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
|
||||||
|
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
|
||||||
|
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)
|
||||||
@@ -379,4 +379,22 @@ Daily stats:
|
|||||||
Update com.google.android.projection.gearhead vers=99632623
|
Update com.google.android.projection.gearhead vers=99632623
|
||||||
Update com.google.android.projection.gearhead vers=99632623
|
Update com.google.android.projection.gearhead vers=99632623
|
||||||
Update com.google.android.projection.gearhead vers=99632623
|
Update com.google.android.projection.gearhead vers=99632623
|
||||||
|
--------- 0.053s was the duration of dumpsys batterystats, ending at: 2024-03-21 11:07:22
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
DUMP OF SERVICE platform_compat:
|
||||||
|
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
|
||||||
|
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
|
||||||
|
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
|
||||||
|
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
|
||||||
|
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
|
||||||
|
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
|
||||||
|
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
|
||||||
|
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
|
||||||
|
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
|
||||||
|
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
|
||||||
|
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
|
||||||
|
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class TestHashes:
|
|||||||
# This needs to be updated when we add or edit files in AndroidQF folder
|
# This needs to be updated when we add or edit files in AndroidQF folder
|
||||||
assert (
|
assert (
|
||||||
hashes[1]["sha256"]
|
hashes[1]["sha256"]
|
||||||
== "1bd255f656a7f9d5647a730f0f0cc47053115576f11532d41bf28c16635b193d"
|
== "9fb6396b64cfff30e2a459a64496d3c1386926d09edd68be2d878de45fa7b3a9"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user