mirror of https://github.com/mvt-project/mvt.git
Refactors DumpsysBatteryHistory and adds related androidqf module
This commit is contained in:
parent
7e0e071c5d
commit
e60e5fdc6e
|
@ -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 .artifact import AndroidArtifact
|
||||
|
||||
|
||||
class DumpsysBatteryHistoryArtifact(AndroidArtifact):
|
||||
"""
|
||||
Parser for dumpsys dattery history events.
|
||||
"""
|
||||
|
||||
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, data: str) -> None:
|
||||
for line in data.splitlines():
|
||||
if line.startswith("Battery History "):
|
||||
continue
|
||||
|
||||
if line.strip() == "":
|
||||
break
|
||||
|
||||
time_elapsed = line.strip().split(" ", 1)[0]
|
||||
|
||||
event = ""
|
||||
if line.find("+job") > 0:
|
||||
event = "start_job"
|
||||
uid = line[line.find("+job") + 5 : line.find(":")]
|
||||
service = line[line.find(":") + 1 :].strip('"')
|
||||
package_name = service.split("/")[0]
|
||||
elif line.find("-job") > 0:
|
||||
event = "end_job"
|
||||
uid = line[line.find("-job") + 5 : line.find(":")]
|
||||
service = line[line.find(":") + 1 :].strip('"')
|
||||
package_name = service.split("/")[0]
|
||||
elif line.find("+running +wake_lock=") > 0:
|
||||
uid = line[line.find("+running +wake_lock=") + 21 : line.find(":")]
|
||||
event = "wake"
|
||||
service = (
|
||||
line[line.find("*walarm*:") + 9 :].split(" ")[0].strip('"').strip()
|
||||
)
|
||||
if service == "" or "/" not in service:
|
||||
continue
|
||||
|
||||
package_name = service.split("/")[0]
|
||||
elif (line.find("+top=") > 0) or (line.find("-top") > 0):
|
||||
if line.find("+top=") > 0:
|
||||
event = "start_top"
|
||||
top_pos = line.find("+top=")
|
||||
else:
|
||||
event = "end_top"
|
||||
top_pos = line.find("-top=")
|
||||
colon_pos = top_pos + line[top_pos:].find(":")
|
||||
uid = line[top_pos + 5 : colon_pos]
|
||||
service = ""
|
||||
package_name = line[colon_pos + 1 :].strip('"')
|
||||
else:
|
||||
continue
|
||||
|
||||
self.results.append(
|
||||
{
|
||||
"time_elapsed": time_elapsed,
|
||||
"event": event,
|
||||
"uid": uid,
|
||||
"package_name": package_name,
|
||||
"service": service,
|
||||
}
|
||||
)
|
|
@ -6,12 +6,12 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.parsers import parse_dumpsys_battery_history
|
||||
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
|
||||
|
||||
from .base import AndroidExtraction
|
||||
|
||||
|
||||
class DumpsysBatteryHistory(AndroidExtraction):
|
||||
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidExtraction):
|
||||
"""This module extracts records from battery history events."""
|
||||
|
||||
def __init__(
|
||||
|
@ -32,22 +32,11 @@ class DumpsysBatteryHistory(AndroidExtraction):
|
|||
results=results,
|
||||
)
|
||||
|
||||
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 --history")
|
||||
self._adb_disconnect()
|
||||
|
||||
self.results = parse_dumpsys_battery_history(output)
|
||||
self.parse(output)
|
||||
|
||||
self.log.info("Extracted %d records from battery history", len(self.results))
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
from .dumpsys_accessibility import DumpsysAccessibility
|
||||
from .dumpsys_activities import DumpsysActivities
|
||||
from .dumpsys_appops import DumpsysAppops
|
||||
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
||||
from .dumpsys_battery_history import DumpsysBatteryHistory
|
||||
from .dumpsys_dbinfo import DumpsysDBInfo
|
||||
from .dumpsys_packages import DumpsysPackages
|
||||
from .dumpsys_receivers import DumpsysReceivers
|
||||
|
@ -20,6 +22,8 @@ ANDROIDQF_MODULES = [
|
|||
DumpsysAccessibility,
|
||||
DumpsysAppops,
|
||||
DumpsysDBInfo,
|
||||
DumpsysBatteryDaily,
|
||||
DumpsysBatteryHistory,
|
||||
Processes,
|
||||
Getprop,
|
||||
Settings,
|
||||
|
|
|
@ -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_history import DumpsysBatteryHistoryArtifact
|
||||
|
||||
from .base import AndroidQFModule
|
||||
|
||||
|
||||
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, 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))
|
|
@ -6,12 +6,12 @@
|
|||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.parsers import parse_dumpsys_battery_history
|
||||
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
|
||||
|
||||
from .base import BugReportModule
|
||||
|
||||
|
||||
class BatteryHistory(BugReportModule):
|
||||
class BatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule):
|
||||
"""This module extracts records from battery daily updates."""
|
||||
|
||||
def __init__(
|
||||
|
@ -32,17 +32,6 @@ class BatteryHistory(BugReportModule):
|
|||
results=results,
|
||||
)
|
||||
|
||||
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:
|
||||
|
@ -52,23 +41,10 @@ class BatteryHistory(BugReportModule):
|
|||
)
|
||||
return
|
||||
|
||||
lines = []
|
||||
in_history = False
|
||||
for line in content.decode(errors="ignore").splitlines():
|
||||
if line.strip().startswith("Battery History "):
|
||||
lines.append(line)
|
||||
in_history = True
|
||||
continue
|
||||
|
||||
if not in_history:
|
||||
continue
|
||||
|
||||
if line.strip() == "":
|
||||
break
|
||||
|
||||
lines.append(line)
|
||||
|
||||
self.results = parse_dumpsys_battery_history("\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 history records", len(self.results)
|
||||
|
|
|
@ -3,7 +3,4 @@
|
|||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .dumpsys import (
|
||||
parse_dumpsys_battery_history,
|
||||
parse_dumpsys_receiver_resolver_table,
|
||||
)
|
||||
from .dumpsys import parse_dumpsys_receiver_resolver_table
|
||||
|
|
|
@ -7,66 +7,6 @@ import re
|
|||
from typing import Any, Dict, List
|
||||
|
||||
|
||||
def parse_dumpsys_battery_history(output: str) -> List[Dict[str, Any]]:
|
||||
results = []
|
||||
|
||||
for line in output.splitlines():
|
||||
if line.startswith("Battery History "):
|
||||
continue
|
||||
|
||||
if line.strip() == "":
|
||||
break
|
||||
|
||||
time_elapsed = line.strip().split(" ", 1)[0]
|
||||
|
||||
event = ""
|
||||
if line.find("+job") > 0:
|
||||
event = "start_job"
|
||||
uid = line[line.find("+job") + 5 : line.find(":")]
|
||||
service = line[line.find(":") + 1 :].strip('"')
|
||||
package_name = service.split("/")[0]
|
||||
elif line.find("-job") > 0:
|
||||
event = "end_job"
|
||||
uid = line[line.find("-job") + 5 : line.find(":")]
|
||||
service = line[line.find(":") + 1 :].strip('"')
|
||||
package_name = service.split("/")[0]
|
||||
elif line.find("+running +wake_lock=") > 0:
|
||||
uid = line[line.find("+running +wake_lock=") + 21 : line.find(":")]
|
||||
event = "wake"
|
||||
service = (
|
||||
line[line.find("*walarm*:") + 9 :].split(" ")[0].strip('"').strip()
|
||||
)
|
||||
if service == "" or "/" not in service:
|
||||
continue
|
||||
|
||||
package_name = service.split("/")[0]
|
||||
elif (line.find("+top=") > 0) or (line.find("-top") > 0):
|
||||
if line.find("+top=") > 0:
|
||||
event = "start_top"
|
||||
top_pos = line.find("+top=")
|
||||
else:
|
||||
event = "end_top"
|
||||
top_pos = line.find("-top=")
|
||||
colon_pos = top_pos + line[top_pos:].find(":")
|
||||
uid = line[top_pos + 5 : colon_pos]
|
||||
service = ""
|
||||
package_name = line[colon_pos + 1 :].strip('"')
|
||||
else:
|
||||
continue
|
||||
|
||||
results.append(
|
||||
{
|
||||
"time_elapsed": time_elapsed,
|
||||
"event": event,
|
||||
"uid": uid,
|
||||
"package_name": package_name,
|
||||
"service": service,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def parse_dumpsys_receiver_resolver_table(output: str) -> Dict[str, Any]:
|
||||
results = {}
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# 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_history import DumpsysBatteryHistoryArtifact
|
||||
from mvt.common.indicators import Indicators
|
||||
|
||||
from ..utils import get_artifact
|
||||
|
||||
|
||||
class TestDumpsysBatteryHistoryArtifact:
|
||||
def test_parsing(self):
|
||||
dba = DumpsysBatteryHistoryArtifact()
|
||||
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) == 5
|
||||
assert dba.results[0]["package_name"] == "com.samsung.android.app.reminder"
|
||||
assert dba.results[1]["event"] == "end_job"
|
||||
assert dba.results[2]["event"] == "start_top"
|
||||
assert dba.results[2]["uid"] == "u0a280"
|
||||
assert dba.results[2]["package_name"] == "com.whatsapp"
|
||||
assert dba.results[3]["event"] == "end_top"
|
||||
assert dba.results[4]["package_name"] == "com.sec.android.app.launcher"
|
||||
|
||||
def test_ioc_check(self, indicator_file):
|
||||
dba = DumpsysBatteryHistoryArtifact()
|
||||
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.samsung.android.app.reminder")
|
||||
dba.indicators = ind
|
||||
assert len(dba.detected) == 0
|
||||
dba.check_indicators()
|
||||
assert len(dba.detected) == 2
|
|
@ -3,31 +3,12 @@
|
|||
# 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.android.parsers.dumpsys import (
|
||||
parse_dumpsys_battery_history,
|
||||
parse_dumpsys_packages,
|
||||
)
|
||||
from mvt.android.parsers.dumpsys import parse_dumpsys_packages
|
||||
|
||||
from ..utils import get_artifact
|
||||
|
||||
|
||||
class TestDumpsysParsing:
|
||||
def test_battery_history_parsing(self):
|
||||
file = get_artifact("android_data/dumpsys_battery.txt")
|
||||
with open(file) as f:
|
||||
data = f.read()
|
||||
|
||||
res = parse_dumpsys_battery_history(data)
|
||||
|
||||
assert len(res) == 5
|
||||
assert res[0]["package_name"] == "com.samsung.android.app.reminder"
|
||||
assert res[1]["event"] == "end_job"
|
||||
assert res[2]["event"] == "start_top"
|
||||
assert res[2]["uid"] == "u0a280"
|
||||
assert res[2]["package_name"] == "com.whatsapp"
|
||||
assert res[3]["event"] == "end_top"
|
||||
assert res[4]["package_name"] == "com.sec.android.app.launcher"
|
||||
|
||||
def test_packages_parsing(self):
|
||||
file = get_artifact("android_data/dumpsys_packages.txt")
|
||||
with open(file) as f:
|
||||
|
|
|
@ -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_history import DumpsysBatteryHistory
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_android_androidqf, list_files
|
||||
|
||||
|
||||
class TestDumpsysBatteryHistoryModule:
|
||||
def test_parsing(self):
|
||||
data_path = get_android_androidqf()
|
||||
m = DumpsysBatteryHistory(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) == 6
|
||||
assert len(m.timeline) == 0
|
||||
assert len(m.detected) == 0
|
|
@ -304,6 +304,13 @@ Battery History (0% used, 11KB used of 4096KB, 79 strings using 9632):
|
|||
+2s042ms (2) 100 c0000020 +wake_lock=u0a12:"Wakeful StateMachine: GeofencerStateMachine"
|
||||
+2s044ms (1) 100 80000020 -wake_lock
|
||||
+2s050ms (2) 100 c0000020 +wake_lock=u0a12:"NlpWakeLock"
|
||||
+23m32s163ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/com.google.android.libraries.internal.growth.growthkit.internal.jobs.impl.GrowthKitJobService"
|
||||
+23m33s713ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/.job.ProviderCreatedJob$ProviderCreatedJobService"
|
||||
+23m33s752ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/com.android.mail.widget.NotifyDatasetChangedJob$NotifyDatasetChangedJobService"
|
||||
+23m33s786ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/.job.ProviderCreatedJob$ProviderCreatedJobService"
|
||||
+23m33s867ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/com.google.android.libraries.internal.growth.growthkit.internal.jobs.impl.GrowthKitJobService"
|
||||
+23m33s910ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/com.android.mail.widget.NotifyDatasetChangedJob$NotifyDatasetChangedJobService"
|
||||
|
||||
|
||||
Daily stats:
|
||||
Current start time: 2023-07-27-02-02-56
|
||||
|
|
|
@ -62,5 +62,5 @@ class TestHashes:
|
|||
assert hashes[1]["file_path"] == os.path.join(path, "dumpsys.txt")
|
||||
assert (
|
||||
hashes[1]["sha256"]
|
||||
== "009f9eaa04658acdd179b463e05e1ea1fffea132e6e7ee556f0c385ee69a0811"
|
||||
== "cfae0e04ef139b5a2ae1e2b3d400ce67eb98e67ff66f56ba2a580fe41bc120d0"
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue