mirror of
https://github.com/mvt-project/mvt.git
synced 2026-07-17 09:57:24 +02:00
Merge branch 'main' into accessibility_enabled
This commit is contained in:
@@ -77,6 +77,6 @@ class TestDumpsysAccessibilityArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.camera")
|
||||
da.indicators = ind
|
||||
assert len(da.detected) == 0
|
||||
assert len(da.alertstore.alerts) == 0
|
||||
da.check_indicators()
|
||||
assert len(da.detected) == 1
|
||||
assert len(da.alertstore.alerts) == 1
|
||||
|
||||
@@ -42,22 +42,25 @@ class TestDumpsysAppopsArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.facebook.katana")
|
||||
da.indicators = ind
|
||||
assert len(da.detected) == 0
|
||||
assert len(da.alertstore.alerts) == 0
|
||||
|
||||
da.check_indicators()
|
||||
detected_by_ioc = [
|
||||
detected for detected in da.detected if detected.get("matched_indicator")
|
||||
alert
|
||||
for alert in da.alertstore.alerts
|
||||
if alert.matched_indicator is not None
|
||||
]
|
||||
detected_by_permission_heuristic = [
|
||||
detected
|
||||
for detected in da.detected
|
||||
alert
|
||||
for alert in da.alertstore.alerts
|
||||
if all(
|
||||
[
|
||||
perm["name"] == "REQUEST_INSTALL_PACKAGES"
|
||||
for perm in detected["permissions"]
|
||||
for perm in alert.event["permissions"]
|
||||
]
|
||||
)
|
||||
]
|
||||
assert len(da.detected) == 3
|
||||
assert len(da.alertstore.alerts) == 3
|
||||
assert len(detected_by_ioc) == 1
|
||||
assert detected_by_ioc[0].matched_indicator is not None
|
||||
assert len(detected_by_permission_heuristic) == 2
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import logging
|
||||
|
||||
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
|
||||
from mvt.common.alerts import AlertLevel
|
||||
from mvt.common.indicators import Indicators
|
||||
|
||||
from ..utils import get_artifact
|
||||
@@ -32,6 +33,39 @@ class TestDumpsysBatteryDailyArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.facebook.system")
|
||||
dba.indicators = ind
|
||||
assert len(dba.detected) == 0
|
||||
assert len(dba.alertstore.alerts) == 0
|
||||
dba.check_indicators()
|
||||
assert len(dba.detected) == 1
|
||||
assert len(dba.alertstore.alerts) == 1
|
||||
|
||||
def test_uninstall_and_downgrade_create_medium_alerts(self):
|
||||
dba = DumpsysBatteryDailyArtifact()
|
||||
dba.parse(
|
||||
"""
|
||||
Daily from 2022-08-16-15-56-39 to 2022-08-17-01-15-45:
|
||||
Update com.example.app vers=10
|
||||
Update com.example.removed vers=0
|
||||
Daily from 2022-08-17-15-56-39 to 2022-08-18-01-15-45:
|
||||
Update com.example.app vers=9
|
||||
"""
|
||||
)
|
||||
|
||||
assert len(dba.results) == 3
|
||||
assert len(dba.alertstore.alerts) == 2
|
||||
|
||||
uninstall_alert, downgrade_alert = dba.alertstore.alerts
|
||||
assert uninstall_alert.level == AlertLevel.MEDIUM
|
||||
assert uninstall_alert.message == (
|
||||
"Detected uninstall of package com.example.removed (vers 0)"
|
||||
)
|
||||
assert uninstall_alert.event_time == "2022-08-16"
|
||||
assert uninstall_alert.event["package_name"] == "com.example.removed"
|
||||
assert uninstall_alert.event["vers"] == "0"
|
||||
|
||||
assert downgrade_alert.level == AlertLevel.MEDIUM
|
||||
assert downgrade_alert.message == (
|
||||
"Detected downgrade of package com.example.app from vers 10 to vers 9"
|
||||
)
|
||||
assert downgrade_alert.event_time == "2022-08-17"
|
||||
assert downgrade_alert.event["package_name"] == "com.example.app"
|
||||
assert downgrade_alert.event["action"] == "downgrade"
|
||||
assert downgrade_alert.event["previous_vers"] == "10"
|
||||
|
||||
@@ -39,6 +39,6 @@ class TestDumpsysBatteryHistoryArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.samsung.android.app.reminder")
|
||||
dba.indicators = ind
|
||||
assert len(dba.detected) == 0
|
||||
assert len(dba.alertstore.alerts) == 0
|
||||
dba.check_indicators()
|
||||
assert len(dba.detected) == 2
|
||||
assert len(dba.alertstore.alerts) == 2
|
||||
|
||||
@@ -37,6 +37,6 @@ class TestDumpsysDBinfoArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.wssyncmldm")
|
||||
dbi.indicators = ind
|
||||
assert len(dbi.detected) == 0
|
||||
assert len(dbi.alertstore.alerts) == 0
|
||||
dbi.check_indicators()
|
||||
assert len(dbi.detected) == 5
|
||||
assert len(dbi.alertstore.alerts) == 5
|
||||
|
||||
@@ -39,6 +39,6 @@ class TestDumpsysPackageActivitiesArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.google.android.gms")
|
||||
dpa.indicators = ind
|
||||
assert len(dpa.detected) == 0
|
||||
assert len(dpa.alertstore.alerts) == 0
|
||||
dpa.check_indicators()
|
||||
assert len(dpa.detected) == 1
|
||||
assert len(dpa.alertstore.alerts) == 1
|
||||
|
||||
@@ -37,6 +37,6 @@ class TestDumpsysPackagesArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate")
|
||||
dpa.indicators = ind
|
||||
assert len(dpa.detected) == 0
|
||||
assert len(dpa.alertstore.alerts) == 0
|
||||
dpa.check_indicators()
|
||||
assert len(dpa.detected) == 1
|
||||
assert len(dpa.alertstore.alerts) == 1
|
||||
|
||||
@@ -35,6 +35,6 @@ class TestDumpsysPlatformCompatArtifact:
|
||||
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
|
||||
assert len(dbi.alertstore.alerts) == 0
|
||||
dbi.check_indicators()
|
||||
assert len(dbi.detected) == 2
|
||||
assert len(dbi.alertstore.alerts) == 2
|
||||
|
||||
@@ -42,6 +42,6 @@ class TestDumpsysReceiversArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.android.storagemanager")
|
||||
dr.indicators = ind
|
||||
assert len(dr.detected) == 0
|
||||
assert len(dr.alertstore.alerts) == 0
|
||||
dr.check_indicators()
|
||||
assert len(dr.detected) == 1
|
||||
assert len(dr.alertstore.alerts) == 1
|
||||
|
||||
@@ -36,6 +36,6 @@ class TestGetPropArtifact:
|
||||
"dalvik.vm.appimageformat"
|
||||
)
|
||||
gp.indicators = ind
|
||||
assert len(gp.detected) == 0
|
||||
assert len(gp.alertstore.alerts) == 0
|
||||
gp.check_indicators()
|
||||
assert len(gp.detected) == 1
|
||||
assert len(gp.alertstore.alerts) == 1
|
||||
|
||||
@@ -33,6 +33,6 @@ class TestProcessesArtifact:
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["processes"].append("lru-add-drain")
|
||||
p.indicators = ind
|
||||
assert len(p.detected) == 0
|
||||
assert len(p.alertstore.alerts) == 0
|
||||
p.check_indicators()
|
||||
assert len(p.detected) == 1
|
||||
assert len(p.alertstore.alerts) == 1
|
||||
|
||||
@@ -60,7 +60,6 @@ class TestBackupParsing:
|
||||
== "33e73df2ede9798dcb3a85c06200ee41c8f52dd2f2e50ffafcceb0407bc13e3a"
|
||||
)
|
||||
sms = parse_tar_for_sms(ddata)
|
||||
print(sms)
|
||||
assert isinstance(sms, list)
|
||||
assert len(sms) == 1
|
||||
assert len(sms[0]["links"]) == 1
|
||||
|
||||
@@ -22,4 +22,4 @@ class TestAndroidqfFilesAnalysis:
|
||||
run_module(m)
|
||||
assert len(m.results) == 3
|
||||
assert len(m.timeline) == 6
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
@@ -26,7 +26,7 @@ class TestAndroidqfGetpropAnalysis:
|
||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||
assert m.results[0]["value"] == "lz4"
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_getprop_parsing_zip(self):
|
||||
fpath = get_artifact("androidqf.zip")
|
||||
@@ -38,7 +38,7 @@ class TestAndroidqfGetpropAnalysis:
|
||||
assert m.results[0]["name"] == "dalvik.vm.appimageformat"
|
||||
assert m.results[0]["value"] == "lz4"
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_androidqf_getprop_detection(self, indicator_file):
|
||||
data_path = get_android_androidqf()
|
||||
@@ -52,5 +52,5 @@ class TestAndroidqfGetpropAnalysis:
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 10
|
||||
assert len(m.detected) == 1
|
||||
assert m.detected[0]["name"] == "dalvik.vm.heapmaxfree"
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
assert m.alertstore.alerts[0].event["name"] == "dalvik.vm.heapmaxfree"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from mvt.common.indicators import Indicator, IndicatorMatch
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_android_androidqf, list_files
|
||||
@@ -72,6 +73,38 @@ class TestAndroidqfMountsArtifact:
|
||||
(("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}"
|
||||
|
||||
def test_mount_ioc_alert_uses_indicator(self):
|
||||
from mvt.android.artifacts.mounts import Mounts as MountsArtifact
|
||||
|
||||
indicator = Indicator(
|
||||
value="/system",
|
||||
type="file_path",
|
||||
name="TestMalware",
|
||||
stix2_file_name="indicators.stix2",
|
||||
)
|
||||
m = MountsArtifact()
|
||||
m.indicators = type(
|
||||
"MountIndicators",
|
||||
(),
|
||||
{
|
||||
"check_file_path": lambda self, path: IndicatorMatch(
|
||||
ioc=indicator, message="matched file path"
|
||||
)
|
||||
if path == "/system"
|
||||
else None
|
||||
},
|
||||
)()
|
||||
|
||||
m.parse("/dev/block/by-name/system on /system type ext4 (rw,seclabel)")
|
||||
m.check_indicators()
|
||||
|
||||
indicator_alerts = [
|
||||
alert for alert in m.alertstore.alerts if alert.matched_indicator
|
||||
]
|
||||
assert len(indicator_alerts) == 1
|
||||
assert indicator_alerts[0].matched_indicator == indicator
|
||||
assert indicator_alerts[0].message == "matched file path"
|
||||
|
||||
|
||||
class TestAndroidqfMountsModule:
|
||||
def test_androidqf_module_no_mounts_file(self):
|
||||
@@ -94,4 +127,6 @@ class TestAndroidqfMountsModule:
|
||||
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}"
|
||||
assert len(m.alertstore.alerts) == 0, (
|
||||
f"Expected no detections, got: {m.alertstore.alerts}"
|
||||
)
|
||||
|
||||
@@ -47,38 +47,35 @@ class TestAndroidqfPackages:
|
||||
def test_non_appstore_warnings(self, caplog, module):
|
||||
run_module(module)
|
||||
|
||||
assert len(module.detected) == 4
|
||||
assert len(module.alertstore.alerts) == 5
|
||||
|
||||
# Not a super test to be searching logs for this but heuristic detections not yet formalised
|
||||
assert (
|
||||
'Found a non-system package installed via adb or another method: "com.whatsapp"'
|
||||
in caplog.text
|
||||
)
|
||||
adb_message = "Found a non-system package installed via adb or another method:"
|
||||
whatsapp_detected = [
|
||||
pkg for pkg in module.detected if pkg["name"] == "com.whatsapp"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "com.whatsapp"
|
||||
]
|
||||
assert len(whatsapp_detected) == 1
|
||||
assert adb_message in whatsapp_detected[0].message
|
||||
|
||||
assert (
|
||||
'Found a package installed via a browser (installer="com.google.android.packageinstaller"): '
|
||||
'"app.revanced.manager.flutter"' in caplog.text
|
||||
)
|
||||
browser_message = 'Found a package installed via a browser (installer="com.google.android.packageinstaller"): '
|
||||
revanced_detected = [
|
||||
pkg
|
||||
for pkg in module.detected
|
||||
if pkg["name"] == "app.revanced.manager.flutter"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "app.revanced.manager.flutter"
|
||||
]
|
||||
assert len(revanced_detected) == 1
|
||||
assert browser_message in revanced_detected[0].message
|
||||
|
||||
assert (
|
||||
'Found a package installed via a third party store (installer="org.fdroid.fdroid"): "org.nuclearfog.apollo"'
|
||||
in caplog.text
|
||||
)
|
||||
# We do not currently flag a third party store as a detection, we only flag the app in the logs.
|
||||
third_party_message = 'Found a package installed via a third party store (installer="org.fdroid.fdroid")'
|
||||
appollo_detected = [
|
||||
pkg for pkg in module.detected if pkg["name"] == "org.nuclearfog.apollo"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "org.nuclearfog.apollo"
|
||||
]
|
||||
assert len(appollo_detected) == 0
|
||||
assert len(appollo_detected) == 1
|
||||
assert third_party_message in appollo_detected[0].message
|
||||
|
||||
def test_packages_ioc_package_names(self, module, indicators_factory):
|
||||
module.indicators = indicators_factory(app_ids=["com.malware.blah"])
|
||||
@@ -86,13 +83,13 @@ class TestAndroidqfPackages:
|
||||
run_module(module)
|
||||
|
||||
possible_detected_app = [
|
||||
pkg for pkg in module.detected if pkg["name"] == "com.malware.blah"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "com.malware.blah"
|
||||
]
|
||||
assert len(possible_detected_app) == 1
|
||||
assert possible_detected_app[0]["name"] == "com.malware.blah"
|
||||
assert (
|
||||
possible_detected_app[0]["matched_indicator"]["value"] == "com.malware.blah"
|
||||
)
|
||||
assert possible_detected_app[0].event["name"] == "com.malware.blah"
|
||||
assert possible_detected_app[0].matched_indicator.value == "com.malware.blah"
|
||||
|
||||
def test_packages_ioc_sha256(self, module, indicators_factory):
|
||||
module.indicators = indicators_factory(
|
||||
@@ -104,12 +101,14 @@ class TestAndroidqfPackages:
|
||||
run_module(module)
|
||||
|
||||
possible_detected_app = [
|
||||
pkg for pkg in module.detected if pkg["name"] == "com.malware.muahaha"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "com.malware.muahaha"
|
||||
]
|
||||
assert len(possible_detected_app) == 1
|
||||
assert possible_detected_app[0]["name"] == "com.malware.muahaha"
|
||||
assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
|
||||
assert (
|
||||
possible_detected_app[0]["matched_indicator"]["value"]
|
||||
possible_detected_app[0].matched_indicator.value
|
||||
== "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa"
|
||||
)
|
||||
|
||||
@@ -123,11 +122,13 @@ class TestAndroidqfPackages:
|
||||
run_module(module)
|
||||
|
||||
possible_detected_app = [
|
||||
pkg for pkg in module.detected if pkg["name"] == "com.malware.muahaha"
|
||||
alert
|
||||
for alert in module.alertstore.alerts
|
||||
if alert.event["name"] == "com.malware.muahaha"
|
||||
]
|
||||
assert len(possible_detected_app) == 1
|
||||
assert possible_detected_app[0]["name"] == "com.malware.muahaha"
|
||||
assert possible_detected_app[0].event["name"] == "com.malware.muahaha"
|
||||
assert (
|
||||
possible_detected_app[0]["matched_indicator"]["value"]
|
||||
possible_detected_app[0].matched_indicator.value
|
||||
== "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730"
|
||||
)
|
||||
|
||||
@@ -22,4 +22,4 @@ class TestAndroidqfProcessesAnalysis:
|
||||
run_module(m)
|
||||
assert len(m.results) == 15
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
@@ -42,7 +42,7 @@ class TestAndroidqfRootBinaries:
|
||||
|
||||
# Should find 4 root binaries from the test file
|
||||
assert len(module.results) == 4
|
||||
assert len(module.detected) == 4
|
||||
assert len(module.alertstore.alerts) == 4
|
||||
|
||||
# Check that all results are detected as indicators
|
||||
binary_paths = [result["path"] for result in module.results]
|
||||
@@ -113,4 +113,4 @@ class TestAndroidqfRootBinaries:
|
||||
run_module(m)
|
||||
|
||||
assert len(m.results) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
@@ -21,4 +21,4 @@ class TestSettingsModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert "random" in m.results.keys()
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
@@ -25,7 +25,7 @@ class TestAndroidqfSMSAnalysis:
|
||||
run_module(m)
|
||||
assert len(m.results) == 2
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_androidqf_sms_encrypted_password_valid(self):
|
||||
data_path = os.path.join(get_artifact_folder(), "androidqf_encrypted")
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# 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.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.mixed.tcc import TCC
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
|
||||
|
||||
class TestTCCModule:
|
||||
def test_tcc(self):
|
||||
m = TCC(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
assert m.results[0]["service"] == "kTCCServiceUbiquity"
|
||||
assert m.results[0]["client"] == "com.apple.Preferences"
|
||||
assert m.results[0]["auth_value"] == "allowed"
|
||||
|
||||
def test_tcc_detection(self, indicator_file):
|
||||
m = TCC(target_path=get_ios_backup_folder())
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
assert m.alertstore.alerts[0].event["service"] == "kTCCServiceLiverpool"
|
||||
assert m.alertstore.alerts[0].event["client"] == "Launch"
|
||||
@@ -36,9 +36,13 @@ class TestBugreportAnalysis:
|
||||
assert len(m.timeline) == 16
|
||||
|
||||
detected_by_ioc = [
|
||||
detected for detected in m.detected if detected.get("matched_indicator")
|
||||
detected
|
||||
for detected in m.alertstore.alerts
|
||||
if detected.event.get("matched_indicator")
|
||||
]
|
||||
assert len(m.detected) == 1 # Hueristic detection for suspicious permissions
|
||||
assert (
|
||||
len(m.alertstore.alerts) == 1
|
||||
) # Hueristic detection for suspicious permissions
|
||||
assert len(detected_by_ioc) == 0
|
||||
|
||||
def test_packages_module(self):
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
# 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/
|
||||
|
||||
from mvt.common.alerts import Alert, AlertLevel, AlertStore
|
||||
|
||||
|
||||
def test_as_json_promotes_nested_matched_indicator():
|
||||
indicator = {"value": "com.apple.weather", "type": "processes"}
|
||||
alertstore = AlertStore()
|
||||
alertstore.add(
|
||||
Alert(
|
||||
level=AlertLevel.CRITICAL,
|
||||
module="datausage",
|
||||
message="Matched indicator",
|
||||
event_time="2026-01-01 00:00:00",
|
||||
event={
|
||||
"proc_name": "WeatherWidget/com.apple.weather",
|
||||
"matched_indicator": indicator,
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
alert = alertstore.as_json()[0]
|
||||
|
||||
assert alert["matched_indicator"] == indicator
|
||||
assert "matched_indicator" not in alert["event"]
|
||||
|
||||
|
||||
def test_as_json_removes_nested_matched_indicator_when_parent_exists():
|
||||
event_indicator = {"value": "nested", "type": "processes"}
|
||||
alert_indicator = {"value": "parent", "type": "processes"}
|
||||
alertstore = AlertStore()
|
||||
alertstore.add(
|
||||
Alert(
|
||||
level=AlertLevel.CRITICAL,
|
||||
module="manifest",
|
||||
message="Matched indicator",
|
||||
event_time="2026-01-01 00:00:00",
|
||||
event={"path": "/tmp/example", "matched_indicator": event_indicator},
|
||||
matched_indicator=alert_indicator,
|
||||
)
|
||||
)
|
||||
|
||||
alert = alertstore.as_json()[0]
|
||||
|
||||
assert alert["matched_indicator"] == alert_indicator
|
||||
assert "matched_indicator" not in alert["event"]
|
||||
@@ -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/
|
||||
|
||||
import json
|
||||
|
||||
from mvt.common.command import Command
|
||||
|
||||
|
||||
class TestCommand:
|
||||
def test_store_alerts_handles_bytes(self, tmp_path):
|
||||
cmd = Command(results_path=str(tmp_path))
|
||||
cmd.alertstore.medium(
|
||||
"bytes event",
|
||||
"",
|
||||
{"payload": b"\xa8\xa9"},
|
||||
)
|
||||
|
||||
cmd._store_alerts()
|
||||
|
||||
alerts = json.loads((tmp_path / "alerts.json").read_text())
|
||||
assert alerts[0]["event"]["payload"] == "\\xa8\\xa9"
|
||||
@@ -18,7 +18,7 @@ class TestCalendarModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
assert m.results[0]["summary"] == "Super interesting meeting"
|
||||
|
||||
def test_calendar_detection(self, indicator_file):
|
||||
@@ -30,4 +30,4 @@ class TestCalendarModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 1
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
|
||||
@@ -7,6 +7,7 @@ import logging
|
||||
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
from mvt.common.alerts import AlertLevel
|
||||
from mvt.ios.modules.mixed.net_datausage import Datausage
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
@@ -19,7 +20,9 @@ class TestDatausageModule:
|
||||
assert m.results[0]["isodate"][0:19] == "2019-08-27 15:08:09"
|
||||
assert len(m.results) == 42
|
||||
assert len(m.timeline) == 60
|
||||
assert len(m.detected) == 0
|
||||
assert (
|
||||
len(m.alertstore.alerts) == 1
|
||||
) # We now have a detection for missing processes.
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
m = Datausage(target_path=get_ios_backup_folder())
|
||||
@@ -29,4 +32,19 @@ class TestDatausageModule:
|
||||
ind.ioc_collections[0]["processes"].append("CumulativeUsageTracker")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.detected) == 2
|
||||
critical_alerts = [
|
||||
alert for alert in m.alertstore.alerts if alert.level == AlertLevel.CRITICAL
|
||||
]
|
||||
assert len(critical_alerts) == 2
|
||||
assert all(
|
||||
"matched_indicator" not in alert.event for alert in critical_alerts
|
||||
)
|
||||
serialized_alerts = [
|
||||
alert
|
||||
for alert in m.alertstore.as_json()
|
||||
if alert["matched_indicator"] is not None
|
||||
]
|
||||
assert len(serialized_alerts) == 2
|
||||
assert all(
|
||||
"matched_indicator" not in alert["event"] for alert in serialized_alerts
|
||||
)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from mvt.common.module import run_module
|
||||
from mvt.common.alerts import AlertLevel
|
||||
from mvt.ios.modules.mixed.global_preferences import GlobalPreferences
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
@@ -15,6 +16,11 @@ class TestGlobalPreferencesModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 16
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
|
||||
lockdown_mode_alert = m.alertstore.alerts[0]
|
||||
assert lockdown_mode_alert.message == "Lockdown mode enabled"
|
||||
assert lockdown_mode_alert.level == AlertLevel.INFORMATIONAL
|
||||
|
||||
assert m.results[0]["entry"] == "WebKitShowLinkPreviews"
|
||||
assert m.results[0]["value"] is False
|
||||
|
||||
@@ -3,22 +3,43 @@
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import gc
|
||||
import logging
|
||||
import warnings
|
||||
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.base import IOSExtraction
|
||||
from mvt.ios.modules.backup.manifest import Manifest
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
|
||||
|
||||
class TestIOSExtraction:
|
||||
def test_get_backup_files_from_manifest_closes_connection(self):
|
||||
m = IOSExtraction(target_path=get_ios_backup_folder())
|
||||
|
||||
with warnings.catch_warnings(record=True) as caught:
|
||||
warnings.simplefilter("always", ResourceWarning)
|
||||
files = list(m._get_backup_files_from_manifest(domain="CameraRollDomain"))
|
||||
gc.collect()
|
||||
|
||||
assert files
|
||||
assert not [
|
||||
warning
|
||||
for warning in caught
|
||||
if issubclass(warning.category, ResourceWarning)
|
||||
and "unclosed database" in str(warning.message)
|
||||
]
|
||||
|
||||
|
||||
class TestManifestModule:
|
||||
def test_manifest(self):
|
||||
m = Manifest(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 3721
|
||||
assert len(m.timeline) == 5881
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
m = Manifest(target_path=get_ios_backup_folder())
|
||||
@@ -27,4 +48,4 @@ class TestManifestModule:
|
||||
ind.ioc_collections[0]["file_names"].append("com.apple.CoreBrightness.plist")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.detected) == 1
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
|
||||
@@ -19,7 +19,7 @@ class TestSafariBrowserStateModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 1
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
m = SafariBrowserState(target_path=get_ios_backup_folder())
|
||||
@@ -30,6 +30,6 @@ class TestSafariBrowserStateModule:
|
||||
ind.ioc_collections[0]["domains"].append("en.wikipedia.org")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.detected) == 1
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
assert len(m.results) == 1
|
||||
assert m.results[0]["tab_url"] == "https://en.wikipedia.org/wiki/NSO_Group"
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestSMSModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 2
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
m = SMS(target_path=get_ios_backup_folder())
|
||||
@@ -28,4 +28,4 @@ class TestSMSModule:
|
||||
ind.ioc_collections[0]["domains"].append("badbadbad.example.org")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.detected) == 1
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
|
||||
@@ -18,7 +18,7 @@ class TestTCCModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
assert m.results[0]["service"] == "kTCCServiceUbiquity"
|
||||
assert m.results[0]["client"] == "com.apple.Preferences"
|
||||
assert m.results[0]["auth_value"] == "allowed"
|
||||
@@ -31,6 +31,6 @@ class TestTCCModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 11
|
||||
assert len(m.timeline) == 11
|
||||
assert len(m.detected) == 1
|
||||
assert m.detected[0]["service"] == "kTCCServiceLiverpool"
|
||||
assert m.detected[0]["client"] == "Launch"
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
assert m.alertstore.alerts[0].event["service"] == "kTCCServiceLiverpool"
|
||||
assert m.alertstore.alerts[0].event["client"] == "Launch"
|
||||
|
||||
@@ -18,4 +18,4 @@ class TestWebkitResourceLoadStatisticsModule:
|
||||
run_module(m)
|
||||
assert len(m.results) == 2
|
||||
assert len(m.timeline) == 2
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# 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.common.indicators import Indicators
|
||||
from mvt.ios.modules.fs.cache_files import CacheFiles
|
||||
|
||||
|
||||
class TestCacheFiles:
|
||||
def test_detection(self, indicator_file):
|
||||
m = CacheFiles(
|
||||
results={
|
||||
"Library/Caches/example/Cache.db": [
|
||||
{
|
||||
"entry_id": 1,
|
||||
"version": 1,
|
||||
"hash_value": 123,
|
||||
"storage_policy": 0,
|
||||
"url": "http://example.com/thisisbad",
|
||||
"isodate": "2026-01-01 00:00:00.000000",
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
m.indicators = ind
|
||||
|
||||
m.check_indicators()
|
||||
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
alert = m.alertstore.alerts[0]
|
||||
assert alert.event["cache_file"] == "Library/Caches/example/Cache.db"
|
||||
assert alert.event["url"] == "http://example.com/thisisbad"
|
||||
assert alert.matched_indicator is not None
|
||||
@@ -17,7 +17,7 @@ class TestFilesystem:
|
||||
run_module(m)
|
||||
assert len(m.results) == 15
|
||||
assert len(m.timeline) == 15
|
||||
assert len(m.detected) == 0
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
m = Filesystem(target_path=get_ios_backup_folder())
|
||||
@@ -31,4 +31,4 @@ class TestFilesystem:
|
||||
run_module(m)
|
||||
assert len(m.results) == 15
|
||||
assert len(m.timeline) == 15
|
||||
assert len(m.detected) == 1
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
# 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 click.testing import CliRunner
|
||||
|
||||
from mvt.android.cli import check_adb
|
||||
|
||||
|
||||
class TestCheckAndroidADBRemovedCommand:
|
||||
def test_check_adb_exits_nonzero(self):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(check_adb)
|
||||
|
||||
assert result.exit_code == 1
|
||||
Reference in New Issue
Block a user