diff --git a/src/mvt/android/cmd_check_adb.py b/src/mvt/android/cmd_check_adb.py index c3c0522..3388792 100644 --- a/src/mvt/android/cmd_check_adb.py +++ b/src/mvt/android/cmd_check_adb.py @@ -7,6 +7,7 @@ import logging from typing import Optional from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.adb import ADB_MODULES @@ -19,9 +20,12 @@ class CmdAndroidCheckADB(Command): target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -29,9 +33,12 @@ class CmdAndroidCheckADB(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, serial=serial, module_options=module_options, + hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, diff --git a/src/mvt/android/cmd_check_androidqf.py b/src/mvt/android/cmd_check_androidqf.py index d821867..564e26b 100644 --- a/src/mvt/android/cmd_check_androidqf.py +++ b/src/mvt/android/cmd_check_androidqf.py @@ -9,23 +9,41 @@ import zipfile from pathlib import Path from typing import List, Optional +from mvt.android.cmd_check_backup import CmdAndroidCheckBackup +from mvt.android.cmd_check_bugreport import CmdAndroidCheckBugreport from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.androidqf import ANDROIDQF_MODULES +from .modules.androidqf.base import AndroidQFModule log = logging.getLogger(__name__) +class NoAndroidQFTargetPath(Exception): + pass + + +class NoAndroidQFBugReport(Exception): + pass + + +class NoAndroidQFBackup(Exception): + pass + + class CmdAndroidCheckAndroidQF(Command): def __init__( self, target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, - hashes: bool = False, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -33,10 +51,12 @@ class CmdAndroidCheckAndroidQF(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, serial=serial, module_options=module_options, hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, @@ -45,27 +65,130 @@ class CmdAndroidCheckAndroidQF(Command): self.name = "check-androidqf" self.modules = ANDROIDQF_MODULES - self.format: Optional[str] = None - self.archive: Optional[zipfile.ZipFile] = None - self.files: List[str] = [] + self.__format: Optional[str] = None + self.__zip: Optional[zipfile.ZipFile] = None + self.__files: List[str] = [] def init(self): if os.path.isdir(self.target_path): - self.format = "dir" + self.__format = "dir" parent_path = Path(self.target_path).absolute().parent.as_posix() target_abs_path = os.path.abspath(self.target_path) for root, subdirs, subfiles in os.walk(target_abs_path): for fname in subfiles: file_path = os.path.relpath(os.path.join(root, fname), parent_path) - self.files.append(file_path) + self.__files.append(file_path) elif os.path.isfile(self.target_path): - self.format = "zip" - self.archive = zipfile.ZipFile(self.target_path) - self.files = self.archive.namelist() + self.__format = "zip" + self.__zip = zipfile.ZipFile(self.target_path) + self.__files = self.__zip.namelist() - def module_init(self, module): - if self.format == "zip": - module.from_zip_file(self.archive, self.files) + def module_init(self, module: AndroidQFModule) -> None: # type: ignore[override] + if self.__format == "zip" and self.__zip: + module.from_zip(self.__zip, self.__files) + return + + if not self.target_path: + raise NoAndroidQFTargetPath + + parent_path = Path(self.target_path).absolute().parent.as_posix() + module.from_dir(parent_path, self.__files) + + def load_bugreport(self) -> zipfile.ZipFile: + bugreport_zip_path = None + for file_name in self.__files: + if file_name.endswith("bugreport.zip"): + bugreport_zip_path = file_name + break else: + raise NoAndroidQFBugReport + + if self.__format == "zip" and self.__zip: + handle = self.__zip.open(bugreport_zip_path) + return zipfile.ZipFile(handle) + + if self.__format == "dir" and self.target_path: parent_path = Path(self.target_path).absolute().parent.as_posix() - module.from_folder(parent_path, self.files) + bug_report_path = os.path.join(parent_path, bugreport_zip_path) + return zipfile.ZipFile(bug_report_path) + + raise NoAndroidQFBugReport + + def load_backup(self) -> bytes: + backup_ab_path = None + for file_name in self.__files: + if file_name.endswith("backup.ab"): + backup_ab_path = file_name + break + else: + raise NoAndroidQFBackup + + if self.__format == "zip" and self.__zip: + backup_file_handle = self.__zip.open(backup_ab_path) + return backup_file_handle.read() + + if self.__format == "dir" and self.target_path: + parent_path = Path(self.target_path).absolute().parent.as_posix() + backup_path = os.path.join(parent_path, backup_ab_path) + with open(backup_path, "rb") as backup_file: + backup_ab_data = backup_file.read() + return backup_ab_data + + raise NoAndroidQFBackup + + def run_bugreport_cmd(self) -> bool: + try: + bugreport = self.load_bugreport() + except NoAndroidQFBugReport: + self.log.warning( + "Skipping bugreport modules as no bugreport.zip found in AndroidQF data." + ) + return False + else: + cmd = CmdAndroidCheckBugreport( + target_path=None, + results_path=self.results_path, + ioc_files=self.ioc_files, + iocs=self.iocs, + module_options=self.module_options, + hashes=self.hashes, + sub_command=True, + ) + cmd.from_zip(bugreport) + cmd.run() + + self.detected_count += cmd.detected_count + self.timeline.extend(cmd.timeline) + self.timeline_detected.extend(cmd.timeline_detected) + + def run_backup_cmd(self) -> bool: + try: + backup = self.load_backup() + except NoAndroidQFBackup: + self.log.warning( + "Skipping backup modules as no backup.ab found in AndroidQF data." + ) + return False + else: + cmd = CmdAndroidCheckBackup( + target_path=None, + results_path=self.results_path, + ioc_files=self.ioc_files, + iocs=self.iocs, + module_options=self.module_options, + hashes=self.hashes, + sub_command=True, + ) + cmd.from_ab(backup) + cmd.run() + + self.detected_count += cmd.detected_count + self.timeline.extend(cmd.timeline) + self.timeline_detected.extend(cmd.timeline_detected) + + def finish(self) -> None: + """ + Run the bugreport and backup modules if the respective files are found in the AndroidQF data. + """ + self.run_bugreport_cmd() + self.run_backup_cmd() diff --git a/src/mvt/android/cmd_check_backup.py b/src/mvt/android/cmd_check_backup.py index 483298e..8bdb05e 100644 --- a/src/mvt/android/cmd_check_backup.py +++ b/src/mvt/android/cmd_check_backup.py @@ -20,6 +20,7 @@ from mvt.android.parsers.backup import ( parse_backup_file, ) from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.backup import BACKUP_MODULES @@ -32,10 +33,12 @@ class CmdAndroidCheckBackup(Command): target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, - hashes: bool = False, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -43,10 +46,12 @@ class CmdAndroidCheckBackup(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, serial=serial, module_options=module_options, hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, @@ -59,6 +64,34 @@ class CmdAndroidCheckBackup(Command): self.backup_archive: Optional[tarfile.TarFile] = None self.backup_files: List[str] = [] + def from_ab(self, ab_file_bytes: bytes) -> None: + self.backup_type = "ab" + header = parse_ab_header(ab_file_bytes) + if not header["backup"]: + log.critical("Invalid backup format, file should be in .ab format") + sys.exit(1) + + password = None + if header["encryption"] != "none": + password = prompt_or_load_android_backup_password(log, self.module_options) + if not password: + log.critical("No backup password provided.") + sys.exit(1) + try: + tardata = parse_backup_file(ab_file_bytes, password=password) + except InvalidBackupPassword: + log.critical("Invalid backup password") + sys.exit(1) + except AndroidBackupParsingError as exc: + log.critical("Impossible to parse this backup file: %s", exc) + log.critical("Please use Android Backup Extractor (ABE) instead") + sys.exit(1) + + dbytes = io.BytesIO(tardata) + self.backup_archive = tarfile.open(fileobj=dbytes) + for member in self.backup_archive: + self.backup_files.append(member.name) + def init(self) -> None: if not self.target_path: return @@ -66,35 +99,8 @@ class CmdAndroidCheckBackup(Command): if os.path.isfile(self.target_path): self.backup_type = "ab" with open(self.target_path, "rb") as handle: - data = handle.read() - - header = parse_ab_header(data) - if not header["backup"]: - log.critical("Invalid backup format, file should be in .ab format") - sys.exit(1) - - password = None - if header["encryption"] != "none": - password = prompt_or_load_android_backup_password( - log, self.module_options - ) - if not password: - log.critical("No backup password provided.") - sys.exit(1) - try: - tardata = parse_backup_file(data, password=password) - except InvalidBackupPassword: - log.critical("Invalid backup password") - sys.exit(1) - except AndroidBackupParsingError as exc: - log.critical("Impossible to parse this backup file: %s", exc) - log.critical("Please use Android Backup Extractor (ABE) instead") - sys.exit(1) - - dbytes = io.BytesIO(tardata) - self.backup_archive = tarfile.open(fileobj=dbytes) - for member in self.backup_archive: - self.backup_files.append(member.name) + ab_file_bytes = handle.read() + self.from_ab(ab_file_bytes) elif os.path.isdir(self.target_path): self.backup_type = "folder" @@ -113,6 +119,6 @@ class CmdAndroidCheckBackup(Command): def module_init(self, module: BackupExtraction) -> None: # type: ignore[override] if self.backup_type == "folder": - module.from_folder(self.target_path, self.backup_files) + module.from_dir(self.target_path, self.backup_files) else: module.from_ab(self.target_path, self.backup_archive, self.backup_files) diff --git a/src/mvt/android/cmd_check_bugreport.py b/src/mvt/android/cmd_check_bugreport.py index b46023e..7cc827f 100644 --- a/src/mvt/android/cmd_check_bugreport.py +++ b/src/mvt/android/cmd_check_bugreport.py @@ -11,6 +11,7 @@ from zipfile import ZipFile from mvt.android.modules.bugreport.base import BugReportModule from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.bugreport import BUGREPORT_MODULES @@ -23,10 +24,12 @@ class CmdAndroidCheckBugreport(Command): target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, - hashes: bool = False, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -34,10 +37,12 @@ class CmdAndroidCheckBugreport(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, serial=serial, module_options=module_options, hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, @@ -46,35 +51,53 @@ class CmdAndroidCheckBugreport(Command): self.name = "check-bugreport" self.modules = BUGREPORT_MODULES - self.bugreport_format: str = "" - self.bugreport_archive: Optional[ZipFile] = None - self.bugreport_files: List[str] = [] + self.__format: str = "" + self.__zip: Optional[ZipFile] = None + self.__files: List[str] = [] + + def from_dir(self, dir_path: str) -> None: + """This method is used to initialize the bug report analysis from an + uncompressed directory. + """ + self.__format = "dir" + self.target_path = dir_path + parent_path = Path(dir_path).absolute().as_posix() + for root, _, subfiles in os.walk(os.path.abspath(dir_path)): + for file_name in subfiles: + file_path = os.path.relpath(os.path.join(root, file_name), parent_path) + self.__files.append(file_path) + + def from_zip(self, bugreport_zip: ZipFile) -> None: + """This method is used to initialize the bug report analysis from a + compressed archive. + """ + # NOTE: This will be invoked either by the CLI directly,or by the + # check-androidqf command. We need this because we want to support + # check-androidqf to analyse compressed archives itself too. + # So, we'll need to extract bugreport.zip from a 'androidqf.zip', and + # since nothing is written on disk, we need to be able to pass this + # command a ZipFile instance in memory. + + self.__format = "zip" + self.__zip = bugreport_zip + for file_name in self.__zip.namelist(): + self.__files.append(file_name) def init(self) -> None: if not self.target_path: return if os.path.isfile(self.target_path): - self.bugreport_format = "zip" - self.bugreport_archive = ZipFile(self.target_path) - for file_name in self.bugreport_archive.namelist(): - self.bugreport_files.append(file_name) + self.from_zip(ZipFile(self.target_path)) elif os.path.isdir(self.target_path): - self.bugreport_format = "dir" - parent_path = Path(self.target_path).absolute().as_posix() - for root, _, subfiles in os.walk(os.path.abspath(self.target_path)): - for file_name in subfiles: - file_path = os.path.relpath( - os.path.join(root, file_name), parent_path - ) - self.bugreport_files.append(file_path) + self.from_dir(self.target_path) def module_init(self, module: BugReportModule) -> None: # type: ignore[override] - if self.bugreport_format == "zip": - module.from_zip(self.bugreport_archive, self.bugreport_files) + if self.__format == "zip": + module.from_zip(self.__zip, self.__files) else: - module.from_folder(self.target_path, self.bugreport_files) + module.from_dir(self.target_path, self.__files) def finish(self) -> None: - if self.bugreport_archive: - self.bugreport_archive.close() + if self.__zip: + self.__zip.close() diff --git a/src/mvt/android/modules/adb/__init__.py b/src/mvt/android/modules/adb/__init__.py index 1832134..b2b4368 100644 --- a/src/mvt/android/modules/adb/__init__.py +++ b/src/mvt/android/modules/adb/__init__.py @@ -4,15 +4,7 @@ # https://license.mvt.re/1.1/ from .chrome_history import ChromeHistory -from .dumpsys_accessibility import DumpsysAccessibility -from .dumpsys_activities import DumpsysActivities -from .dumpsys_appops import DumpsysAppOps -from .dumpsys_battery_daily import DumpsysBatteryDaily -from .dumpsys_battery_history import DumpsysBatteryHistory -from .dumpsys_dbinfo import DumpsysDBInfo -from .dumpsys_adbstate import DumpsysADBState from .dumpsys_full import DumpsysFull -from .dumpsys_receivers import DumpsysReceivers from .files import Files from .getprop import Getprop from .logcat import Logcat @@ -32,15 +24,7 @@ ADB_MODULES = [ Getprop, Settings, SELinuxStatus, - DumpsysBatteryHistory, - DumpsysBatteryDaily, - DumpsysReceivers, - DumpsysActivities, - DumpsysAccessibility, - DumpsysDBInfo, - DumpsysADBState, DumpsysFull, - DumpsysAppOps, Packages, Logcat, RootBinaries, diff --git a/src/mvt/android/modules/adb/dumpsys_accessibility.py b/src/mvt/android/modules/adb/dumpsys_accessibility.py deleted file mode 100644 index a987ae4..0000000 --- a/src/mvt/android/modules/adb/dumpsys_accessibility.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact - -from .base import AndroidExtraction - - -class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidExtraction): - """This module extracts stats on accessibility.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys accessibility") - self._adb_disconnect() - - self.parse(output) - - for result in self.results: - self.log.info( - 'Found installed accessibility service "%s"', result.get("service") - ) - - self.log.info( - "Identified a total of %d accessibility services", len(self.results) - ) diff --git a/src/mvt/android/modules/adb/dumpsys_activities.py b/src/mvt/android/modules/adb/dumpsys_activities.py deleted file mode 100644 index 5125cbf..0000000 --- a/src/mvt/android/modules/adb/dumpsys_activities.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_package_activities import ( - DumpsysPackageActivitiesArtifact, -) - -from .base import AndroidExtraction - - -class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidExtraction): - """This module extracts details on receivers for risky activities.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - 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.log.info("Extracted %d package activities", len(self.results)) diff --git a/src/mvt/android/modules/adb/dumpsys_adbstate.py b/src/mvt/android/modules/adb/dumpsys_adbstate.py deleted file mode 100644 index 0bcd8fd..0000000 --- a/src/mvt/android/modules/adb/dumpsys_adbstate.py +++ /dev/null @@ -1,45 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact - -from .base import AndroidExtraction - - -class DumpsysADBState(DumpsysADBArtifact, AndroidExtraction): - """This module extracts ADB keystore state.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys adb", decode=False) - self._adb_disconnect() - - self.parse(output) - if self.results: - self.log.info( - "Identified a total of %d trusted ADB keys", - len(self.results[0].get("user_keys", [])), - ) diff --git a/src/mvt/android/modules/adb/dumpsys_appops.py b/src/mvt/android/modules/adb/dumpsys_appops.py deleted file mode 100644 index 7a7594e..0000000 --- a/src/mvt/android/modules/adb/dumpsys_appops.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact - -from .base import AndroidExtraction - - -class DumpsysAppOps(DumpsysAppopsArtifact, AndroidExtraction): - """This module extracts records from App-op Manager.""" - - slug = "dumpsys_appops" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys appops") - self._adb_disconnect() - - self.parse(output) - - self.log.info( - "Extracted a total of %d records from app-ops manager", len(self.results) - ) diff --git a/src/mvt/android/modules/adb/dumpsys_battery_daily.py b/src/mvt/android/modules/adb/dumpsys_battery_daily.py deleted file mode 100644 index 3a9eee6..0000000 --- a/src/mvt/android/modules/adb/dumpsys_battery_daily.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact - -from .base import AndroidExtraction - - -class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidExtraction): - """This module extracts records from battery daily updates.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys batterystats --daily") - self._adb_disconnect() - - self.parse(output) - - self.log.info( - "Extracted %d records from battery daily stats", len(self.results) - ) diff --git a/src/mvt/android/modules/adb/dumpsys_battery_history.py b/src/mvt/android/modules/adb/dumpsys_battery_history.py deleted file mode 100644 index aac134c..0000000 --- a/src/mvt/android/modules/adb/dumpsys_battery_history.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact - -from .base import AndroidExtraction - - -class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidExtraction): - """This module extracts records from battery history events.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys batterystats --history") - self._adb_disconnect() - - self.parse(output) - - self.log.info("Extracted %d records from battery history", len(self.results)) diff --git a/src/mvt/android/modules/adb/dumpsys_dbinfo.py b/src/mvt/android/modules/adb/dumpsys_dbinfo.py deleted file mode 100644 index e6b772b..0000000 --- a/src/mvt/android/modules/adb/dumpsys_dbinfo.py +++ /dev/null @@ -1,47 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact - -from .base import AndroidExtraction - - -class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidExtraction): - """This module extracts records from battery daily updates.""" - - slug = "dumpsys_dbinfo" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - self._adb_connect() - output = self._adb_command("dumpsys dbinfo") - self._adb_disconnect() - - self.parse(output) - - self.log.info( - "Extracted a total of %d records from database information", - len(self.results), - ) diff --git a/src/mvt/android/modules/adb/dumpsys_receivers.py b/src/mvt/android/modules/adb/dumpsys_receivers.py deleted file mode 100644 index c4759c4..0000000 --- a/src/mvt/android/modules/adb/dumpsys_receivers.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact - -from .base import AndroidExtraction - - -class DumpsysReceivers(DumpsysReceiversArtifact, AndroidExtraction): - """This module extracts details on receivers for risky activities.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - self.results = results if results else {} - - def run(self) -> None: - self._adb_connect() - - output = self._adb_command("dumpsys package") - self.parse(output) - - self._adb_disconnect() - self.log.info("Extracted receivers for %d intents", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/__init__.py b/src/mvt/android/modules/androidqf/__init__.py index bcb1e32..9009f52 100644 --- a/src/mvt/android/modules/androidqf/__init__.py +++ b/src/mvt/android/modules/androidqf/__init__.py @@ -3,42 +3,22 @@ # Use of this software is governed by the MVT License 1.1 that can be found at # https://license.mvt.re/1.1/ -from .dumpsys_accessibility import DumpsysAccessibility -from .dumpsys_activities import DumpsysActivities -from .dumpsys_appops import DumpsysAppops -from .dumpsys_battery_daily import DumpsysBatteryDaily -from .dumpsys_battery_history import DumpsysBatteryHistory -from .dumpsys_dbinfo import DumpsysDBInfo -from .dumpsys_packages import DumpsysPackages -from .dumpsys_receivers import DumpsysReceivers -from .dumpsys_adb import DumpsysADBState -from .getprop import Getprop -from .packages import Packages -from .dumpsys_platform_compat import DumpsysPlatformCompat -from .processes import Processes -from .settings import Settings -from .sms import SMS -from .files import Files -from .root_binaries import RootBinaries +from .aqf_files import AQFFiles +from .aqf_getprop import AQFGetProp +from .aqf_packages import AQFPackages +from .aqf_processes import AQFProcesses +from .aqf_settings import AQFSettings from .mounts import Mounts +from .root_binaries import RootBinaries +from .sms import SMS ANDROIDQF_MODULES = [ - DumpsysActivities, - DumpsysReceivers, - DumpsysAccessibility, - DumpsysAppops, - DumpsysDBInfo, - DumpsysBatteryDaily, - DumpsysBatteryHistory, - DumpsysADBState, - Packages, - DumpsysPlatformCompat, - Processes, - Getprop, - Settings, + AQFPackages, + AQFProcesses, + AQFGetProp, + AQFSettings, + AQFFiles, SMS, - DumpsysPackages, - Files, RootBinaries, Mounts, ] diff --git a/src/mvt/android/modules/androidqf/files.py b/src/mvt/android/modules/androidqf/aqf_files.py similarity index 94% rename from src/mvt/android/modules/androidqf/files.py rename to src/mvt/android/modules/androidqf/aqf_files.py index 22b832c..90eb3b8 100644 --- a/src/mvt/android/modules/androidqf/files.py +++ b/src/mvt/android/modules/androidqf/aqf_files.py @@ -21,8 +21,13 @@ SUSPICIOUS_PATHS = [ ] -class Files(AndroidQFModule): - """This module analyse list of files""" +class AQFFiles(AndroidQFModule): + """ + This module analyzes the files.json dump generated by AndroidQF. + + The format needs to be kept in sync with the AndroidQF module code. + https://github.com/mvt-project/androidqf/blob/main/android-collector/cmd/find.go#L28 + """ def __init__( self, diff --git a/src/mvt/android/modules/androidqf/getprop.py b/src/mvt/android/modules/androidqf/aqf_getprop.py similarity index 96% rename from src/mvt/android/modules/androidqf/getprop.py rename to src/mvt/android/modules/androidqf/aqf_getprop.py index e14abd9..35514f8 100644 --- a/src/mvt/android/modules/androidqf/getprop.py +++ b/src/mvt/android/modules/androidqf/aqf_getprop.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact from .base import AndroidQFModule -class Getprop(GetPropArtifact, AndroidQFModule): +class AQFGetProp(GetPropArtifact, AndroidQFModule): """This module extracts data from get properties.""" def __init__( diff --git a/src/mvt/android/modules/androidqf/logfile_timestamps.py b/src/mvt/android/modules/androidqf/aqf_log_timestamps.py similarity index 92% rename from src/mvt/android/modules/androidqf/logfile_timestamps.py rename to src/mvt/android/modules/androidqf/aqf_log_timestamps.py index b37851d..e5a1410 100644 --- a/src/mvt/android/modules/androidqf/logfile_timestamps.py +++ b/src/mvt/android/modules/androidqf/aqf_log_timestamps.py @@ -13,10 +13,10 @@ from .base import AndroidQFModule from mvt.android.artifacts.file_timestamps import FileTimestampsArtifact -class LogsFileTimestamps(FileTimestampsArtifact, AndroidQFModule): - """This module extracts records from battery daily updates.""" +class AQFLogTimestamps(FileTimestampsArtifact, AndroidQFModule): + """This module creates timeline for log files extracted by AQF.""" - slug = "logfile_timestamps" + slug = "aqf_log_timestamps" def __init__( self, diff --git a/src/mvt/android/modules/androidqf/packages.py b/src/mvt/android/modules/androidqf/aqf_packages.py similarity index 99% rename from src/mvt/android/modules/androidqf/packages.py rename to src/mvt/android/modules/androidqf/aqf_packages.py index 1d36777..500b3d4 100644 --- a/src/mvt/android/modules/androidqf/packages.py +++ b/src/mvt/android/modules/androidqf/aqf_packages.py @@ -19,7 +19,7 @@ from mvt.android.utils import ( from .base import AndroidQFModule -class Packages(AndroidQFModule): +class AQFPackages(AndroidQFModule): """This module examines the installed packages in packages.json""" def __init__( diff --git a/src/mvt/android/modules/androidqf/processes.py b/src/mvt/android/modules/androidqf/aqf_processes.py similarity index 95% rename from src/mvt/android/modules/androidqf/processes.py rename to src/mvt/android/modules/androidqf/aqf_processes.py index f2c5e08..3faabb4 100644 --- a/src/mvt/android/modules/androidqf/processes.py +++ b/src/mvt/android/modules/androidqf/aqf_processes.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.processes import Processes as ProcessesArtifact from .base import AndroidQFModule -class Processes(ProcessesArtifact, AndroidQFModule): +class AQFProcesses(ProcessesArtifact, AndroidQFModule): """This module analyse running processes""" def __init__( diff --git a/src/mvt/android/modules/androidqf/settings.py b/src/mvt/android/modules/androidqf/aqf_settings.py similarity index 96% rename from src/mvt/android/modules/androidqf/settings.py rename to src/mvt/android/modules/androidqf/aqf_settings.py index 79f55ef..46a70fb 100644 --- a/src/mvt/android/modules/androidqf/settings.py +++ b/src/mvt/android/modules/androidqf/aqf_settings.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.settings import Settings as SettingsArtifact from .base import AndroidQFModule -class Settings(SettingsArtifact, AndroidQFModule): +class AQFSettings(SettingsArtifact, AndroidQFModule): """This module analyse setting files""" def __init__( diff --git a/src/mvt/android/modules/androidqf/base.py b/src/mvt/android/modules/androidqf/base.py index d871059..43e6210 100644 --- a/src/mvt/android/modules/androidqf/base.py +++ b/src/mvt/android/modules/androidqf/base.py @@ -37,11 +37,11 @@ class AndroidQFModule(MVTModule): self.files: List[str] = [] self.archive: Optional[zipfile.ZipFile] = None - def from_folder(self, parent_path: str, files: List[str]): + def from_dir(self, parent_path: str, files: List[str]) -> None: self.parent_path = parent_path self.files = files - def from_zip_file(self, archive: zipfile.ZipFile, files: List[str]): + def from_zip(self, archive: zipfile.ZipFile, files: List[str]) -> None: self.archive = archive self.files = files diff --git a/src/mvt/android/modules/androidqf/dumpsys_accessibility.py b/src/mvt/android/modules/androidqf/dumpsys_accessibility.py deleted file mode 100644 index 0712ef4..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_accessibility.py +++ /dev/null @@ -1,51 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact - -from .base import AndroidQFModule - - -class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidQFModule): - """This module analyses dumpsys accessibility""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - 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( - 'Found installed accessibility service "%s"', result.get("service") - ) - - self.log.info( - "Identified a total of %d accessibility services", len(self.results) - ) diff --git a/src/mvt/android/modules/androidqf/dumpsys_activities.py b/src/mvt/android/modules/androidqf/dumpsys_activities.py deleted file mode 100644 index 950d0e5..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_activities.py +++ /dev/null @@ -1,50 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_package_activities import ( - DumpsysPackageActivitiesArtifact, -) - -from .base import AndroidQFModule - - -class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidQFModule): - """This module extracts details on receivers for risky activities.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - self.results = results if results else [] - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - # 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) - - self.log.info("Extracted %d package activities", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_adb.py b/src/mvt/android/modules/androidqf/dumpsys_adb.py deleted file mode 100644 index 10d8a4d..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_adb.py +++ /dev/null @@ -1,51 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_adb import DumpsysADBArtifact - -from .base import AndroidQFModule - - -class DumpsysADBState(DumpsysADBArtifact, AndroidQFModule): - """This module extracts ADB keystore state.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - full_dumpsys = self._get_file_content(dumpsys_file[0]) - content = self.extract_dumpsys_section( - full_dumpsys, - b"DUMP OF SERVICE adb:", - binary=True, - ) - self.parse(content) - if self.results: - self.log.info( - "Identified a total of %d trusted ADB keys", - len(self.results[0].get("user_keys", [])), - ) diff --git a/src/mvt/android/modules/androidqf/dumpsys_appops.py b/src/mvt/android/modules/androidqf/dumpsys_appops.py deleted file mode 100644 index 350b5c8..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_appops.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact - -from .base import AndroidQFModule - - -class DumpsysAppops(DumpsysAppopsArtifact, AndroidQFModule): - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - # Extract section - data = self._get_file_content(dumpsys_file[0]) - section = self.extract_dumpsys_section( - data.decode("utf-8", errors="replace"), "DUMP OF SERVICE appops:" - ) - - # Parse it - self.parse(section) - self.log.info("Identified %d applications in AppOps Manager", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_battery_daily.py b/src/mvt/android/modules/androidqf/dumpsys_battery_daily.py deleted file mode 100644 index 4a19178..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_battery_daily.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact - -from .base import AndroidQFModule - - -class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidQFModule): - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - # Extract section - data = self._get_file_content(dumpsys_file[0]) - section = self.extract_dumpsys_section( - data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:" - ) - - # Parse it - self.parse(section) - self.log.info("Extracted a total of %d battery daily stats", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_battery_history.py b/src/mvt/android/modules/androidqf/dumpsys_battery_history.py deleted file mode 100644 index 4a4bef3..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_battery_history.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact - -from .base import AndroidQFModule - - -class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidQFModule): - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - # Extract section - data = self._get_file_content(dumpsys_file[0]) - section = self.extract_dumpsys_section( - data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:" - ) - - # Parse it - self.parse(section) - self.log.info("Extracted a total of %d battery daily stats", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_dbinfo.py b/src/mvt/android/modules/androidqf/dumpsys_dbinfo.py deleted file mode 100644 index 09c8f6f..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_dbinfo.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact - -from .base import AndroidQFModule - - -class DumpsysDBInfo(DumpsysDBInfoArtifact, AndroidQFModule): - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - # Extract dumpsys DBInfo section - data = self._get_file_content(dumpsys_file[0]) - section = self.extract_dumpsys_section( - data.decode("utf-8", errors="replace"), "DUMP OF SERVICE dbinfo:" - ) - - # Parse it - self.parse(section) - self.log.info("Identified %d DB Info entries", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_packages.py b/src/mvt/android/modules/androidqf/dumpsys_packages.py deleted file mode 100644 index 8df7144..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_packages.py +++ /dev/null @@ -1,62 +0,0 @@ -# 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 typing import Any, Dict, List, Optional - -from mvt.android.artifacts.dumpsys_packages import DumpsysPackagesArtifact -from mvt.android.modules.adb.packages import ( - DANGEROUS_PERMISSIONS, - DANGEROUS_PERMISSIONS_THRESHOLD, -) - -from .base import AndroidQFModule - - -class DumpsysPackages(DumpsysPackagesArtifact, AndroidQFModule): - """This module analyse dumpsys packages""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[List[Dict[str, Any]]] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if len(dumpsys_file) != 1: - self.log.info("Dumpsys file not found") - return - - data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace") - content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:") - self.parse(content) - - for result in self.results: - dangerous_permissions_count = 0 - for perm in result["permissions"]: - if perm["name"] in DANGEROUS_PERMISSIONS: - dangerous_permissions_count += 1 - - if dangerous_permissions_count >= DANGEROUS_PERMISSIONS_THRESHOLD: - self.log.info( - 'Found package "%s" requested %d potentially dangerous permissions', - result["package_name"], - dangerous_permissions_count, - ) - - self.log.info("Extracted details on %d packages", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_platform_compat.py b/src/mvt/android/modules/androidqf/dumpsys_platform_compat.py deleted file mode 100644 index 869c476..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_platform_compat.py +++ /dev/null @@ -1,44 +0,0 @@ -# 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 typing import Optional - -from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatArtifact - -from .base import AndroidQFModule - - -class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, AndroidQFModule): - """This module extracts details on uninstalled apps.""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Optional[list] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - - data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace") - content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:") - self.parse(content) - - self.log.info("Found %d uninstalled apps", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/dumpsys_receivers.py b/src/mvt/android/modules/androidqf/dumpsys_receivers.py deleted file mode 100644 index 9c64d2c..0000000 --- a/src/mvt/android/modules/androidqf/dumpsys_receivers.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 typing import Any, Dict, List, Optional, Union - -from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact - -from .base import AndroidQFModule - - -class DumpsysReceivers(DumpsysReceiversArtifact, AndroidQFModule): - """This module analyse dumpsys receivers""" - - def __init__( - self, - file_path: Optional[str] = None, - target_path: Optional[str] = None, - results_path: Optional[str] = None, - module_options: Optional[dict] = None, - log: logging.Logger = logging.getLogger(__name__), - results: Union[List[Any], Dict[str, Any], None] = None, - ) -> None: - super().__init__( - file_path=file_path, - target_path=target_path, - results_path=results_path, - module_options=module_options, - log=log, - results=results, - ) - - self.results = results if results else {} - - def run(self) -> None: - dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt") - if not dumpsys_file: - return - data = self._get_file_content(dumpsys_file[0]) - - dumpsys_section = self.extract_dumpsys_section( - data.decode("utf-8", errors="replace"), "DUMP OF SERVICE package:" - ) - - self.parse(dumpsys_section) - - self.log.info("Extracted receivers for %d intents", len(self.results)) diff --git a/src/mvt/android/modules/androidqf/sms.py b/src/mvt/android/modules/androidqf/sms.py index d7e3a9c..893e517 100644 --- a/src/mvt/android/modules/androidqf/sms.py +++ b/src/mvt/android/modules/androidqf/sms.py @@ -19,7 +19,13 @@ from .base import AndroidQFModule class SMS(AndroidQFModule): - """This module analyse SMS file in backup""" + """ + This module analyse SMS file in backup + + XXX: We should also de-duplicate this AQF module, but first we + need to add tests for loading encrypted SMS backups through the backup + sub-module. + """ def __init__( self, diff --git a/src/mvt/android/modules/backup/base.py b/src/mvt/android/modules/backup/base.py index 5141bfe..29238ba 100644 --- a/src/mvt/android/modules/backup/base.py +++ b/src/mvt/android/modules/backup/base.py @@ -37,10 +37,7 @@ class BackupExtraction(MVTModule): self.tar = None self.files = [] - def from_folder(self, backup_path: Optional[str], files: List[str]) -> None: - """ - Get all the files and list them - """ + def from_dir(self, backup_path: Optional[str], files: List[str]) -> None: self.backup_path = backup_path self.files = files @@ -58,14 +55,16 @@ class BackupExtraction(MVTModule): return fnmatch.filter(self.files, pattern) def _get_file_content(self, file_path: str) -> bytes: - if self.ab: + if self.tar: try: member = self.tar.getmember(file_path) except KeyError: return None handle = self.tar.extractfile(member) - else: + elif self.backup_path: handle = open(os.path.join(self.backup_path, file_path), "rb") + else: + raise ValueError("No backup path or tar file provided") data = handle.read() handle.close() diff --git a/src/mvt/android/modules/backup/sms.py b/src/mvt/android/modules/backup/sms.py index a75be26..a194a1e 100644 --- a/src/mvt/android/modules/backup/sms.py +++ b/src/mvt/android/modules/backup/sms.py @@ -50,13 +50,13 @@ class SMS(BackupExtraction): def run(self) -> None: sms_path = "apps/com.android.providers.telephony/d_f/*_sms_backup" for file in self._get_files_by_pattern(sms_path): - self.log.info("Processing SMS backup file at %s", file) + self.log.debug("Processing SMS backup file at %s", file) data = self._get_file_content(file) self.results.extend(parse_sms_file(data)) mms_path = "apps/com.android.providers.telephony/d_f/*_mms_backup" for file in self._get_files_by_pattern(mms_path): - self.log.info("Processing MMS backup file at %s", file) + self.log.debug("Processing MMS backup file at %s", file) data = self._get_file_content(file) self.results.extend(parse_sms_file(data)) diff --git a/src/mvt/android/modules/bugreport/__init__.py b/src/mvt/android/modules/bugreport/__init__.py index b5a1247..1594af9 100644 --- a/src/mvt/android/modules/bugreport/__init__.py +++ b/src/mvt/android/modules/bugreport/__init__.py @@ -3,31 +3,31 @@ # Use of this software is governed by the MVT License 1.1 that can be found at # https://license.mvt.re/1.1/ -from .accessibility import Accessibility -from .activities import Activities -from .appops import Appops -from .battery_daily import BatteryDaily -from .battery_history import BatteryHistory -from .dbinfo import DBInfo -from .getprop import Getprop -from .packages import Packages -from .platform_compat import PlatformCompat -from .receivers import Receivers -from .adb_state import DumpsysADBState +from .dumpsys_accessibility import DumpsysAccessibility +from .dumpsys_activities import DumpsysActivities +from .dumpsys_appops import DumpsysAppops +from .dumpsys_battery_daily import DumpsysBatteryDaily +from .dumpsys_battery_history import DumpsysBatteryHistory +from .dumpsys_dbinfo import DumpsysDBInfo +from .dumpsys_getprop import DumpsysGetProp +from .dumpsys_packages import DumpsysPackages +from .dumpsys_platform_compat import DumpsysPlatformCompat +from .dumpsys_receivers import DumpsysReceivers +from .dumpsys_adb_state import DumpsysADBState from .fs_timestamps import BugReportTimestamps from .tombstones import Tombstones BUGREPORT_MODULES = [ - Accessibility, - Activities, - Appops, - BatteryDaily, - BatteryHistory, - DBInfo, - Getprop, - Packages, - PlatformCompat, - Receivers, + DumpsysAccessibility, + DumpsysActivities, + DumpsysAppops, + DumpsysBatteryDaily, + DumpsysBatteryHistory, + DumpsysDBInfo, + DumpsysGetProp, + DumpsysPackages, + DumpsysPlatformCompat, + DumpsysReceivers, DumpsysADBState, BugReportTimestamps, Tombstones, diff --git a/src/mvt/android/modules/bugreport/base.py b/src/mvt/android/modules/bugreport/base.py index 77802b2..158bc28 100644 --- a/src/mvt/android/modules/bugreport/base.py +++ b/src/mvt/android/modules/bugreport/base.py @@ -39,9 +39,7 @@ class BugReportModule(MVTModule): self.extract_files: List[str] = [] self.zip_files: List[str] = [] - def from_folder( - self, extract_path: Optional[str], extract_files: List[str] - ) -> None: + def from_dir(self, extract_path: str, extract_files: List[str]) -> None: self.extract_path = extract_path self.extract_files = extract_files diff --git a/src/mvt/android/modules/bugreport/accessibility.py b/src/mvt/android/modules/bugreport/dumpsys_accessibility.py similarity index 95% rename from src/mvt/android/modules/bugreport/accessibility.py rename to src/mvt/android/modules/bugreport/dumpsys_accessibility.py index 7d30eb0..e141b2f 100644 --- a/src/mvt/android/modules/bugreport/accessibility.py +++ b/src/mvt/android/modules/bugreport/dumpsys_accessibility.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArti from .base import BugReportModule -class Accessibility(DumpsysAccessibilityArtifact, BugReportModule): +class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule): """This module extracts stats on accessibility.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/activities.py b/src/mvt/android/modules/bugreport/dumpsys_activities.py similarity index 95% rename from src/mvt/android/modules/bugreport/activities.py rename to src/mvt/android/modules/bugreport/dumpsys_activities.py index c2a20dd..a58c6f4 100644 --- a/src/mvt/android/modules/bugreport/activities.py +++ b/src/mvt/android/modules/bugreport/dumpsys_activities.py @@ -13,7 +13,7 @@ from mvt.android.artifacts.dumpsys_package_activities import ( from .base import BugReportModule -class Activities(DumpsysPackageActivitiesArtifact, BugReportModule): +class DumpsysActivities(DumpsysPackageActivitiesArtifact, BugReportModule): """This module extracts details on receivers for risky activities.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/adb_state.py b/src/mvt/android/modules/bugreport/dumpsys_adb_state.py similarity index 100% rename from src/mvt/android/modules/bugreport/adb_state.py rename to src/mvt/android/modules/bugreport/dumpsys_adb_state.py diff --git a/src/mvt/android/modules/bugreport/appops.py b/src/mvt/android/modules/bugreport/dumpsys_appops.py similarity index 96% rename from src/mvt/android/modules/bugreport/appops.py rename to src/mvt/android/modules/bugreport/dumpsys_appops.py index 4fb1e7f..96b4796 100644 --- a/src/mvt/android/modules/bugreport/appops.py +++ b/src/mvt/android/modules/bugreport/dumpsys_appops.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact from .base import BugReportModule -class Appops(DumpsysAppopsArtifact, BugReportModule): +class DumpsysAppops(DumpsysAppopsArtifact, BugReportModule): """This module extracts information on package from App-Ops Manager.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/battery_daily.py b/src/mvt/android/modules/bugreport/dumpsys_battery_daily.py similarity index 95% rename from src/mvt/android/modules/bugreport/battery_daily.py rename to src/mvt/android/modules/bugreport/dumpsys_battery_daily.py index 4fdcf74..7fc8329 100644 --- a/src/mvt/android/modules/bugreport/battery_daily.py +++ b/src/mvt/android/modules/bugreport/dumpsys_battery_daily.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtif from .base import BugReportModule -class BatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule): +class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule): """This module extracts records from battery daily updates.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/battery_history.py b/src/mvt/android/modules/bugreport/dumpsys_battery_history.py similarity index 95% rename from src/mvt/android/modules/bugreport/battery_history.py rename to src/mvt/android/modules/bugreport/dumpsys_battery_history.py index 968bbbe..729f801 100644 --- a/src/mvt/android/modules/bugreport/battery_history.py +++ b/src/mvt/android/modules/bugreport/dumpsys_battery_history.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryA from .base import BugReportModule -class BatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule): +class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule): """This module extracts records from battery daily updates.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/dbinfo.py b/src/mvt/android/modules/bugreport/dumpsys_dbinfo.py similarity index 96% rename from src/mvt/android/modules/bugreport/dbinfo.py rename to src/mvt/android/modules/bugreport/dumpsys_dbinfo.py index 780d9fc..73902bb 100644 --- a/src/mvt/android/modules/bugreport/dbinfo.py +++ b/src/mvt/android/modules/bugreport/dumpsys_dbinfo.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfoArtifact from .base import BugReportModule -class DBInfo(DumpsysDBInfoArtifact, BugReportModule): +class DumpsysDBInfo(DumpsysDBInfoArtifact, BugReportModule): """This module extracts records from battery daily updates.""" slug = "dbinfo" diff --git a/src/mvt/android/modules/bugreport/getprop.py b/src/mvt/android/modules/bugreport/dumpsys_getprop.py similarity index 97% rename from src/mvt/android/modules/bugreport/getprop.py rename to src/mvt/android/modules/bugreport/dumpsys_getprop.py index 106d63c..acec15c 100644 --- a/src/mvt/android/modules/bugreport/getprop.py +++ b/src/mvt/android/modules/bugreport/dumpsys_getprop.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.getprop import GetProp as GetPropArtifact from .base import BugReportModule -class Getprop(GetPropArtifact, BugReportModule): +class DumpsysGetProp(GetPropArtifact, BugReportModule): """This module extracts device properties from getprop command.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/packages.py b/src/mvt/android/modules/bugreport/dumpsys_packages.py similarity index 97% rename from src/mvt/android/modules/bugreport/packages.py rename to src/mvt/android/modules/bugreport/dumpsys_packages.py index f1b9d63..fccf102 100644 --- a/src/mvt/android/modules/bugreport/packages.py +++ b/src/mvt/android/modules/bugreport/dumpsys_packages.py @@ -12,7 +12,7 @@ from mvt.android.utils import DANGEROUS_PERMISSIONS, DANGEROUS_PERMISSIONS_THRES from .base import BugReportModule -class Packages(DumpsysPackagesArtifact, BugReportModule): +class DumpsysPackages(DumpsysPackagesArtifact, BugReportModule): """This module extracts details on receivers for risky activities.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/platform_compat.py b/src/mvt/android/modules/bugreport/dumpsys_platform_compat.py similarity index 95% rename from src/mvt/android/modules/bugreport/platform_compat.py rename to src/mvt/android/modules/bugreport/dumpsys_platform_compat.py index fadac92..e9d10e6 100644 --- a/src/mvt/android/modules/bugreport/platform_compat.py +++ b/src/mvt/android/modules/bugreport/dumpsys_platform_compat.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_platform_compat import DumpsysPlatformCompatA from mvt.android.modules.bugreport.base import BugReportModule -class PlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule): +class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule): """This module extracts details on uninstalled apps.""" def __init__( diff --git a/src/mvt/android/modules/bugreport/receivers.py b/src/mvt/android/modules/bugreport/dumpsys_receivers.py similarity index 95% rename from src/mvt/android/modules/bugreport/receivers.py rename to src/mvt/android/modules/bugreport/dumpsys_receivers.py index 57a87ce..591af2f 100644 --- a/src/mvt/android/modules/bugreport/receivers.py +++ b/src/mvt/android/modules/bugreport/dumpsys_receivers.py @@ -11,7 +11,7 @@ from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact from .base import BugReportModule -class Receivers(DumpsysReceiversArtifact, BugReportModule): +class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule): """This module extracts details on receivers for risky activities.""" def __init__( diff --git a/src/mvt/common/cmd_check_iocs.py b/src/mvt/common/cmd_check_iocs.py index 0663296..1111b77 100644 --- a/src/mvt/common/cmd_check_iocs.py +++ b/src/mvt/common/cmd_check_iocs.py @@ -22,6 +22,8 @@ class CmdCheckIOCS(Command): module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -32,6 +34,8 @@ class CmdCheckIOCS(Command): module_name=module_name, serial=serial, module_options=module_options, + hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, diff --git a/src/mvt/common/command.py b/src/mvt/common/command.py index 036de7c..0eb633a 100644 --- a/src/mvt/common/command.py +++ b/src/mvt/common/command.py @@ -27,10 +27,12 @@ class Command: target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, - hashes: bool = False, + hashes: Optional[bool] = False, + sub_command: Optional[bool] = False, log: logging.Logger = logging.getLogger(__name__), disable_version_check: bool = False, disable_indicator_check: bool = False, @@ -44,6 +46,7 @@ class Command: self.module_name = module_name self.serial = serial self.log = log + self.sub_command = sub_command self.disable_version_check = disable_version_check self.disable_indicator_check = disable_indicator_check @@ -64,8 +67,12 @@ class Command: # Load IOCs self._create_storage() self._setup_logging() - self.iocs = Indicators(log=log) - self.iocs.load_indicators_files(self.ioc_files) + + if iocs is not None: + self.iocs = iocs + else: + self.iocs = Indicators(self.log) + self.iocs.load_indicators_files(self.ioc_files) def _create_storage(self) -> None: if self.results_path and not os.path.exists(self.results_path): @@ -251,6 +258,10 @@ class Command: except NotImplementedError: pass + # We only store the timeline from the parent/main command + if self.sub_command: + return + self._store_timeline() self._store_info() diff --git a/src/mvt/ios/cmd_check_backup.py b/src/mvt/ios/cmd_check_backup.py index b1a373a..9200964 100644 --- a/src/mvt/ios/cmd_check_backup.py +++ b/src/mvt/ios/cmd_check_backup.py @@ -7,6 +7,7 @@ import logging from typing import Optional from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.backup import BACKUP_MODULES from .modules.mixed import MIXED_MODULES @@ -20,10 +21,12 @@ class CmdIOSCheckBackup(Command): target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + sub_command: bool = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -31,10 +34,12 @@ class CmdIOSCheckBackup(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, serial=serial, module_options=module_options, hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, diff --git a/src/mvt/ios/cmd_check_fs.py b/src/mvt/ios/cmd_check_fs.py index b09fba0..78325ba 100644 --- a/src/mvt/ios/cmd_check_fs.py +++ b/src/mvt/ios/cmd_check_fs.py @@ -7,6 +7,7 @@ import logging from typing import Optional from mvt.common.command import Command +from mvt.common.indicators import Indicators from .modules.fs import FS_MODULES from .modules.mixed import MIXED_MODULES @@ -20,10 +21,12 @@ class CmdIOSCheckFS(Command): target_path: Optional[str] = None, results_path: Optional[str] = None, ioc_files: Optional[list] = None, + iocs: Optional[Indicators] = None, module_name: Optional[str] = None, serial: Optional[str] = None, module_options: Optional[dict] = None, hashes: bool = False, + sub_command: bool = False, disable_version_check: bool = False, disable_indicator_check: bool = False, ) -> None: @@ -31,9 +34,11 @@ class CmdIOSCheckFS(Command): target_path=target_path, results_path=results_path, ioc_files=ioc_files, + iocs=iocs, module_name=module_name, module_options=module_options, hashes=hashes, + sub_command=sub_command, log=log, disable_version_check=disable_version_check, disable_indicator_check=disable_indicator_check, diff --git a/tests/android/test_backup_module.py b/tests/android/test_backup_module.py index 29bc8e9..57cb891 100644 --- a/tests/android/test_backup_module.py +++ b/tests/android/test_backup_module.py @@ -22,7 +22,7 @@ class TestBackupModule: for root, subdirs, subfiles in os.walk(os.path.abspath(backup_path)): for fname in subfiles: files.append(os.path.relpath(os.path.join(root, fname), backup_path)) - mod.from_folder(backup_path, files) + mod.from_dir(backup_path, files) run_module(mod) assert len(mod.results) == 2 assert len(mod.results[0]["links"]) == 1 diff --git a/tests/android_androidqf/test_dumpsys_adbstate.py b/tests/android_androidqf/test_dumpsys_adbstate.py deleted file mode 100644 index c94fe34..0000000 --- a/tests/android_androidqf/test_dumpsys_adbstate.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_adb import DumpsysADBState -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysADBModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysADBState(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 1 - assert len(m.detected) == 0 - - adb_statedump = m.results[0] - assert "user_keys" in adb_statedump - assert len(adb_statedump["user_keys"]) == 1 diff --git a/tests/android_androidqf/test_dumpsys_battery_daily.py b/tests/android_androidqf/test_dumpsys_battery_daily.py deleted file mode 100644 index 66a45d7..0000000 --- a/tests/android_androidqf/test_dumpsys_battery_daily.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_battery_daily import DumpsysBatteryDaily -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysBatteryDailyModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysBatteryDaily(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 3 - assert len(m.timeline) == 3 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_dumpsys_battery_history.py b/tests/android_androidqf/test_dumpsys_battery_history.py deleted file mode 100644 index 8b76459..0000000 --- a/tests/android_androidqf/test_dumpsys_battery_history.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_battery_history import DumpsysBatteryHistory -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysBatteryHistoryModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysBatteryHistory(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 6 - assert len(m.timeline) == 0 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_dumpsys_dbinfo.py b/tests/android_androidqf/test_dumpsys_dbinfo.py deleted file mode 100644 index 83addbf..0000000 --- a/tests/android_androidqf/test_dumpsys_dbinfo.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_dbinfo import DumpsysDBInfo -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysDBInfoModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysDBInfo(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 6 - assert len(m.timeline) == 0 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_dumpsys_platform_compat.py b/tests/android_androidqf/test_dumpsys_platform_compat.py deleted file mode 100644 index 8123432..0000000 --- a/tests/android_androidqf/test_dumpsys_platform_compat.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_platform_compat import DumpsysPlatformCompat -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysPlatformCompatModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysPlatformCompat(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 2 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_dumpsysaccessbility.py b/tests/android_androidqf/test_dumpsysaccessbility.py deleted file mode 100644 index 1a217d0..0000000 --- a/tests/android_androidqf/test_dumpsysaccessbility.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_accessibility import DumpsysAccessibility -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysAccessibilityModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysAccessibility(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 4 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_dumpsysappops.py b/tests/android_androidqf/test_dumpsysappops.py deleted file mode 100644 index b0649a1..0000000 --- a/tests/android_androidqf/test_dumpsysappops.py +++ /dev/null @@ -1,29 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_appops import DumpsysAppops -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysAppOpsModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysAppops(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 12 - assert len(m.timeline) == 16 - - detected_by_ioc = [ - detected for detected in m.detected if detected.get("matched_indicator") - ] - assert len(m.detected) == 1 - assert len(detected_by_ioc) == 0 diff --git a/tests/android_androidqf/test_dumpsyspackages.py b/tests/android_androidqf/test_dumpsyspackages.py deleted file mode 100644 index 798628f..0000000 --- a/tests/android_androidqf/test_dumpsyspackages.py +++ /dev/null @@ -1,46 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_packages import DumpsysPackages -from mvt.common.indicators import Indicators -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysPackagesModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysPackages(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 2 - assert len(m.detected) == 0 - assert len(m.timeline) == 6 - assert ( - m.results[0]["package_name"] - == "com.samsung.android.provider.filterprovider" - ) - - def test_detection_pkgname(self, indicator_file): - data_path = get_android_androidqf() - m = DumpsysPackages(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - ind = Indicators(log=logging.getLogger()) - ind.parse_stix2(indicator_file) - ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.DataCreate") - m.indicators = ind - run_module(m) - assert len(m.results) == 2 - assert len(m.detected) == 1 - assert len(m.timeline) == 6 - assert m.detected[0]["package_name"] == "com.sec.android.app.DataCreate" diff --git a/tests/android_androidqf/test_dumpsysreceivers.py b/tests/android_androidqf/test_dumpsysreceivers.py deleted file mode 100644 index ce4b37a..0000000 --- a/tests/android_androidqf/test_dumpsysreceivers.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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 pathlib import Path - -from mvt.android.modules.androidqf.dumpsys_receivers import DumpsysReceivers -from mvt.common.module import run_module - -from ..utils import get_android_androidqf, list_files - - -class TestDumpsysReceiversModule: - def test_parsing(self): - data_path = get_android_androidqf() - m = DumpsysReceivers(target_path=data_path) - files = list_files(data_path) - parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) - run_module(m) - assert len(m.results) == 4 - assert len(m.detected) == 0 diff --git a/tests/android_androidqf/test_files.py b/tests/android_androidqf/test_files.py index 80981a2..c0d45b5 100644 --- a/tests/android_androidqf/test_files.py +++ b/tests/android_androidqf/test_files.py @@ -6,7 +6,7 @@ import logging from pathlib import Path -from mvt.android.modules.androidqf.files import Files +from mvt.android.modules.androidqf.aqf_files import AQFFiles from mvt.common.module import run_module from ..utils import get_android_androidqf, list_files @@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files class TestAndroidqfFilesAnalysis: def test_androidqf_files(self): data_path = get_android_androidqf() - m = Files(target_path=data_path, log=logging) + m = AQFFiles(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 3 assert len(m.timeline) == 6 diff --git a/tests/android_androidqf/test_getprop.py b/tests/android_androidqf/test_getprop.py index 4688b73..3947acd 100644 --- a/tests/android_androidqf/test_getprop.py +++ b/tests/android_androidqf/test_getprop.py @@ -7,7 +7,7 @@ import logging import zipfile from pathlib import Path -from mvt.android.modules.androidqf.getprop import Getprop +from mvt.android.modules.androidqf.aqf_getprop import AQFGetProp from mvt.common.indicators import Indicators from mvt.common.module import run_module @@ -17,10 +17,10 @@ from ..utils import get_android_androidqf, get_artifact, list_files class TestAndroidqfGetpropAnalysis: def test_androidqf_getprop(self): data_path = get_android_androidqf() - m = Getprop(target_path=data_path, log=logging) + m = AQFGetProp(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 10 assert m.results[0]["name"] == "dalvik.vm.appimageformat" @@ -30,9 +30,9 @@ class TestAndroidqfGetpropAnalysis: def test_getprop_parsing_zip(self): fpath = get_artifact("androidqf.zip") - m = Getprop(target_path=fpath, log=logging) + m = AQFGetProp(target_path=fpath, log=logging) archive = zipfile.ZipFile(fpath) - m.from_zip_file(archive, archive.namelist()) + m.from_zip(archive, archive.namelist()) run_module(m) assert len(m.results) == 10 assert m.results[0]["name"] == "dalvik.vm.appimageformat" @@ -42,10 +42,10 @@ class TestAndroidqfGetpropAnalysis: def test_androidqf_getprop_detection(self, indicator_file): data_path = get_android_androidqf() - m = Getprop(target_path=data_path, log=logging) + m = AQFGetProp(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) ind = Indicators(log=logging.getLogger()) ind.parse_stix2(indicator_file) ind.ioc_collections[0]["android_property_names"].append("dalvik.vm.heapmaxfree") diff --git a/tests/android_androidqf/test_mounts.py b/tests/android_androidqf/test_mounts.py index 89e5e17..95e8060 100644 --- a/tests/android_androidqf/test_mounts.py +++ b/tests/android_androidqf/test_mounts.py @@ -85,7 +85,7 @@ class TestAndroidqfMountsModule: m = Mounts(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) diff --git a/tests/android_androidqf/test_packages.py b/tests/android_androidqf/test_packages.py index a7fce95..966d8a6 100644 --- a/tests/android_androidqf/test_packages.py +++ b/tests/android_androidqf/test_packages.py @@ -8,7 +8,7 @@ from pathlib import Path import pytest -from mvt.android.modules.androidqf.packages import Packages +from mvt.android.modules.androidqf.aqf_packages import AQFPackages from mvt.common.module import run_module from ..utils import get_android_androidqf, list_files @@ -31,8 +31,8 @@ def file_list(data_path): @pytest.fixture() def module(parent_data_path, file_list): - m = Packages(target_path=parent_data_path, log=logging) - m.from_folder(parent_data_path, file_list) + m = AQFPackages(target_path=parent_data_path, log=logging) + m.from_dir(parent_data_path, file_list) return m diff --git a/tests/android_androidqf/test_processes.py b/tests/android_androidqf/test_processes.py index 8aacb0b..bcd4013 100644 --- a/tests/android_androidqf/test_processes.py +++ b/tests/android_androidqf/test_processes.py @@ -6,7 +6,7 @@ import logging from pathlib import Path -from mvt.android.modules.androidqf.processes import Processes +from mvt.android.modules.androidqf.aqf_processes import AQFProcesses from mvt.common.module import run_module from ..utils import get_android_androidqf, list_files @@ -15,10 +15,10 @@ from ..utils import get_android_androidqf, list_files class TestAndroidqfProcessesAnalysis: def test_androidqf_processes(self): data_path = get_android_androidqf() - m = Processes(target_path=data_path, log=logging) + m = AQFProcesses(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 15 assert len(m.timeline) == 0 diff --git a/tests/android_androidqf/test_root_binaries.py b/tests/android_androidqf/test_root_binaries.py index a59ecf5..5d6b770 100644 --- a/tests/android_androidqf/test_root_binaries.py +++ b/tests/android_androidqf/test_root_binaries.py @@ -32,7 +32,7 @@ def file_list(data_path): @pytest.fixture() def module(parent_data_path, file_list): m = RootBinaries(target_path=parent_data_path, log=logging) - m.from_folder(parent_data_path, file_list) + m.from_dir(parent_data_path, file_list) return m @@ -108,7 +108,7 @@ class TestAndroidqfRootBinaries: # Test behavior when no root_binaries.json file is present empty_file_list = [] m = RootBinaries(target_path=parent_data_path, log=logging) - m.from_folder(parent_data_path, empty_file_list) + m.from_dir(parent_data_path, empty_file_list) run_module(m) diff --git a/tests/android_androidqf/test_settings.py b/tests/android_androidqf/test_settings.py index c282901..75527a7 100644 --- a/tests/android_androidqf/test_settings.py +++ b/tests/android_androidqf/test_settings.py @@ -5,7 +5,7 @@ from pathlib import Path -from mvt.android.modules.androidqf.settings import Settings +from mvt.android.modules.androidqf.aqf_settings import AQFSettings from mvt.common.module import run_module from ..utils import get_android_androidqf, list_files @@ -14,10 +14,10 @@ from ..utils import get_android_androidqf, list_files class TestSettingsModule: def test_parsing(self): data_path = get_android_androidqf() - m = Settings(target_path=data_path) + m = AQFSettings(target_path=data_path) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 1 assert "random" in m.results.keys() diff --git a/tests/android_androidqf/test_sms.py b/tests/android_androidqf/test_sms.py index 8bff799..d7433cf 100644 --- a/tests/android_androidqf/test_sms.py +++ b/tests/android_androidqf/test_sms.py @@ -21,7 +21,7 @@ class TestAndroidqfSMSAnalysis: m = SMS(target_path=data_path, log=logging) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 2 assert len(m.timeline) == 0 @@ -36,7 +36,7 @@ class TestAndroidqfSMSAnalysis: ) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 1 @@ -52,7 +52,7 @@ class TestAndroidqfSMSAnalysis: ) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert prompt_mock.call_count == 1 assert len(m.results) == 1 @@ -67,7 +67,7 @@ class TestAndroidqfSMSAnalysis: ) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 0 assert "Invalid backup password" in caplog.text @@ -82,7 +82,7 @@ class TestAndroidqfSMSAnalysis: ) files = list_files(data_path) parent_path = Path(data_path).absolute().parent.as_posix() - m.from_folder(parent_path, files) + m.from_dir(parent_path, files) run_module(m) assert len(m.results) == 0 assert ( diff --git a/tests/android_bugreport/test_bugreport.py b/tests/android_bugreport/test_bugreport.py index 5af58e8..18fa387 100644 --- a/tests/android_bugreport/test_bugreport.py +++ b/tests/android_bugreport/test_bugreport.py @@ -6,9 +6,9 @@ import os from pathlib import Path -from mvt.android.modules.bugreport.appops import Appops -from mvt.android.modules.bugreport.getprop import Getprop -from mvt.android.modules.bugreport.packages import Packages +from mvt.android.modules.bugreport.dumpsys_appops import DumpsysAppops +from mvt.android.modules.bugreport.dumpsys_getprop import DumpsysGetProp +from mvt.android.modules.bugreport.dumpsys_packages import DumpsysPackages from mvt.android.modules.bugreport.tombstones import Tombstones from mvt.common.module import run_module @@ -26,12 +26,12 @@ class TestBugreportAnalysis: folder_files.append( os.path.relpath(os.path.join(root, file_name), parent_path) ) - m.from_folder(fpath, folder_files) + m.from_dir(fpath, folder_files) run_module(m) return m def test_appops_module(self): - m = self.launch_bug_report_module(Appops) + m = self.launch_bug_report_module(DumpsysAppops) assert len(m.results) == 12 assert len(m.timeline) == 16 @@ -42,7 +42,7 @@ class TestBugreportAnalysis: assert len(detected_by_ioc) == 0 def test_packages_module(self): - m = self.launch_bug_report_module(Packages) + m = self.launch_bug_report_module(DumpsysPackages) assert len(m.results) == 2 assert ( m.results[0]["package_name"] @@ -53,7 +53,7 @@ class TestBugreportAnalysis: assert len(m.results[1]["permissions"]) == 32 def test_getprop_module(self): - m = self.launch_bug_report_module(Getprop) + m = self.launch_bug_report_module(DumpsysGetProp) assert len(m.results) == 0 def test_tombstones_modules(self): diff --git a/tests/test_check_android_androidqf.py b/tests/test_check_android_androidqf.py index 167b5b7..c6e4221 100644 --- a/tests/test_check_android_androidqf.py +++ b/tests/test_check_android_androidqf.py @@ -32,7 +32,8 @@ class TestCheckAndroidqfCommand: path = os.path.join(get_artifact_folder(), "androidqf_encrypted") result = runner.invoke(check_androidqf, [path]) - assert prompt_mock.call_count == 1 + # Called twice, once in AnroidQF SMS module and once in Backup SMS module + assert prompt_mock.call_count == 2 assert result.exit_code == 0 def test_check_encrypted_backup_cli(self, mocker):