mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-16 18:32:46 +00:00
Compare commits
3 Commits
v2.6.1
...
feature/an
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f5e5c627 | ||
|
|
2bb613fe09 | ||
|
|
355850bd5c |
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -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
|
||||||
1
.github/workflows/update-ios-data.yml
vendored
1
.github/workflows/update-ios-data.yml
vendored
@@ -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: |
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -19,25 +19,25 @@ classifiers = [
|
|||||||
"Programming Language :: Python"
|
"Programming Language :: Python"
|
||||||
]
|
]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"click==8.2.1",
|
"click >=8.1.3",
|
||||||
"rich==14.0.0",
|
"rich >=12.6.0",
|
||||||
"tld==0.13.1",
|
"tld >=0.12.6",
|
||||||
"requests==2.32.2",
|
"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.3",
|
"cryptography >=42.0.5",
|
||||||
"PyYAML>=6.0.2",
|
"pyyaml >=6.0",
|
||||||
"pyahocorasick==2.1.0",
|
"pyahocorasick >= 2.0.0",
|
||||||
"betterproto==1.2.5",
|
"betterproto >=1.2.0",
|
||||||
"pydantic==2.11.5",
|
"pydantic >= 2.10.0",
|
||||||
"pydantic-settings==2.9.1",
|
"pydantic-settings >= 2.7.0",
|
||||||
"NSKeyedUnArchiver==1.5",
|
'backports.zoneinfo; python_version < "3.9"',
|
||||||
]
|
]
|
||||||
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/"
|
||||||
@@ -103,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"}
|
||||||
@@ -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 = []
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ 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
|
||||||
@@ -124,9 +124,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
|
|||||||
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(
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -891,10 +891,6 @@
|
|||||||
"version": "15.8.2",
|
"version": "15.8.2",
|
||||||
"build": "19H384"
|
"build": "19H384"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "15.8.4",
|
|
||||||
"build": "19H390"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"build": "20A362",
|
"build": "20A362",
|
||||||
"version": "16.0"
|
"version": "16.0"
|
||||||
@@ -996,10 +992,6 @@
|
|||||||
"version": "16.7.8",
|
"version": "16.7.8",
|
||||||
"build": "20H343"
|
"build": "20H343"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"version": "16.7.11",
|
|
||||||
"build": "20H360"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"version": "17.0",
|
"version": "17.0",
|
||||||
"build": "21A327"
|
"build": "21A327"
|
||||||
@@ -1084,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"
|
||||||
@@ -1115,21 +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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -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"
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
Reference in New Issue
Block a user