diff --git a/mvt/android/artifacts/artifact.py b/mvt/android/artifacts/artifact.py index d757569..67114b1 100644 --- a/mvt/android/artifacts/artifact.py +++ b/mvt/android/artifacts/artifact.py @@ -6,4 +6,30 @@ from mvt.common.artifact import Artifact class AndroidArtifact(Artifact): - pass + @staticmethod + def extract_dumpsys_section(dumpsys: str, separator: str) -> str: + """ + Extract a section from a full dumpsys file. + + :param dumpsys: content of the full dumpsys file (string) + :param separator: content of the first line separator (string) + :return: section extracted (string) + """ + lines = [] + in_section = False + for line in dumpsys.splitlines(): + if line.strip() == separator: + in_section = True + continue + + if not in_section: + continue + + if line.strip().startswith( + "------------------------------------------------------------------------------" + ): + break + + lines.append(line) + + return "\n".join(lines) diff --git a/mvt/android/artifacts/dumpsys_accessibility.py b/mvt/android/artifacts/dumpsys_accessibility.py new file mode 100644 index 0000000..5755c2c --- /dev/null +++ b/mvt/android/artifacts/dumpsys_accessibility.py @@ -0,0 +1,46 @@ +# 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 DumpsysAccessibility(AndroidArtifact): + def check_indicators(self) -> None: + if not self.indicators: + return + + for result in self.results: + ioc = self.indicators.check_app_id(result["package_name"]) + if ioc: + result["matched_indicator"] = ioc + self.detected.append(result) + continue + + def parse(self, content: str): + """ + Parse the Dumpsys Accessibility section/ + Adds results to self.results (List[Dict[str, str]]) + + :param content: content of the accessibility section (string) + """ + in_services = False + for line in content.splitlines(): + if line.strip().startswith("installed services:"): + in_services = True + continue + + if not in_services: + continue + + if line.strip() == "}": + break + + service = line.split(":")[1].strip() + + self.results.append( + { + "package_name": service.split("/")[0], + "service": service, + } + ) diff --git a/mvt/android/modules/adb/dumpsys_accessibility.py b/mvt/android/modules/adb/dumpsys_accessibility.py index c3a5634..764d0b9 100644 --- a/mvt/android/modules/adb/dumpsys_accessibility.py +++ b/mvt/android/modules/adb/dumpsys_accessibility.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_accessibility +from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibility as DAA from .base import AndroidExtraction -class DumpsysAccessibility(AndroidExtraction): +class DumpsysAccessibility(DAA, AndroidExtraction): """This module extracts stats on accessibility.""" def __init__( @@ -32,23 +32,12 @@ class DumpsysAccessibility(AndroidExtraction): results=results, ) - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - ioc = self.indicators.check_app_id(result["package_name"]) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - continue - def run(self) -> None: self._adb_connect() output = self._adb_command("dumpsys accessibility") self._adb_disconnect() - self.results = parse_dumpsys_accessibility(output) + self.parse(output) for result in self.results: self.log.info( diff --git a/mvt/android/modules/androidqf/dumpsys_accessibility.py b/mvt/android/modules/androidqf/dumpsys_accessibility.py index a7f15c7..04af10d 100644 --- a/mvt/android/modules/androidqf/dumpsys_accessibility.py +++ b/mvt/android/modules/androidqf/dumpsys_accessibility.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_accessibility +from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibility as DAA from .base import AndroidQFModule -class DumpsysAccessibility(AndroidQFModule): +class DumpsysAccessibility(DAA, AndroidQFModule): """This module analyse dumpsys accessbility""" def __init__( @@ -32,40 +32,14 @@ class DumpsysAccessibility(AndroidQFModule): results=results, ) - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - ioc = self.indicators.check_app_id(result["package_name"]) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - def run(self) -> None: dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") if not dumpsys_file: return - lines = [] - in_accessibility = False - data = self._get_file_content(dumpsys_file[0]) - for line in data.decode("utf-8").split("\n"): - if line.strip().startswith("DUMP OF SERVICE accessibility:"): - in_accessibility = True - continue - - if not in_accessibility: - continue - - if line.strip().startswith( - "-------------------------------------------------------------------------------" - ): # pylint: disable=line-too-long - break - - lines.append(line.rstrip()) - - self.results = parse_dumpsys_accessibility("\n".join(lines)) + data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace") + content = self.extract_dumpsys_section(data, "DUMP OF SERVICE accessibility:") + self.parse(content) for result in self.results: self.log.info( diff --git a/mvt/android/modules/bugreport/accessibility.py b/mvt/android/modules/bugreport/accessibility.py index a7695b3..4d9098c 100644 --- a/mvt/android/modules/bugreport/accessibility.py +++ b/mvt/android/modules/bugreport/accessibility.py @@ -6,12 +6,12 @@ import logging from typing import Optional -from mvt.android.parsers import parse_dumpsys_accessibility +from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibility as DAA from .base import BugReportModule -class Accessibility(BugReportModule): +class Accessibility(DAA, BugReportModule): """This module extracts stats on accessibility.""" def __init__( @@ -32,44 +32,21 @@ class Accessibility(BugReportModule): results=results, ) - def check_indicators(self) -> None: - if not self.indicators: - return - - for result in self.results: - ioc = self.indicators.check_app_id(result["package_name"]) - if ioc: - result["matched_indicator"] = ioc - self.detected.append(result) - continue - def run(self) -> None: - content = self._get_dumpstate_file() - if not content: + full_dumpsys = self._get_dumpstate_file() + if not full_dumpsys: self.log.error( "Unable to find dumpstate file. " "Did you provide a valid bug report archive?" ) return - lines = [] - in_accessibility = False - for line in content.decode(errors="ignore").splitlines(): - if line.strip() == "DUMP OF SERVICE accessibility:": - in_accessibility = True - continue + content = self.extract_dumpsys_section( + full_dumpsys.decode("utf-8", errors="ignore"), + "DUMP OF SERVICE accessibility:", + ) + self.parse(content) - if not in_accessibility: - continue - - if line.strip().startswith( - "------------------------------------------------------------------------------" - ): # pylint: disable=line-too-long - break - - lines.append(line) - - self.results = parse_dumpsys_accessibility("\n".join(lines)) for result in self.results: self.log.info( 'Found installed accessibility service "%s"', result.get("service") diff --git a/mvt/android/parsers/__init__.py b/mvt/android/parsers/__init__.py index dcdc4ff..e1ef665 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_accessibility, parse_dumpsys_activity_resolver_table, parse_dumpsys_appops, parse_dumpsys_battery_daily, diff --git a/mvt/android/parsers/dumpsys.py b/mvt/android/parsers/dumpsys.py index 3c47515..081f377 100644 --- a/mvt/android/parsers/dumpsys.py +++ b/mvt/android/parsers/dumpsys.py @@ -10,33 +10,6 @@ from typing import Any, Dict, List from mvt.common.utils import convert_datetime_to_iso -def parse_dumpsys_accessibility(output: str) -> List[Dict[str, str]]: - results = [] - - in_services = False - for line in output.splitlines(): - if line.strip().startswith("installed services:"): - in_services = True - continue - - if not in_services: - continue - - if line.strip() == "}": - break - - service = line.split(":")[1].strip() - - results.append( - { - "package_name": service.split("/")[0], - "service": service, - } - ) - - return results - - def parse_dumpsys_activity_resolver_table(output: str) -> Dict[str, Any]: results = {} diff --git a/tests/android/test_artifact.py b/tests/android/test_artifact.py new file mode 100644 index 0000000..dc0ef89 --- /dev/null +++ b/tests/android/test_artifact.py @@ -0,0 +1,20 @@ +# 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 mvt.android.artifacts.artifact import AndroidArtifact + +from ..utils import get_artifact + + +class TestAndroidArtifact: + def test_extract_dumpsys_section(self): + file = get_artifact("androidqf/dumpsys.txt") + with open(file) as f: + data = f.read() + + section = AndroidArtifact.extract_dumpsys_section( + data, "DUMP OF SERVICE package:" + ) + assert isinstance(section, str) + assert len(section) == 3907 diff --git a/tests/android/test_artifact_dumpsys_accessibility.py b/tests/android/test_artifact_dumpsys_accessibility.py new file mode 100644 index 0000000..f18407e --- /dev/null +++ b/tests/android/test_artifact_dumpsys_accessibility.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_accessibility import DumpsysAccessibility +from mvt.common.indicators import Indicators + +from ..utils import get_artifact + + +class TestDumpsysAccessibilityArtifact: + def test_parsing(self): + da = DumpsysAccessibility() + file = get_artifact("android_data/dumpsys_accessibility.txt") + with open(file) as f: + data = f.read() + + assert len(da.results) == 0 + da.parse(data) + assert len(da.results) == 4 + assert da.results[0]["package_name"] == "com.android.settings" + assert ( + da.results[0]["service"] + == "com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor" + ) + + def test_ioc_check(self, indicator_file): + da = DumpsysAccessibility() + file = get_artifact("android_data/dumpsys_accessibility.txt") + with open(file) as f: + data = f.read() + da.parse(data) + + ind = Indicators(log=logging.getLogger()) + 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 + da.check_indicators() + assert len(da.detected) == 1 diff --git a/tests/android_adb/__init__.py b/tests/android_adb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/artifacts/android_data/dumpsys_accessibility.txt b/tests/artifacts/android_data/dumpsys_accessibility.txt new file mode 100644 index 0000000..9209109 --- /dev/null +++ b/tests/artifacts/android_data/dumpsys_accessibility.txt @@ -0,0 +1,40 @@ +ACCESSIBILITY MANAGER (dumpsys accessibility) + +User state[attributes:{id=0, currentUser=true + mIsNavBarMagnificationAssignedToAccessibilityButton = false + + mIsNavBarMagnifierWindowAssignedToAccessibilityButton = false + mIsNavBarAmplifyAmbientSoundAssignedToAccessibilityButton = false + mIsAmplifyAmbientSoundEnabled = false + mIsBixbyRunning = false + mIsMagniferWindowEnabled = false + mIsFollowTypingFocusEnabled = false + mIsTapDurationEnabled = false + mIsTouchBlockingEnabled = false + mIsTextHighContrastEnabled = false + mIsDisplayMagnificationEnabled = false + mIsNavBarMagnificationEnabled = false + mIsAutoclickEnabled = false + mIsPerformGesturesEnabled = false + mIsFilterKeyEventsEnabled = false + mAccessibilityFocusOnlyInActiveWindow = true + mUserNonInteractiveUiTimeout = 0 + mUserInteractiveUiTimeout = 0 + mBindInstantServiceAllowed = false + mIsGestureNaviBar = false + } + installed services: { + 0 : com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor + 1 : com.samsung.accessibility/.universalswitch.UniversalSwitchService + 2 : com.samsung.accessibility/com.samsung.android.app.talkback.TalkBackService + 3 : com.sec.android.app.camera/com.samsung.android.glview.AccessibilityGestureHandler + } + enabled services: { + } + binding services: { + } + bound services:{ + } + AccessibilityInputFilter:{ + }] +