Compare commits

..

3 Commits

Author SHA1 Message Date
Donncha Ó Cearbhaill
82f5e5c627 Merge branch 'main' into feature/android-sub-module-loading 2025-02-06 20:51:59 +01:00
Donncha Ó Cearbhaill
2bb613fe09 Return after loading bugreport module 2024-10-28 11:19:45 +01:00
Donncha Ó Cearbhaill
355850bd5c WIP: Run bugreport modules against bugreport.zip in AndroidQF extraction 2024-10-28 11:12:20 +01:00
32 changed files with 192 additions and 751 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
@@ -35,4 +35,4 @@ jobs:
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
with: with:
pytest-coverage-path: ./pytest-coverage.txt pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml junitxml-path: ./pytest.xml

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,7 +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-proto-parsers:
# Generate python parsers for protobuf files # Generate python parsers for protobuf files

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.1 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,61 +16,48 @@ 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.4", "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", "betterproto >=1.2.0",
"pydantic==2.11.7", "pydantic >= 2.10.0",
"pydantic-settings==2.10.1", "pydantic-settings >= 2.7.0",
"NSKeyedUnArchiver==1.5.2", 'backports.zoneinfo; python_version < "3.9"',
"python-dateutil==2.9.0.post0",
"tzdata==2025.2",
] ]
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
@@ -78,13 +67,15 @@ 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
ignore = [ ignore = [
"E501", # don't enforce line length violations "E501", # don't enforce line length violations
"C901", # complex-structure "C901", # complex-structure
# These were previously ignored but don't seem to be required: # These were previously ignored but don't seem to be required:
# "E265", # no-space-after-block-comment # "E265", # no-space-after-block-comment
@@ -96,14 +87,14 @@ ignore = [
] ]
[tool.ruff.lint.per-file-ignores] [tool.ruff.lint.per-file-ignores]
"__init__.py" = ["F401"] # unused-import "__init__.py" = ["F401"] # unused-import
[tool.ruff.lint.mccabe] [tool.ruff.lint.mccabe]
max-complexity = 10 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"]
@@ -112,4 +103,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) key_raw = base64.b64decode(key_base64)
else: key_fingerprint = hashlib.md5(key_raw).hexdigest().upper()
key_base64, user = user_key, b"" key_fingerprint_colon = ":".join(
[key_fingerprint[i : i + 2] for i in range(0, len(key_fingerprint), 2)]
try: )
key_raw = base64.b64decode(key_base64)
key_fingerprint = hashlib.md5(key_raw).hexdigest().upper()
key_fingerprint_colon = ":".join(
[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,24 +115,8 @@ 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 parsed = parsed["debugging_manager"]
# 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"]
# Calculate key fingerprints for better readability # Calculate key fingerprints for better readability
key_info = [] key_info = []

View File

@@ -1,186 +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 Any
from .artifact import AndroidArtifact
SUSPICIOUS_MOUNT_POINTS = [
"/system",
"/vendor",
"/product",
"/system_ext",
]
SUSPICIOUS_OPTIONS = [
"rw",
"remount",
"noatime",
"nodiratime",
]
ALLOWLIST_NOATIME = [
"/system_dlkm",
"/system_ext",
"/product",
"/vendor",
"/vendor_dlkm",
]
class Mounts(AndroidArtifact):
"""
This artifact parses mount information from /proc/mounts or similar mount data.
It can detect potentially suspicious mount configurations that may indicate
a rooted or compromised device.
"""
def parse(self, entry: str) -> None:
"""
Parse mount information from the provided entry.
Examples:
/dev/block/bootdevice/by-name/system /system ext4 ro,seclabel,relatime 0 0
/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)
"""
self.results: list[dict[str, Any]] = []
for line in entry.splitlines():
line = line.strip()
if not line:
continue
device = None
mount_point = None
filesystem_type = None
mount_options = ""
if " on " in line and " type " in line:
try:
# Format: device on mount_point type filesystem_type (options)
device_part, rest = line.split(" on ", 1)
device = device_part.strip()
# Split by 'type' to get mount_point and filesystem info
mount_part, fs_part = rest.split(" type ", 1)
mount_point = mount_part.strip()
# Parse filesystem and options
if "(" in fs_part and fs_part.endswith(")"):
# Format: filesystem_type (options)
fs_and_opts = fs_part.strip()
paren_idx = fs_and_opts.find("(")
filesystem_type = fs_and_opts[:paren_idx].strip()
mount_options = fs_and_opts[paren_idx + 1 : -1].strip()
else:
# No options in parentheses, just filesystem type
filesystem_type = fs_part.strip()
mount_options = ""
# Skip if we don't have essential info
if not device or not mount_point or not filesystem_type:
continue
# Parse options into list
options_list = (
[opt.strip() for opt in mount_options.split(",") if opt.strip()]
if mount_options
else []
)
# Check if it's a system partition
is_system_partition = mount_point in SUSPICIOUS_MOUNT_POINTS or any(
mount_point.startswith(sp) for sp in SUSPICIOUS_MOUNT_POINTS
)
# Check if it's mounted read-write
is_read_write = "rw" in options_list
mount_entry = {
"device": device,
"mount_point": mount_point,
"filesystem_type": filesystem_type,
"mount_options": mount_options,
"options_list": options_list,
"is_system_partition": is_system_partition,
"is_read_write": is_read_write,
}
self.results.append(mount_entry)
except ValueError:
# If parsing fails, skip this line
continue
else:
# Skip lines that don't match expected format
continue
def check_indicators(self) -> None:
"""
Check for suspicious mount configurations that may indicate root access
or other security concerns.
"""
system_rw_mounts = []
suspicious_mounts = []
for mount in self.results:
mount_point = mount["mount_point"]
options = mount["options_list"]
# Check for system partitions mounted as read-write
if mount["is_system_partition"] and mount["is_read_write"]:
system_rw_mounts.append(mount)
if mount_point == "/system":
self.log.warning(
"Root detected /system partition is mounted as read-write (rw). "
)
else:
self.log.warning(
"System partition %s is mounted as read-write (rw). This may indicate system modifications.",
mount_point,
)
# Check for other suspicious mount options
suspicious_opts = [opt for opt in options if opt in SUSPICIOUS_OPTIONS]
if suspicious_opts and mount["is_system_partition"]:
if (
"noatime" in mount["mount_options"]
and mount["mount_point"] in ALLOWLIST_NOATIME
):
continue
suspicious_mounts.append(mount)
self.log.warning(
"Suspicious mount options found for %s: %s",
mount_point,
", ".join(suspicious_opts),
)
# Log interesting mount information
if mount_point == "/data" or mount_point.startswith("/sdcard"):
self.log.info(
"Data partition: %s mounted as %s with options: %s",
mount_point,
mount["filesystem_type"],
mount["mount_options"],
)
self.log.info("Parsed %d mount entries", len(self.results))
# Check indicators if available
if not self.indicators:
return
for mount in self.results:
# Check if any mount points match indicators
ioc = self.indicators.check_file_path(mount.get("mount_point", ""))
if ioc:
mount["matched_indicator"] = ioc
self.detected.append(mount)
# Check device paths for indicators
ioc = self.indicators.check_file_path(mount.get("device", ""))
if ioc:
mount["matched_indicator"] = ioc
self.detected.append(mount)

View File

@@ -51,6 +51,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",

View File

@@ -8,7 +8,6 @@ from typing import List, Optional, Union
import pydantic import pydantic
import betterproto import betterproto
from dateutil import parser
from mvt.common.utils import convert_datetime_to_iso from mvt.common.utils import convert_datetime_to_iso
from mvt.android.parsers.proto.tombstone import Tombstone from mvt.android.parsers.proto.tombstone import Tombstone
@@ -53,7 +52,7 @@ class TombstoneCrashResult(pydantic.BaseModel):
file_name: str file_name: str
file_timestamp: str # We store the timestamp as a string to avoid timezone issues file_timestamp: str # We store the timestamp as a string to avoid timezone issues
build_fingerprint: str build_fingerprint: str
revision: str revision: int
arch: Optional[str] = None arch: Optional[str] = None
timestamp: str # We store the timestamp as a string to avoid timezone issues timestamp: str # We store the timestamp as a string to avoid timezone issues
process_uptime: Optional[int] = None process_uptime: Optional[int] = None
@@ -63,14 +62,14 @@ class TombstoneCrashResult(pydantic.BaseModel):
process_name: Optional[str] = None process_name: Optional[str] = None
binary_path: Optional[str] = None binary_path: Optional[str] = None
selinux_label: Optional[str] = None selinux_label: Optional[str] = None
uid: int uid: Optional[int] = None
signal_info: SignalInfo signal_info: SignalInfo
cause: Optional[str] = None cause: Optional[str] = None
extra: Optional[str] = None extra: Optional[str] = None
class TombstoneCrashArtifact(AndroidArtifact): class TombstoneCrashArtifact(AndroidArtifact):
""" """ "
Parser for Android tombstone crash files. Parser for Android tombstone crash files.
This parser can parse both text and protobuf tombstone crash files. This parser can parse both text and protobuf tombstone crash files.
@@ -121,11 +120,11 @@ class TombstoneCrashArtifact(AndroidArtifact):
def parse_protobuf( def parse_protobuf(
self, file_name: str, file_timestamp: datetime.datetime, data: bytes self, file_name: str, file_timestamp: datetime.datetime, data: bytes
) -> None: ) -> None:
"""Parse Android tombstone crash files from a protobuf object.""" """
Parse Android tombstone crash files from a protobuf object.
"""
tombstone_pb = Tombstone().parse(data) tombstone_pb = Tombstone().parse(data)
tombstone_dict = tombstone_pb.to_dict( tombstone_dict = tombstone_pb.to_dict(betterproto.Casing.SNAKE)
betterproto.Casing.SNAKE, include_default_values=True
)
# Add some extra metadata # Add some extra metadata
tombstone_dict["timestamp"] = self._parse_timestamp_string( tombstone_dict["timestamp"] = self._parse_timestamp_string(
@@ -142,23 +141,21 @@ class TombstoneCrashArtifact(AndroidArtifact):
def parse( def parse(
self, file_name: str, file_timestamp: datetime.datetime, content: bytes self, file_name: str, file_timestamp: datetime.datetime, content: bytes
) -> None: ) -> None:
"""Parse text Android tombstone crash files.""" """
Parse text Android tombstone crash files.
"""
# Split the tombstone file into a dictonary
tombstone_dict = { tombstone_dict = {
"file_name": file_name, "file_name": file_name,
"file_timestamp": convert_datetime_to_iso(file_timestamp), "file_timestamp": convert_datetime_to_iso(file_timestamp),
} }
lines = content.decode("utf-8").splitlines() lines = content.decode("utf-8").splitlines()
for line_num, line in enumerate(lines, 1): for line in lines:
if not line.strip() or TOMBSTONE_DELIMITER in line: if not line.strip() or TOMBSTONE_DELIMITER in line:
continue continue
try: for key, destination_key in TOMBSTONE_TEXT_KEY_MAPPINGS.items():
for key, destination_key in TOMBSTONE_TEXT_KEY_MAPPINGS.items(): self._parse_tombstone_line(line, key, destination_key, tombstone_dict)
if self._parse_tombstone_line(
line, key, destination_key, tombstone_dict
):
break
except Exception as e:
raise ValueError(f"Error parsing line {line_num}: {str(e)}")
# Validate the tombstone and add it to the results # Validate the tombstone and add it to the results
tombstone = TombstoneCrashResult.model_validate(tombstone_dict) tombstone = TombstoneCrashResult.model_validate(tombstone_dict)
@@ -168,7 +165,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
self, line: str, key: str, destination_key: str, tombstone: dict self, line: str, key: str, destination_key: str, tombstone: dict
) -> bool: ) -> bool:
if not line.startswith(f"{key}"): if not line.startswith(f"{key}"):
return False return None
if key == "pid": if key == "pid":
return self._load_pid_line(line, tombstone) return self._load_pid_line(line, tombstone)
@@ -187,7 +184,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
raise ValueError(f"Expected key {key}, got {line_key}") raise ValueError(f"Expected key {key}, got {line_key}")
value_clean = value.strip().strip("'") value_clean = value.strip().strip("'")
if destination_key == "uid": if destination_key in ["uid", "revision"]:
tombstone[destination_key] = int(value_clean) tombstone[destination_key] = int(value_clean)
elif destination_key == "process_uptime": elif destination_key == "process_uptime":
# eg. "Process uptime: 40s" # eg. "Process uptime: 40s"
@@ -200,50 +197,51 @@ class TombstoneCrashArtifact(AndroidArtifact):
return True return True
def _load_pid_line(self, line: str, tombstone: dict) -> bool: def _load_pid_line(self, line: str, tombstone: dict) -> bool:
try: pid_part, tid_part, name_part = [part.strip() for part in line.split(",")]
parts = line.split(" >>> ") if " >>> " in line else line.split(">>>")
process_info = parts[0]
# Parse pid, tid, name from process info pid_key, pid_value = pid_part.split(":", 1)
info_parts = [p.strip() for p in process_info.split(",")] if pid_key != "pid":
for info in info_parts: raise ValueError(f"Expected key pid, got {pid_key}")
key, value = info.split(":", 1) pid_value = int(pid_value.strip())
key = key.strip()
value = value.strip()
if key == "pid": tid_key, tid_value = tid_part.split(":", 1)
tombstone["pid"] = int(value) if tid_key != "tid":
elif key == "tid": raise ValueError(f"Expected key tid, got {tid_key}")
tombstone["tid"] = int(value) tid_value = int(tid_value.strip())
elif key == "name":
tombstone["process_name"] = value
# Extract binary path if it exists name_key, name_value = name_part.split(":", 1)
if len(parts) > 1: if name_key != "name":
tombstone["binary_path"] = parts[1].strip().rstrip(" <") raise ValueError(f"Expected key name, got {name_key}")
name_value = name_value.strip()
process_name, binary_path = self._parse_process_name(name_value, tombstone)
return True tombstone["pid"] = pid_value
tombstone["tid"] = tid_value
tombstone["process_name"] = process_name
tombstone["binary_path"] = binary_path
return True
except Exception as e: def _parse_process_name(self, process_name_part, tombstone: dict) -> bool:
raise ValueError(f"Failed to parse PID line: {str(e)}") process_name, process_path = process_name_part.split(">>>")
process_name = process_name.strip()
binary_path = process_path.strip().split(" ")[0]
return process_name, binary_path
def _load_signal_line(self, line: str, tombstone: dict) -> bool: def _load_signal_line(self, line: str, tombstone: dict) -> bool:
signal_part, code_part = map(str.strip, line.split(",")[:2]) signal, code, _ = [part.strip() for part in line.split(",", 2)]
signal = signal.split("signal ")[1]
signal_code, signal_name = signal.split(" ")
signal_name = signal_name.strip("()")
def parse_part(part: str, prefix: str) -> tuple[int, str]: code_part = code.split("code ")[1]
match = part.split(prefix)[1] code_number, code_name = code_part.split(" ")
number = int(match.split()[0]) code_name = code_name.strip("()")
name = match.split("(")[1].split(")")[0] if "(" in match else "UNKNOWN"
return number, name
signal_number, signal_name = parse_part(signal_part, "signal ")
code_number, code_name = parse_part(code_part, "code ")
tombstone["signal_info"] = { tombstone["signal_info"] = {
"code": code_number, "code": int(code_number),
"code_name": code_name, "code_name": code_name,
"name": signal_name, "name": signal_name,
"number": signal_number, "number": int(signal_code),
} }
return True return True
@@ -254,7 +252,13 @@ class TombstoneCrashArtifact(AndroidArtifact):
@staticmethod @staticmethod
def _parse_timestamp_string(timestamp: str) -> str: def _parse_timestamp_string(timestamp: str) -> str:
timestamp_parsed = parser.parse(timestamp) timestamp_date, timezone = timestamp.split("+")
# Truncate microseconds before parsing
timestamp_without_micro = timestamp_date.split(".")[0] + "+" + timezone
timestamp_parsed = datetime.datetime.strptime(
timestamp_without_micro, "%Y-%m-%d %H:%M:%S%z"
)
# HACK: Swap the local timestamp to UTC, so keep the original time and avoid timezone conversion. # HACK: Swap the local timestamp to UTC, so keep the original time and avoid timezone conversion.
local_timestamp = timestamp_parsed.replace(tzinfo=datetime.timezone.utc) local_timestamp = timestamp_parsed.replace(tzinfo=datetime.timezone.utc)
return convert_datetime_to_iso(local_timestamp) return convert_datetime_to_iso(local_timestamp)

View File

@@ -12,6 +12,8 @@ from typing import List, Optional
from mvt.common.command import Command from mvt.common.command import Command
from .modules.androidqf import ANDROIDQF_MODULES from .modules.androidqf import ANDROIDQF_MODULES
from .modules.bugreport import BUGREPORT_MODULES
from .modules.bugreport.base import BugReportModule
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -39,7 +41,11 @@ class CmdAndroidCheckAndroidQF(Command):
) )
self.name = "check-androidqf" self.name = "check-androidqf"
self.modules = ANDROIDQF_MODULES
# We can load AndroidQF and bugreport modules here, as
# AndroidQF dump will contain a bugreport.
self.modules = ANDROIDQF_MODULES + BUGREPORT_MODULES
# TODO: Check how to namespace and deduplicate modules.
self.format: Optional[str] = None self.format: Optional[str] = None
self.archive: Optional[zipfile.ZipFile] = None self.archive: Optional[zipfile.ZipFile] = None
@@ -54,12 +60,44 @@ class CmdAndroidCheckAndroidQF(Command):
for fname in subfiles: for fname in subfiles:
file_path = os.path.relpath(os.path.join(root, fname), parent_path) file_path = os.path.relpath(os.path.join(root, fname), parent_path)
self.files.append(file_path) self.files.append(file_path)
elif os.path.isfile(self.target_path): elif os.path.isfile(self.target_path):
self.format = "zip" self.format = "zip"
self.archive = zipfile.ZipFile(self.target_path) self.archive = zipfile.ZipFile(self.target_path)
self.files = self.archive.namelist() self.files = self.archive.namelist()
def load_bugreport(self):
# Refactor this file list loading
# First we need to find the bugreport file location
bugreport_zip_path = None
for file_name in self.files:
if file_name.endswith("bugreport.zip"):
bugreport_zip_path = file_name
break
else:
self.log.warning("No bugreport.zip found in the AndroidQF dump")
return None
if self.format == "zip":
# Create handle to the bugreport.zip file inside the AndroidQF dump
handle = self.archive.open(bugreport_zip_path)
bugreport_zip = zipfile.ZipFile(handle)
else:
# Load the bugreport.zip file from the extracted AndroidQF dump on disk.
parent_path = Path(self.target_path).absolute().parent.as_posix()
bug_report_path = os.path.join(parent_path, bugreport_zip_path)
bugreport_zip = zipfile.ZipFile(bug_report_path)
return bugreport_zip
def module_init(self, module): def module_init(self, module):
if isinstance(module, BugReportModule):
bugreport_archive = self.load_bugreport()
if not bugreport_archive:
return
module.from_zip(bugreport_archive, bugreport_archive.namelist())
return
if self.format == "zip": if self.format == "zip":
module.from_zip_file(self.archive, self.files) module.from_zip_file(self.archive, self.files)
else: else:

