Compare commits

..

6 Commits
v2.7.0 ... main

Author SHA1 Message Date
besendorf
f2d9f420f2 Detect uninstall and downgrade in battery daily (#736) 2026-03-16 12:32:54 +01:00
github-actions[bot]
e2f8437831 Add new iOS versions and build numbers (#742)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2026-03-05 05:48:15 +01:00
github-actions[bot]
0134bf80d1 Add new iOS versions and build numbers (#739)
Co-authored-by: DonnchaC <DonnchaC@users.noreply.github.com>
2026-02-19 08:47:07 -05:00
Max-RSF
c8f82f796b Add AQF support for bugreport modules (#741) 2026-02-16 17:11:16 +01:00
github-actions[bot]
61947d17af Add new iOS versions and build numbers (#738) 2026-02-04 20:51:11 +01:00
viktor3002
7173e02a6f Check receiver names for IoCs (#721)
* receiver names are checked if a known malicious app id is a substring

* ruff syntax fixes

---------

Co-authored-by: Viktor <vik@tor.me>
Co-authored-by: besendorf <janik@besendorf.org>
2026-01-10 15:24:20 +01:00
5 changed files with 134 additions and 15 deletions

View File

@@ -14,12 +14,23 @@ class DumpsysBatteryDailyArtifact(AndroidArtifact):
""" """
def serialize(self, record: dict) -> Union[dict, list]: def serialize(self, record: dict) -> Union[dict, list]:
action = record.get("action", "update")
package_name = record["package_name"]
vers = record["vers"]
if vers == "0":
data = f"Recorded uninstall of package {package_name} (vers 0)"
elif action == "downgrade":
prev_vers = record.get("previous_vers", "unknown")
data = f"Recorded downgrade of package {package_name} from vers {prev_vers} to vers {vers}"
else:
data = f"Recorded update of package {package_name} with vers {vers}"
return { return {
"timestamp": record["from"], "timestamp": record["from"],
"module": self.__class__.__name__, "module": self.__class__.__name__,
"event": "battery_daily", "event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} " "data": data,
f"with vers {record['vers']}",
} }
def check_indicators(self) -> None: def check_indicators(self) -> None:
@@ -36,6 +47,7 @@ class DumpsysBatteryDailyArtifact(AndroidArtifact):
def parse(self, output: str) -> None: def parse(self, output: str) -> None:
daily = None daily = None
daily_updates = [] daily_updates = []
package_versions = {} # Track package versions to detect downgrades
for line in output.splitlines(): for line in output.splitlines():
if line.startswith(" Daily from "): if line.startswith(" Daily from "):
if len(daily_updates) > 0: if len(daily_updates) > 0:
@@ -64,15 +76,44 @@ class DumpsysBatteryDailyArtifact(AndroidArtifact):
break break
if not already_seen: if not already_seen:
daily_updates.append( update_record = {
{ "action": "update",
"action": "update", "from": daily["from"],
"from": daily["from"], "to": daily["to"],
"to": daily["to"], "package_name": package_name,
"package_name": package_name, "vers": vers_nr,
"vers": vers_nr, }
}
) # Check for uninstall (version 0)
if vers_nr == "0":
self.log.warning(
"Detected uninstall of package %s (vers 0) on %s",
package_name,
daily["from"],
)
# Check for downgrade
elif package_name in package_versions:
try:
current_vers = int(vers_nr)
previous_vers = int(package_versions[package_name])
if current_vers < previous_vers:
update_record["action"] = "downgrade"
update_record["previous_vers"] = str(previous_vers)
self.log.warning(
"Detected downgrade of package %s from vers %d to vers %d on %s",
package_name,
previous_vers,
current_vers,
daily["from"],
)
except ValueError:
# If version numbers aren't integers, skip comparison
pass
# Update tracking dictionary
package_versions[package_name] = vers_nr
daily_updates.append(update_record)
if len(daily_updates) > 0: if len(daily_updates) > 0:
self.results.extend(daily_updates) self.results.extend(daily_updates)

View File

@@ -84,13 +84,17 @@ class BugReportModule(MVTModule):
return self._get_file_content(main_content.decode().strip()) return self._get_file_content(main_content.decode().strip())
except KeyError: except KeyError:
return None return None
else:
dumpstate_logs = self._get_files_by_pattern("dumpState_*.log")
if not dumpstate_logs:
return None
dumpstate_logs = self._get_files_by_pattern("dumpState_*.log")
if dumpstate_logs:
return self._get_file_content(dumpstate_logs[0]) return self._get_file_content(dumpstate_logs[0])
dumpsys_files = self._get_files_by_pattern("*/dumpsys.txt")
if dumpsys_files:
return self._get_file_content(dumpsys_files[0])
return None
def _get_file_modification_time(self, file_path: str) -> dict: def _get_file_modification_time(self, file_path: str) -> dict:
if self.zip_archive: if self.zip_archive:
file_timetuple = self.zip_archive.getinfo(file_path).date_time file_timetuple = self.zip_archive.getinfo(file_path).date_time

View File

@@ -34,6 +34,20 @@ class DumpsysReceivers(DumpsysReceiversArtifact, BugReportModule):
self.results = results if results else {} self.results = results if results else {}
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
receiver_name = self.results[result][0]["receiver"]
# return IoC if the stix2 process name a substring of the receiver name
ioc = self.indicators.check_receiver_prefix(receiver_name)
if ioc:
self.results[result][0]["matched_indicator"] = ioc
self.detected.append(result)
continue
def run(self) -> None: def run(self) -> None:
content = self._get_dumpstate_file() content = self._get_dumpstate_file()
if not content: if not content:

View File

@@ -768,6 +768,30 @@ class Indicators:
return None return None
def check_receiver_prefix(self, receiver_name: str) -> Union[dict, None]:
"""Check the provided receiver name against the list of indicators.
An IoC match is detected when a substring of the receiver matches the indicator
:param app_id: App ID to check against the list of indicators
:type app_id: str
:returns: Indicator details if matched, otherwise None
"""
if not receiver_name:
return None
for ioc in self.get_iocs("app_ids"):
if ioc["value"].lower() in receiver_name.lower():
self.log.warning(
'Found a known suspicious receiver with name "%s" '
'matching indicators from "%s"',
receiver_name,
ioc["name"],
)
return ioc
return None
def check_android_property_name(self, property_name: str) -> Optional[dict]: def check_android_property_name(self, property_name: str) -> Optional[dict]:
"""Check the android property name against the list of indicators. """Check the android property name against the list of indicators.

View File

@@ -631,6 +631,10 @@
"build": "16H81", "build": "16H81",
"version": "12.5.7" "version": "12.5.7"
}, },
{
"version": "12.5.8",
"build": "16H88"
},
{ {
"build": "17A577", "build": "17A577",
"version": "13.0" "version": "13.0"
@@ -899,6 +903,10 @@
"version": "15.8.5", "version": "15.8.5",
"build": "19H394" "build": "19H394"
}, },
{
"version": "15.8.6",
"build": "19H402"
},
{ {
"build": "20A362", "build": "20A362",
"version": "16.0" "version": "16.0"
@@ -1008,6 +1016,10 @@
"version": "16.7.12", "version": "16.7.12",
"build": "20H364" "build": "20H364"
}, },
{
"version": "16.7.14",
"build": "20H370"
},
{ {
"version": "17.0", "version": "17.0",
"build": "21A327" "build": "21A327"
@@ -1164,6 +1176,18 @@
"version": "18.7.3", "version": "18.7.3",
"build": "22H217" "build": "22H217"
}, },
{
"version": "18.7.4",
"build": "22H218"
},
{
"version": "18.7.5",
"build": "22H311"
},
{
"version": "18.7.6",
"build": "22H320"
},
{ {
"version": "26", "version": "26",
"build": "23A341" "build": "23A341"
@@ -1179,5 +1203,17 @@
{ {
"version": "26.2", "version": "26.2",
"build": "23C55" "build": "23C55"
},
{
"version": "26.2.1",
"build": "23C71"
},
{
"version": "26.3",
"build": "23D127"
},
{
"version": "26.3.1",
"build": "23D8133"
} }
] ]