Skip to content

Commit

Permalink
Add a module to parse uninstalled apps from dumpsys data, for both bu…
Browse files Browse the repository at this point in the history
…greport and AndroidQF output, and match them against package name IoCs.
  • Loading branch information
tesitura committed Dec 11, 2024
1 parent 22fce28 commit 9d81b5b
Show file tree
Hide file tree
Showing 12 changed files with 257 additions and 2 deletions.
44 changes: 44 additions & 0 deletions src/mvt/android/artifacts/dumpsys_platform_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# 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 DumpsysPlatformCompatArtifact(AndroidArtifact):
"""
Parser for uninstalled apps listed in platform_compat section.
"""

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 not line.startswith("ChangeId(168419799; name=DOWNSCALED;"):
continue

if line.strip() == "":
break

# Look for rawOverrides field
if "rawOverrides={" in line:
# Extract the content inside the braces for rawOverrides
overrides_field = line.split("rawOverrides={", 1)[1].split("};", 1)[0]

for entry in overrides_field.split(", "):
# Extract app name
uninstall_app = entry.split("=")[0].strip()

self.results.append(
{"package_name": uninstall_app}
)
2 changes: 2 additions & 0 deletions src/mvt/android/modules/androidqf/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .dumpsys_adb import DumpsysADBState
from .getprop import Getprop
from .packages import Packages
from .dumpsys_platform_compat import DumpsysPlatformCompat
from .processes import Processes
from .settings import Settings
from .sms import SMS
Expand All @@ -29,6 +30,7 @@
DumpsysBatteryHistory,
DumpsysADBState,
Packages,
DumpsysPlatformCompat,
Processes,
Getprop,
Settings,
Expand Down
44 changes: 44 additions & 0 deletions src/mvt/android/modules/androidqf/dumpsys_platform_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# 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_platform_compat import DumpsysPlatformCompatArtifact

from .base import AndroidQFModule


class DumpsysPlatformCompat(DumpsysPlatformCompatArtifact, AndroidQFModule):
"""This module extracts details on uninstalled apps."""

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

data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
self.parse(content)

self.log.info("Found %d uninstalled apps", len(self.results))
2 changes: 2 additions & 0 deletions src/mvt/android/modules/bugreport/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from .dbinfo import DBInfo
from .getprop import Getprop
from .packages import Packages
from .platform_compat import PlatformCompat
from .receivers import Receivers
from .adb_state import DumpsysADBState

Expand All @@ -23,6 +24,7 @@
DBInfo,
Getprop,
Packages,
PlatformCompat,
Receivers,
DumpsysADBState,
]
48 changes: 48 additions & 0 deletions src/mvt/android/modules/bugreport/platform_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# 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_platform_compat import DumpsysPlatformCompatArtifact

from mvt.android.modules.bugreport.base import BugReportModule


class PlatformCompat(DumpsysPlatformCompatArtifact, BugReportModule):
"""This module extracts details on uninstalled apps."""

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:
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

data = data.decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE platform_compat:")
self.parse(content)