View File

@@ -107,7 +107,8 @@ class Packages(AndroidExtraction):
result["matched_indicator"] = ioc result["matched_indicator"] = ioc
self.detected.append(result) self.detected.append(result)
def check_virustotal(self, packages: list) -> None: @staticmethod
def check_virustotal(packages: list) -> None:
hashes = [] hashes = []
for package in packages: for package in packages:
for file in package.get("files", []): for file in package.get("files", []):
@@ -142,15 +143,8 @@ class Packages(AndroidExtraction):
for package in packages: for package in packages:
for file in package.get("files", []): for file in package.get("files", []):
if "package_name" in package: row = [package["package_name"], file["path"]]
row = [package["package_name"], file["path"]]
elif "name" in package:
row = [package["name"], file["path"]]
else:
self.log.error(
f"Package {package} has no name or package_name. packages.json or apks.json is malformed"
)
continue
if file["sha256"] in detections: if file["sha256"] in detections:
detection = detections[file["sha256"]] detection = detections[file["sha256"]]
positives = detection.split("/")[0] positives = detection.split("/")[0]

View File

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

View File

@@ -1,74 +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
import json
from typing import Optional
from mvt.android.artifacts.mounts import Mounts as MountsArtifact
from .base import AndroidQFModule
class Mounts(MountsArtifact, AndroidQFModule):
"""This module extracts and analyzes mount information from AndroidQF acquisitions."""
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,
)
self.results = []
def run(self) -> None:
"""
Run the mounts analysis module.
This module looks for mount information files collected by androidqf
and analyzes them for suspicious configurations, particularly focusing
on detecting root access indicators like /system mounted as read-write.
"""
mount_files = self._get_files_by_pattern("*/mounts.json")
if not mount_files:
self.log.info("No mount information file found")
return
self.log.info("Found mount information file: %s", mount_files[0])
try:
data = self._get_file_content(mount_files[0]).decode(
"utf-8", errors="replace"
)
except Exception as exc:
self.log.error("Failed to read mount information file: %s", exc)
return
# Parse the mount data
try:
json_data = json.loads(data)
if isinstance(json_data, list):
# AndroidQF format: array of strings like
# "/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)"
mount_content = "\n".join(json_data)
self.parse(mount_content)
except Exception as exc:
self.log.error("Failed to parse mount information: %s", exc)
return
self.log.info("Extracted a total of %d mount entries", len(self.results))

