mirror of
https://github.com/mvt-project/mvt.git
synced 2026-10-19 14:07:23 +02:00
Fix AndroidQF artifact discovery in root-level ZIP archives
This commit is contained in:
1 parent
fd37c4d8d9
commit
0c0b1b977e
3 files changed
+115
-2
No files matched your search
@@ -46,8 +46,22 @@ class AndroidQFModule(MVTModule):
|
||||
self.archive = archive
|
||||
self.files = files
|
||||
|
||||
def _get_files_by_pattern(self, pattern: str):
|
||||
return fnmatch.filter(self.files, pattern)
|
||||
def _get_files_by_pattern(self, pattern: str) -> List[str]:
|
||||
pattern = pattern.replace("\\", "/")
|
||||
patterns = [pattern]
|
||||
# ZIPs may store artifacts at their root, without the acquisition
|
||||
# directory prefix present in paths collected by from_dir().
|
||||
if pattern.startswith("*/"):
|
||||
patterns.append(pattern[2:])
|
||||
|
||||
return [
|
||||
file_path
|
||||
for file_path in self.files
|
||||
if any(
|
||||
fnmatch.fnmatch(file_path.replace("\\", "/"), candidate)
|
||||
for candidate in patterns
|
||||
)
|
||||
]
|
||||
|
||||
def _get_device_timezone(self):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2026 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 zipfile
|
||||
|
||||
import pytest
|
||||
|
||||
from mvt.android.modules.androidqf.base import AndroidQFModule
|
||||
|
||||
|
||||
@pytest.mark.parametrize("prefix", ["", "./", "acquisition/", "outer/acquisition/"])
|
||||
@pytest.mark.parametrize(
|
||||
"pattern, filename",
|
||||
[
|
||||
("*/packages.json", "packages.json"),
|
||||
("*/getprop.txt", "getprop.txt"),
|
||||
("*/ps.txt", "ps.txt"),
|
||||
("*/settings_*.txt", "settings_global.txt"),
|
||||
("*/files.json", "files.json"),
|
||||
("*/logs/*", "logs/logcat.txt"),
|
||||
("*/mounts.json", "mounts.json"),
|
||||
("*/root_binaries.json", "root_binaries.json"),
|
||||
],
|
||||
)
|
||||
def test_artifact_lookup_preserves_archive_names(tmp_path, prefix, pattern, filename):
|
||||
member = prefix + filename
|
||||
with zipfile.ZipFile(tmp_path / "acquisition.zip", "w") as archive:
|
||||
archive.writestr(member, b"artifact")
|
||||
archive.writestr(prefix + "unrelated.txt", b"unrelated")
|
||||
module = AndroidQFModule()
|
||||
module.from_zip(archive, archive.namelist())
|
||||
|
||||
assert module._get_files_by_pattern(pattern) == [member]
|
||||
assert module._get_file_content(member) == b"artifact"
|
||||
|
||||
|
||||
def test_artifact_lookup_preserves_windows_paths():
|
||||
module = AndroidQFModule()
|
||||
module.from_dir("parent", [r"acquisition\logs\logcat.txt"])
|
||||
|
||||
assert module._get_files_by_pattern("*/logs/*") == [r"acquisition\logs\logcat.txt"]
|
||||
|
||||
|
||||
def test_artifact_lookup_preserves_order_without_duplicates():
|
||||
module = AndroidQFModule()
|
||||
module.files = [
|
||||
"acquisition/settings_global.txt",
|
||||
"settings_secure.txt",
|
||||
"./settings_system.txt",
|
||||
"not_settings_global.txt",
|
||||
"settings_global.txt.bak",
|
||||
]
|
||||
|
||||
assert module._get_files_by_pattern("*/settings_*.txt") == module.files[:3]
|
||||
assert module._get_files_by_pattern("settings_secure.txt") == [
|
||||
"settings_secure.txt"
|
||||
]
|
||||
assert module._get_files_by_pattern("*/missing.txt") == []
|
||||
@@ -10,6 +10,7 @@ import shutil
|
||||
import tempfile
|
||||
import zipfile
|
||||
|
||||
import pytest
|
||||
from click.testing import CliRunner
|
||||
|
||||
from mvt.android.cli import check_androidqf
|
||||
@@ -23,6 +24,44 @@ from .utils import get_artifact_folder
|
||||
TEST_BACKUP_PASSWORD = "123456"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"layout", ["directory", "", "./", "acquisition/", "outer/acquisition/"]
|
||||
)
|
||||
def test_androidqf_property_detection_across_layouts(
|
||||
tmp_path, indicators_factory, monkeypatch, layout
|
||||
):
|
||||
monkeypatch.setattr(settings, "NETWORK_ACCESS_ALLOWED", False)
|
||||
content = (
|
||||
"[persist.sys.timezone]: [Europe/Berlin]\n"
|
||||
"[test.suspicious.property]: [enabled]\n"
|
||||
)
|
||||
if layout == "directory":
|
||||
target = tmp_path / "acquisition"
|
||||
target.mkdir()
|
||||
(target / "getprop.txt").write_text(content)
|
||||
else:
|
||||
target = tmp_path / "acquisition.zip"
|
||||
with zipfile.ZipFile(target, "w") as archive:
|
||||
archive.writestr(layout + "getprop.txt", content)
|
||||
|
||||
command = CmdAndroidCheckAndroidQF(
|
||||
target_path=str(target),
|
||||
module_name="AQFGetProp",
|
||||
iocs=indicators_factory(android_property_names=["test.suspicious.property"]),
|
||||
)
|
||||
command.run()
|
||||
|
||||
assert len(command.executed) == 1
|
||||
module = command.executed[0]
|
||||
assert module.results == [
|
||||
{"name": "persist.sys.timezone", "value": "Europe/Berlin"},
|
||||
{"name": "test.suspicious.property", "value": "enabled"},
|
||||
]
|
||||
assert module._get_device_timezone() == "Europe/Berlin"
|
||||
assert len(command.alertstore.alerts) == 1
|
||||
assert command.alertstore.alerts[0].event == module.results[1]
|
||||
|
||||
|
||||
class TestCheckAndroidqfCommand:
|
||||
def test_log_timestamps_module_is_registered(self):
|
||||
assert AQFLogTimestamps in ANDROIDQF_MODULES
|
||||
|
||||
Reference in new issue
Block a user