mirror of
https://github.com/mvt-project/mvt.git
synced 2026-09-30 21:19:41 +02:00
refactor dumpsys package activity code
This commit is contained in:
1 parent
ae0e470c56
commit
f96f2fe34a
10 files changed
+182
-146
No files matched your search
@@ -0,0 +1,4 @@
|
|||||||
|
# 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/
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# 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 DumpsysPackageActivities(AndroidArtifact):
|
||||||
|
def check_indicators(self) -> None:
|
||||||
|
if not self.indicators:
|
||||||
|
return
|
||||||
|
|
||||||
|
for activity in self.results:
|
||||||
|
ioc = self.indicators.check_app_id(activity["package_name"])
|
||||||
|
if ioc:
|
||||||
|
activity["matched_indicator"] = ioc
|
||||||
|
self.detected.append(activity)
|
||||||
|
continue
|
||||||
|
|
||||||
|
def parse(self, content: str):
|
||||||
|
"""
|
||||||
|
Parse the Dumpsys Package section for activities
|
||||||
|
Adds results to self.results
|
||||||
|
|
||||||
|
:param content: content of the package section (string)
|
||||||
|
"""
|
||||||
|
self.results = []
|
||||||
|
|
||||||
|
in_activity_resolver_table = False
|
||||||
|
in_non_data_actions = False
|
||||||
|
intent = None
|
||||||
|
for line in content.splitlines():
|
||||||
|
if line.startswith("Activity Resolver Table:"):
|
||||||
|
in_activity_resolver_table = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not in_activity_resolver_table:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith(" Non-Data Actions:"):
|
||||||
|
in_non_data_actions = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not in_non_data_actions:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we hit an empty line, the Non-Data Actions section should be
|
||||||
|
# finished.
|
||||||
|
if line.strip() == "":
|
||||||
|
break
|
||||||
|
|
||||||
|
# We detect the action name.
|
||||||
|
if (
|
||||||
|
line.startswith(" " * 6)
|
||||||
|
and not line.startswith(" " * 8)
|
||||||
|
and ":" in line
|
||||||
|
):
|
||||||
|
intent = line.strip().replace(":", "")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we are not in an intent block yet, skip.
|
||||||
|
if not intent:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we are in a block but the line does not start with 8 spaces
|
||||||
|
# it means the block ended a new one started, so we reset and
|
||||||
|
# continue.
|
||||||
|
if not line.startswith(" " * 8):
|
||||||
|
intent = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
# If we got this far, we are processing receivers for the
|
||||||
|
# activities we are interested in.
|
||||||
|
activity = line.strip().split(" ")[1]
|
||||||
|
package_name = activity.split("/")[0]
|
||||||
|
|
||||||
|
self.results.append(
|
||||||
|
{
|
||||||
|
"intent": intent,
|
||||||
|
"package_name": package_name,
|
||||||
|
"activity": activity,
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
|
from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities
|
||||||
|
|
||||||
from .base import AndroidExtraction
|
from .base import AndroidExtraction
|
||||||
|
|
||||||
|
|
||||||
class DumpsysActivities(AndroidExtraction):
|
class DumpsysActivities(DumpsysPackageActivities, AndroidExtraction):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -32,25 +32,12 @@ class DumpsysActivities(AndroidExtraction):
|
|||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else []
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
if not self.indicators:
|
|
||||||
return
|
|
||||||
|
|
||||||
for intent, activities in self.results.items():
|
|
||||||
for activity in activities:
|
|
||||||
ioc = self.indicators.check_app_id(activity["package_name"])
|
|
||||||
if ioc:
|
|
||||||
activity["matched_indicator"] = ioc
|
|
||||||
self.detected.append({intent: activity})
|
|
||||||
continue
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
self._adb_connect()
|
self._adb_connect()
|
||||||
output = self._adb_command("dumpsys package")
|
output = self._adb_command("dumpsys package")
|
||||||
self._adb_disconnect()
|
self._adb_disconnect()
|
||||||
|
self.parse(output)
|
||||||
|
|
||||||
self.results = parse_dumpsys_activity_resolver_table(output)
|
self.log.info("Extracted %d package activities", len(self.results))
|
||||||
|
|
||||||
self.log.info("Extracted activities for %d intents", len(self.results))
|
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
|
from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities
|
||||||
|
|
||||||
from .base import AndroidQFModule
|
from .base import AndroidQFModule
|
||||||
|
|
||||||
|
|
||||||
class DumpsysActivities(AndroidQFModule):
|
class DumpsysActivities(DumpsysPackageActivities, AndroidQFModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -32,42 +32,17 @@ class DumpsysActivities(AndroidQFModule):
|
|||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else []
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
if not self.indicators:
|
|
||||||
return
|
|
||||||
|
|
||||||
for intent, activities in self.results.items():
|
|
||||||
for activity in activities:
|
|
||||||
ioc = self.indicators.check_app_id(activity["package_name"])
|
|
||||||
if ioc:
|
|
||||||
activity["matched_indicator"] = ioc
|
|
||||||
self.detected.append({intent: activity})
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
|
||||||
if not dumpsys_file:
|
if not dumpsys_file:
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = []
|
# Get data and extract the dumpsys section
|
||||||
in_package = False
|
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
|
||||||
data = self._get_file_content(dumpsys_file[0])
|
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
|
||||||
for line in data.decode("utf-8").split("\n"):
|
# Parse it
|
||||||
if line.strip() == "DUMP OF SERVICE package:":
|
self.parse(content)
|
||||||
in_package = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_package:
|
self.log.info("Extracted %d package activities", len(self.results))
|
||||||
continue
|
|
||||||
|
|
||||||
if line.strip().startswith(
|
|
||||||
"------------------------------------------------------------------------------"
|
|
||||||
): # pylint: disable=line-too-long
|
|
||||||
break
|
|
||||||
|
|
||||||
lines.append(line.rstrip())
|
|
||||||
|
|
||||||
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
|
|
||||||
|
|
||||||
self.log.info("Extracted activities for %d intents", len(self.results))
|
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
|
from mvt.android.artifacts.dumpsys_package_activities import DumpsysPackageActivities
|
||||||
|
|
||||||
from .base import BugReportModule
|
from .base import BugReportModule
|
||||||
|
|
||||||
|
|
||||||
class Activities(BugReportModule):
|
class Activities(DumpsysPackageActivities, BugReportModule):
|
||||||
"""This module extracts details on receivers for risky activities."""
|
"""This module extracts details on receivers for risky activities."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@@ -32,19 +32,7 @@ class Activities(BugReportModule):
|
|||||||
results=results,
|
results=results,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.results = results if results else {}
|
self.results = results if results else []
|
||||||
|
|
||||||
def check_indicators(self) -> None:
|
|
||||||
if not self.indicators:
|
|
||||||
return
|
|
||||||
|
|
||||||
for intent, activities in self.results.items():
|
|
||||||
for activity in activities:
|
|
||||||
ioc = self.indicators.check_app_id(activity["package_name"])
|
|
||||||
if ioc:
|
|
||||||
activity["matched_indicator"] = ioc
|
|
||||||
self.detected.append({intent: activity})
|
|
||||||
continue
|
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
content = self._get_dumpstate_file()
|
content = self._get_dumpstate_file()
|
||||||
@@ -55,23 +43,12 @@ class Activities(BugReportModule):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
lines = []
|
# Extract package section
|
||||||
in_package = False
|
section = self.extract_dumpsys_section(
|
||||||
for line in content.decode(errors="ignore").splitlines():
|
content.decode("utf-8", errors="ignore"), "DUMP OF SERVICE package:"
|
||||||
if line.strip() == "DUMP OF SERVICE package:":
|
)
|
||||||
in_package = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_package:
|
# Parse
|
||||||
continue
|
self.parse(section)
|
||||||
|
|
||||||
if line.strip().startswith(
|
self.log.info("Extracted %d package activities", len(self.results))
|
||||||
"------------------------------------------------------------------------------"
|
|
||||||
): # pylint: disable=line-too-long
|
|
||||||
break
|
|
||||||
|
|
||||||
lines.append(line)
|
|
||||||
|
|
||||||
self.results = parse_dumpsys_activity_resolver_table("\n".join(lines))
|
|
||||||
|
|
||||||
self.log.info("Extracted activities for %d intents", len(self.results))
|
|
||||||
@@ -4,7 +4,6 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .dumpsys import (
|
from .dumpsys import (
|
||||||
parse_dumpsys_activity_resolver_table,
|
|
||||||
parse_dumpsys_appops,
|
parse_dumpsys_appops,
|
||||||
parse_dumpsys_battery_daily,
|
parse_dumpsys_battery_daily,
|
||||||
parse_dumpsys_battery_history,
|
parse_dumpsys_battery_history,
|
||||||
|
|||||||
@@ -10,64 +10,6 @@ from typing import Any, Dict, List
|
|||||||
from mvt.common.utils import convert_datetime_to_iso
|
from mvt.common.utils import convert_datetime_to_iso
|
||||||
|
|
||||||
|
|
||||||
def parse_dumpsys_activity_resolver_table(output: str) -> Dict[str, Any]:
|
|
||||||
results = {}
|
|
||||||
|
|
||||||
in_activity_resolver_table = False
|
|
||||||
in_non_data_actions = False
|
|
||||||
intent = None
|
|
||||||
for line in output.splitlines():
|
|
||||||
if line.startswith("Activity Resolver Table:"):
|
|
||||||
in_activity_resolver_table = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_activity_resolver_table:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if line.startswith(" Non-Data Actions:"):
|
|
||||||
in_non_data_actions = True
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not in_non_data_actions:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we hit an empty line, the Non-Data Actions section should be
|
|
||||||
# finished.
|
|
||||||
if line.strip() == "":
|
|
||||||
break
|
|
||||||
|
|
||||||
# We detect the action name.
|
|
||||||
if line.startswith(" " * 6) and not line.startswith(" " * 8) and ":" in line:
|
|
||||||
intent = line.strip().replace(":", "")
|
|
||||||
results[intent] = []
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we are not in an intent block yet, skip.
|
|
||||||
if not intent:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we are in a block but the line does not start with 8 spaces
|
|
||||||
# it means the block ended a new one started, so we reset and
|
|
||||||
# continue.
|
|
||||||
if not line.startswith(" " * 8):
|
|
||||||
intent = None
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If we got this far, we are processing receivers for the
|
|
||||||
# activities we are interested in.
|
|
||||||
activity = line.strip().split(" ")[1]
|
|
||||||
package_name = activity.split("/")[0]
|
|
||||||
|
|
||||||
results[intent].append(
|
|
||||||
{
|
|
||||||
"package_name": package_name,
|
|
||||||
"activity": activity,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
|
|
||||||
def parse_dumpsys_battery_daily(output: str) -> list:
|
def parse_dumpsys_battery_daily(output: str) -> list:
|
||||||
results = []
|
results = []
|
||||||
daily = None
|
daily = None
|
||||||
|
|||||||
@@ -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_package_activities import DumpsysPackageActivities
|
||||||
|
from mvt.common.indicators import Indicators
|
||||||
|
|
||||||
|
from ..utils import get_artifact
|
||||||
|
|
||||||
|
|
||||||
|
class TestDumpsysPackageActivitiesArtifact:
|
||||||
|
def test_parsing(self):
|
||||||
|
dpa = DumpsysPackageActivities()
|
||||||
|
file = get_artifact("android_data/dumpsys_packages.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
|
||||||
|
assert len(dpa.results) == 0
|
||||||
|
dpa.parse(data)
|
||||||
|
assert len(dpa.results) == 4
|
||||||
|
assert dpa.results[0]["package_name"] == "com.samsung.android.app.social"
|
||||||
|
assert (
|
||||||
|
dpa.results[0]["activity"]
|
||||||
|
== "com.samsung.android.app.social/.feed.FeedsActivity"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_ioc_check(self, indicator_file):
|
||||||
|
dpa = DumpsysPackageActivities()
|
||||||
|
file = get_artifact("android_data/dumpsys_packages.txt")
|
||||||
|
with open(file) as f:
|
||||||
|
data = f.read()
|
||||||
|
dpa.parse(data)
|
||||||
|
|
||||||
|
ind = Indicators(log=logging.getLogger())
|
||||||
|
ind.parse_stix2(indicator_file)
|
||||||
|
ind.ioc_collections[0]["app_ids"].append("com.google.android.gms")
|
||||||
|
dpa.indicators = ind
|
||||||
|
assert len(dpa.detected) == 0
|
||||||
|
dpa.check_indicators()
|
||||||
|
assert len(dpa.detected) == 1
|
||||||
@@ -9,6 +9,32 @@ Libraries:
|
|||||||
android.test.mock -> (jar) /system/framework/android.test.mock.jar
|
android.test.mock -> (jar) /system/framework/android.test.mock.jar
|
||||||
|
|
||||||
|
|
||||||
|
Activity Resolver Table:
|
||||||
|
Full MIME Types:
|
||||||
|
text/comma-separated-values:
|
||||||
|
18d43d com.samsung.android.messaging/.ui.RcsTransferContent
|
||||||
|
72c6194 com.samsung.android.scloud/.app.ui.drive.activity.UploadToDriveActivity2 (2 filters)
|
||||||
|
a8101e7 com.samsung.android.scloud/.app.ui.drive.activity.UploadToDriveActivity (2 filters)
|
||||||
|
application/com.google.android.gms.car:
|
||||||
|
f6b2432 com.google.android.gms/.car.FirstActivity
|
||||||
|
slide/*:
|
||||||
|
651d583 com.android.bluetooth/.opp.BluetoothOppLauncherActivity (2 filters)
|
||||||
|
application/pkix-cert:
|
||||||
|
6380300 com.android.certinstaller/.CertInstallerMain
|
||||||
|
|
||||||
|
Non-Data Actions:
|
||||||
|
com.samsung.android.intent.SOCIAL_UPDATES_FEED_VIEW:
|
||||||
|
b928288 com.samsung.android.app.social/.feed.FeedsActivity
|
||||||
|
android.settings.FINGERPRINT_SETUP:
|
||||||
|
e573421 com.android.settings/.biometrics.fingerprint.SetupFingerprintEnrollIntroduction
|
||||||
|
com.msc.action.samsungaccount.myinfowebview_external:
|
||||||
|
f897046 com.osp.app.signin/com.samsung.android.samsungaccount.authentication.ui.check.user.MyProfileWebView
|
||||||
|
com.google.android.gms.autofill.ACTION_SETTINGS:
|
||||||
|
9de5207 com.google.android.gms/.autofill.ui.AutofillSettingsPrivacyHubActivity
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Packages:
|
Packages:
|
||||||
Package [com.samsung.android.provider.filterprovider] (d64f8e0):
|
Package [com.samsung.android.provider.filterprovider] (d64f8e0):
|
||||||
|
|||||||
@@ -2,14 +2,15 @@
|
|||||||
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
# Copyright (c) 2021-2023 Claudio Guarnieri.
|
||||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
import pytest
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
from mvt.common.indicators import Indicators
|
from mvt.common.indicators import Indicators
|
||||||
from mvt.common.module import run_module
|
from mvt.common.module import run_module
|
||||||
from mvt.ios.modules.fs.filesystem import Filesystem
|
from mvt.ios.modules.fs.filesystem import Filesystem
|
||||||
|
|
||||||
from ..utils import get_ios_backup_folder, delete_tmp_db_files
|
from ..utils import delete_tmp_db_files, get_ios_backup_folder
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
|
|||||||
Reference in new issue
Block a user