View File

@@ -231,7 +231,6 @@ def parse_sms_file(data):
entry.pop("mms_body") entry.pop("mms_body")
body = entry.get("body", None) body = entry.get("body", None)
message_links = None
if body: if body:
message_links = check_for_links(entry["body"]) message_links = check_for_links(entry["body"])

View File

@@ -65,10 +65,6 @@ class CmdCheckIOCS(Command):
m = iocs_module.from_json( m = iocs_module.from_json(
file_path, log=logging.getLogger(iocs_module.__module__) file_path, log=logging.getLogger(iocs_module.__module__)
) )
if not m:
log.warning("No result from this module, skipping it")
continue
if self.iocs.total_ioc_count > 0: if self.iocs.total_ioc_count > 0:
m.indicators = self.iocs m.indicators = self.iocs
m.indicators.log = m.log m.indicators.log = m.log

View File

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

View File

@@ -69,14 +69,10 @@ class MVTModule:
@classmethod @classmethod
def from_json(cls, json_path: str, log: logging.Logger): def from_json(cls, json_path: str, log: logging.Logger):
with open(json_path, "r", encoding="utf-8") as handle: with open(json_path, "r", encoding="utf-8") as handle:
try: results = json.load(handle)
results = json.load(handle) if log:
if log: log.info('Loaded %d results from "%s"', len(results), json_path)
log.info('Loaded %d results from "%s"', len(results), json_path) return cls(results=results, log=log)
return cls(results=results, log=log)
except json.decoder.JSONDecodeError as err:
log.error('Error to decode the json "%s" file: "%s"', json_path, err)
return None
@classmethod @classmethod
def get_slug(cls) -> str: def get_slug(cls) -> str:

