diff --git a/src/mvt/ios/modules/fs/shutdownlog.py b/src/mvt/ios/modules/fs/shutdownlog.py index 6c1fca7..3136177 100644 --- a/src/mvt/ios/modules/fs/shutdownlog.py +++ b/src/mvt/ios/modules/fs/shutdownlog.py @@ -6,6 +6,7 @@ import logging from typing import Optional +from mvt.common.module import DatabaseNotFoundError from mvt.common.module_types import ( ModuleAtomicResult, ModuleResults, @@ -17,6 +18,7 @@ from ..base import IOSExtraction SHUTDOWN_LOG_PATH = [ "private/var/db/diagnostics/shutdown.log", + "private/var/db/diagnostics/shutdown.*.log", ] @@ -132,10 +134,17 @@ class ShutdownLog(IOSExtraction): self.results = sorted(self.results, key=lambda entry: entry["isodate"]) def run(self) -> None: - self._find_ios_database(root_paths=SHUTDOWN_LOG_PATH) - self.log.info("Found shutdown log at path: %s", self.file_path) + if self.file_path: + shutdown_log_paths = [self.file_path] + else: + shutdown_log_paths = sorted( + self._get_fs_files_from_patterns(SHUTDOWN_LOG_PATH) + ) - if not self.file_path: - return - with open(self.file_path, "r", encoding="utf-8") as handle: - self.process_shutdownlog(handle.read()) + if not shutdown_log_paths: + raise DatabaseNotFoundError("unable to find any shutdown log files") + + for shutdown_log_path in shutdown_log_paths: + self.log.info("Found shutdown log at path: %s", shutdown_log_path) + with open(shutdown_log_path, "r", encoding="utf-8") as handle: + self.process_shutdownlog(handle.read()) diff --git a/tests/conftest.py b/tests/conftest.py index fb5cabf..c89f629 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -35,6 +35,7 @@ def indicators_factory(indicator_file): domains=[], emails=[], file_names=[], + file_paths=[], processes=[], app_ids=[], app_cert_hashes=[], @@ -47,6 +48,7 @@ def indicators_factory(indicator_file): ind.ioc_collections[0]["domains"].extend(domains) ind.ioc_collections[0]["emails"].extend(emails) ind.ioc_collections[0]["file_names"].extend(file_names) + ind.ioc_collections[0]["file_paths"].extend(file_paths) ind.ioc_collections[0]["processes"].extend(processes) ind.ioc_collections[0]["app_ids"].extend(app_ids) ind.ioc_collections[0]["android_property_names"].extend(android_property_names) diff --git a/tests/ios_fs/test_shutdownlog.py b/tests/ios_fs/test_shutdownlog.py new file mode 100644 index 0000000..e3a5016 --- /dev/null +++ b/tests/ios_fs/test_shutdownlog.py @@ -0,0 +1,59 @@ +# 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 mvt.common.module import run_module +from mvt.ios.modules.fs.shutdownlog import ShutdownLog + + +def _shutdown_log_entry(pid: int, client: str, timestamp: int) -> str: + return ( + f"remaining client pid: {pid} ({client})\n" + f"SIGTERM: [{timestamp}]\n" + ) + + +class TestShutdownLog: + def test_discovers_rotated_shutdown_logs(self, tmp_path): + diagnostics_path = tmp_path / "private/var/db/diagnostics" + diagnostics_path.mkdir(parents=True) + (diagnostics_path / "shutdown.log").write_text( + _shutdown_log_entry(100, "/usr/libexec/first", 1_700_000_000), + encoding="utf-8", + ) + (diagnostics_path / "shutdown.0.log").write_text( + _shutdown_log_entry(200, "/usr/libexec/second", 1_700_000_001), + encoding="utf-8", + ) + + module = ShutdownLog(target_path=str(tmp_path)) + run_module(module) + + assert {result["client"] for result in module.results} == { + "/usr/libexec/first", + "/usr/libexec/second", + } + + def test_file_path_indicator_matches_client_with_trailing_uuid( + self, indicators_factory + ): + executable_path = "/usr/sbin/filecoordinationd" + client = f"{executable_path}/123e4567-e89b-12d3-a456-426614174000" + module = ShutdownLog( + results=[ + { + "isodate": "2023-11-14 22:13:20.000000", + "pid": "100", + "client": client, + "delay": 0.0, + "times_delayed": 0, + } + ] + ) + module.indicators = indicators_factory(file_paths=[executable_path]) + + module.check_indicators() + + assert len(module.alertstore.alerts) == 1 + assert module.alertstore.alerts[0].matched_indicator.value == executable_path