mirror of
https://github.com/mvt-project/mvt.git
synced 2026-04-13 15:28:34 +02:00
Compare commits
2 Commits
accessibil
...
warn-encry
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6342e3261b | ||
|
|
642add21b0 |
@@ -22,13 +22,13 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
|
||||
def parse(self, content: str) -> None:
|
||||
"""
|
||||
Parse the Dumpsys Accessibility section.
|
||||
Adds results to self.results (List[Dict[str, Any]])
|
||||
Parse the Dumpsys Accessibility section/
|
||||
Adds results to self.results (List[Dict[str, str]])
|
||||
|
||||
:param content: content of the accessibility section (string)
|
||||
"""
|
||||
|
||||
# Parse installed services
|
||||
# "Old" syntax
|
||||
in_services = False
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("installed services:"):
|
||||
@@ -39,6 +39,7 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
continue
|
||||
|
||||
if line.strip() == "}":
|
||||
# At end of installed services
|
||||
break
|
||||
|
||||
service = line.split(":")[1].strip()
|
||||
@@ -47,66 +48,21 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
{
|
||||
"package_name": service.split("/")[0],
|
||||
"service": service,
|
||||
"enabled": False,
|
||||
}
|
||||
)
|
||||
|
||||
# Parse enabled services from both old and new formats.
|
||||
#
|
||||
# Old format (multi-line block):
|
||||
# enabled services: {
|
||||
# 0 : com.example/.MyService
|
||||
# }
|
||||
#
|
||||
# New format (single line, AOSP >= 14):
|
||||
# Enabled services:{{com.example/com.example.MyService}, {com.other/com.other.Svc}}
|
||||
enabled_services = set()
|
||||
# "New" syntax - AOSP >= 14 (?)
|
||||
# Looks like:
|
||||
# Enabled services:{{com.azure.authenticator/com.microsoft.brooklyn.module.accessibility.BrooklynAccessibilityService}, {com.agilebits.onepassword/com.agilebits.onepassword.filling.accessibility.FillingAccessibilityService}}
|
||||
|
||||
in_enabled = False
|
||||
for line in content.splitlines():
|
||||
stripped = line.strip()
|
||||
if line.strip().startswith("Enabled services:"):
|
||||
matches = re.finditer(r"{([^{]+?)}", line)
|
||||
|
||||
if in_enabled:
|
||||
if stripped == "}":
|
||||
in_enabled = False
|
||||
continue
|
||||
service = line.split(":")[1].strip()
|
||||
enabled_services.add(service)
|
||||
continue
|
||||
|
||||
if re.match(r"enabled services:\s*\{\s*$", stripped, re.IGNORECASE):
|
||||
# Old multi-line format: "enabled services: {"
|
||||
in_enabled = True
|
||||
continue
|
||||
|
||||
if re.match(r"enabled services:\s*\{", stripped, re.IGNORECASE):
|
||||
# New single-line format: "Enabled services:{{pkg/svc}, {pkg2/svc2}}"
|
||||
matches = re.finditer(r"\{([^{}]+)\}", stripped)
|
||||
for match in matches:
|
||||
enabled_services.add(match.group(1).strip())
|
||||
# Each match is in format: <package_name>/<service>
|
||||
package_name, _, service = match.group(1).partition("/")
|
||||
|
||||
# Mark installed services that are enabled.
|
||||
# Installed service names may include trailing annotations like
|
||||
# "(A11yTool)" that are absent from the enabled services list,
|
||||
# so strip annotations before comparing.
|
||||
def _strip_annotation(s: str) -> str:
|
||||
return re.sub(r"\s+\(.*?\)\s*$", "", s)
|
||||
|
||||
installed_stripped = {
|
||||
_strip_annotation(r["service"]): r for r in self.results
|
||||
}
|
||||
for enabled in enabled_services:
|
||||
if enabled in installed_stripped:
|
||||
installed_stripped[enabled]["enabled"] = True
|
||||
|
||||
# Add enabled services not found in the installed list
|
||||
for service in enabled_services:
|
||||
if service not in installed_stripped:
|
||||
package_name, _, _ = service.partition("/")
|
||||
self.results.append(
|
||||
{
|
||||
"package_name": package_name,
|
||||
"service": service,
|
||||
"enabled": True,
|
||||
}
|
||||
)
|
||||
self.results.append(
|
||||
{"package_name": package_name, "service": service}
|
||||
)
|
||||
|
||||
@@ -49,14 +49,9 @@ class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule):
|
||||
|
||||
for result in self.results:
|
||||
self.log.info(
|
||||
'Found installed accessibility service "%s" (enabled: %s)',
|
||||
result.get("service"),
|
||||
result.get("enabled", False),
|
||||
'Found installed accessibility service "%s"', result.get("service")
|
||||
)
|
||||
|
||||
enabled_count = sum(1 for r in self.results if r.get("enabled"))
|
||||
self.log.info(
|
||||
"Identified a total of %d accessibility services, %d enabled",
|
||||
len(self.results),
|
||||
enabled_count,
|
||||
"Identified a total of %d accessibility services", len(self.results)
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from mvt.common.indicators import Indicators
|
||||
from mvt.common.module import MVTModule, run_module, save_timeline
|
||||
from mvt.common.module import EncryptedBackupError, MVTModule, run_module, save_timeline
|
||||
from mvt.common.utils import (
|
||||
convert_datetime_to_iso,
|
||||
generate_hashes_from_path,
|
||||
@@ -244,7 +244,14 @@ class Command:
|
||||
except NotImplementedError:
|
||||
pass
|
||||
|
||||
run_module(m)
|
||||
try:
|
||||
run_module(m)
|
||||
except EncryptedBackupError:
|
||||
self.log.critical(
|
||||
"The backup appears to be encrypted. "
|
||||
"Please decrypt it first using `mvt-ios decrypt-backup`."
|
||||
)
|
||||
return
|
||||
|
||||
self.executed.append(m)
|
||||
|
||||
|
||||
@@ -21,6 +21,10 @@ class DatabaseCorruptedError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class EncryptedBackupError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InsufficientPrivileges(Exception):
|
||||
pass
|
||||
|
||||
@@ -169,6 +173,8 @@ def run_module(module: MVTModule) -> None:
|
||||
|
||||
try:
|
||||
exec_or_profile("module.run()", globals(), locals())
|
||||
except EncryptedBackupError:
|
||||
raise
|
||||
except NotImplementedError:
|
||||
module.log.exception(
|
||||
"The run() procedure of module %s was not implemented yet!",
|
||||
|
||||
@@ -8,9 +8,10 @@ import io
|
||||
import logging
|
||||
import os
|
||||
import plistlib
|
||||
import sqlite3
|
||||
from typing import Optional
|
||||
|
||||
from mvt.common.module import DatabaseNotFoundError
|
||||
from mvt.common.module import DatabaseNotFoundError, EncryptedBackupError
|
||||
from mvt.common.url import URL
|
||||
from mvt.common.utils import convert_datetime_to_iso, convert_unix_to_iso
|
||||
|
||||
@@ -127,7 +128,14 @@ class Manifest(IOSExtraction):
|
||||
conn = self._open_sqlite_db(manifest_db_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("SELECT * FROM Files;")
|
||||
try:
|
||||
cur.execute("SELECT * FROM Files;")
|
||||
except sqlite3.DatabaseError:
|
||||
conn.close()
|
||||
raise EncryptedBackupError(
|
||||
"Manifest.db is not a valid SQLite database. "
|
||||
"The backup may be encrypted."
|
||||
)
|
||||
names = [description[0] for description in cur.description]
|
||||
|
||||
for file_entry in cur:
|
||||
|
||||
@@ -25,9 +25,6 @@ class TestDumpsysAccessibilityArtifact:
|
||||
da.results[0]["service"]
|
||||
== "com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor"
|
||||
)
|
||||
# All services are installed but none enabled in this fixture
|
||||
for result in da.results:
|
||||
assert result["enabled"] is False
|
||||
|
||||
def test_parsing_v14_aosp_format(self):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
@@ -39,32 +36,7 @@ class TestDumpsysAccessibilityArtifact:
|
||||
da.parse(data)
|
||||
assert len(da.results) == 1
|
||||
assert da.results[0]["package_name"] == "com.malware.accessibility"
|
||||
assert (
|
||||
da.results[0]["service"]
|
||||
== "com.malware.accessibility/com.malware.service.malwareservice"
|
||||
)
|
||||
assert da.results[0]["enabled"] is True
|
||||
|
||||
def test_parsing_installed_and_enabled(self):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
file = get_artifact("android_data/dumpsys_accessibility_enabled.txt")
|
||||
with open(file) as f:
|
||||
data = f.read()
|
||||
|
||||
assert len(da.results) == 0
|
||||
da.parse(data)
|
||||
assert len(da.results) == 5
|
||||
|
||||
enabled = [r for r in da.results if r["enabled"]]
|
||||
assert len(enabled) == 1
|
||||
assert enabled[0]["package_name"] == "com.samsung.accessibility"
|
||||
assert (
|
||||
enabled[0]["service"]
|
||||
== "com.samsung.accessibility/.universalswitch.UniversalSwitchService (A11yTool)"
|
||||
)
|
||||
|
||||
not_enabled = [r for r in da.results if not r["enabled"]]
|
||||
assert len(not_enabled) == 4
|
||||
assert da.results[0]["service"] == "com.malware.service.malwareservice"
|
||||
|
||||
def test_ioc_check(self, indicator_file):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
ACCESSIBILITY MANAGER (dumpsys accessibility)
|
||||
|
||||
currentUserId=0
|
||||
User state[
|
||||
attributes:{id=0, touchExplorationEnabled=false, installedServiceCount=5}
|
||||
installed services: {
|
||||
0 : com.google.android.apps.accessibility.voiceaccess/.JustSpeakService (A11yTool)
|
||||
1 : com.microsoft.appmanager/com.microsoft.mmx.screenmirroringsrc.accessibility.ScreenMirroringAccessibilityService
|
||||
2 : com.samsung.accessibility/.assistantmenu.serviceframework.AssistantMenuService (A11yTool)
|
||||
3 : com.samsung.accessibility/.universalswitch.UniversalSwitchService (A11yTool)
|
||||
4 : com.samsung.android.accessibility.talkback/com.samsung.android.marvin.talkback.TalkBackService (A11yTool)
|
||||
}
|
||||
Bound services:{}
|
||||
Enabled services:{{com.samsung.accessibility/.universalswitch.UniversalSwitchService}}
|
||||
Binding services:{}
|
||||
Crashed services:{}
|
||||
Reference in New Issue
Block a user