mirror of
https://github.com/mvt-project/mvt.git
synced 2026-02-12 16:42:45 +00:00
Refactor code of DumpsysDBInfo
This commit is contained in:
82
mvt/android/artifacts/dumpsys_dbinfo.py
Normal file
82
mvt/android/artifacts/dumpsys_dbinfo.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# 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 re
|
||||
|
||||
from .artifact import AndroidArtifact
|
||||
|
||||
|
||||
class DumpsysDBInfo(AndroidArtifact):
|
||||
"""
|
||||
Parser for dumpsys DBInfo service
|
||||
"""
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
path = result.get("path", "")
|
||||
for part in path.split("/"):
|
||||
ioc = self.indicators.check_app_id(part)
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
def parse(self, output: str) -> None:
|
||||
rxp = re.compile(
|
||||
r".*\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})\].*\[Pid:\((\d+)\)\](\w+).*sql\=\"(.+?)\""
|
||||
) # pylint: disable=line-too-long
|
||||
rxp_no_pid = re.compile(
|
||||
r".*\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})\][ ]{1}(\w+).*sql\=\"(.+?)\""
|
||||
) # pylint: disable=line-too-long
|
||||
|
||||
pool = None
|
||||
in_operations = False
|
||||
for line in output.splitlines():
|
||||
if line.startswith("Connection pool for "):
|
||||
pool = line.replace("Connection pool for ", "").rstrip(":")
|
||||
|
||||
if not pool:
|
||||
continue
|
||||
|
||||
if line.strip() == "Most recently executed operations:":
|
||||
in_operations = True
|
||||
continue
|
||||
|
||||
if not in_operations:
|
||||
continue
|
||||
|
||||
if not line.startswith(" "):
|
||||
in_operations = False
|
||||
pool = None
|
||||
continue
|
||||
|
||||
matches = rxp.findall(line)
|
||||
if not matches:
|
||||
matches = rxp_no_pid.findall(line)
|
||||
if not matches:
|
||||
continue
|
||||
|
||||
match = matches[0]
|
||||
self.results.append(
|
||||
{
|
||||
"isodate": match[0],
|
||||
"action": match[1],
|
||||
"sql": match[2],
|
||||
"path": pool,
|
||||
}
|
||||
)
|
||||
else:
|
||||
match = matches[0]
|
||||
self.results.append(
|
||||
{
|
||||
"isodate": match[0],
|
||||
"pid": match[1],
|
||||
"action": match[2],
|
||||
"sql": match[3],
|
||||
"path": pool,
|
||||
}
|
||||
)
|
||||
@@ -6,12 +6,12 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.parsers import parse_dumpsys_dbinfo
|
||||
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfo as DBI
|
||||
|
||||
from .base import AndroidExtraction
|
||||
|
||||
|
||||
class DumpsysDBInfo(AndroidExtraction):
|
||||
class DumpsysDBInfo(DBI, AndroidExtraction):
|
||||
"""This module extracts records from battery daily updates."""
|
||||
|
||||
slug = "dumpsys_dbinfo"
|
||||
@@ -34,25 +34,12 @@ class DumpsysDBInfo(AndroidExtraction):
|
||||
results=results,
|
||||
)
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
path = result.get("path", "")
|
||||
for part in path.split("/"):
|
||||
ioc = self.indicators.check_app_id(part)
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
def run(self) -> None:
|
||||
self._adb_connect()
|
||||
output = self._adb_command("dumpsys dbinfo")
|
||||
self._adb_disconnect()
|
||||
|
||||
self.results = parse_dumpsys_dbinfo(output)
|
||||
self.parse(output)
|
||||
|
||||
self.log.info(
|
||||
"Extracted a total of %d records from database information",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
from .dumpsys_accessibility import DumpsysAccessibility
|
||||
from .dumpsys_activities import DumpsysActivities
|
||||
from .dumpsys_appops import DumpsysAppops
|
||||
from .dumpsys_dbinfo import DumpsysDBInfo
|
||||
from .dumpsys_packages import DumpsysPackages
|
||||
from .dumpsys_receivers import DumpsysReceivers
|
||||
from .getprop import Getprop
|
||||
@@ -18,6 +19,7 @@ ANDROIDQF_MODULES = [
|
||||
DumpsysReceivers,
|
||||
DumpsysAccessibility,
|
||||
DumpsysAppops,
|
||||
DumpsysDBInfo,
|
||||
Processes,
|
||||
Getprop,
|
||||
Settings,
|
||||
|
||||
46
mvt/android/modules/androidqf/dumpsys_dbinfo.py
Normal file
46
mvt/android/modules/androidqf/dumpsys_dbinfo.py
Normal 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_dbinfo import DumpsysDBInfo as DBI
|
||||
|
||||
from .base import AndroidQFModule
|
||||
|
||||
|
||||
class DumpsysDBInfo(DBI, 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 dumpsys DBInfo section
|
||||
data = self._get_file_content(dumpsys_file[0])
|
||||
section = self.extract_dumpsys_section(
|
||||
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE dbinfo:"
|
||||
)
|
||||
|
||||
# Parse it
|
||||
self.parse(section)
|
||||
self.log.info("Identified %d DB Info entries", len(self.results))
|
||||
@@ -6,12 +6,12 @@
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
from mvt.android.parsers import parse_dumpsys_dbinfo
|
||||
from mvt.android.artifacts.dumpsys_dbinfo import DumpsysDBInfo
|
||||
|
||||
from .base import BugReportModule
|
||||
|
||||
|
||||
class DBInfo(BugReportModule):
|
||||
class DBInfo(DumpsysDBInfo, BugReportModule):
|
||||
"""This module extracts records from battery daily updates."""
|
||||
|
||||
slug = "dbinfo"
|
||||
@@ -34,47 +34,20 @@ class DBInfo(BugReportModule):
|
||||
results=results,
|
||||
)
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
if not self.indicators:
|
||||
return
|
||||
|
||||
for result in self.results:
|
||||
path = result.get("path", "")
|
||||
for part in path.split("/"):
|
||||
ioc = self.indicators.check_app_id(part)
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
def run(self) -> None:
|
||||
content = self._get_dumpstate_file()
|
||||
if not content:
|
||||
data = self._get_dumpstate_file()
|
||||
if not data:
|
||||
self.log.error(
|
||||
"Unable to find dumpstate file. "
|
||||
"Did you provide a valid bug report archive?"
|
||||
)
|
||||
return
|
||||
|
||||
in_dbinfo = False
|
||||
lines = []
|
||||
for line in content.decode(errors="ignore").splitlines():
|
||||
if line.strip() == "DUMP OF SERVICE dbinfo:":
|
||||
in_dbinfo = True
|
||||
continue
|
||||
|
||||
if not in_dbinfo:
|
||||
continue
|
||||
|
||||
if line.strip().startswith(
|
||||
"------------------------------------------------------------------------------"
|
||||
): # pylint: disable=line-too-long
|
||||
break
|
||||
|
||||
lines.append(line)
|
||||
|
||||
self.results = parse_dumpsys_dbinfo("\n".join(lines))
|
||||
section = self.extract_dumpsys_section(
|
||||
data.decode("utf-8", errors="ignore"), "DUMP OF SERVICE dbinfo:"
|
||||
)
|
||||
|
||||
self.parse(section)
|
||||
self.log.info(
|
||||
"Extracted a total of %d database connection pool records",
|
||||
len(self.results),
|
||||
|
||||
@@ -7,6 +7,5 @@ from .dumpsys import (
|
||||
parse_dumpsys_appops,
|
||||
parse_dumpsys_battery_daily,
|
||||
parse_dumpsys_battery_history,
|
||||
parse_dumpsys_dbinfo,
|
||||
parse_dumpsys_receiver_resolver_table,
|
||||
)
|
||||
|
||||
@@ -118,67 +118,6 @@ def parse_dumpsys_battery_history(output: str) -> List[Dict[str, Any]]:
|
||||
return results
|
||||
|
||||
|
||||
def parse_dumpsys_dbinfo(output: str) -> List[Dict[str, Any]]:
|
||||
results = []
|
||||
|
||||
rxp = re.compile(
|
||||
r".*\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})\].*\[Pid:\((\d+)\)\](\w+).*sql\=\"(.+?)\""
|
||||
) # pylint: disable=line-too-long
|
||||
rxp_no_pid = re.compile(
|
||||
r".*\[([0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3})\][ ]{1}(\w+).*sql\=\"(.+?)\""
|
||||
) # pylint: disable=line-too-long
|
||||
|
||||
pool = None
|
||||
in_operations = False
|
||||
for line in output.splitlines():
|
||||
if line.startswith("Connection pool for "):
|
||||
pool = line.replace("Connection pool for ", "").rstrip(":")
|
||||
|
||||
if not pool:
|
||||
continue
|
||||
|
||||
if line.strip() == "Most recently executed operations:":
|
||||
in_operations = True
|
||||
continue
|
||||
|
||||
if not in_operations:
|
||||
continue
|
||||
|
||||
if not line.startswith(" "):
|
||||
in_operations = False
|
||||
pool = None
|
||||
continue
|
||||
|
||||
matches = rxp.findall(line)
|
||||
if not matches:
|
||||
matches = rxp_no_pid.findall(line)
|
||||
if not matches:
|
||||
continue
|
||||
|
||||
match = matches[0]
|
||||
results.append(
|
||||
{
|
||||
"isodate": match[0],
|
||||
"action": match[1],
|
||||
"sql": match[2],
|
||||
"path": pool,
|
||||
}
|
||||
)
|
||||
else:
|
||||
match = matches[0]
|
||||
results.append(
|
||||
{
|
||||
"isodate": match[0],
|
||||
"pid": match[1],
|
||||
"action": match[2],
|
||||
"sql": match[3],
|
||||
"path": pool,
|
||||
}
|
||||
)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def parse_dumpsys_receiver_resolver_table(output: str) -> Dict[str, Any]:
|
||||
results = {}
|
||||
|
||||
|
||||
42
tests/android/test_artifact_dumpsys_dbinfo.py
Normal file
42
tests/android/test_artifact_dumpsys_dbinfo.py
Normal file
@@ -0,0 +1,42 @@
|
||||
# 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_dbinfo import DumpsysDBInfo
|
||||
from mvt.common.indicators import Indicators
|
||||
|
||||
from ..utils import get_artifact
|
||||
|
||||
|
||||
class TestDumpsysDBinfoArtifact:
|
||||
def test_parsing(self):
|
||||
dbi = DumpsysDBInfo()
|
||||
file = get_artifact("android_data/dumpsys_dbinfo.txt")
|
||||
with open(file) as f:
|
||||
data = f.read()
|
||||
|
||||
assert len(dbi.results) == 0
|
||||
dbi.parse(data)
|
||||
assert len(dbi.results) == 5
|
||||
assert dbi.results[0]["action"] == "executeForCursorWindow"
|
||||
assert dbi.results[0]["sql"] == "PRAGMA database_list;"
|
||||
assert (
|
||||
dbi.results[0]["path"] == "/data/user/0/com.wssyncmldm/databases/idmsdk.db"
|
||||
)
|
||||
|
||||
def test_ioc_check(self, indicator_file):
|
||||
dbi = DumpsysDBInfo()
|
||||
file = get_artifact("android_data/dumpsys_dbinfo.txt")
|
||||
with open(file) as f:
|
||||
data = f.read()
|
||||
dbi.parse(data)
|
||||
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["app_ids"].append("com.wssyncmldm")
|
||||
dbi.indicators = ind
|
||||
assert len(dbi.detected) == 0
|
||||
dbi.check_indicators()
|
||||
assert len(dbi.detected) == 5
|
||||
24
tests/android_androidqf/test_dumpsys_dbinfo.py
Normal file
24
tests/android_androidqf/test_dumpsys_dbinfo.py
Normal 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_dbinfo import DumpsysDBInfo
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_android_androidqf, list_files
|
||||
|
||||
|
||||
class TestDumpsysDBInfoModule:
|
||||
def test_parsing(self):
|
||||
data_path = get_android_androidqf()
|
||||
m = DumpsysDBInfo(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
|
||||
23
tests/artifacts/android_data/dumpsys_dbinfo.txt
Normal file
23
tests/artifacts/android_data/dumpsys_dbinfo.txt
Normal file
@@ -0,0 +1,23 @@
|
||||
DUMP OF SERVICE dbinfo:
|
||||
Applications Database Info:
|
||||
|
||||
** Database info for pid 8817 [com.wssyncmldm] **
|
||||
|
||||
Attached db: false
|
||||
Connection pool for /data/user/0/com.wssyncmldm/databases/idmsdk.db:
|
||||
Open: true
|
||||
Max connections: 1
|
||||
Total execution time: 46
|
||||
Configuration: openFlags=268435456, isLegacyCompatibilityWalEnabled=false, journalMode=, syncMode=
|
||||
Secure db: false
|
||||
Available primary connection:
|
||||
Connection #0:
|
||||
isPrimaryConnection: true
|
||||
onlyAllowReadOnlyOperations: true
|
||||
Most recently executed operations:
|
||||
0: [2023-07-27 12:21:44.097] [Pid:(0)]executeForCursorWindow took 2ms - succeeded, sql="PRAGMA database_list;", path=/data/user/0/com.wssyncmldm/databases/idmsdk.db
|
||||
1: [2023-07-27 12:21:44.096] [Pid:(0)]executeForLong took 0ms - succeeded, sql="PRAGMA page_size;", path=/data/user/0/com.wssyncmldm/databases/idmsdk.db
|
||||
2: [2023-07-27 12:21:44.092] [Pid:(0)]executeForLong took 4ms - succeeded, sql="PRAGMA page_count;", path=/data/user/0/com.wssyncmldm/databases/idmsdk.db
|
||||
3: [2023-07-26 19:27:41.386] [Pid:(8817)]executeForCursorWindow took 2ms - succeeded, sql="SELECT path, name, acl, scope, format, type, depth, value FROM x6g1q14r75 WHERE (path = './DMAcc/x6g1q14r75/AppAuth/client/AAuthName') ORDER BY depth ASC", path=/data/user/0/com.wssyncmldm/databases/idmsdk.db
|
||||
4: [2023-07-26 19:27:41.385] [Pid:(8817)]prepare took 0ms - succeeded, sql="SELECT path, name, acl, scope, format, type, depth, value FROM x6g1q14r75 WHERE (path = './DMAcc/x6g1q14r75/AppAuth/client/AAuthName') ORDER BY depth ASC", path=/data/user/0/com.wssyncmldm/databases/idmsdk.db
|
||||
|
||||
@@ -250,5 +250,30 @@ Current AppOps Service state:
|
||||
WRITE_EXTERNAL_STORAGE (allow):
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
DUMP OF SERVICE dbinfo:
|
||||
Applications Database Info:
|
||||
|
||||
** Database info for pid 5748 [com.sec.android.inputmethod] **
|
||||
|
||||
Attached db: false
|
||||
Connection pool for /data/user/0/com.sec.android.inputmethod/databases/StickerRecentList:
|
||||
Open: true
|
||||
Max connections: 4
|
||||
Total execution time: 61
|
||||
Configuration: openFlags=805306368, isLegacyCompatibilityWalEnabled=false, journalMode=, syncMode=
|
||||
Secure db: false
|
||||
Use WAL mode.
|
||||
Available primary connection:
|
||||
Connection #0:
|
||||
isPrimaryConnection: true
|
||||
onlyAllowReadOnlyOperations: false
|
||||
Most recently executed operations:
|
||||
0: [2023-07-27 12:21:44.458] [Pid:(0)]executeForCursorWindow took 1ms - succeeded, sql="PRAGMA database_list;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
|
||||
1: [2023-07-27 12:21:44.456] [Pid:(0)]executeForLong took 0ms - succeeded, sql="PRAGMA page_size;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
|
||||
2: [2023-07-27 12:21:44.455] [Pid:(0)]executeForLong took 2ms - succeeded, sql="PRAGMA page_count;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
|
||||
3: [2023-07-26 16:50:25.321] [Pid:(0)]executeForCursorWindow took 0ms - succeeded, sql="PRAGMA database_list;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
|
||||
4: [2023-07-26 16:50:25.320] [Pid:(0)]executeForLong took 0ms - succeeded, sql="PRAGMA page_size;", path=/data/user/0/com.sec.android.inputmethod/databases/StickerRecentList
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -62,5 +62,5 @@ class TestHashes:
|
||||
assert hashes[1]["file_path"] == os.path.join(path, "dumpsys.txt")
|
||||
assert (
|
||||
hashes[1]["sha256"]
|
||||
== "bac858001784657a43c7cfa771fd1fc4a49428eb6b7c458a1ebf2fdeef78dd86"
|
||||
== "c6be3ada77674f5bb9750d24e84b9b7ccf8db0cd4a896d9c17f9456eeab4bd0b"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user