View File

@@ -3,4 +3,4 @@
# 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/
MVT_VERSION = "2.6.1" MVT_VERSION = "2.6.0"

View File

@@ -891,14 +891,6 @@
"version": "15.8.2", "version": "15.8.2",
"build": "19H384" "build": "19H384"
}, },
{
"version": "15.8.4",
"build": "19H390"
},
{
"version": "15.8.5",
"build": "19H394"
},
{ {
"build": "20A362", "build": "20A362",
"version": "16.0" "version": "16.0"
@@ -1000,14 +992,6 @@
"version": "16.7.8", "version": "16.7.8",
"build": "20H343" "build": "20H343"
}, },
{
"version": "16.7.11",
"build": "20H360"
},
{
"version": "16.7.12",
"build": "20H364"
},
{ {
"version": "17.0", "version": "17.0",
"build": "21A327" "build": "21A327"
@@ -1092,10 +1076,6 @@
"version": "17.6.1", "version": "17.6.1",
"build": "21G101" "build": "21G101"
}, },
{
"version": "17.7.7",
"build": "21H433"
},
{ {
"version": "18", "version": "18",
"build": "22A3354" "build": "22A3354"
@@ -1123,45 +1103,5 @@
{ {
"version": "18.3", "version": "18.3",
"build": "22D63" "build": "22D63"
},
{
"version": "18.3.1",
"build": "22D72"
},
{
"version": "18.4",
"build": "22E240"
},
{
"version": "18.4.1",
"build": "22E252"
},
{
"version": "18.5",
"build": "22F76"
},
{
"version": "18.6",
"build": "22G86"
},
{
"version": "18.6.1",
"build": "22G90"
},
{
"version": "18.6.2",
"build": "22G100"
},
{
"version": "18.7",
"build": "22H20"
},
{
"version": "26",
"build": "23A341"
},
{
"version": "26.0.1",
"build": "23A355"
} }
] ]

View File

@@ -43,8 +43,6 @@ class GlobalPreferences(IOSExtraction):
self.log.warning("Lockdown mode enabled") self.log.warning("Lockdown mode enabled")
else: else:
self.log.warning("Lockdown mode disabled") self.log.warning("Lockdown mode disabled")
return
self.log.warning("Lockdown mode disabled")
def process_file(self, file_path: str) -> None: def process_file(self, file_path: str) -> None:
with open(file_path, "rb") as handle: with open(file_path, "rb") as handle:

View File

@@ -95,17 +95,14 @@ class SafariBrowserState(IOSExtraction):
) )
except sqlite3.OperationalError: except sqlite3.OperationalError:
# Old version iOS <12 likely # Old version iOS <12 likely
try: cur.execute(
cur.execute(
"""
SELECT
title, url, user_visible_url, last_viewed_time, session_data
FROM tabs
ORDER BY last_viewed_time;
""" """
) SELECT
except sqlite3.OperationalError as e: title, url, user_visible_url, last_viewed_time, session_data
self.log.error(f"Error executing query: {e}") FROM tabs
ORDER BY last_viewed_time;
"""
)
for row in cur: for row in cur:
session_entries = [] session_entries = []

View File

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

9
test-requirements.txt Normal file
View File

@@ -0,0 +1,9 @@
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]

