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

Compare commits

..

19 Commits

Author SHA1 Message Date
User
b44944aecf fix ruff 2025-08-19 08:51:14 +02:00
User
514c400017 Fix AndroidQF file count test 2025-08-19 08:49:44 +02:00
besendorf
41d22a8fdc Merge branch 'main' into root_binaries 2025-08-15 11:02:34 +02:00
Janik Besendorf
d90654e74a Add root_binaries androidqf module 2025-08-15 11:00:49 +02:00
dependabot[bot]
86a0772eb2 Bump cryptography from 45.0.5 to 45.0.6 (#675)
Bumps [cryptography](https://github.com/pyca/cryptography) from 45.0.5 to 45.0.6.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/45.0.5...45.0.6)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 45.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-12 10:38:19 +02:00
github-actions[bot]
7d0be9db4f Add new iOS versions and build numbers (#673)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2025-07-31 13:20:34 +02:00
dependabot[bot]
4e120b2640 Bump pydantic-settings from 2.9.1 to 2.10.1 (#655)
Bumps [pydantic-settings](https://github.com/pydantic/pydantic-settings) from 2.9.1 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic-settings/releases)
- [Commits](https://github.com/pydantic/pydantic-settings/compare/v2.9.1...2.10.1)

---
updated-dependencies:
- dependency-name: pydantic-settings
  dependency-version: 2.10.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:58:12 +02:00
dependabot[bot]
dbe9e5db9b Bump mkdocstrings from 0.29.1 to 0.30.0 (#671)
Bumps [mkdocstrings](https://github.com/mkdocstrings/mkdocstrings) from 0.29.1 to 0.30.0.
- [Release notes](https://github.com/mkdocstrings/mkdocstrings/releases)
- [Changelog](https://github.com/mkdocstrings/mkdocstrings/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mkdocstrings/mkdocstrings/compare/0.29.1...0.30.0)

---
updated-dependencies:
- dependency-name: mkdocstrings
  dependency-version: 0.30.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tek <tek@randhome.io>
2025-07-28 22:42:37 +02:00
dependabot[bot]
0b00398729 Bump rich from 14.0.0 to 14.1.0 (#670)
Bumps [rich](https://github.com/Textualize/rich) from 14.0.0 to 14.1.0.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.0.0...v14.1.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 14.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:37:42 +02:00
dependabot[bot]
87034d2c7a Bump mkdocs-material from 9.6.14 to 9.6.16 (#672)
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.6.14 to 9.6.16.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.14...9.6.16)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-version: 9.6.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 22:29:04 +02:00
besendorf
595a2f6536 Merge pull request #656 from mvt-project/fix/install_non_market_apps
remove deprecated install_non_market_apps permission check
2025-07-22 19:32:05 +02:00
besendorf
8ead44a31e Merge branch 'main' into fix/install_non_market_apps 2025-07-22 19:12:44 +02:00
besendorf
5c19d02a73 Merge pull request #659 from mvt-project/fix/tcc
fix #579 TCC: no such table: access
2025-07-22 19:02:32 +02:00
besendorf
14ebc9ee4e Merge branch 'main' into fix/tcc 2025-07-22 18:56:10 +02:00
besendorf
de53cc07f8 Merge pull request #660 from mvt-project/fix/safari_browserstate
catch sqlite exception in safari_browserstate.py
2025-07-22 18:33:39 +02:00
besendorf
242052b8ec Merge branch 'main' into fix/install_non_market_apps 2025-07-17 11:45:34 +02:00
besendorf
10915f250c catch tcc error 2025-07-04 17:46:50 +02:00
besendorf
c60cef4009 Merge branch 'main' into fix/install_non_market_apps 2025-07-04 17:04:13 +02:00
besendorf
49108e67e2 remove deprecated install_non_market_apps permission check 2025-07-02 10:11:35 +02:00
10 changed files with 265 additions and 18 deletions

View File

@@ -1,5 +1,5 @@
mkdocs==1.6.1
mkdocs-autorefs==1.4.2
mkdocs-material==9.6.14
mkdocs-material==9.6.16
mkdocs-material-extensions==1.3.1
mkdocstrings==0.29.1
mkdocstrings==0.30.0

View File

@@ -20,7 +20,7 @@ classifiers = [
]
dependencies = [
"click==8.2.1",
"rich==14.0.0",
"rich==14.1.0",
"tld==0.13.1",
"requests==2.32.4",
"simplejson==3.20.1",
@@ -29,12 +29,12 @@ dependencies = [
"iOSbackup==0.9.925",
"adb-shell[usb]==0.4.4",
"libusb1==3.3.1",
"cryptography==45.0.5",
"cryptography==45.0.6",
"PyYAML>=6.0.2",
"pyahocorasick==2.2.0",
"betterproto==1.2.5",
"pydantic==2.11.7",
"pydantic-settings==2.9.1",
"pydantic-settings==2.10.1",
"NSKeyedUnArchiver==1.5.2",
"python-dateutil==2.9.0.post0",
]

View File

@@ -51,11 +51,6 @@ ANDROID_DANGEROUS_SETTINGS = [
"key": "send_action_app_error",
"safe_value": "1",
},
{
"description": "enabled installation of non Google Play apps",
"key": "install_non_market_apps",
"safe_value": "0",
},
{
"description": "enabled accessibility services",
"key": "accessibility_enabled",

View File

@@ -19,6 +19,7 @@ from .processes import Processes
from .settings import Settings
from .sms import SMS
from .files import Files
from .root_binaries import RootBinaries
ANDROIDQF_MODULES = [
DumpsysActivities,
@@ -37,4 +38,5 @@ ANDROIDQF_MODULES = [
SMS,
DumpsysPackages,
Files,
RootBinaries,
]

View File

@@ -0,0 +1,121 @@
# 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 json
import logging
from typing import Optional
from .base import AndroidQFModule
class RootBinaries(AndroidQFModule):
"""This module analyzes root_binaries.json for root binaries found by androidqf."""
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 serialize(self, record: dict) -> dict:
return {
"timestamp": record.get("timestamp"),
"module": self.__class__.__name__,
"event": "root_binary_found",
"data": f"Root binary found: {record['path']} (binary: {record['binary_name']})",
}
def check_indicators(self) -> None:
"""Check for indicators of device rooting."""
if not self.results:
return
# All found root binaries are considered indicators of rooting
for result in self.results:
self.log.warning(
'Found root binary "%s" at path "%s"',
result["binary_name"],
result["path"],
)
self.detected.append(result)
if self.detected:
self.log.warning(
"Device shows signs of rooting with %d root binaries found",
len(self.detected),
)
def run(self) -> None:
"""Run the root binaries analysis."""
root_binaries_files = self._get_files_by_pattern("*/root_binaries.json")
if not root_binaries_files:
self.log.info("No root_binaries.json file found")
return
rawdata = self._get_file_content(root_binaries_files[0]).decode(
"utf-8", errors="ignore"
)
try:
root_binary_paths = json.loads(rawdata)
except json.JSONDecodeError as e:
self.log.error("Failed to parse root_binaries.json: %s", e)
return
if not isinstance(root_binary_paths, list):
self.log.error("Expected root_binaries.json to contain a list of paths")
return
# Known root binary names that might be found and their descriptions
# This maps the binary name to a human-readable description
known_root_binaries = {
"su": "SuperUser binary",
"busybox": "BusyBox utilities",
"supersu": "SuperSU root management",
"Superuser.apk": "Superuser app",
"KingoUser.apk": "KingRoot app",
"SuperSu.apk": "SuperSU app",
"magisk": "Magisk root framework",
"magiskhide": "Magisk hide utility",
"magiskinit": "Magisk init binary",
"magiskpolicy": "Magisk policy binary",
}
for path in root_binary_paths:
if not path or not isinstance(path, str):
continue
# Extract binary name from path
binary_name = path.split("/")[-1].lower()
# Check if this matches a known root binary by exact name match
description = "Unknown root binary"
for known_binary in known_root_binaries:
if binary_name == known_binary.lower():
description = known_root_binaries[known_binary]
break
result = {
"path": path.strip(),
"binary_name": binary_name,
"description": description,
}
self.results.append(result)
self.log.info("Found %d root binaries", len(self.results))

View File

@@ -1131,5 +1131,9 @@
{
"version": "18.5",
"build": "22F76"
},
{
"version": "18.6",
"build": "22G86"
}
]

View File

@@ -116,13 +116,16 @@ class TCC(IOSExtraction):
)
db_version = "v2"
except sqlite3.OperationalError:
cur.execute(
"""SELECT
service, client, client_type, allowed,
prompt_count
FROM access;"""
)
db_version = "v1"
try:
cur.execute(
"""SELECT
service, client, client_type, allowed,
prompt_count
FROM access;"""
)
db_version = "v1"
except sqlite3.OperationalError as e:
self.log.error(f"Error parsing TCC database: {e}")
for row in cur:
service = row[0]

View File

@@ -0,0 +1,116 @@
# 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 pathlib import Path
import pytest
from mvt.android.modules.androidqf.root_binaries import RootBinaries
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
@pytest.fixture()
def data_path():
return get_android_androidqf()
@pytest.fixture()
def parent_data_path(data_path):
return Path(data_path).absolute().parent.as_posix()
@pytest.fixture()
def file_list(data_path):
return list_files(data_path)
@pytest.fixture()
def module(parent_data_path, file_list):
m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, file_list)
return m
class TestAndroidqfRootBinaries:
def test_root_binaries_detection(self, module):
run_module(module)
# Should find 4 root binaries from the test file
assert len(module.results) == 4
assert len(module.detected) == 4
# Check that all results are detected as indicators
binary_paths = [result["path"] for result in module.results]
assert "/system/bin/su" in binary_paths
assert "/system/xbin/busybox" in binary_paths
assert "/data/local/tmp/magisk" in binary_paths
assert "/system/bin/magiskhide" in binary_paths
def test_root_binaries_descriptions(self, module):
run_module(module)
# Check that binary descriptions are correctly identified
su_result = next((r for r in module.results if "su" in r["binary_name"]), None)
assert su_result is not None
assert "SuperUser binary" in su_result["description"]
busybox_result = next(
(r for r in module.results if "busybox" in r["binary_name"]), None
)
assert busybox_result is not None
assert "BusyBox utilities" in busybox_result["description"]
magisk_result = next(
(r for r in module.results if r["binary_name"] == "magisk"), None
)
assert magisk_result is not None
assert "Magisk root framework" in magisk_result["description"]
magiskhide_result = next(
(r for r in module.results if "magiskhide" in r["binary_name"]), None
)
assert magiskhide_result is not None
assert "Magisk hide utility" in magiskhide_result["description"]
def test_root_binaries_warnings(self, caplog, module):
run_module(module)
# Check that warnings are logged for each root binary found
assert 'Found root binary "su" at path "/system/bin/su"' in caplog.text
assert (
'Found root binary "busybox" at path "/system/xbin/busybox"' in caplog.text
)
assert (
'Found root binary "magisk" at path "/data/local/tmp/magisk"' in caplog.text
)
assert (
'Found root binary "magiskhide" at path "/system/bin/magiskhide"'
in caplog.text
)
assert "Device shows signs of rooting with 4 root binaries found" in caplog.text
def test_serialize_method(self, module):
run_module(module)
# Test that serialize method works correctly
if module.results:
serialized = module.serialize(module.results[0])
assert serialized["module"] == "RootBinaries"
assert serialized["event"] == "root_binary_found"
assert "Root binary found:" in serialized["data"]
def test_no_root_binaries_file(self, parent_data_path):
# Test behavior when no root_binaries.json file is present
empty_file_list = []
m = RootBinaries(target_path=parent_data_path, log=logging)
m.from_folder(parent_data_path, empty_file_list)
run_module(m)
assert len(m.results) == 0
assert len(m.detected) == 0

View File

@@ -0,0 +1,6 @@
[
"/system/bin/su",
"/system/xbin/busybox",
"/data/local/tmp/magisk",
"/system/bin/magiskhide"
]

View File

@@ -62,7 +62,7 @@ class TestHashes:
def test_hash_from_folder(self):
path = os.path.join(get_artifact_folder(), "androidqf")
hashes = list(generate_hashes_from_path(path, logging))
assert len(hashes) == 7
assert len(hashes) == 8
# Sort the files to have reliable order for tests.
hashes = sorted(hashes, key=lambda x: x["file_path"])
assert hashes[0]["file_path"] == os.path.join(path, "backup.ab")