self.log.info("Found %d uninstalled apps", len(self.results))
1 change: 1 addition & 0 deletions src/mvt/common/indicators.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ def load_indicators_files(

def get_iocs(self, ioc_type: str) -> Iterator[Dict[str, Any]]:
for ioc_collection in self.ioc_collections:

for ioc in ioc_collection.get(ioc_type, []):
yield {
"value": ioc,
Expand Down
40 changes: 40 additions & 0 deletions tests/android/test_artifact_dumpsys_platform_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# 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_platform_compat import DumpsysPlatformCompatArtifact
from mvt.common.indicators import Indicators

from ..utils import get_artifact


class TestDumpsysPlatformCompatArtifact:
def test_parsing(self):
dbi = DumpsysPlatformCompatArtifact()
file = get_artifact("android_data/dumpsys_platform_compat.txt")
with open(file) as f:
data = f.read()

assert len(dbi.results) == 0
dbi.parse(data)
assert len(dbi.results) == 2
assert dbi.results[0]["package_name"] == "org.torproject.torbrowser"
assert dbi.results[1]["package_name"] == "org.article19.circulo.next"

def test_ioc_check(self, indicator_file):
dbi = DumpsysPlatformCompatArtifact()
file = get_artifact("android_data/dumpsys_platform_compat.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("org.torproject.torbrowser")
ind.ioc_collections[0]["app_ids"].append("org.article19.circulo.next")
dbi.indicators = ind
assert len(dbi.detected) == 0
dbi.check_indicators()
assert len(dbi.detected) == 2
23 changes: 23 additions & 0 deletions tests/android_androidqf/test_dumpsys_platform_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Mobile Verification Toolkit (MVT)
# Copyright (c) 2021-2023 The MVT Authors.
# 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_platform_compat import DumpsysPlatformCompat
from mvt.common.module import run_module

from ..utils import get_android_androidqf, list_files


class TestDumpsysPlatformCompatModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysPlatformCompat(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) == 2
assert len(m.detected) == 0
19 changes: 18 additions & 1 deletion tests/artifacts/android_data/bugreport/dumpstate.txt
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,23 @@ Packages:
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivity
com.instagram.share.handleractivity.ClipsShareHandlerActivity
com.instagram.direct.share.handler.DirectMultipleExternalMediaShareActivityInterop

--------- 0.053s was the duration of dumpsys appops, ending at: 2022-03-29 23:14:27
-------------------------------------------------------------------------------
DUMP OF SERVICE platform_compat:
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)


16 changes: 16 additions & 0 deletions tests/artifacts/android_data/dumpsys_platform_compat.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
DUMP OF SERVICE platform_compat:
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)
18 changes: 18 additions & 0 deletions tests/artifacts/androidqf/dumpsys.txt
Original file line number Diff line number Diff line change
Expand Up @@ -379,4 +379,22 @@ Daily stats:
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623
--------- 0.053s was the duration of dumpsys batterystats, ending at: 2024-03-21 11:07:22
-------------------------------------------------------------------------------
DUMP OF SERVICE platform_compat:
ChangeId(180326845; name=OVERRIDE_MIN_ASPECT_RATIO_MEDIUM; disabled; overridable)
ChangeId(189969744; name=DOWNSCALE_65; disabled; overridable)
ChangeId(183372781; name=ENABLE_RAW_SYSTEM_GALLERY_ACCESS; enableSinceTargetSdk=30)
ChangeId(150939131; name=ADD_CONTENT_OBSERVER_FLAGS; enableSinceTargetSdk=30)
ChangeId(226439802; name=SCHEDULE_EXACT_ALARM_DENIED_BY_DEFAULT; disabled)
ChangeId(270674727; name=ENABLE_STRICT_FORMATTER_VALIDATION; enableSinceTargetSdk=35)
ChangeId(183155436; name=ALWAYS_USE_CONTEXT_USER; enableSinceTargetSdk=33)
ChangeId(303742236; name=ROLE_MANAGER_USER_HANDLE_AWARE; enableSinceTargetSdk=35)
ChangeId(203800354; name=MEDIA_CONTROL_SESSION_ACTIONS; enableSinceTargetSdk=33)
ChangeId(144027538; name=BLOCK_GPS_STATUS_USAGE; enableSinceTargetSdk=31)
ChangeId(189969749; name=DOWNSCALE_35; disabled; overridable)
ChangeId(143539591; name=SELINUX_LATEST_CHANGES; disabled)
ChangeId(247079863; name=DISALLOW_INVALID_GROUP_REFERENCE; enableSinceTargetSdk=34)
ChangeId(174227820; name=FORCE_DISABLE_HEVC_SUPPORT; disabled)
ChangeId(168419799; name=DOWNSCALED; disabled; packageOverrides={com.google.android.apps.tachyon=false, org.torproject.torbrowser=false}; rawOverrides={org.torproject.torbrowser=false, org.article19.circulo.next=false}; overridable)

2 changes: 1 addition & 1 deletion tests/common/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ def test_hash_from_folder(self):
# This needs to be updated when we add or edit files in AndroidQF folder
assert (
hashes[1]["sha256"]
== "1bd255f656a7f9d5647a730f0f0cc47053115576f11532d41bf28c16635b193d"
== "9fb6396b64cfff30e2a459a64496d3c1386926d09edd68be2d878de45fa7b3a9"
)


Expand Down

0 comments on commit 9d81b5b

Please sign in to comment.