mirror of
https://github.com/mvt-project/mvt.git
synced 2026-04-15 00:08:34 +02:00
Compare commits
3 Commits
fix_sms
...
accessibil
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9bcbaac3a2 | ||
|
|
f26303c930 | ||
|
|
4edab3c4f8 |
@@ -4,6 +4,9 @@
|
||||
|
||||
# Mobile Verification Toolkit
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Soon we will merge the v3 pull request which will result in breaking changes. If you rely on mvt output in other script make sure to the the branch before we merge. More details: https://github.com/mvt-project/mvt/issues/757
|
||||
|
||||
[](https://pypi.org/project/mvt/)
|
||||
[](https://docs.mvt.re/en/latest/?badge=latest)
|
||||
[](https://github.com/mvt-project/mvt/actions/workflows/tests.yml)
|
||||
|
||||
@@ -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, str]])
|
||||
Parse the Dumpsys Accessibility section.
|
||||
Adds results to self.results (List[Dict[str, Any]])
|
||||
|
||||
:param content: content of the accessibility section (string)
|
||||
"""
|
||||
|
||||
# "Old" syntax
|
||||
# Parse installed services
|
||||
in_services = False
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("installed services:"):
|
||||
@@ -39,7 +39,6 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
continue
|
||||
|
||||
if line.strip() == "}":
|
||||
# At end of installed services
|
||||
break
|
||||
|
||||
service = line.split(":")[1].strip()
|
||||
@@ -48,21 +47,66 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
{
|
||||
"package_name": service.split("/")[0],
|
||||
"service": service,
|
||||
"enabled": False,
|
||||
}
|
||||
)
|
||||
|
||||
# "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}}
|
||||
# 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()
|
||||
|
||||
in_enabled = False
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("Enabled services:"):
|
||||
matches = re.finditer(r"{([^{]+?)}", line)
|
||||
stripped = line.strip()
|
||||
|
||||
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:
|
||||
# Each match is in format: <package_name>/<service>
|
||||
package_name, _, service = match.group(1).partition("/")
|
||||
enabled_services.add(match.group(1).strip())
|
||||
|
||||
self.results.append(
|
||||
{"package_name": package_name, "service": service}
|
||||
)
|
||||
# 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,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -49,9 +49,14 @@ class DumpsysAccessibility(DumpsysAccessibilityArtifact, BugReportModule):
|
||||
|
||||
for result in self.results:
|
||||
self.log.info(
|
||||
'Found installed accessibility service "%s"', result.get("service")
|
||||
'Found installed accessibility service "%s" (enabled: %s)',
|
||||
result.get("service"),
|
||||
result.get("enabled", False),
|
||||
)
|
||||
|
||||
enabled_count = sum(1 for r in self.results if r.get("enabled"))
|
||||
self.log.info(
|
||||
"Identified a total of %d accessibility services", len(self.results)
|
||||
"Identified a total of %d accessibility services, %d enabled",
|
||||
len(self.results),
|
||||
enabled_count,
|
||||
)
|
||||
|
||||
@@ -25,6 +25,9 @@ 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()
|
||||
@@ -36,7 +39,32 @@ 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.service.malwareservice"
|
||||
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
|
||||
|
||||
def test_ioc_check(self, indicator_file):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
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