diff --git a/mvt/android/artifacts/__init__.py b/mvt/android/artifacts/__init__.py index e69de29..9968a57 100644 --- a/mvt/android/artifacts/__init__.py +++ b/mvt/android/artifacts/__init__.py @@ -0,0 +1,4 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ diff --git a/mvt/android/artifacts/dumpsys_package_activities.py b/mvt/android/artifacts/dumpsys_package_activities.py new file mode 100644 index 0000000..6f2f1fd --- /dev/null +++ b/mvt/android/artifacts/dumpsys_package_activities.py @@ -0,0 +1,83 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# Use of this software is governed by the MVT License 1.1 that can be found at +# https://license.mvt.re/1.1/ +from .artifact import AndroidArtifact + + +class DumpsysPackageActivities(AndroidArtifact): + def check_indicators(self) -> None: + if not self.indicators: + return + + for activity in self.results: + ioc = self.indicators.check_app_id(activity["package_name"]) + if ioc: + activity["matched_indicator"] = ioc + self.detected.append(activity) + continue + + def parse(self, content: str): + """ + Parse the Dumpsys Package section for activities + Adds results to self.results + + :param content: content of the package section (string) + """ + self.results = [] + + in_activity_resolver_table = False + in_non_data_actions = False + intent = None + for line in content.splitlines(): + if line.startswith("Activity Resolver Table:"): + in_activity_resolver_table = True + continue + + if not in_activity_resolver_table: + continue + + if line.startswith(" Non-Data Actions:"): + in_non_data_actions = True + continue + + if not in_non_data_actions: + continue + + # If we hit an empty line, the Non-Data Actions section should be + # finished. + if line.strip() == "": + break + + # We detect the action name. + if ( + line.startswith(" " * 6) + and not line.startswith(" " * 8) + and ":" in line + ): + intent = line.strip().replace(":", "") + continue + + # If we are not in an intent block yet, skip. + if not intent: + continue + + # If we are in a block but the line does not start with 8 spaces + # it means the block ended a new one started, so we reset and + # continue. + if not line.startswith(" " * 8): + intent = None + continue + + # If we got this far, we are processing receivers for the + # activities we are interested in. + activity = line.strip().split(" ")[1] + package_name = activity.split("/")[0] + + self.results.append( + { + "intent": intent, + "package_name": package_name, + "activity": activity, + } + ) diff --git a/mvt/android/modules/adb/dumpsys_activities.py b/mvt/android/modules/adb/dumpsys_activities.py index 3c809db..b000863 100644 --- a/mvt/android/modules/adb/dumpsys_activities.py +++ b/mvt/android/modules/adb/dumpsys_activities.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_activity_resolver_table +from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities from .base import AndroidExtraction -class DumpsysActivities(AndroidExtraction): +class DumpsysActivities(DumpsysPackageActivities, AndroidExtraction): """This module extracts details on receivers for risky activities.""" def __init__( @@ -32,25 +32,12 @@ class DumpsysActivities(AndroidExtraction): results=results, ) - self.results = results if results else {} - - def check_indicators(self) -> None: - if not self.indicators: - return - - for intent, activities in self.results.items(): - for activity in activities: - ioc = self.indicators.check_app_id(activity["package_name"]) - if ioc: - activity["matched_indicator"] = ioc - self.detected.append({intent: activity}) - continue + self.results = results if results else [] def run(self) -> None: self._adb_connect() output = self._adb_command("dumpsys package") self._adb_disconnect() + self.parse(output) - self.results = parse_dumpsys_activity_resolver_table(output) - - self.log.info("Extracted activities for %d intents", len(self.results)) + self.log.info("Extracted %d package activities", len(self.results)) diff --git a/mvt/android/modules/androidqf/dumpsys_activities.py b/mvt/android/modules/androidqf/dumpsys_activities.py index 08b9f64..6bdf14d 100644 --- a/mvt/android/modules/androidqf/dumpsys_activities.py +++ b/mvt/android/modules/androidqf/dumpsys_activities.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_activity_resolver_table +from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities from .base import AndroidQFModule -class DumpsysActivities(AndroidQFModule): +class DumpsysActivities(DumpsysPackageActivities, AndroidQFModule): """This module extracts details on receivers for risky activities.""" def __init__( @@ -32,42 +32,17 @@ class DumpsysActivities(AndroidQFModule): results=results, ) - self.results = results if results else {} - - def check_indicators(self) -> None: - if not self.indicators: - return - - for intent, activities in self.results.items(): - for activity in activities: - ioc = self.indicators.check_app_id(activity["package_name"]) - if ioc: - activity["matched_indicator"] = ioc - self.detected.append({intent: activity}) + self.results = results if results else [] def run(self) -> None: dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") if not dumpsys_file: return - lines = [] - in_package = False - data = self._get_file_content(dumpsys_file[0]) - for line in data.decode("utf-8").split("\n"): - if line.strip() == "DUMP OF SERVICE package:": - in_package = True - continue + # Get data and extract the dumpsys section + data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace") + content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:") + # Parse it + self.parse(content) - if not in_package: - continue - - if line.strip().startswith( - "------------------------------------------------------------------------------" - ): # pylint: disable=line-too-long - break - - lines.append(line.rstrip()) - - self.results = parse_dumpsys_activity_resolver_table("\n".join(lines)) - - self.log.info("Extracted activities for %d intents", len(self.results)) + self.log.info("Extracted %d package activities", len(self.results)) diff --git a/mvt/android/modules/bugreport/activities.py b/mvt/android/modules/bugreport/activities.py index 0039082..556f3bb 100644 --- a/mvt/android/modules/bugreport/activities.py +++ b/mvt/android/modules/bugreport/activities.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_activity_resolver_table +from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities from .base import BugReportModule -class Activities(BugReportModule): +class Activities(DumpsysPackageActivities, BugReportModule): """This module extracts details on receivers for risky activities.""" def __init__( @@ -32,19 +32,7 @@ class Activities(BugReportModule): results=results, ) - self.results = results if results else {} - - def check_indicators(self) -> None: - if not self.indicators: - return - - for intent, activities in self.results.items(): - for activity in activities: - ioc = self.indicators.check_app_id(activity["package_name"]) - if ioc: - activity["matched_indicator"] = ioc - self.detected.append({intent: activity}) - continue + self.results = results if results else [] def run(self) -> None: content = self._get_dumpstate_file() @@ -55,23 +43,12 @@ class Activities(BugReportModule): ) return - lines = [] - in_package = False - for line in content.decode(errors="ignore").splitlines(): - if line.strip() == "DUMP OF SERVICE package:": - in_package = True - continue + # Extract package section + section = self.extract_dumpsys_section( + content.decode("utf-8", errors="ignore"), "DUMP OF SERVICE package:" + ) - if not in_package: - continue + # Parse + self.parse(section) - if line.strip().startswith( - "------------------------------------------------------------------------------" - ): # pylint: disable=line-too-long - break - - lines.append(line) - - self.results = parse_dumpsys_activity_resolver_table("\n".join(lines)) - - self.log.info("Extracted activities for %d intents", len(self.results)) + self.log.info("Extracted %d package activities", len(self.results)) diff --git a/mvt/android/parsers/__init__.py b/mvt/android/parsers/__init__.py index e1ef665..787210c 100644 --- a/mvt/android/parsers/__init__.py +++ b/mvt/android/parsers/__init__.py @@ -4,7 +4,6 @@ # https://license.mvt.re/1.1/ from .dumpsys import ( - parse_dumpsys_activity_resolver_table, parse_dumpsys_appops, parse_dumpsys_battery_daily, parse_dumpsys_battery_history, diff --git a/mvt/android/parsers/dumpsys.py b/mvt/android/parsers/dumpsys.py index 081f377..eeeffbb 100644 --- a/mvt/android/parsers/dumpsys.py +++ b/mvt/android/parsers/dumpsys.py @@ -10,64 +10,6 @@ from typing import Any, Dict, List from mvt.common.utils import convert_datetime_to_iso -def parse_dumpsys_activity_resolver_table(output: str) -> Dict[str, Any]: - results = {} - - in_activity_resolver_table = False - in_non_data_actions = False - intent = None - for line in output.splitlines(): - if line.startswith("Activity Resolver Table:"): - in_activity_resolver_table = True - continue - - if not in_activity_resolver_table: - continue - - if line.startswith(" Non-Data Actions:"): - in_non_data_actions = True - continue - - if not in_non_data_actions: - continue - - # If we hit an empty line, the Non-Data Actions section should be - # finished. - if line.strip() == "": - break - - # We detect the action name. - if line.startswith(" " * 6) and not line.startswith(" " * 8) and ":" in line: - intent = line.strip().replace(":", "") - results[intent] = [] - continue - - # If we are not in an intent block yet, skip. - if not intent: - continue - - # If we are in a block but the line does not start with 8 spaces - # it means the block ended a new one started, so we reset and - # continue. - if not line.startswith(" " * 8): - intent = None - continue - - # If we got this far, we are processing receivers for the - # activities we are interested in. - activity = line.strip().split(" ")[1] - package_name = activity.split("/")[0] - - results[intent].append( - { - "package_name": package_name, - "activity": activity, - } - ) - - return results - - def parse_dumpsys_battery_daily(output: str) -> list: results = [] daily = None diff --git a/tests/android/test_artifact_dumpsys_package_activities.py b/tests/android/test_artifact_dumpsys_package_activities.py new file mode 100644 index 0000000..473f28f --- /dev/null +++ b/tests/android/test_artifact_dumpsys_package_activities.py @@ -0,0 +1,42 @@ +# Mobile Verification Toolkit (MVT) +# Copyright (c) 2021-2023 Claudio Guarnieri. +# 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.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities +from mvt.common.indicators import Indicators + +from ..utils import get_artifact + + +class TestDumpsysPackageActivitiesArtifact: + def test_parsing(self): + dpa = DumpsysPackageActivities() + file = get_artifact("android_data/dumpsys_packages.txt") + with open(file) as f: + data = f.read() + + assert len(dpa.results) == 0 + dpa.parse(data) + assert len(dpa.results) == 4 + assert dpa.results[0]["package_name"] == "com.samsung.android.app.social" + assert ( + dpa.results[0]["activity"] + == "com.samsung.android.app.social/.feed.FeedsActivity" + ) + + def test_ioc_check(self, indicator_file): + dpa = DumpsysPackageActivities() + file = get_artifact("android_data/dumpsys_packages.txt") + with open(file) as f: + data = f.read() + dpa.parse(data) + + ind = Indicators(log=logging.getLogger()) + ind.parse_stix2(indicator_file) + ind.ioc_collections[0]["app_ids"].append("com.google.android.gms") + dpa.indicators = ind + assert len(dpa.detected) == 0 + dpa.check_indicators() + assert len(dpa.detected) == 1 diff --git a/tests/artifacts/android_data/dumpsys_packages.txt b/tests/artifacts/android_data/dumpsys_packages.txt index 356f185..e80bf8c 100644 --- a/tests/artifacts/android_data/dumpsys_packages.txt +++ b/tests/artifacts/android_data/dumpsys_packages.txt @@ -9,6 +9,32 @@ Libraries: android.test.mock -> (jar) /system/framework/android.test.mock.jar +Activity Resolver Table: + Full MIME Types: + text/comma-separated-values: + 18d43d com.samsung.android.messaging/.ui.RcsTransferContent + 72c6194 com.samsung.android.scloud/.app.ui.drive.activity.UploadToDriveActivity2 (2 filters) + a8101e7 com.samsung.android.scloud/.app.ui.drive.activity.UploadToDriveActivity (2 filters) + application/com.google.android.gms.car: + f6b2432 com.google.android.gms/.car.FirstActivity + slide/*: + 651d583 com.android.bluetooth/.opp.BluetoothOppLauncherActivity (2 filters) + application/pkix-cert: + 6380300 com.android.certinstaller/.CertInstallerMain + + Non-Data Actions: + com.samsung.android.intent.SOCIAL_UPDATES_FEED_VIEW: + b928288 com.samsung.android.app.social/.feed.FeedsActivity + android.settings.FINGERPRINT_SETUP: + e573421 com.android.settings/.biometrics.fingerprint.SetupFingerprintEnrollIntroduction + com.msc.action.samsungaccount.myinfowebview_external: + f897046 com.osp.app.signin/com.samsung.android.samsungaccount.authentication.ui.check.user.MyProfileWebView + com.google.android.gms.autofill.ACTION_SETTINGS: + 9de5207 com.google.android.gms/.autofill.ui.AutofillSettingsPrivacyHubActivity + + + + Packages: Package [com.samsung.android.provider.filterprovider] (d64f8e0): diff --git a/tests/ios_fs/test_filesystem.py b/tests/ios_fs/test_filesystem.py index c0827ea..869aed2 100644 --- a/tests/ios_fs/test_filesystem.py +++ b/tests/ios_fs/test_filesystem.py @@ -2,14 +2,15 @@ # Copyright (c) 2021-2023 Claudio Guarnieri. # Use of this software is governed by the MVT License 1.1 that can be found at # https://license.mvt.re/1.1/ -import pytest import logging +import pytest + from mvt.common.indicators import Indicators from mvt.common.module import run_module from mvt.ios.modules.fs.filesystem import Filesystem -from ..utils import get_ios_backup_folder, delete_tmp_db_files +from ..utils import delete_tmp_db_files, get_ios_backup_folder @pytest.fixture()