View File

@@ -29,28 +29,3 @@ class TestDumpsysADBArtifact:
user_key["fingerprint"] == "F0:A1:3D:8C:B3:F4:7B:09:9F:EE:8B:D8:38:2E:BD:C6" user_key["fingerprint"] == "F0:A1:3D:8C:B3:F4:7B:09:9F:EE:8B:D8:38:2E:BD:C6"
) )
assert user_key["user"] == "user@linux" assert user_key["user"] == "user@linux"
def test_parsing_adb_xml(self):
da_adb = DumpsysADBArtifact()
file = get_artifact("android_data/dumpsys_adb_xml.txt")
with open(file, "rb") as f:
data = f.read()
da_adb.parse(data)
assert len(da_adb.results) == 1
adb_data = da_adb.results[0]
assert "user_keys" in adb_data
assert len(adb_data["user_keys"]) == 1
# Check key and fingerprint parsed successfully.
expected_fingerprint = "F0:0B:27:08:E3:68:7B:FA:4C:79:A2:B4:BF:0E:CF:70"
user_key = adb_data["user_keys"][0]
user_key["fingerprint"] == expected_fingerprint
assert user_key["user"] == "user@laptop"
key_store_entry = adb_data["keystore"][0]
assert key_store_entry["user"] == "user@laptop"
assert key_store_entry["fingerprint"] == expected_fingerprint
assert key_store_entry["last_connected"] == "1628501829898"

View File

@@ -64,4 +64,4 @@ class TestTombstoneCrashArtifact:
# We often don't know the time offset for a log entry and so can't convert everything to UTC. # We often don't know the time offset for a log entry and so can't convert everything to UTC.
# MVT should output the local time only: # MVT should output the local time only:
# So original 2023-04-12 12:32:40.518290770+0200 -> 2023-04-12 12:32:40.000000 # So original 2023-04-12 12:32:40.518290770+0200 -> 2023-04-12 12:32:40.000000
assert tombstone_result.get("timestamp") == "2023-04-12 12:32:40.518290" assert tombstone_result.get("timestamp") == "2023-04-12 12:32:40.000000"

View File

