Refactor DumpsysBatteryDaily module and add related artifact

This commit is contained in:
tek 2023-08-04 16:17:52 +02:00
parent b259db30f8
commit 7e0e071c5d
11 changed files with 330 additions and 122 deletions

View File

@ -0,0 +1,78 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 Claudio Guarnieri.
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
from typing import Union
from .artifact import AndroidArtifact
class DumpsysBatteryDailyArtifact(AndroidArtifact):
"""
Parser for dumpsys dattery daily updates.
"""
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_app_id(result["package_name"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
def parse(self, output: str) -> None:
daily = None
daily_updates = []
for line in output.splitlines():
if line.startswith(" Daily from "):
if len(daily_updates) > 0:
self.results.extend(daily_updates)
daily_updates = []
timeframe = line[13:].strip()
date_from, date_to = timeframe.strip(":").split(" to ", 1)
daily = {"from": date_from[0:10], "to": date_to[0:10]}
continue
if not daily:
continue
if not line.strip().startswith("Update "):
continue
line = line.strip().replace("Update ", "")
package_name, vers = line.split(" ", 1)
vers_nr = vers.split("=", 1)[1]
already_seen = False
for update in daily_updates:
if package_name == update["package_name"] and vers_nr == update["vers"]:
already_seen = True
break
if not already_seen:
daily_updates.append(
{
"action": "update",
"from": daily["from"],
"to": daily["to"],
"package_name": package_name,
"vers": vers_nr,
}
)
if len(daily_updates) > 0:
self.results.extend(daily_updates)

View File

@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_daily
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import AndroidExtraction
class DumpsysBatteryDaily(AndroidExtraction):
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidExtraction):
"""This module extracts records from battery daily updates."""
def __init__(
@ -32,32 +32,12 @@ class DumpsysBatteryDaily(AndroidExtraction):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_app_id(result["package_name"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys batterystats --daily")
self._adb_disconnect()
self.results = parse_dumpsys_battery_daily(output)
self.parse(output)
self.log.info(
"Extracted %d records from battery daily stats", len(self.results)

View File

@ -0,0 +1,46 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 Claudio Guarnieri.
# 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))

View File

@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_daily
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import BugReportModule
class BatteryDaily(BugReportModule):
class BatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule):
"""This module extracts records from battery daily updates."""
def __init__(
@ -32,26 +32,6 @@ class BatteryDaily(BugReportModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
def check_indicators(self) -> None:
if not self.indicators:
return
for result in self.results:
ioc = self.indicators.check_app_id(result["package_name"])
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
def run(self) -> None:
content = self._get_dumpstate_file()
if not content:
@ -61,30 +41,9 @@ class BatteryDaily(BugReportModule):
)
return
lines = []
in_batterystats = False
in_daily = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE batterystats:":
in_batterystats = True
continue
if not in_batterystats:
continue
if line.strip() == "Daily stats:":
lines.append(line)
in_daily = True
continue
if not in_daily:
continue
if line.strip() == "":
break
lines.append(line)
self.results = parse_dumpsys_battery_daily("\n".join(lines))
dumpsys_section = self.extract_dumpsys_section(
content.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
self.parse(dumpsys_section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))

View File

@ -4,7 +4,6 @@
# https://license.mvt.re/1.1/
from .dumpsys import (
parse_dumpsys_battery_daily,
parse_dumpsys_battery_history,
parse_dumpsys_receiver_resolver_table,
)

View File

@ -7,54 +7,6 @@ import re
from typing import Any, Dict, List
def parse_dumpsys_battery_daily(output: str) -> list:
results = []
daily = None
daily_updates = []
for line in output.splitlines():
if line.startswith(" Daily from "):
if len(daily_updates) > 0:
results.extend(daily_updates)
daily_updates = []
timeframe = line[13:].strip()
date_from, date_to = timeframe.strip(":").split(" to ", 1)
daily = {"from": date_from[0:10], "to": date_to[0:10]}
continue
if not daily:
continue
if not line.strip().startswith("Update "):
continue
line = line.strip().replace("Update ", "")
package_name, vers = line.split(" ", 1)
vers_nr = vers.split("=", 1)[1]
already_seen = False
for update in daily_updates:
if package_name == update["package_name"] and vers_nr == update["vers"]:
already_seen = True
break
if not already_seen:
daily_updates.append(
{
"action": "update",
"from": daily["from"],
"to": daily["to"],
"package_name": package_name,
"vers": vers_nr,
}
)
if len(daily_updates) > 0:
results.extend(daily_updates)
return results
def parse_dumpsys_battery_history(output: str) -> List[Dict[str, Any]]:
results = []

View File

@ -0,0 +1,37 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 Claudio Guarnieri.
# 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 mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysBatteryDailyArtifact:
def test_parsing(self):
dba = DumpsysBatteryDailyArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
assert len(dba.results) == 0
dba.parse(data)
assert len(dba.results) == 3
def test_ioc_check(self, indicator_file):
dba = DumpsysBatteryDailyArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
dba.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.facebook.system")
dba.indicators = ind
assert len(dba.detected) == 0
dba.check_indicators()
assert len(dba.detected) == 1

View File

@ -0,0 +1,24 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 Claudio Guarnieri.
# 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

View File

@ -9,3 +9,52 @@ Battery History (0% used, 2720 used of 4096KB, 31 strings using 2694):
+2d23h22m24s588ms (2) 079 +usb_data +top=u0a44:"com.sec.android.app.launcher"
Daily stats:
Current start time: 2022-08-17-01-15-45
Next min deadline: 2022-08-18-01-00-00
Next max deadline: 2022-08-18-03-00-00
Current daily discharge step durations:
#0: +3h32m16s12ms to 96 (power-save-off)
#1: +2h44m44s14ms to 97 (screen-off, power-save-off, device-idle-on)
#2: +2h0m41s988ms to 98 (screen-off, power-save-off, device-idle-on)
Discharge total time: 11d 12h 30m 0s 400ms (from 3 steps)
Discharge screen off time: 9d 21h 51m 40s 100ms (from 2 steps)
Discharge screen off device idle time: 9d 21h 51m 40s 100ms (from 2 steps)
Current daily charge step durations:
#0: +5m4s541ms to 99 (screen-off, power-save-off, device-idle-off)
#1: +3m33s300ms to 98 (screen-off, power-save-off, device-idle-off)
Charge total time: 7h 11m 32s 0ms (from 2 steps)
Charge screen off time: 7h 11m 32s 0ms (from 2 steps)
Daily from 2022-08-16-15-56-39 to 2022-08-17-01-15-45:
Charge step durations:
#0: +5m15s53ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +5m35s358ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +3m43s598ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +3m33s400ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +2m32s364ms to 96 (screen-off, power-save-off, device-idle-off)
#5: +3m53s485ms to 95 (screen-off, power-save-off, device-idle-off)
#6: +3m43s317ms to 94 (screen-off, power-save-off, device-idle-off)
#7: +3m13s27ms to 93 (screen-off, power-save-off, device-idle-off)
#8: +1h9m49s978ms to 92 (power-save-off, device-idle-off)
#9: +3m43s682ms to 92 (screen-off, power-save-off, device-idle-off)
#10: +6m15s588ms to 91 (screen-off, power-save-off, device-idle-off)
Package changes:
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279

View File

@ -277,3 +277,87 @@ Connection pool for /data/user/0/com.sec.android.inputmethod/databases/StickerRe
5: [2023-07-26 16:50:25.318] [Pid:(0)]executeForLong took 2ms - succeeded, sql="PRAGMA page_count;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
-------------------------------------------------------------------------------
DUMP OF SERVICE batterystats:
Battery History (0% used, 11KB used of 4096KB, 79 strings using 9632):
0 (19) RESET:TIME: 2023-07-27-12-34-18
0 (2) 100 c0100024 status=discharging health=good plug=none temp=260 volt=4345 current=226 ap_temp=27 -nr_connected -wifi_ap -otg misc_event=0x0 online=1 current_event=0x0 txshare_event=0x0 charge=3000 modemRailChargemAh=0 wifiRailChargemAh=0 +running +wake_lock +screen phone_signal_strength=great brightness=bright +wifi_running +wifi +usb_data wifi_signal_strength=3 wifi_suppl=disconn +ble_scan top=1000:"com.wssyncmldm"
0 (2) 100 c0100024 user=0:"0"
0 (2) 100 c0100024 userfg=0:"0"
+343ms (3) 100 80000024 -wake_lock -screen -usb_data stats=0:"get-stats"
+1s235ms (4) 100 c0000020 +wake_lock=1000:"ActivityManager-Sleep" brightness=dark stats=0:"screen-state"
+1s314ms (1) 100 80000020 -wake_lock
+1s320ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s321ms (1) 100 80000020 -wake_lock
+1s321ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s332ms (1) 100 80000020 -wake_lock
+1s332ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s334ms (1) 100 80000020 -wake_lock
+1s441ms (2) 100 c0000020 +wake_lock=1000:"startDream"
+1s751ms (1) 100 80000020 -wake_lock
+1s809ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s811ms (1) 100 80000020 -wake_lock
+1s811ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s821ms (1) 100 80000020 -wake_lock
+1s821ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s823ms (1) 100 80000020 -wake_lock -ble_scan
+2s042ms (2) 100 c0000020 +wake_lock=u0a12:"Wakeful StateMachine: GeofencerStateMachine"
+2s044ms (1) 100 80000020 -wake_lock
+2s050ms (2) 100 c0000020 +wake_lock=u0a12:"NlpWakeLock"
Daily stats:
Current start time: 2023-07-27-02-02-56
Next min deadline: 2023-07-28-01-00-00
Next max deadline: 2023-07-28-03-00-00
Current daily discharge step durations:
#0: +2h44m59s971ms to 98 (screen-off, power-save-off, device-idle-on)
Discharge total time: 11d 10h 59m 57s 100ms (from 1 steps)
Discharge screen off time: 11d 10h 59m 57s 100ms (from 1 steps)
Discharge screen off device idle time: 11d 10h 59m 57s 100ms (from 1 steps)
Current daily charge step durations:
#0: +2m32s269ms to 100 (power-save-off, device-idle-off)
Charge total time: 4h 13m 46s 900ms (from 1 steps)
Daily from 2023-07-26-03-02-02 to 2023-07-27-02-02-56:
Discharge step durations:
#0: +2h21m35s4ms to 75 (screen-off, power-save-off)
#1: +2h19m0s999ms to 76 (screen-off, power-save-off)
#2: +1h46m26s999ms to 77 (screen-off, power-save-off)
#3: +2h24m32s6ms to 78 (screen-off, power-save-off, device-idle-on)
#4: +2h44m58s966ms to 79 (screen-off, power-save-off, device-idle-on)
Discharge total time: 9d 16h 11m 19s 400ms (from 5 steps)
Discharge screen off time: 9d 16h 11m 19s 400ms (from 5 steps)
Discharge screen off device idle time: 10d 17h 55m 48s 600ms (from 2 steps)
Charge step durations:
#0: +5m45s118ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +1m0s998ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +2m1s894ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +1m0s973ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +3m33s239ms to 96 (screen-off, power-save-off, device-idle-off)
Charge step durations:
#0: +30s531ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +30s527ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +30s571ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +1m1s53ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +30s580ms to 96 (screen-off, power-save-off, device-idle-off)
#5: +30s568ms to 95 (screen-off, power-save-off, device-idle-off)
#6: +20s407ms to 94 (screen-off, power-save-off, device-idle-off)
#7: +7m16s300ms to 93 (screen-off, power-save-off, device-idle-off)
#8: +5m55s313ms to 92 (screen-off, power-save-off, device-idle-off)
#9: +6m35s856ms to 91 (screen-off, power-save-off, device-idle-off)
#10: +4m17s981ms to 90 (screen-off, power-save-off, device-idle-off)
#11: +3m43s342ms to 89 (screen-off, power-save-off, device-idle-off)
Charge total time: 4h 24m 18s 500ms (from 12 steps)
Charge screen off time: 4h 24m 18s 500ms (from 12 steps)
Package changes:
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623

View File

@ -62,5 +62,5 @@ class TestHashes:
assert hashes[1]["file_path"] == os.path.join(path, "dumpsys.txt")
assert (
hashes[1]["sha256"]
== "c6be3ada77674f5bb9750d24e84b9b7ccf8db0cd4a896d9c17f9456eeab4bd0b"
== "009f9eaa04658acdd179b463e05e1ea1fffea132e6e7ee556f0c385ee69a0811"
)