Add parsed timestamps to alerts

This commit is contained in:
Janik Besendorf
2026-08-14 11:37:38 +02:00
parent 7b64463727
commit 0123bf6e59
36 changed files with 191 additions and 56 deletions
+13 -1
View File
@@ -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
@@ -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
@@ -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
+4 -1
View File
@@ -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
@@ -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:
@@ -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,
)
@@ -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
+4 -1
View File
@@ -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:
@@ -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
+5 -2
View File
@@ -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,
)
+8 -2
View File
@@ -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
+5 -2
View File
@@ -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,
)
+8 -2
View File
@@ -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:
+4 -1
View File
@@ -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):
+5 -2
View File
@@ -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,
)
+4 -1
View File
@@ -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
+4 -4
View File
@@ -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,
)
+5 -2
View File
@@ -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,
)
+4 -1
View File
@@ -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
+4 -1
View File
@@ -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:
+4 -1
View File
@@ -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:
+4 -1
View File
@@ -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:
+5 -2
View File
@@ -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,
)
+13 -5
View File
@@ -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,
)
@@ -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:
@@ -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,
)
+4 -1
View File
@@ -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):
+4 -1
View File
@@ -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:
+4 -1
View File
@@ -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:
+1 -1
View File
@@ -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,
)
+4 -1
View File
@@ -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):
@@ -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:
@@ -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,
)
+4 -1
View File
@@ -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:
@@ -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
+1
View File
@@ -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"]