@@ -1,97 +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 pathlib import Path
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestAndroidqfMountsArtifact:
def test_parse_mounts_token_checks(self):
"""
Test the artifact-level `parse` method using tolerant token checks.
Different parser variants may place mount tokens into different dict
keys (for example `mount_options`, `pass_num`, `dump_freq`, etc.). To
avoid brittle assertions we concatenate each parsed entry's values and
look for expected tokens (device names, mount points, options) somewhere
in the combined representation.
"""
from mvt.android.artifacts.mounts import Mounts as MountsArtifact
m = MountsArtifact()
mount_lines = [
"/dev/block/dm-12 on / type ext4 (ro,seclabel,noatime)",
"/dev/block/by-name/system on /system type ext4 (rw,seclabel,noatime)",
"/dev/block/by-name/data on /data type f2fs (rw,nosuid,nodev,noatime)",
]
mount_content = "\n".join(mount_lines)
# Parse the mount lines (artifact-level)
m.parse(mount_content)
# Basic sanity: parser should return one entry per input line
assert len(m.results) == 3, f"Expected 3 parsed mounts, got: {m.results}"
# Concatenate each entry's values into a single string so token checks
# are tolerant to which dict keys were used by the parser.
def concat_values(entry):
parts = []
for v in entry.values():
try:
parts.append(str(v))
except Exception:
# Skip values that can't be stringified
continue
return " ".join(parts)
concatenated = [concat_values(e) for e in m.results]
# Token expectations (tolerant):
# - Root line should include 'dm-12' and 'noatime' (and typically 'ro')
assert any("dm-12" in s and "noatime" in s for s in concatenated), (
f"No root-like tokens (dm-12 + noatime) found in parsed results: {concatenated}"
)
# - System line should include '/system' or 'by-name/system' and 'rw'
assert any(
(("by-name/system" in s or "/system" in s) and "rw" in s)
for s in concatenated
), (
f"No system-like tokens (system + rw) found in parsed results: {concatenated}"
)
# - Data line should include '/data' or 'by-name/data' and 'rw'
assert any(
(("by-name/data" in s or "/data" in s) and "rw" in s) for s in concatenated
), f"No data-like tokens (data + rw) found in parsed results: {concatenated}"
class TestAndroidqfMountsModule:
def test_androidqf_module_no_mounts_file(self):
"""
When no `mounts.json` is present in the androidqf dataset, the module
should not produce results nor detections.
"""
from mvt.android.modules.androidqf.mounts import Mounts
data_path = get_android_androidqf()
m = Mounts(target_path=data_path, log=logging)
files = list_files(data_path)
parent_path = Path(data_path).absolute().parent.as_posix()
m.from_folder(parent_path, files)
run_module(m)
# The provided androidqf test dataset does not include mounts.json, so
# results should remain empty.
assert len(m.results) == 0, (
f"Expected no results when mounts.json is absent, got: {m.results}"
)
assert len(m.detected) == 0, f"Expected no detections, got: {m.detected}"

View File

@@ -9,7 +9,6 @@ from pathlib import Path
from mvt.android.modules.bugreport.appops import Appops from mvt.android.modules.bugreport.appops import Appops
from mvt.android.modules.bugreport.getprop import Getprop from mvt.android.modules.bugreport.getprop import Getprop
from mvt.android.modules.bugreport.packages import Packages from mvt.android.modules.bugreport.packages import Packages
from mvt.android.modules.bugreport.tombstones import Tombstones
from mvt.common.module import run_module from mvt.common.module import run_module
from ..utils import get_artifact_folder from ..utils import get_artifact_folder
@@ -55,8 +54,3 @@ class TestBugreportAnalysis:
def test_getprop_module(self): def test_getprop_module(self):
m = self.launch_bug_report_module(Getprop) m = self.launch_bug_report_module(Getprop)
assert len(m.results) == 0 assert len(m.results) == 0
def test_tombstones_modules(self):
m = self.launch_bug_report_module(Tombstones)
assert len(m.results) == 2
assert m.results[1]["pid"] == 3559

View File

