diff --git a/src/mvt/android/modules/androidqf/packages.py b/src/mvt/android/modules/androidqf/packages.py index 095edb02..e3de0f5a 100644 --- a/src/mvt/android/modules/androidqf/packages.py +++ b/src/mvt/android/modules/androidqf/packages.py @@ -12,6 +12,8 @@ PLAY_STORE_INSTALLERS, ROOT_PACKAGES, THIRD_PARTY_STORE_INSTALLERS, + SECURITY_PACKAGES, + SYSTEM_UPDATE_PACKAGES, ) from .base import AndroidQFModule @@ -62,14 +64,29 @@ def check_indicators(self) -> None: result["installer"], result["name"], ) + self.detected.append(result) elif result["installer"] == "null" and result["system"] is False: self.log.warning( 'Found a non-system package installed via adb or another method: "%s"', result["name"], ) + self.detected.append(result) elif result["installer"] in PLAY_STORE_INSTALLERS: pass + # Check for disabled security or software update packages + package_disabled = result.get("disabled", None) + if result["name"] in SECURITY_PACKAGES and package_disabled: + self.log.warning( + 'Security package "%s" disabled on the phone', result["name"] + ) + + if result["name"] in SYSTEM_UPDATE_PACKAGES and package_disabled: + self.log.warning( + 'System OTA update package "%s" disabled on the phone', + result["name"], + ) + if not self.indicators: continue @@ -96,6 +113,10 @@ def check_indicators(self) -> None: self.detected.append(result) break + # Deduplicate the detected packages + dedupe_detected_dict = {str(item): item for item in self.detected} + self.detected = list(dedupe_detected_dict.values()) + def run(self) -> None: packages = self._get_files_by_pattern("*/packages.json") if not packages: diff --git a/tests/android_androidqf/test_packages.py b/tests/android_androidqf/test_packages.py index 7af18329..a7fce958 100644 --- a/tests/android_androidqf/test_packages.py +++ b/tests/android_androidqf/test_packages.py @@ -43,33 +43,56 @@ def test_packages_list(self, module): # There should just be 7 packages listed, no detections assert len(module.results) == 7 assert len(module.timeline) == 0 - assert len(module.detected) == 0 def test_non_appstore_warnings(self, caplog, module): run_module(module) + assert len(module.detected) == 4 + # 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 ) + whatsapp_detected = [ + pkg for pkg in module.detected if pkg["name"] == "com.whatsapp" + ] + assert len(whatsapp_detected) == 1 + assert ( 'Found a package installed via a browser (installer="com.google.android.packageinstaller"): ' '"app.revanced.manager.flutter"' in caplog.text ) + revanced_detected = [ + pkg + for pkg in module.detected + if pkg["name"] == "app.revanced.manager.flutter" + ] + assert len(revanced_detected) == 1 + 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. + appollo_detected = [ + pkg for pkg in module.detected if pkg["name"] == "org.nuclearfog.apollo" + ] + assert len(appollo_detected) == 0 def test_packages_ioc_package_names(self, module, indicators_factory): module.indicators = indicators_factory(app_ids=["com.malware.blah"]) run_module(module) - assert len(module.detected) == 1 - assert module.detected[0]["name"] == "com.malware.blah" - assert module.detected[0]["matched_indicator"]["value"] == "com.malware.blah" + possible_detected_app = [ + pkg for pkg in module.detected if pkg["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" + ) def test_packages_ioc_sha256(self, module, indicators_factory): module.indicators = indicators_factory( @@ -80,10 +103,13 @@ def test_packages_ioc_sha256(self, module, indicators_factory): run_module(module) - assert len(module.detected) == 1 - assert module.detected[0]["name"] == "com.malware.muahaha" + possible_detected_app = [ + pkg for pkg in module.detected if pkg["name"] == "com.malware.muahaha" + ] + assert len(possible_detected_app) == 1 + assert possible_detected_app[0]["name"] == "com.malware.muahaha" assert ( - module.detected[0]["matched_indicator"]["value"] + possible_detected_app[0]["matched_indicator"]["value"] == "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa" ) @@ -96,9 +122,12 @@ def test_packages_certificate_hash_ioc(self, module, indicators_factory): run_module(module) - assert len(module.detected) == 1 - assert module.detected[0]["name"] == "com.malware.muahaha" + possible_detected_app = [ + pkg for pkg in module.detected if pkg["name"] == "com.malware.muahaha" + ] + assert len(possible_detected_app) == 1 + assert possible_detected_app[0]["name"] == "com.malware.muahaha" assert ( - module.detected[0]["matched_indicator"]["value"] + possible_detected_app[0]["matched_indicator"]["value"] == "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730" )