diff --git a/src/mvt/android/modules/androidqf/packages.py b/src/mvt/android/modules/androidqf/packages.py index f1a809d..095edb0 100644 --- a/src/mvt/android/modules/androidqf/packages.py +++ b/src/mvt/android/modules/androidqf/packages.py @@ -77,7 +77,6 @@ class Packages(AndroidQFModule): if ioc: result["matched_indicator"] = ioc self.detected.append(result) - continue for package_file in result.get("files", []): ioc = self.indicators.check_file_hash(package_file["sha256"]) @@ -85,6 +84,18 @@ class Packages(AndroidQFModule): result["matched_indicator"] = ioc self.detected.append(result) + if "certificate" not in package_file: + continue + + # The keys generated by AndroidQF have a leading uppercase character + for hash_type in ["Md5", "Sha1", "Sha256"]: + certificate_hash = package_file["certificate"][hash_type] + ioc = self.indicators.check_app_certificate_hash(certificate_hash) + if ioc: + result["matched_indicator"] = ioc + self.detected.append(result) + break + def run(self) -> None: packages = self._get_files_by_pattern("*/packages.json") if not packages: diff --git a/src/mvt/common/indicators.py b/src/mvt/common/indicators.py index c3c3f28..a73938d 100644 --- a/src/mvt/common/indicators.py +++ b/src/mvt/common/indicators.py @@ -82,6 +82,7 @@ class Indicators: "files_md5": [], "files_sha1": [], "files_sha256": [], + "app_cert_hashes": [], "app_ids": [], "ios_profile_ids": [], "android_property_names": [], @@ -107,7 +108,7 @@ class Indicators: ioc_coll=collection, ioc_coll_list=collection["domains"], ) - if key == "ipv4-addr:value": + elif key == "ipv4-addr:value": # We treat IP addresses as simple domains here to ease checks. self._add_indicator( ioc=value.strip(), @@ -145,6 +146,24 @@ class Indicators: self._add_indicator( ioc=value, ioc_coll=collection, ioc_coll_list=collection["files_sha256"] ) + elif key == "app:cert.md5": + self._add_indicator( + ioc=value, + ioc_coll=collection, + ioc_coll_list=collection["app_cert_hashes"], + ) + elif key == "app:cert.sha1": + self._add_indicator( + ioc=value, + ioc_coll=collection, + ioc_coll_list=collection["app_cert_hashes"], + ) + elif key == "app:cert.sha256": + self._add_indicator( + ioc=value, + ioc_coll=collection, + ioc_coll_list=collection["app_cert_hashes"], + ) elif key == "app:id": self._add_indicator( ioc=value, ioc_coll=collection, ioc_coll_list=collection["app_ids"] @@ -155,7 +174,6 @@ class Indicators: ioc_coll=collection, ioc_coll_list=collection["ios_profile_ids"], ) - elif key == "android-property:name": self._add_indicator( ioc=value, @@ -703,6 +721,29 @@ class Indicators: return None + def check_app_certificate_hash(self, cert_hash: str) -> Union[dict, None]: + """Check the provided cert hash against the list of indicators. + + :param cert_hash: hash to check + :type cert_hash: str + :returns: Indicator details if matched, otherwise None + + """ + if not cert_hash: + return None + + for ioc in self.get_iocs("app_cert_hashes"): + if cert_hash.lower() == ioc["value"].lower(): + self.log.warning( + 'Found a known suspicious app certfificate with hash "%s" ' + 'matching indicators from "%s"', + cert_hash, + ioc["name"], + ) + return ioc + + return None + def check_app_id(self, app_id: str) -> Union[dict, None]: """Check the provided app identifier (typically an Android package name) against the list of indicators. diff --git a/tests/android_androidqf/test_packages.py b/tests/android_androidqf/test_packages.py index a2035db..7af1832 100644 --- a/tests/android_androidqf/test_packages.py +++ b/tests/android_androidqf/test_packages.py @@ -86,3 +86,19 @@ class TestAndroidqfPackages: module.detected[0]["matched_indicator"]["value"] == "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa" ) + + def test_packages_certificate_hash_ioc(self, module, indicators_factory): + module.indicators = indicators_factory( + app_cert_hashes=[ + "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730" + ] + ) + + run_module(module) + + assert len(module.detected) == 1 + assert module.detected[0]["name"] == "com.malware.muahaha" + assert ( + module.detected[0]["matched_indicator"]["value"] + == "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730" + ) diff --git a/tests/artifacts/androidqf/packages.json b/tests/artifacts/androidqf/packages.json index 2182581..9d10b90 100644 --- a/tests/artifacts/androidqf/packages.json +++ b/tests/artifacts/androidqf/packages.json @@ -212,7 +212,7 @@ "certificate": { "Md5": "54d5b5aca1e7e76bb1a26c61a9381b93", "Sha1": "4ba9d1f82adb7be841bcf53b03ddae857747199a", - "Sha256": "31037a27af59d4914906c01ad14a318eee2f3e31d48da8954dca62a99174e3fa", + "Sha256": "c7e56178748be1441370416d4c10e34817ea0c961eb636c8e9d98e0fd79bf730", "ValidFrom": "2021-01-15T22:03:53Z", "ValidTo": "2051-01-15T22:03:53Z", "Issuer": "C=US, ST=California, L=Mountain View, O=Google Inc., OU=Android, CN=Android", diff --git a/tests/conftest.py b/tests/conftest.py index 3e564c2..fb5cabf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,6 +37,7 @@ def indicators_factory(indicator_file): file_names=[], processes=[], app_ids=[], + app_cert_hashes=[], android_property_names=[], files_sha256=[], ): @@ -50,6 +51,7 @@ def indicators_factory(indicator_file): ind.ioc_collections[0]["app_ids"].extend(app_ids) ind.ioc_collections[0]["android_property_names"].extend(android_property_names) ind.ioc_collections[0]["files_sha256"].extend(files_sha256) + ind.ioc_collections[0]["app_cert_hashes"].extend(app_cert_hashes) return ind