@@ -1,27 +0,0 @@
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/a10eea/a10:10/.190711.020/A105:user/release-keys'
Revision: '5'
ABI: 'arm'
Timestamp: 2021-09-29 17:43:49+0200
pid: 9850, tid: 9893, name: UsbFfs-worker >>> /system/bin/adbd <<<
uid: 2000
signal 6 (SIGABRT), code -1 (SI_QUEUE), fault addr --------
Abort message: 'Check failed: payload.size() <= bytes_left (payload.size()=99, bytes_left=51) '
r0 00000000 r1 000026a5 r2 00000006 r3 f11fad98
r4 f11fadac r5 f11fad90 r6 0000267a r7 0000016b
r8 f11fada8 r9 f11fad98 r10 f11fadc8 r11 f11fadb8
ip 000026a5 sp f11fad68 lr f20c23b7 pc f20c23ca
backtrace:
#00 pc 000603ca /apex/com.android.runtime/lib/bionic/libc.so (abort+166) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)
#01 pc 00007e23 /system/lib/libbase.so (android::base::DefaultAborter(char const*)+6) (BuildId: a28585ee446ea17e3e6fcf9c907fff2a)
#02 pc 0000855f /system/lib/libbase.so (android::base::LogMessage::~LogMessage()+406) (BuildId: a28585ee446ea17e3e6fcf9c907fff2a)
#03 pc 000309cf /system/lib/libadbd.so (UsbFfsConnection::ProcessRead(IoBlock*)+814) (BuildId: 3645b175977ae210c156a57b25dfa599)
#04 pc 00030459 /system/lib/libadbd.so (UsbFfsConnection::HandleRead(TransferId, long long)+84) (BuildId: 3645b175977ae210c156a57b25dfa599)
#05 pc 00030349 /system/lib/libadbd.so (UsbFfsConnection::ReadEvents()+92) (BuildId: 3645b175977ae210c156a57b25dfa599)
#06 pc 00030169 /system/lib/libadbd.so (_ZZN16UsbFfsConnection11StartWorkerEvENKUlvE_clEv+504) (BuildId: 3645b175977ae210c156a57b25dfa599)
#07 pc 0002ff53 /system/lib/libadbd.so (_ZNSt3__114__thread_proxyINS_5tupleIJNS_10unique_ptrINS_15__thread_structENS_14default_deleteIS3_EEEEZN16UsbFfsConnection11StartWorkerEvEUlvE_EEEEEPvSA_+26) (BuildId: 3645b175977ae210c156a57b25dfa599)
#08 pc 000a75b3 /apex/com.android.runtime/lib/bionic/libc.so (__pthread_start(void*)+20) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)
#09 pc 00061b33 /apex/com.android.runtime/lib/bionic/libc.so (__start_thread+30) (BuildId: 320fbdc2a1289fadd7dacae7f2eb77a3)

View File

@@ -1,38 +0,0 @@
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'samsung/a10eea/a10:11/RP1A.200720.012/A105:user/release-keys'
Revision: '5'
ABI: 'arm'
Timestamp: 2023-08-21 23:28:59-0400
pid: 3559, tid: 3568, name: tzts_daemon >>> /vendor/bin/tzts_daemon <<<
uid: 1000
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0xe8b4d14c
r0 e8b4d14c r1 e8b4d14c r2 0000002b r3 00000004
r4 00000000 r5 e8b4d14c r6 00000000 r7 00000000
r8 e7ef78b0 r9 0000002b r10 e7ef7dad r11 e7ef7400
ip 00000000 sp e7ef7208 lr e89f4b01 pc e89c273a
backtrace:
#00 pc 0005f73a /apex/com.android.runtime/lib/bionic/libc.so (strlen_a15+54) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#01 pc 00091afd /apex/com.android.runtime/lib/bionic/libc.so (__vfprintf+3364) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#02 pc 000a68e5 /apex/com.android.runtime/lib/bionic/libc.so (vsnprintf+152) (BuildId: fef5b751123147ea65bf3f4f798c9518)
#03 pc 000051cf /system/lib/liblog.so (__android_log_vprint+74) (BuildId: 3fcead474cd0ecbdafb529ff176b0d13)
#04 pc 000012e8 /vendor/bin/tzts_daemon
memory near r0:
e8b4d12c -------- -------- -------- -------- ................
e8b4d13c -------- -------- -------- -------- ................
e8b4d14c -------- -------- -------- -------- ................
e8b4d15c -------- -------- -------- -------- ................
e8b4d16c -------- -------- -------- -------- ................
e8b4d17c -------- -------- -------- -------- ................
e8b4d18c -------- -------- -------- -------- ................
e8b4d19c -------- -------- -------- -------- ................
e8b4d1ac -------- -------- -------- -------- ................
e8b4d1bc -------- -------- -------- -------- ................
e8b4d1cc -------- -------- -------- -------- ................
e8b4d1dc -------- -------- -------- -------- ................
e8b4d1ec -------- -------- -------- -------- ................
e8b4d1fc -------- -------- -------- -------- ................
e8b4d20c -------- -------- -------- -------- ................
e8b4d21c -------- -------- -------- -------- ................

View File

@@ -1,16 +0,0 @@
-------------------------------------------------------------------------------
DUMP OF SERVICE adb:
ADB MANAGER STATE (dumpsys adb):
{
debugging_manager={
connected_to_adb=true
user_keys=QAAAAAcgbytJst31DsaSP7hn8QcBXKR9NPVPK9MZssFVSNIP user@laptop
keystore=<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<keyStore version="1">
<adbKey key="QAAAAAcgbytJst31DsaSP7hn8QcBXKR9NPVPK9MZssFVSNIP user@laptop" lastConnection="1628501829898" />
</keyStore>
}
}
--------- 0.012s was the duration of dumpsys adb, ending at: 2025-02-04 20:25:58