From 0123bf6e59c2dc9db6bff6461a21855c232bd3c2 Mon Sep 17 00:00:00 2001 From: Janik Besendorf Date: Fri, 14 Aug 2026 11:37:38 +0200 Subject: [PATCH] Add parsed timestamps to alerts --- src/mvt/android/artifacts/dumpsys_appops.py | 14 +++++++++++++- .../android/artifacts/dumpsys_battery_daily.py | 5 ++++- .../artifacts/dumpsys_battery_history.py | 5 ++++- src/mvt/android/artifacts/dumpsys_dbinfo.py | 5 ++++- src/mvt/android/artifacts/dumpsys_packages.py | 7 +++++-- src/mvt/android/artifacts/tombstone_crashes.py | 12 +++++++++--- src/mvt/android/modules/androidqf/aqf_files.py | 12 +++++++++--- src/mvt/android/modules/backup/sms.py | 5 ++++- .../modules/backup/configuration_profiles.py | 9 +++++++-- src/mvt/ios/modules/backup/manifest.py | 7 +++++-- src/mvt/ios/modules/backup/profile_events.py | 10 ++++++++-- src/mvt/ios/modules/fs/analytics.py | 7 +++++-- src/mvt/ios/modules/fs/filesystem.py | 10 ++++++++-- src/mvt/ios/modules/fs/safari_favicon.py | 5 ++++- src/mvt/ios/modules/fs/shutdownlog.py | 7 +++++-- src/mvt/ios/modules/fs/webkit_base.py | 5 ++++- src/mvt/ios/modules/mixed/applications.py | 8 ++++---- src/mvt/ios/modules/mixed/calendar.py | 7 +++++-- src/mvt/ios/modules/mixed/chrome_favicon.py | 5 ++++- src/mvt/ios/modules/mixed/chrome_history.py | 5 ++++- src/mvt/ios/modules/mixed/firefox_favicon.py | 5 ++++- src/mvt/ios/modules/mixed/firefox_history.py | 5 ++++- src/mvt/ios/modules/mixed/idstatuscache.py | 7 +++++-- src/mvt/ios/modules/mixed/locationd.py | 18 +++++++++++++----- .../ios/modules/mixed/osanalytics_addaily.py | 5 ++++- .../ios/modules/mixed/safari_browserstate.py | 7 +++++-- src/mvt/ios/modules/mixed/safari_history.py | 5 ++++- src/mvt/ios/modules/mixed/shortcuts.py | 5 ++++- src/mvt/ios/modules/mixed/sms.py | 5 ++++- src/mvt/ios/modules/mixed/sms_attachments.py | 2 +- src/mvt/ios/modules/mixed/tcc.py | 5 ++++- .../mixed/webkit_resource_load_statistics.py | 5 ++++- .../mixed/webkit_session_resource_log.py | 11 +++++++++-- src/mvt/ios/modules/mixed/whatsapp.py | 5 ++++- tests/android/test_artifact_dumpsys_appops.py | 6 ++++++ tests/ios_backup/test_calendar.py | 1 + 36 files changed, 191 insertions(+), 56 deletions(-) diff --git a/src/mvt/android/artifacts/dumpsys_appops.py b/src/mvt/android/artifacts/dumpsys_appops.py index 492eac7..12a596a 100644 --- a/src/mvt/android/artifacts/dumpsys_appops.py +++ b/src/mvt/android/artifacts/dumpsys_appops.py @@ -42,11 +42,23 @@ class DumpsysAppopsArtifact(AndroidArtifact): def check_indicators(self) -> None: for result in self.results: + event_time = max( + ( + entry["timestamp"] + for permission in result["permissions"] + for entry in permission.get("entries", []) + if entry.get("timestamp") + ), + default="", + ) if self.indicators: ioc_match = self.indicators.check_app_id(result.get("package_name")) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + event_time, + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/android/artifacts/dumpsys_battery_daily.py b/src/mvt/android/artifacts/dumpsys_battery_daily.py index c0f2c5e..045015e 100644 --- a/src/mvt/android/artifacts/dumpsys_battery_daily.py +++ b/src/mvt/android/artifacts/dumpsys_battery_daily.py @@ -43,7 +43,10 @@ class DumpsysBatteryDailyArtifact(AndroidArtifact): ioc_match = self.indicators.check_app_id(result["package_name"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("period_start") or "", + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/android/artifacts/dumpsys_battery_history.py b/src/mvt/android/artifacts/dumpsys_battery_history.py index 77d9d3c..937f139 100644 --- a/src/mvt/android/artifacts/dumpsys_battery_history.py +++ b/src/mvt/android/artifacts/dumpsys_battery_history.py @@ -19,7 +19,10 @@ class DumpsysBatteryHistoryArtifact(AndroidArtifact): ioc_match = self.indicators.check_app_id(result["package_name"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/android/artifacts/dumpsys_dbinfo.py b/src/mvt/android/artifacts/dumpsys_dbinfo.py index 050582d..9b13ebb 100644 --- a/src/mvt/android/artifacts/dumpsys_dbinfo.py +++ b/src/mvt/android/artifacts/dumpsys_dbinfo.py @@ -23,7 +23,10 @@ class DumpsysDBInfoArtifact(AndroidArtifact): ioc_match = self.indicators.check_app_id(part) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/android/artifacts/dumpsys_packages.py b/src/mvt/android/artifacts/dumpsys_packages.py index 81da22e..53dc771 100644 --- a/src/mvt/android/artifacts/dumpsys_packages.py +++ b/src/mvt/android/artifacts/dumpsys_packages.py @@ -22,7 +22,7 @@ class DumpsysPackagesArtifact(AndroidArtifact): alerted_root_packages.add(result["package_name"]) self.alertstore.medium( f'Found an installed package related to rooting/jailbreaking: "{result["package_name"]}"', - "", + result.get("timestamp") or "", result, ) continue @@ -33,7 +33,10 @@ class DumpsysPackagesArtifact(AndroidArtifact): ioc_match = self.indicators.check_app_id(result.get("package_name", "")) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) def serialize(self, record: ModuleAtomicResult) -> ModuleSerializedResult: diff --git a/src/mvt/android/artifacts/tombstone_crashes.py b/src/mvt/android/artifacts/tombstone_crashes.py index bcfb389..22a69e5 100644 --- a/src/mvt/android/artifacts/tombstone_crashes.py +++ b/src/mvt/android/artifacts/tombstone_crashes.py @@ -99,7 +99,10 @@ class TombstoneCrashArtifact(AndroidArtifact): ioc_match = self.indicators.check_process(result["process_name"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -108,7 +111,10 @@ class TombstoneCrashArtifact(AndroidArtifact): ioc_match = self.indicators.check_process(command_name) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -123,7 +129,7 @@ class TombstoneCrashArtifact(AndroidArtifact): f"Potentially suspicious crash in process '{result['process_name']}' " f"running as UID '{result['uid']}' in tombstone '{result['file_name']}' at {result['timestamp']}" ), - "", + result.get("timestamp") or "", result, ) diff --git a/src/mvt/android/modules/androidqf/aqf_files.py b/src/mvt/android/modules/androidqf/aqf_files.py index de9b44a..ad5aa19 100644 --- a/src/mvt/android/modules/androidqf/aqf_files.py +++ b/src/mvt/android/modules/androidqf/aqf_files.py @@ -90,7 +90,10 @@ class AQFFiles(AndroidQFModule): ioc_match = self.indicators.check_file_path(result["path"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("modified_time") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -105,7 +108,7 @@ class AQFFiles(AndroidQFModule): file_type = "executable " msg = f'Found {file_type}file at suspicious path "{result["path"]}"' - self.alertstore.high(msg, "", result) + self.alertstore.high(msg, result.get("modified_time") or "", result) for hash_key in ("sha256", "sha1", "md5"): file_hash = result.get(hash_key, "") @@ -114,7 +117,10 @@ class AQFFiles(AndroidQFModule): ioc_match = self.indicators.check_file_hash(file_hash) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("modified_time") or "", + result, + matched_indicator=ioc_match.ioc, ) break diff --git a/src/mvt/android/modules/backup/sms.py b/src/mvt/android/modules/backup/sms.py index 1c73ea2..d66b90e 100644 --- a/src/mvt/android/modules/backup/sms.py +++ b/src/mvt/android/modules/backup/sms.py @@ -54,7 +54,10 @@ class SMS(BackupModule): ): if ioc_match: self.alertstore.critical( - ioc_match.message, "", message, matched_indicator=ioc_match.ioc + ioc_match.message, + message.get("isodate") or "", + message, + matched_indicator=ioc_match.ioc, ) def collect_url_results(self) -> None: diff --git a/src/mvt/ios/modules/backup/configuration_profiles.py b/src/mvt/ios/modules/backup/configuration_profiles.py index 9c0343e..f292eb5 100644 --- a/src/mvt/ios/modules/backup/configuration_profiles.py +++ b/src/mvt/ios/modules/backup/configuration_profiles.py @@ -74,7 +74,10 @@ class ConfigurationProfiles(IOSExtraction): if ioc_match: warning_message = f'Found a known malicious configuration profile "{result["plist"]["PayloadDisplayName"]}" with UUID "{result["plist"]["PayloadUUID"]}"' self.alertstore.critical( - warning_message, "", result, matched_indicator=ioc_match.ioc + warning_message, + result.get("install_date") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -82,7 +85,9 @@ class ConfigurationProfiles(IOSExtraction): # to hide notifications. if payload_content["PayloadType"] in ["com.apple.notificationsettings"]: warning_message = f'Found a potentially suspicious configuration profile "{result["plist"]["PayloadDisplayName"]}" with payload type {payload_content["PayloadType"]}' - self.alertstore.medium(warning_message, "", result) + self.alertstore.medium( + warning_message, result.get("install_date") or "", result + ) continue @staticmethod diff --git a/src/mvt/ios/modules/backup/manifest.py b/src/mvt/ios/modules/backup/manifest.py index cc74cbb..8b4bab6 100644 --- a/src/mvt/ios/modules/backup/manifest.py +++ b/src/mvt/ios/modules/backup/manifest.py @@ -104,7 +104,10 @@ class Manifest(IOSExtraction): ioc_match = self.indicators.check_file_path("/" + result["relative_path"]) if ioc_match: self.alertstore.high( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("modified") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -120,7 +123,7 @@ class Manifest(IOSExtraction): if ioc_match: self.alertstore.high( f'Found mention of domain "{ioc_match.ioc.value}" in a backup file with path: {rel_path}', - "", + result.get("modified") or "", result, matched_indicator=ioc_match.ioc, ) diff --git a/src/mvt/ios/modules/backup/profile_events.py b/src/mvt/ios/modules/backup/profile_events.py index 1fc6d7e..65fcceb 100644 --- a/src/mvt/ios/modules/backup/profile_events.py +++ b/src/mvt/ios/modules/backup/profile_events.py @@ -67,14 +67,20 @@ class ProfileEvents(IOSExtraction): ioc_match = self.indicators.check_process(result.get("process") or "") if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue ioc_match = self.indicators.check_profile(result.get("profile_id") or "") if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) @staticmethod diff --git a/src/mvt/ios/modules/fs/analytics.py b/src/mvt/ios/modules/fs/analytics.py index 2bf29e4..47478ab 100644 --- a/src/mvt/ios/modules/fs/analytics.py +++ b/src/mvt/ios/modules/fs/analytics.py @@ -68,7 +68,10 @@ class Analytics(IOSExtraction): warning_message = f'Found mention of a malicious process "{value}" in {result["artifact"]} file at {result["isodate"]}' new_result = copy.copy(result) self.alertstore.critical( - warning_message, "", new_result, matched_indicator=ioc_match.ioc + warning_message, + result.get("isodate") or "", + new_result, + matched_indicator=ioc_match.ioc, ) continue @@ -77,7 +80,7 @@ class Analytics(IOSExtraction): new_result = copy.copy(result) self.alertstore.critical( ioc_match.message, - "", + result.get("isodate") or "", new_result, matched_indicator=ioc_match.ioc, ) diff --git a/src/mvt/ios/modules/fs/filesystem.py b/src/mvt/ios/modules/fs/filesystem.py index 563d039..a1c656e 100644 --- a/src/mvt/ios/modules/fs/filesystem.py +++ b/src/mvt/ios/modules/fs/filesystem.py @@ -59,7 +59,10 @@ class Filesystem(IOSExtraction): ioc_match = self.indicators.check_file_path(result["path"]) if ioc_match: self.alertstore.high( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("modified") or "", + result, + matched_indicator=ioc_match.ioc, ) # If we are instructed to run fast, we skip the rest. @@ -69,7 +72,10 @@ class Filesystem(IOSExtraction): ioc_match = self.indicators.check_file_path_process(result["path"]) if ioc_match: self.alertstore.high( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("modified") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/fs/safari_favicon.py b/src/mvt/ios/modules/fs/safari_favicon.py index a9c0b65..928a824 100644 --- a/src/mvt/ios/modules/fs/safari_favicon.py +++ b/src/mvt/ios/modules/fs/safari_favicon.py @@ -63,7 +63,10 @@ class SafariFavicon(IOSExtraction): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def _process_favicon_db(self, file_path): diff --git a/src/mvt/ios/modules/fs/shutdownlog.py b/src/mvt/ios/modules/fs/shutdownlog.py index 3136177..501161f 100644 --- a/src/mvt/ios/modules/fs/shutdownlog.py +++ b/src/mvt/ios/modules/fs/shutdownlog.py @@ -60,7 +60,10 @@ class ShutdownLog(IOSExtraction): ioc_match = self.indicators.check_file_path(result["client"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -69,7 +72,7 @@ class ShutdownLog(IOSExtraction): if ioc.value in parts: self.alertstore.critical( f'Found mention of a known malicious process "{ioc.value}" in shutdown.log', - "", + result.get("isodate") or "", result, matched_indicator=ioc, ) diff --git a/src/mvt/ios/modules/fs/webkit_base.py b/src/mvt/ios/modules/fs/webkit_base.py index 5cccfd5..e07a796 100644 --- a/src/mvt/ios/modules/fs/webkit_base.py +++ b/src/mvt/ios/modules/fs/webkit_base.py @@ -21,7 +21,10 @@ class WebkitBase(IOSExtraction): ioc_match = self.indicators.check_url(result["url"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/ios/modules/mixed/applications.py b/src/mvt/ios/modules/mixed/applications.py index 45b88cd..874acfe 100644 --- a/src/mvt/ios/modules/mixed/applications.py +++ b/src/mvt/ios/modules/mixed/applications.py @@ -68,7 +68,7 @@ class Applications(IOSExtraction): if "softwareVersionBundleId" not in result: self.alertstore.medium( "Suspicious application identified without softwareVersionBundleId", - "", + result.get("isodate") or "", result, ) continue @@ -79,7 +79,7 @@ class Applications(IOSExtraction): if ioc_match: self.alertstore.critical( f"Malicious application {result['softwareVersionBundleId']} identified", - "", + result.get("isodate") or "", result, matched_indicator=ioc_match.ioc, ) @@ -91,7 +91,7 @@ class Applications(IOSExtraction): if ioc_match: self.alertstore.critical( f"Malicious application {result['softwareVersionBundleId']} identified", - "", + result.get("isodate") or "", result, matched_indicator=ioc_match.ioc, ) @@ -104,7 +104,7 @@ class Applications(IOSExtraction): ): self.alertstore.medium( f"Suspicious app not installed from the App Store or MDM: {result['softwareVersionBundleId']}", - "", + result.get("isodate") or "", result, ) diff --git a/src/mvt/ios/modules/mixed/calendar.py b/src/mvt/ios/modules/mixed/calendar.py index 40f8ad9..459e10d 100644 --- a/src/mvt/ios/modules/mixed/calendar.py +++ b/src/mvt/ios/modules/mixed/calendar.py @@ -72,7 +72,10 @@ class Calendar(IOSExtraction): ioc_match = self.indicators.check_email(result["participant_email"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("start_date") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -80,7 +83,7 @@ class Calendar(IOSExtraction): if result["summary"] == "Meeting" and result["description"] == "Notes": self.alertstore.high( f"Potential Quadream exploit event identified: {result['uuid']}", - "", + result.get("start_date") or "", result, ) diff --git a/src/mvt/ios/modules/mixed/chrome_favicon.py b/src/mvt/ios/modules/mixed/chrome_favicon.py index af3df2e..0ed9440 100644 --- a/src/mvt/ios/modules/mixed/chrome_favicon.py +++ b/src/mvt/ios/modules/mixed/chrome_favicon.py @@ -61,7 +61,10 @@ class ChromeFavicon(IOSExtraction): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) continue diff --git a/src/mvt/ios/modules/mixed/chrome_history.py b/src/mvt/ios/modules/mixed/chrome_history.py index 4c5afae..e480a3b 100644 --- a/src/mvt/ios/modules/mixed/chrome_history.py +++ b/src/mvt/ios/modules/mixed/chrome_history.py @@ -62,7 +62,10 @@ class ChromeHistory(IOSExtraction): ioc_match = self.indicators.check_url(result["url"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/mixed/firefox_favicon.py b/src/mvt/ios/modules/mixed/firefox_favicon.py index ad92e73..12dc84e 100644 --- a/src/mvt/ios/modules/mixed/firefox_favicon.py +++ b/src/mvt/ios/modules/mixed/firefox_favicon.py @@ -64,7 +64,10 @@ class FirefoxFavicon(IOSExtraction): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/mixed/firefox_history.py b/src/mvt/ios/modules/mixed/firefox_history.py index 7ab1eba..16e3b48 100644 --- a/src/mvt/ios/modules/mixed/firefox_history.py +++ b/src/mvt/ios/modules/mixed/firefox_history.py @@ -64,7 +64,10 @@ class FirefoxHistory(IOSExtraction): ioc_match = self.indicators.check_url(result["url"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/mixed/idstatuscache.py b/src/mvt/ios/modules/mixed/idstatuscache.py index c841f1e..73087d9 100644 --- a/src/mvt/ios/modules/mixed/idstatuscache.py +++ b/src/mvt/ios/modules/mixed/idstatuscache.py @@ -66,14 +66,17 @@ class IDStatusCache(IOSExtraction): ioc_match = self.indicators.check_email(email) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) continue if "\\x00\\x00" in result.get("user", ""): self.alertstore.high( f"Found an ID Status Cache entry with suspicious patterns: {result.get('user')}", - "", + result.get("isodate") or "", result, ) diff --git a/src/mvt/ios/modules/mixed/locationd.py b/src/mvt/ios/modules/mixed/locationd.py index 86de85b..6eabaf0 100644 --- a/src/mvt/ios/modules/mixed/locationd.py +++ b/src/mvt/ios/modules/mixed/locationd.py @@ -79,6 +79,14 @@ class LocationdClients(IOSExtraction): return for result in self.results: + event_time = next( + ( + result[timestamp] + for timestamp in self.timestamps + if result.get(timestamp) + ), + "", + ) parts = result["package"].split("/") proc_name = parts[len(parts) - 1] @@ -86,7 +94,7 @@ class LocationdClients(IOSExtraction): if ioc_match: self.alertstore.high( f"Found a suspicious process name in LocationD entry {result['package']}", - "", + event_time, result, matched_indicator=ioc_match.ioc, ) @@ -97,7 +105,7 @@ class LocationdClients(IOSExtraction): if ioc_match: self.alertstore.high( f"Found a suspicious process name in LocationD entry {result['package']}", - "", + event_time, result, matched_indicator=ioc_match.ioc, ) @@ -107,7 +115,7 @@ class LocationdClients(IOSExtraction): if ioc_match: self.alertstore.high( f"Found a known malicious domain in LocationD entry {result['package']}", - "", + event_time, result, matched_indicator=ioc_match.ioc, ) @@ -118,7 +126,7 @@ class LocationdClients(IOSExtraction): if ioc_match: self.alertstore.high( f"Found a suspicious file path in LocationD entry {result['Executable']}", - "", + event_time, result, matched_indicator=ioc_match.ioc, ) @@ -133,7 +141,7 @@ class LocationdClients(IOSExtraction): if ioc_match: self.alertstore.high( f"Found a suspicious file path in LocationD entry {result['Registered']}", - "", + event_time, result, matched_indicator=ioc_match.ioc, ) diff --git a/src/mvt/ios/modules/mixed/osanalytics_addaily.py b/src/mvt/ios/modules/mixed/osanalytics_addaily.py index 8a5db3f..eb17b4b 100644 --- a/src/mvt/ios/modules/mixed/osanalytics_addaily.py +++ b/src/mvt/ios/modules/mixed/osanalytics_addaily.py @@ -65,7 +65,10 @@ class OSAnalyticsADDaily(IOSExtraction): ioc_match = self.indicators.check_process(result["package"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("ts") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/mixed/safari_browserstate.py b/src/mvt/ios/modules/mixed/safari_browserstate.py index e67b7d3..e8fdd09 100644 --- a/src/mvt/ios/modules/mixed/safari_browserstate.py +++ b/src/mvt/ios/modules/mixed/safari_browserstate.py @@ -73,7 +73,10 @@ class SafariBrowserState(IOSExtraction): ioc_match = self.indicators.check_url(result["tab_url"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("last_viewed_timestamp") or "", + result, + matched_indicator=ioc_match.ioc, ) continue @@ -86,7 +89,7 @@ class SafariBrowserState(IOSExtraction): if ioc_match: self.alertstore.critical( ioc_match.message, - "", + result.get("last_viewed_timestamp") or "", result, matched_indicator=ioc_match.ioc, ) diff --git a/src/mvt/ios/modules/mixed/safari_history.py b/src/mvt/ios/modules/mixed/safari_history.py index 4a50f02..290364c 100644 --- a/src/mvt/ios/modules/mixed/safari_history.py +++ b/src/mvt/ios/modules/mixed/safari_history.py @@ -126,7 +126,10 @@ class SafariHistory(IOSExtraction): ioc_match = self.indicators.check_url(result["url"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def _process_history_db(self, history_path): diff --git a/src/mvt/ios/modules/mixed/shortcuts.py b/src/mvt/ios/modules/mixed/shortcuts.py index eb749ee..8703d2c 100644 --- a/src/mvt/ios/modules/mixed/shortcuts.py +++ b/src/mvt/ios/modules/mixed/shortcuts.py @@ -82,7 +82,10 @@ class Shortcuts(IOSExtraction): ): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def run(self) -> None: diff --git a/src/mvt/ios/modules/mixed/sms.py b/src/mvt/ios/modules/mixed/sms.py index d1afd06..8a037c3 100644 --- a/src/mvt/ios/modules/mixed/sms.py +++ b/src/mvt/ios/modules/mixed/sms.py @@ -98,7 +98,10 @@ class SMS(IOSExtraction): ): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def collect_url_results(self) -> None: diff --git a/src/mvt/ios/modules/mixed/sms_attachments.py b/src/mvt/ios/modules/mixed/sms_attachments.py index d02bd27..25bacd8 100644 --- a/src/mvt/ios/modules/mixed/sms_attachments.py +++ b/src/mvt/ios/modules/mixed/sms_attachments.py @@ -67,7 +67,7 @@ class SMSAttachments(IOSExtraction): if ioc_match: self.alertstore.high( ioc_match.message, - "", + attachment.get("isodate") or "", attachment, matched_indicator=ioc_match.ioc, ) diff --git a/src/mvt/ios/modules/mixed/tcc.py b/src/mvt/ios/modules/mixed/tcc.py index ed878c5..2248bb1 100644 --- a/src/mvt/ios/modules/mixed/tcc.py +++ b/src/mvt/ios/modules/mixed/tcc.py @@ -97,7 +97,10 @@ class TCC(IOSExtraction): ioc_match = self.indicators.check_process(result["client"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("last_modified") or "", + result, + matched_indicator=ioc_match.ioc, ) def process_db(self, file_path): diff --git a/src/mvt/ios/modules/mixed/webkit_resource_load_statistics.py b/src/mvt/ios/modules/mixed/webkit_resource_load_statistics.py index ef7b58f..df37de5 100644 --- a/src/mvt/ios/modules/mixed/webkit_resource_load_statistics.py +++ b/src/mvt/ios/modules/mixed/webkit_resource_load_statistics.py @@ -69,7 +69,10 @@ class WebkitResourceLoadStatistics(IOSExtraction): ioc_match = self.indicators.check_url(result["registrable_domain"]) if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("last_seen_isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def _process_observations_db(self, db_path: str, domain: str, path: str) -> None: diff --git a/src/mvt/ios/modules/mixed/webkit_session_resource_log.py b/src/mvt/ios/modules/mixed/webkit_session_resource_log.py index eb34311..39fa039 100644 --- a/src/mvt/ios/modules/mixed/webkit_session_resource_log.py +++ b/src/mvt/ios/modules/mixed/webkit_session_resource_log.py @@ -105,7 +105,12 @@ class WebkitSessionResourceLog(IOSExtraction): if ioc_match: entry, source_domains, destination_domains = record self.alertstore.critical( - ioc_match.message, "", entry, matched_indicator=ioc_match.ioc + ioc_match.message, + entry.get("last_seen") + or entry.get("most_recent_interaction") + or "", + entry, + matched_indicator=ioc_match.ioc, ) redirect_path = "" @@ -129,7 +134,9 @@ class WebkitSessionResourceLog(IOSExtraction): self.alertstore.high( f"Found HTTP redirect between suspicious domains: {redirect_path}", - "", + entry.get("last_seen") + or entry.get("most_recent_interaction") + or "", entry, ) diff --git a/src/mvt/ios/modules/mixed/whatsapp.py b/src/mvt/ios/modules/mixed/whatsapp.py index 0a80aad..449f08f 100644 --- a/src/mvt/ios/modules/mixed/whatsapp.py +++ b/src/mvt/ios/modules/mixed/whatsapp.py @@ -67,7 +67,10 @@ class Whatsapp(IOSExtraction): ): if ioc_match: self.alertstore.critical( - ioc_match.message, "", result, matched_indicator=ioc_match.ioc + ioc_match.message, + result.get("isodate") or "", + result, + matched_indicator=ioc_match.ioc, ) def collect_url_results(self) -> None: diff --git a/tests/android/test_artifact_dumpsys_appops.py b/tests/android/test_artifact_dumpsys_appops.py index 862b872..50da897 100644 --- a/tests/android/test_artifact_dumpsys_appops.py +++ b/tests/android/test_artifact_dumpsys_appops.py @@ -63,4 +63,10 @@ class TestDumpsysAppopsArtifact: assert len(da.alertstore.alerts) == 3 assert len(detected_by_ioc) == 1 assert detected_by_ioc[0].matched_indicator is not None + assert detected_by_ioc[0].event_time == max( + entry["timestamp"] + for permission in detected_by_ioc[0].event["permissions"] + for entry in permission.get("entries", []) + if entry.get("timestamp") + ) assert len(detected_by_permission_heuristic) == 2 diff --git a/tests/ios_backup/test_calendar.py b/tests/ios_backup/test_calendar.py index 5f1035d..1b288a2 100644 --- a/tests/ios_backup/test_calendar.py +++ b/tests/ios_backup/test_calendar.py @@ -44,3 +44,4 @@ class TestCalendarModule: assert len(m.results) == 1 assert len(m.timeline) == 4 assert len(m.alertstore.alerts) == 1 + assert m.alertstore.alerts[0].event_time == m.results[0]["start_date"]