Compare commits

...

17 Commits

Author SHA1 Message Date
tek
c719c4da1e Bumps version 2023-09-05 13:01:20 +02:00
tek
0f3e93c152 Adds missing iphone models 2023-09-05 12:53:19 +02:00
tek
a2ee46b8f8 Refactors dumpsys receiver parsing into an artifact 2023-08-08 20:23:09 +02:00
tek
e60e5fdc6e Refactors DumpsysBatteryHistory and adds related androidqf module 2023-08-04 19:20:14 +02:00
tek
7e0e071c5d Refactor DumpsysBatteryDaily module and add related artifact 2023-08-04 16:17:52 +02:00
Nex
b259db30f8 Added missing empty lines 2023-08-03 08:06:59 +02:00
Donncha Ó Cearbhaill
26f981244d Merge pull request #380 from a-sdi/patch-1
Update applications.py to add extra valid source
2023-08-02 20:03:30 +02:00
Donncha Ó Cearbhaill
2069e2b760 Fix style error (need space after # in comment) 2023-08-02 19:57:26 +02:00
a-sdi
355480414f Update applications.py
Some apps installed from apple store with sourceApp "com.apple.AppStore.ProductPageExtension"
2023-08-02 19:26:06 +03:00
tek
9a831b5930 Adds GlobalPreferences iOS module 2023-08-02 15:28:16 +02:00
tek
a103b50759 Rename artifacts to avoid name collisions 2023-08-02 13:32:58 +02:00
tek
84dc13144d Refactor DumpsysAppOps 2023-08-01 11:58:20 +02:00
tek
6356a4ff87 Refactor code of DumpsysDBInfo 2023-07-31 23:43:20 +02:00
tek
f96f2fe34a refactor dumpsys package activity code 2023-07-31 18:38:41 +02:00
Donncha Ó Cearbhaill
ae0e470c56 Fix inconsisent filesytem tests on some platforms 2023-07-31 11:45:53 +02:00
tek
4c175530a8 Refactor dumpsys accessibility in an artifact 2023-07-27 19:42:06 +02:00
Donncha Ó Cearbhaill
ecf75447aa Only add coverage comment to pull requests 2023-07-27 17:44:18 +02:00
67 changed files with 1746 additions and 1165 deletions

View File

@@ -43,6 +43,7 @@ jobs:
run: pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
- name: Pytest coverage comment
uses: MishaKav/pytest-coverage-comment@main
if: github.event_name == 'pull_request'
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./pytest.xml

View File

@@ -142,6 +142,16 @@ If indicators are provided through the command-line, they are checked against th
---
### `global_preferences.json`
!!! info "Availability"
Backup: :material-check:
Full filesystem dump: :material-check:
This JSON file is created by mvt-ios' `GlobalPreferences` module. The module extracts records from a Plist file located at */private/var/mobile/Library/Preferences/.GlobalPreferences.plist*, which contains a system preferences including if Lockdown Mode is enabled.
---
### `id_status_cache.json`
!!! info "Availability"

View File

@@ -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/

View File

@@ -2,8 +2,35 @@
# 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 mvt.common.artifact import Artifact
class AndroidArtifact(Artifact):
pass
@staticmethod
def extract_dumpsys_section(dumpsys: str, separator: str) -> str:
"""
Extract a section from a full dumpsys file.
:param dumpsys: content of the full dumpsys file (string)
:param separator: content of the first line separator (string)
:return: section extracted (string)
"""
lines = []
in_section = False
for line in dumpsys.splitlines():
if line.strip() == separator:
in_section = True
continue
if not in_section:
continue
if line.strip().startswith(
"------------------------------------------------------------------------------"
):
break
lines.append(line)
return "\n".join(lines)

View File

@@ -0,0 +1,47 @@
# 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 DumpsysAccessibilityArtifact(AndroidArtifact):
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, content: str) -> None:
"""
Parse the Dumpsys Accessibility section/
Adds results to self.results (List[Dict[str, str]])
:param content: content of the accessibility section (string)
"""
in_services = False
for line in content.splitlines():
if line.strip().startswith("installed services:"):
in_services = True
continue
if not in_services:
continue
if line.strip() == "}":
break
service = line.split(":")[1].strip()
self.results.append(
{
"package_name": service.split("/")[0],
"service": service,
}
)

View File

@@ -0,0 +1,150 @@
# 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 datetime import datetime
from typing import Any, Dict, List, Union
from mvt.common.utils import convert_datetime_to_iso
from .artifact import AndroidArtifact
class DumpsysAppopsArtifact(AndroidArtifact):
"""
Parser for dumpsys app ops info
"""
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append(
{
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to "
f"{perm['name']}: {entry['access']}",
}
)
return records
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if (
perm["name"] == "REQUEST_INSTALL_PACKAGES"
and perm["access"] == "allow"
):
self.log.info(
"Package %s with REQUEST_INSTALL_PACKAGES " "permission",
result["package_name"],
)
def parse(self, output: str) -> None:
self.results: List[Dict[str, Any]] = []
perm = {}
package = {}
entry = {}
uid = None
in_packages = False
for line in output.splitlines():
if line.startswith(" Uid 0:"):
in_packages = True
if not in_packages:
continue
if line.startswith(" Uid "):
uid = line[6:-1]
if entry:
perm["entries"].append(entry)
entry = {}
if package:
if perm:
package["permissions"].append(perm)
perm = {}
self.results.append(package)
package = {}
continue
if line.startswith(" Package "):
if entry:
perm["entries"].append(entry)
entry = {}
if package:
if perm:
package["permissions"].append(perm)
perm = {}
self.results.append(package)
package = {
"package_name": line[12:-1],
"permissions": [],
"uid": uid,
}
continue
if package and line.startswith(" ") and line[6] != " ":
if entry:
perm["entries"].append(entry)
entry = {}
if perm:
package["permissions"].append(perm)
perm = {}
perm["name"] = line.split()[0]
perm["entries"] = []
if len(line.split()) > 1:
perm["access"] = line.split()[1][1:-2]
continue
if line.startswith(" "):
# Permission entry like:
# Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
if entry:
perm["entries"].append(entry)
entry = {}
entry["access"] = line.split(":")[0].strip()
entry["type"] = line[line.find("[") + 1 : line.find("]")]
try:
entry["timestamp"] = convert_datetime_to_iso(
datetime.strptime(
line[line.find("]") + 1 : line.find("(")].strip(),
"%Y-%m-%d %H:%M:%S.%f",
)
)
except ValueError:
# Invalid date format
pass
if line.strip() == "":
break
if entry:
perm["entries"].append(entry)
if perm:
package["permissions"].append(perm)
if package:
self.results.append(package)

View File

@@ -0,0 +1,78 @@
# 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 typing import Union
from .artifact import AndroidArtifact
class DumpsysBatteryDailyArtifact(AndroidArtifact):
"""
Parser for dumpsys dattery daily updates.
"""
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
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, output: str) -> None:
daily = None
daily_updates = []
for line in output.splitlines():
if line.startswith(" Daily from "):
if len(daily_updates) > 0:
self.results.extend(daily_updates)
daily_updates = []
timeframe = line[13:].strip()
date_from, date_to = timeframe.strip(":").split(" to ", 1)
daily = {"from": date_from[0:10], "to": date_to[0:10]}
continue
if not daily:
continue
if not line.strip().startswith("Update "):
continue
line = line.strip().replace("Update ", "")
package_name, vers = line.split(" ", 1)
vers_nr = vers.split("=", 1)[1]
already_seen = False
for update in daily_updates:
if package_name == update["package_name"] and vers_nr == update["vers"]:
already_seen = True
break
if not already_seen:
daily_updates.append(
{
"action": "update",
"from": daily["from"],
"to": daily["to"],
"package_name": package_name,
"vers": vers_nr,
}
)
if len(daily_updates) > 0:
self.results.extend(daily_updates)

View File

@@ -0,0 +1,78 @@
# 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 DumpsysBatteryHistoryArtifact(AndroidArtifact):
"""
Parser for dumpsys dattery history events.
"""
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 line.startswith("Battery History "):
continue
if line.strip() == "":
break
time_elapsed = line.strip().split(" ", 1)[0]
event = ""
if line.find("+job") > 0:
event = "start_job"
uid = line[line.find("+job") + 5 : line.find(":")]
service = line[line.find(":") + 1 :].strip('"')
package_name = service.split("/")[0]
elif line.find("-job") > 0:
event = "end_job"
uid = line[line.find("-job") + 5 : line.find(":")]
service = line[line.find(":") + 1 :].strip('"')
package_name = service.split("/")[0]
elif line.find("+running +wake_lock=") > 0:
uid = line[line.find("+running +wake_lock=") + 21 : line.find(":")]
event = "wake"
service = (
line[line.find("*walarm*:") + 9 :].split(" ")[0].strip('"').strip()
)
if service == "" or "/" not in service:
continue
package_name = service.split("/")[0]
elif (line.find("+top=") > 0) or (line.find("-top") > 0):
if line.find("+top=") > 0:
event = "start_top"
top_pos = line.find("+top=")
else:
event = "end_top"
top_pos = line.find("-top=")
colon_pos = top_pos + line[top_pos:].find(":")
uid = line[top_pos + 5 : colon_pos]
service = ""
package_name = line[colon_pos + 1 :].strip('"')
else:
continue
self.results.append(
{
"time_elapsed": time_elapsed,
"event": event,
"uid": uid,
"package_name": package_name,
"service": service,
}
)

View File

@@ -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/
import re
from .artifact import AndroidArtifact
class DumpsysDBInfoArtifact(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,
}
)

View File

@@ -0,0 +1,84 @@
# 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 DumpsysPackageActivitiesArtifact(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,
}
)

View File

@@ -0,0 +1,116 @@
# 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
INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
INTENT_PHONE_STATE = "android.intent.action.PHONE_STATE"
INTENT_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"
class DumpsysReceiversArtifact(AndroidArtifact):
"""
Parser for dumpsys receivers in the package section
"""
def check_indicators(self) -> None:
for intent, receivers in self.results.items():
for receiver in receivers:
if intent == INTENT_NEW_OUTGOING_SMS:
self.log.info(
'Found a receiver to intercept outgoing SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_DATA_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming data SMS message: "%s"',
receiver["receiver"],
)
elif intent == INTENT_PHONE_STATE:
self.log.info(
"Found a receiver monitoring "
'telephony state/incoming calls: "%s"',
receiver["receiver"],
)
elif intent == INTENT_NEW_OUTGOING_CALL:
self.log.info(
'Found a receiver monitoring outgoing calls: "%s"',
receiver["receiver"],
)
if not self.indicators:
continue
ioc = self.indicators.check_app_id(receiver["package_name"])
if ioc:
receiver["matched_indicator"] = ioc
self.detected.append({intent: receiver})
continue
def parse(self, output: str) -> None:
self.results = {}
in_receiver_resolver_table = False
in_non_data_actions = False
intent = None
for line in output.splitlines():
if line.startswith("Receiver Resolver Table:"):
in_receiver_resolver_table = True
continue
if not in_receiver_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(":", "")
self.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.
receiver = line.strip().split(" ")[1]
package_name = receiver.split("/")[0]
self.results[intent].append(
{
"package_name": package_name,
"receiver": receiver,
}
)

View File

@@ -2,6 +2,7 @@
# 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 typing import Dict, List

View File

@@ -2,6 +2,7 @@
# 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

View File

@@ -2,6 +2,7 @@
# 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
ANDROID_DANGEROUS_SETTINGS = [

View File

@@ -6,12 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_accessibility
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
from .base import AndroidExtraction
class DumpsysAccessibility(AndroidExtraction):
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidExtraction):
"""This module extracts stats on accessibility."""
def __init__(
@@ -32,23 +32,12 @@ class DumpsysAccessibility(AndroidExtraction):
results=results,
)
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 run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys accessibility")
self._adb_disconnect()
self.results = parse_dumpsys_accessibility(output)
self.parse(output)
for result in self.results:
self.log.info(

View File

@@ -6,12 +6,14 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
from mvt.android.artifacts.dumpsys_package_activities import (
DumpsysPackageActivitiesArtifact,
)
from .base import AndroidExtraction
class DumpsysActivities(AndroidExtraction):
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidExtraction):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -32,25 +34,12 @@ class DumpsysActivities(AndroidExtraction):
results=results,
)
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
self.results = results if results else []
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys package")
self._adb_disconnect()
self.parse(output)
self.results = parse_dumpsys_activity_resolver_table(output)
self.log.info("Extracted activities for %d intents", len(self.results))
self.log.info("Extracted %d package activities", len(self.results))

View File

@@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers.dumpsys import parse_dumpsys_appops
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import AndroidExtraction
class DumpsysAppOps(AndroidExtraction):
class DumpsysAppOps(DumpsysAppopsArtifact, AndroidExtraction):
"""This module extracts records from App-op Manager."""
slug = "dumpsys_appops"
@@ -34,51 +34,12 @@ class DumpsysAppOps(AndroidExtraction):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append(
{
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to "
f"{perm['name']}: {entry['access']}",
}
)
return records
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if (
perm["name"] == "REQUEST_INSTALL_PACKAGES"
and perm["access"] == "allow"
):
self.log.info(
"Package %s with REQUEST_INSTALL_PACKAGES " "permission",
result["package_name"],
)
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys appops")
self._adb_disconnect()
self.results = parse_dumpsys_appops(output)
self.parse(output)
self.log.info(
"Extracted a total of %d records from app-ops manager", len(self.results)

View File

@@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_daily
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import AndroidExtraction
class DumpsysBatteryDaily(AndroidExtraction):
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, AndroidExtraction):
"""This module extracts records from battery daily updates."""
def __init__(
@@ -32,32 +32,12 @@ class DumpsysBatteryDaily(AndroidExtraction):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
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 run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys batterystats --daily")
self._adb_disconnect()
self.results = parse_dumpsys_battery_daily(output)
self.parse(output)
self.log.info(
"Extracted %d records from battery daily stats", len(self.results)

View File

@@ -6,12 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_history
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
from .base import AndroidExtraction
class DumpsysBatteryHistory(AndroidExtraction):
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, AndroidExtraction):
"""This module extracts records from battery history events."""
def __init__(
@@ -32,22 +32,11 @@ class DumpsysBatteryHistory(AndroidExtraction):
results=results,
)
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 run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys batterystats --history")
self._adb_disconnect()
self.results = parse_dumpsys_battery_history(output)
self.parse(output)
self.log.info("Extracted %d records from battery history", len(self.results))

View File

@@ -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 DumpsysDBInfoArtifact
from .base import AndroidExtraction
class DumpsysDBInfo(AndroidExtraction):
class DumpsysDBInfo(DumpsysDBInfoArtifact, 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",

View File

@@ -6,18 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import AndroidExtraction
INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
INTENT_PHONE_STATE = "android.intent.action.PHONE_STATE"
INTENT_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"
class DumpsysReceivers(AndroidExtraction):
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidExtraction):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -40,49 +34,11 @@ class DumpsysReceivers(AndroidExtraction):
self.results = results if results else {}
def check_indicators(self) -> None:
if not self.indicators:
return
for intent, receivers in self.results.items():
for receiver in receivers:
if intent == INTENT_NEW_OUTGOING_SMS:
self.log.info(
'Found a receiver to intercept outgoing SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_DATA_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming data SMS message: "%s"',
receiver["receiver"],
)
elif intent == INTENT_PHONE_STATE:
self.log.info(
"Found a receiver monitoring "
'telephony state/incoming calls: "%s"',
receiver["receiver"],
)
elif intent == INTENT_NEW_OUTGOING_CALL:
self.log.info(
'Found a receiver monitoring outgoing calls: "%s"',
receiver["receiver"],
)
ioc = self.indicators.check_app_id(receiver["package_name"])
if ioc:
receiver["matched_indicator"] = ioc
self.detected.append({intent: receiver})
continue
def run(self) -> None:
self._adb_connect()
output = self._adb_command("dumpsys package")
self.results = parse_dumpsys_receiver_resolver_table(output)
self.parse(output)
self._adb_disconnect()
self.log.info("Extracted receivers for %d intents", len(self.results))

View File

@@ -6,6 +6,9 @@
from .dumpsys_accessibility import DumpsysAccessibility
from .dumpsys_activities import DumpsysActivities
from .dumpsys_appops import DumpsysAppops
from .dumpsys_battery_daily import DumpsysBatteryDaily
from .dumpsys_battery_history import DumpsysBatteryHistory
from .dumpsys_dbinfo import DumpsysDBInfo
from .dumpsys_packages import DumpsysPackages
from .dumpsys_receivers import DumpsysReceivers
from .getprop import Getprop
@@ -18,6 +21,9 @@ ANDROIDQF_MODULES = [
DumpsysReceivers,
DumpsysAccessibility,
DumpsysAppops,
DumpsysDBInfo,
DumpsysBatteryDaily,
DumpsysBatteryHistory,
Processes,
Getprop,
Settings,

View File

@@ -6,12 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_accessibility
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
from .base import AndroidQFModule
class DumpsysAccessibility(AndroidQFModule):
class DumpsysAccessibility(DumpsysAccessibilityArtifact, AndroidQFModule):
"""This module analyse dumpsys accessbility"""
def __init__(
@@ -32,40 +32,14 @@ class DumpsysAccessibility(AndroidQFModule):
results=results,
)
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)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_accessibility = False
data = self._get_file_content(dumpsys_file[0])
for line in data.decode("utf-8").split("\n"):
if line.strip().startswith("DUMP OF SERVICE accessibility:"):
in_accessibility = True
continue
if not in_accessibility:
continue
if line.strip().startswith(
"-------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_accessibility("\n".join(lines))
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE accessibility:")
self.parse(content)
for result in self.results:
self.log.info(

View File

@@ -6,12 +6,14 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
from mvt.android.artifacts.dumpsys_package_activities import (
DumpsysPackageActivitiesArtifact,
)
from .base import AndroidQFModule
class DumpsysActivities(AndroidQFModule):
class DumpsysActivities(DumpsysPackageActivitiesArtifact, AndroidQFModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -32,42 +34,17 @@ class DumpsysActivities(AndroidQFModule):
results=results,
)
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})
self.results = results if results else []
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_package = False
data = self._get_file_content(dumpsys_file[0])
for line in data.decode("utf-8").split("\n"):
if line.strip() == "DUMP OF SERVICE package:":
in_package = True
continue
# Get data and extract the dumpsys section
data = self._get_file_content(dumpsys_file[0]).decode("utf-8", errors="replace")
content = self.extract_dumpsys_section(data, "DUMP OF SERVICE package:")
# Parse it
self.parse(content)
if not in_package:
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))
self.log.info("Extracted %d package activities", len(self.results))

View File

@@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_appops
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import AndroidQFModule
class DumpsysAppops(AndroidQFModule):
class DumpsysAppops(DumpsysAppopsArtifact, AndroidQFModule):
def __init__(
self,
file_path: Optional[str] = None,
@@ -30,65 +30,17 @@ class DumpsysAppops(AndroidQFModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append(
{
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to "
f"{perm['name']} : {entry['access']}",
}
)
return records
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if (
perm["name"] == "REQUEST_INSTALL_PACKAGES"
and perm["access"] == "allow"
):
self.log.info(
"Package %s with REQUEST_INSTALL_PACKAGES permission",
result["package_name"],
)
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
lines = []
in_package = False
# Extract section
data = self._get_file_content(dumpsys_file[0])
for line in data.decode("utf-8").split("\n"):
if line.startswith("DUMP OF SERVICE appops:"):
in_package = True
continue
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE appops:"
)
if in_package:
if line.startswith(
"-------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_appops("\n".join(lines))
# Parse it
self.parse(section)
self.log.info("Identified %d applications in AppOps Manager", len(self.results))

View 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_battery_daily import DumpsysBatteryDailyArtifact
from .base import AndroidQFModule
class DumpsysBatteryDaily(DumpsysBatteryDailyArtifact, 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 section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
# Parse it
self.parse(section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))

View 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_battery_history import DumpsysBatteryHistoryArtifact
from .base import AndroidQFModule
class DumpsysBatteryHistory(DumpsysBatteryHistoryArtifact, 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 section
data = self._get_file_content(dumpsys_file[0])
section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
# Parse it
self.parse(section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))

View 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 DumpsysDBInfoArtifact
from .base import AndroidQFModule
class DumpsysDBInfo(DumpsysDBInfoArtifact, 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))

View File

@@ -6,19 +6,12 @@
import logging
from typing import Any, Dict, List, Optional, Union
from mvt.android.modules.adb.dumpsys_receivers import (
INTENT_DATA_SMS_RECEIVED,
INTENT_NEW_OUTGOING_CALL,
INTENT_NEW_OUTGOING_SMS,
INTENT_PHONE_STATE,
INTENT_SMS_RECEIVED,
)
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import AndroidQFModule
class DumpsysReceivers(AndroidQFModule):
class DumpsysReceivers(DumpsysReceiversArtifact, AndroidQFModule):
"""This module analyse dumpsys receivers"""
def __init__(
@@ -41,67 +34,16 @@ class DumpsysReceivers(AndroidQFModule):
self.results = results if results else {}
def check_indicators(self) -> None:
if not self.indicators:
return
for intent, receivers in self.results.items():
for receiver in receivers:
if intent == INTENT_NEW_OUTGOING_SMS:
self.log.info(
'Found a receiver to intercept outgoing SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_DATA_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming data SMS message: "%s"',
receiver["receiver"],
)
elif intent == INTENT_PHONE_STATE:
self.log.info(
"Found a receiver monitoring "
'telephony state/incoming calls: "%s"',
receiver["receiver"],
)
elif intent == INTENT_NEW_OUTGOING_CALL:
self.log.info(
'Found a receiver monitoring outgoing calls: "%s"',
receiver["receiver"],
)
ioc = self.indicators.check_app_id(receiver["package_name"])
if ioc:
receiver["matched_indicator"] = ioc
self.detected.append({intent: receiver})
def run(self) -> None:
dumpsys_file = self._get_files_by_pattern("*/dumpsys.txt")
if not dumpsys_file:
return
in_receivers = False
lines = []
data = self._get_file_content(dumpsys_file[0])
for line in data.decode("utf-8").split("\n"):
if line.strip() == "DUMP OF SERVICE package:":
in_receivers = True
continue
if not in_receivers:
continue
dumpsys_section = self.extract_dumpsys_section(
data.decode("utf-8", errors="replace"), "DUMP OF SERVICE package:"
)
if line.strip().startswith(
"------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line.rstrip())
self.results = parse_dumpsys_receiver_resolver_table("\n".join(lines))
self.parse(dumpsys_section)
self.log.info("Extracted receivers for %d intents", len(self.results))

View File

@@ -6,12 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_accessibility
from mvt.android.artifacts.dumpsys_accessibility import DumpsysAccessibilityArtifact
from .base import BugReportModule
class Accessibility(BugReportModule):
class Accessibility(DumpsysAccessibilityArtifact, BugReportModule):
"""This module extracts stats on accessibility."""
def __init__(
@@ -32,44 +32,21 @@ class Accessibility(BugReportModule):
results=results,
)
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 run(self) -> None:
content = self._get_dumpstate_file()
if not content:
full_dumpsys = self._get_dumpstate_file()
if not full_dumpsys:
self.log.error(
"Unable to find dumpstate file. "
"Did you provide a valid bug report archive?"
)
return
lines = []
in_accessibility = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE accessibility:":
in_accessibility = True
continue
content = self.extract_dumpsys_section(
full_dumpsys.decode("utf-8", errors="ignore"),
"DUMP OF SERVICE accessibility:",
)
self.parse(content)
if not in_accessibility:
continue
if line.strip().startswith(
"------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line)
self.results = parse_dumpsys_accessibility("\n".join(lines))
for result in self.results:
self.log.info(
'Found installed accessibility service "%s"', result.get("service")

View File

@@ -6,12 +6,14 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_activity_resolver_table
from mvt.android.artifacts.dumpsys_package_activities import (
DumpsysPackageActivitiesArtifact,
)
from .base import BugReportModule
class Activities(BugReportModule):
class Activities(DumpsysPackageActivitiesArtifact, BugReportModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -32,19 +34,7 @@ class Activities(BugReportModule):
results=results,
)
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
self.results = results if results else []
def run(self) -> None:
content = self._get_dumpstate_file()
@@ -55,23 +45,12 @@ class Activities(BugReportModule):
)
return
lines = []
in_package = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE package:":
in_package = True
continue
# Extract package section
section = self.extract_dumpsys_section(
content.decode("utf-8", errors="ignore"), "DUMP OF SERVICE package:"
)
if not in_package:
continue
# Parse
self.parse(section)
if line.strip().startswith(
"------------------------------------------------------------------------------"
): # 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))
self.log.info("Extracted %d package activities", len(self.results))

View File

@@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_appops
from mvt.android.artifacts.dumpsys_appops import DumpsysAppopsArtifact
from .base import BugReportModule
class Appops(BugReportModule):
class Appops(DumpsysAppopsArtifact, BugReportModule):
"""This module extracts information on package from App-Ops Manager."""
def __init__(
@@ -32,45 +32,6 @@ class Appops(BugReportModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
records = []
for perm in record["permissions"]:
if "entries" not in perm:
continue
for entry in perm["entries"]:
if "timestamp" in entry:
records.append(
{
"timestamp": entry["timestamp"],
"module": self.__class__.__name__,
"event": entry["access"],
"data": f"{record['package_name']} access to "
f"{perm['name']}: {entry['access']}",
}
)
return records
def check_indicators(self) -> None:
for result in self.results:
if self.indicators:
ioc = self.indicators.check_app_id(result.get("package_name"))
if ioc:
result["matched_indicator"] = ioc
self.detected.append(result)
continue
for perm in result["permissions"]:
if (
perm["name"] == "REQUEST_INSTALL_PACKAGES"
and perm["access"] == "allow"
):
self.log.info(
"Package %s with REQUEST_INSTALL_PACKAGES permission",
result["package_name"],
)
def run(self) -> None:
content = self._get_dumpstate_file()
if not content:
@@ -80,24 +41,10 @@ class Appops(BugReportModule):
)
return
lines = []
in_appops = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE appops:":
in_appops = True
continue
if not in_appops:
continue
if line.strip().startswith(
"------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line)
self.results = parse_dumpsys_appops("\n".join(lines))
section = self.extract_dumpsys_section(
content.decode("utf-8", errors="replace"), "DUMP OF SERVICE appops:"
)
self.parse(section)
self.log.info(
"Identified a total of %d packages in App-Ops Manager", len(self.results)

View File

@@ -4,14 +4,14 @@
# https://license.mvt.re/1.1/
import logging
from typing import Optional, Union
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_daily
from mvt.android.artifacts.dumpsys_battery_daily import DumpsysBatteryDailyArtifact
from .base import BugReportModule
class BatteryDaily(BugReportModule):
class BatteryDaily(DumpsysBatteryDailyArtifact, BugReportModule):
"""This module extracts records from battery daily updates."""
def __init__(
@@ -32,26 +32,6 @@ class BatteryDaily(BugReportModule):
results=results,
)
def serialize(self, record: dict) -> Union[dict, list]:
return {
"timestamp": record["from"],
"module": self.__class__.__name__,
"event": "battery_daily",
"data": f"Recorded update of package {record['package_name']} "
f"with vers {record['vers']}",
}
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 run(self) -> None:
content = self._get_dumpstate_file()
if not content:
@@ -61,30 +41,9 @@ class BatteryDaily(BugReportModule):
)
return
lines = []
in_batterystats = False
in_daily = False
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE batterystats:":
in_batterystats = True
continue
if not in_batterystats:
continue
if line.strip() == "Daily stats:":
lines.append(line)
in_daily = True
continue
if not in_daily:
continue
if line.strip() == "":
break
lines.append(line)
self.results = parse_dumpsys_battery_daily("\n".join(lines))
dumpsys_section = self.extract_dumpsys_section(
content.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
self.parse(dumpsys_section)
self.log.info("Extracted a total of %d battery daily stats", len(self.results))

View File

@@ -6,12 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_battery_history
from mvt.android.artifacts.dumpsys_battery_history import DumpsysBatteryHistoryArtifact
from .base import BugReportModule
class BatteryHistory(BugReportModule):
class BatteryHistory(DumpsysBatteryHistoryArtifact, BugReportModule):
"""This module extracts records from battery daily updates."""
def __init__(
@@ -32,17 +32,6 @@ class BatteryHistory(BugReportModule):
results=results,
)
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 run(self) -> None:
content = self._get_dumpstate_file()
if not content:
@@ -52,23 +41,10 @@ class BatteryHistory(BugReportModule):
)
return
lines = []
in_history = False
for line in content.decode(errors="ignore").splitlines():
if line.strip().startswith("Battery History "):
lines.append(line)
in_history = True
continue
if not in_history:
continue
if line.strip() == "":
break
lines.append(line)
self.results = parse_dumpsys_battery_history("\n".join(lines))
dumpsys_section = self.extract_dumpsys_section(
content.decode("utf-8", errors="replace"), "DUMP OF SERVICE batterystats:"
)
self.parse(dumpsys_section)
self.log.info(
"Extracted a total of %d battery history records", len(self.results)

View File

@@ -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 DumpsysDBInfoArtifact
from .base import BugReportModule
class DBInfo(BugReportModule):
class DBInfo(DumpsysDBInfoArtifact, 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),

View File

@@ -6,18 +6,12 @@
import logging
from typing import Optional
from mvt.android.parsers import parse_dumpsys_receiver_resolver_table
from mvt.android.artifacts.dumpsys_receivers import DumpsysReceiversArtifact
from .base import BugReportModule
INTENT_NEW_OUTGOING_SMS = "android.provider.Telephony.NEW_OUTGOING_SMS"
INTENT_SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED"
INTENT_DATA_SMS_RECEIVED = "android.intent.action.DATA_SMS_RECEIVED"
INTENT_PHONE_STATE = "android.intent.action.PHONE_STATE"
INTENT_NEW_OUTGOING_CALL = "android.intent.action.NEW_OUTGOING_CALL"
class Receivers(BugReportModule):
class Receivers(DumpsysReceiversArtifact, BugReportModule):
"""This module extracts details on receivers for risky activities."""
def __init__(
@@ -40,45 +34,6 @@ class Receivers(BugReportModule):
self.results = results if results else {}
def check_indicators(self) -> None:
if not self.indicators:
return
for intent, receivers in self.results.items():
for receiver in receivers:
if intent == INTENT_NEW_OUTGOING_SMS:
self.log.info(
'Found a receiver to intercept outgoing SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming SMS messages: "%s"',
receiver["receiver"],
)
elif intent == INTENT_DATA_SMS_RECEIVED:
self.log.info(
'Found a receiver to intercept incoming data SMS message: "%s"',
receiver["receiver"],
)
elif intent == INTENT_PHONE_STATE:
self.log.info(
"Found a receiver monitoring "
'telephony state/incoming calls: "%s"',
receiver["receiver"],
)
elif intent == INTENT_NEW_OUTGOING_CALL:
self.log.info(
'Found a receiver monitoring outgoing calls: "%s"',
receiver["receiver"],
)
ioc = self.indicators.check_app_id(receiver["package_name"])
if ioc:
receiver["matched_indicator"] = ioc
self.detected.append({intent: receiver})
continue
def run(self) -> None:
content = self._get_dumpstate_file()
if not content:
@@ -88,23 +43,9 @@ class Receivers(BugReportModule):
)
return
in_receivers = False
lines = []
for line in content.decode(errors="ignore").splitlines():
if line.strip() == "DUMP OF SERVICE package:":
in_receivers = True
continue
if not in_receivers:
continue
if line.strip().startswith(
"------------------------------------------------------------------------------"
): # pylint: disable=line-too-long
break
lines.append(line)
self.results = parse_dumpsys_receiver_resolver_table("\n".join(lines))
dumpsys_section = self.extract_dumpsys_section(
content.decode("utf-8", errors="replace"), "DUMP OF SERVICE package:"
)
self.parse(dumpsys_section)
self.log.info("Extracted receivers for %d intents", len(self.results))

View File

@@ -2,13 +2,3 @@
# 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 .dumpsys import (
parse_dumpsys_accessibility,
parse_dumpsys_activity_resolver_table,
parse_dumpsys_appops,
parse_dumpsys_battery_daily,
parse_dumpsys_battery_history,
parse_dumpsys_dbinfo,
parse_dumpsys_receiver_resolver_table,
)

View File

@@ -4,421 +4,8 @@
# https://license.mvt.re/1.1/
import re
from datetime import datetime
from typing import Any, Dict, List
from mvt.common.utils import convert_datetime_to_iso
def parse_dumpsys_accessibility(output: str) -> List[Dict[str, str]]:
results = []
in_services = False
for line in output.splitlines():
if line.strip().startswith("installed services:"):
in_services = True
continue
if not in_services:
continue
if line.strip() == "}":
break
service = line.split(":")[1].strip()
results.append(
{
"package_name": service.split("/")[0],
"service": service,
}
)
return results
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:
results = []
daily = None
daily_updates = []
for line in output.splitlines():
if line.startswith(" Daily from "):
if len(daily_updates) > 0:
results.extend(daily_updates)
daily_updates = []
timeframe = line[13:].strip()
date_from, date_to = timeframe.strip(":").split(" to ", 1)
daily = {"from": date_from[0:10], "to": date_to[0:10]}
continue
if not daily:
continue
if not line.strip().startswith("Update "):
continue
line = line.strip().replace("Update ", "")
package_name, vers = line.split(" ", 1)
vers_nr = vers.split("=", 1)[1]
already_seen = False
for update in daily_updates:
if package_name == update["package_name"] and vers_nr == update["vers"]:
already_seen = True
break
if not already_seen:
daily_updates.append(
{
"action": "update",
"from": daily["from"],
"to": daily["to"],
"package_name": package_name,
"vers": vers_nr,
}
)
if len(daily_updates) > 0:
results.extend(daily_updates)
return results
def parse_dumpsys_battery_history(output: str) -> List[Dict[str, Any]]:
results = []
for line in output.splitlines():
if line.startswith("Battery History "):
continue
if line.strip() == "":
break
time_elapsed = line.strip().split(" ", 1)[0]
event = ""
if line.find("+job") > 0:
event = "start_job"
uid = line[line.find("+job") + 5 : line.find(":")]
service = line[line.find(":") + 1 :].strip('"')
package_name = service.split("/")[0]
elif line.find("-job") > 0:
event = "end_job"
uid = line[line.find("-job") + 5 : line.find(":")]
service = line[line.find(":") + 1 :].strip('"')
package_name = service.split("/")[0]
elif line.find("+running +wake_lock=") > 0:
uid = line[line.find("+running +wake_lock=") + 21 : line.find(":")]
event = "wake"
service = (
line[line.find("*walarm*:") + 9 :].split(" ")[0].strip('"').strip()
)
if service == "" or "/" not in service:
continue
package_name = service.split("/")[0]
elif (line.find("+top=") > 0) or (line.find("-top") > 0):
if line.find("+top=") > 0:
event = "start_top"
top_pos = line.find("+top=")
else:
event = "end_top"
top_pos = line.find("-top=")
colon_pos = top_pos + line[top_pos:].find(":")
uid = line[top_pos + 5 : colon_pos]
service = ""
package_name = line[colon_pos + 1 :].strip('"')
else:
continue
results.append(
{
"time_elapsed": time_elapsed,
"event": event,
"uid": uid,
"package_name": package_name,
"service": service,
}
)
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 = {}
in_receiver_resolver_table = False
in_non_data_actions = False
intent = None
for line in output.splitlines():
if line.startswith("Receiver Resolver Table:"):
in_receiver_resolver_table = True
continue
if not in_receiver_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.
receiver = line.strip().split(" ")[1]
package_name = receiver.split("/")[0]
results[intent].append(
{
"package_name": package_name,
"receiver": receiver,
}
)
return results
def parse_dumpsys_appops(output: str) -> List[Dict[str, Any]]:
results = []
perm = {}
package = {}
entry = {}
uid = None
in_packages = False
for line in output.splitlines():
if line.startswith(" Uid 0:"):
in_packages = True
if not in_packages:
continue
if line.startswith(" Uid "):
uid = line[6:-1]
if entry:
perm["entries"].append(entry)
entry = {}
if package:
if perm:
package["permissions"].append(perm)
perm = {}
results.append(package)
package = {}
continue
if line.startswith(" Package "):
if entry:
perm["entries"].append(entry)
entry = {}
if package:
if perm:
package["permissions"].append(perm)
perm = {}
results.append(package)
package = {
"package_name": line[12:-1],
"permissions": [],
"uid": uid,
}
continue
if package and line.startswith(" ") and line[6] != " ":
if entry:
perm["entries"].append(entry)
entry = {}
if perm:
package["permissions"].append(perm)
perm = {}
perm["name"] = line.split()[0]
perm["entries"] = []
if len(line.split()) > 1:
perm["access"] = line.split()[1][1:-2]
continue
if line.startswith(" "):
# Permission entry like:
# Reject: [fg-s]2021-05-19 22:02:52.054 (-314d1h25m2s33ms)
if entry:
perm["entries"].append(entry)
entry = {}
entry["access"] = line.split(":")[0].strip()
entry["type"] = line[line.find("[") + 1 : line.find("]")]
try:
entry["timestamp"] = convert_datetime_to_iso(
datetime.strptime(
line[line.find("]") + 1 : line.find("(")].strip(),
"%Y-%m-%d %H:%M:%S.%f",
)
)
except ValueError:
# Invalid date format
pass
if line.strip() == "":
break
if entry:
perm["entries"].append(entry)
if perm:
package["permissions"].append(perm)
if package:
results.append(package)
return results
def parse_dumpsys_package_for_details(output: str) -> Dict[str, Any]:
"""

View File

@@ -3,4 +3,4 @@
# Use of this software is governed by the MVT License 1.1 that can be found at
# https://license.mvt.re/1.1/
MVT_VERSION = "2.4.1"
MVT_VERSION = "2.4.2"

View File

@@ -151,6 +151,22 @@
"identifier": "iPhone14,3",
"description": "iPhone 13 Pro Max"
},
{
"identifier": "iPhone14,4",
"description": "iPhone 13 Mini"
},
{
"identifier": "iPhone14,5",
"description": "iPhone 13"
},
{
"identifier": "iPhone14,6",
"description": "iPhone SE 3rd Gen"
},
{
"identifier": "iPhone14,7",
"description": "iPhone 14"
},
{
"identifier": "iPhone14,8",
"decription": "iPhone 14 Plus"
@@ -163,4 +179,4 @@
"identifier": "iPhone15,3",
"description": "iPhone 14 Pro Max"
}
]
]

View File

@@ -11,6 +11,7 @@ from .chrome_history import ChromeHistory
from .contacts import Contacts
from .firefox_favicon import FirefoxFavicon
from .firefox_history import FirefoxHistory
from .global_preferences import GlobalPreferences
from .idstatuscache import IDStatusCache
from .interactionc import InteractionC
from .locationd import LocationdClients
@@ -49,4 +50,5 @@ MIXED_MODULES = [
Shortcuts,
Applications,
Calendar,
GlobalPreferences,
]

View File

@@ -79,9 +79,10 @@ class Applications(IOSExtraction):
result["matched_indicator"] = ioc
self.detected.append(result)
continue
# Some apps installed from apple store with sourceApp "com.apple.AppStore.ProductPageExtension"
if result.get("sourceApp", "com.apple.AppStore") not in [
"com.apple.AppStore",
"com.apple.AppStore.ProductPageExtension",
"com.apple.dmd",
"dmd",
]:

View File

@@ -0,0 +1,63 @@
# 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
import plistlib
from typing import Optional
from ..base import IOSExtraction
GLOBAL_PREFERENCES_BACKUP_IDS = ["0dc926a1810f7aee4e8f38793ed788701f93bf9d"]
GLOBAL_PREFERENCES_ROOT_PATHS = [
"private/var/mobile/Library/Preferences/.GlobalPreferences.plist",
]
class GlobalPreferences(IOSExtraction):
"""This module extracts Global Preferences to check if Lockdown mode is enabled."""
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 check_indicators(self) -> None:
for entry in self.results:
if entry["entry"] == "LDMGlobalEnabled":
if entry["value"]:
self.log.info("Lockdown mode enabled")
else:
self.log.info("Lockdown mode disabled")
def process_file(self, file_path: str) -> None:
with open(file_path, "rb") as handle:
data = plistlib.load(handle)
for entry in data:
self.results.append({"entry": entry, "value": data[entry]})
def run(self) -> None:
self._find_ios_database(
backup_ids=GLOBAL_PREFERENCES_BACKUP_IDS,
root_paths=GLOBAL_PREFERENCES_ROOT_PATHS,
)
self.log.info("Found Global Preference database at path: %s", self.file_path)
self.process_file(self.file_path)
self.log.info("Extracted a total of %d Global Preferences", len(self.results))

View File

@@ -0,0 +1,20 @@
# 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 mvt.android.artifacts.artifact import AndroidArtifact
from ..utils import get_artifact
class TestAndroidArtifact:
def test_extract_dumpsys_section(self):
file = get_artifact("androidqf/dumpsys.txt")
with open(file) as f:
data = f.read()
section = AndroidArtifact.extract_dumpsys_section(
data, "DUMP OF SERVICE package:"
)
assert isinstance(section, str)
assert len(section) == 3907

View 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_accessibility import DumpsysAccessibilityArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysAccessibilityArtifact:
def test_parsing(self):
da = DumpsysAccessibilityArtifact()
file = get_artifact("android_data/dumpsys_accessibility.txt")
with open(file) as f:
data = f.read()
assert len(da.results) == 0
da.parse(data)
assert len(da.results) == 4
assert da.results[0]["package_name"] == "com.android.settings"
assert (
da.results[0]["service"]
== "com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor"
)
def test_ioc_check(self, indicator_file):
da = DumpsysAccessibilityArtifact()
file = get_artifact("android_data/dumpsys_accessibility.txt")
with open(file) as f:
data = f.read()
da.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.sec.android.app.camera")
da.indicators = ind
assert len(da.detected) == 0
da.check_indicators()
assert len(da.detected) == 1

View File

@@ -0,0 +1,47 @@
# 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_appops import DumpsysAppopsArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysAppopsArtifact:
def test_parsing(self):
da = DumpsysAppopsArtifact()
da.log = logging
file = get_artifact("android_data/dumpsys_appops.txt")
with open(file) as f:
data = f.read()
assert len(da.results) == 0
da.parse(data)
assert len(da.results) == 13
assert da.results[0]["package_name"] == "com.android.phone"
assert da.results[0]["uid"] == "0"
assert len(da.results[0]["permissions"]) == 1
assert da.results[0]["permissions"][0]["name"] == "MANAGE_IPSEC_TUNNELS"
assert da.results[0]["permissions"][0]["access"] == "allow"
assert da.results[6]["package_name"] == "com.sec.factory.camera"
assert len(da.results[6]["permissions"][1]["entries"]) == 1
assert len(da.results[11]["permissions"]) == 4
def test_ioc_check(self, indicator_file):
da = DumpsysAppopsArtifact()
da.log = logging
file = get_artifact("android_data/dumpsys_appops.txt")
with open(file) as f:
data = f.read()
da.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.facebook.katana")
da.indicators = ind
assert len(da.detected) == 0
da.check_indicators()
assert len(da.detected) == 1

View File

@@ -0,0 +1,37 @@
# 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_battery_daily import DumpsysBatteryDailyArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysBatteryDailyArtifact:
def test_parsing(self):
dba = DumpsysBatteryDailyArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
assert len(dba.results) == 0
dba.parse(data)
assert len(dba.results) == 3
def test_ioc_check(self, indicator_file):
dba = DumpsysBatteryDailyArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
dba.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.facebook.system")
dba.indicators = ind
assert len(dba.detected) == 0
dba.check_indicators()
assert len(dba.detected) == 1

View File

@@ -0,0 +1,44 @@
# 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_battery_history import DumpsysBatteryHistoryArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysBatteryHistoryArtifact:
def test_parsing(self):
dba = DumpsysBatteryHistoryArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
assert len(dba.results) == 0
dba.parse(data)
assert len(dba.results) == 5
assert dba.results[0]["package_name"] == "com.samsung.android.app.reminder"
assert dba.results[1]["event"] == "end_job"
assert dba.results[2]["event"] == "start_top"
assert dba.results[2]["uid"] == "u0a280"
assert dba.results[2]["package_name"] == "com.whatsapp"
assert dba.results[3]["event"] == "end_top"
assert dba.results[4]["package_name"] == "com.sec.android.app.launcher"
def test_ioc_check(self, indicator_file):
dba = DumpsysBatteryHistoryArtifact()
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
dba.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.samsung.android.app.reminder")
dba.indicators = ind
assert len(dba.detected) == 0
dba.check_indicators()
assert len(dba.detected) == 2

View 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 DumpsysDBInfoArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysDBinfoArtifact:
def test_parsing(self):
dbi = DumpsysDBInfoArtifact()
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 = DumpsysDBInfoArtifact()
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

View File

@@ -0,0 +1,44 @@
# 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 (
DumpsysPackageActivitiesArtifact,
)
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysPackageActivitiesArtifact:
def test_parsing(self):
dpa = DumpsysPackageActivitiesArtifact()
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 = DumpsysPackageActivitiesArtifact()
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

View File

@@ -0,0 +1,47 @@
# 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_receivers import DumpsysReceiversArtifact
from mvt.common.indicators import Indicators
from ..utils import get_artifact
class TestDumpsysReceiversArtifact:
def test_parsing(self):
dr = DumpsysReceiversArtifact()
file = get_artifact("android_data/dumpsys_packages.txt")
with open(file) as f:
data = f.read()
assert len(dr.results) == 0
dr.parse(data)
assert len(dr.results) == 4
assert (
list(dr.results.keys())[0]
== "com.android.storagemanager.automatic.SHOW_NOTIFICATION"
)
assert (
dr.results["com.android.storagemanager.automatic.SHOW_NOTIFICATION"][0][
"package_name"
]
== "com.android.storagemanager"
)
def test_ioc_check(self, indicator_file):
dr = DumpsysReceiversArtifact()
file = get_artifact("android_data/dumpsys_packages.txt")
with open(file) as f:
data = f.read()
dr.parse(data)
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
ind.ioc_collections[0]["app_ids"].append("com.android.storagemanager")
dr.indicators = ind
assert len(dr.detected) == 0
dr.check_indicators()
assert len(dr.detected) == 1

View File

@@ -1,62 +0,0 @@
# 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 mvt.android.parsers.dumpsys import (
parse_dumpsys_appops,
parse_dumpsys_battery_history,
parse_dumpsys_packages,
)
from ..utils import get_artifact
class TestDumpsysParsing:
def test_appops_parsing(self):
file = get_artifact("android_data/dumpsys_appops.txt")
with open(file) as f:
data = f.read()
res = parse_dumpsys_appops(data)
assert len(res) == 13
assert res[0]["package_name"] == "com.android.phone"
assert res[0]["uid"] == "0"
assert len(res[0]["permissions"]) == 1
assert res[0]["permissions"][0]["name"] == "MANAGE_IPSEC_TUNNELS"
assert res[0]["permissions"][0]["access"] == "allow"
assert res[6]["package_name"] == "com.sec.factory.camera"
assert len(res[6]["permissions"][1]["entries"]) == 1
assert len(res[11]["permissions"]) == 4
def test_battery_history_parsing(self):
file = get_artifact("android_data/dumpsys_battery.txt")
with open(file) as f:
data = f.read()
res = parse_dumpsys_battery_history(data)
assert len(res) == 5
assert res[0]["package_name"] == "com.samsung.android.app.reminder"
assert res[1]["event"] == "end_job"
assert res[2]["event"] == "start_top"
assert res[2]["uid"] == "u0a280"
assert res[2]["package_name"] == "com.whatsapp"
assert res[3]["event"] == "end_top"
assert res[4]["package_name"] == "com.sec.android.app.launcher"
def test_packages_parsing(self):
file = get_artifact("android_data/dumpsys_packages.txt")
with open(file) as f:
data = f.read()
res = parse_dumpsys_packages(data)
assert len(res) == 2
assert res[0]["package_name"] == "com.samsung.android.provider.filterprovider"
assert res[1]["package_name"] == "com.sec.android.app.DataCreate"
assert len(res[0]["permissions"]) == 4
assert len(res[0]["requested_permissions"]) == 0
assert len(res[1]["permissions"]) == 34
assert len(res[1]["requested_permissions"]) == 11

View File

View 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_battery_daily import DumpsysBatteryDaily
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysBatteryDailyModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysBatteryDaily(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) == 3
assert len(m.timeline) == 3
assert len(m.detected) == 0

View 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_battery_history import DumpsysBatteryHistory
from mvt.common.module import run_module
from ..utils import get_android_androidqf, list_files
class TestDumpsysBatteryHistoryModule:
def test_parsing(self):
data_path = get_android_androidqf()
m = DumpsysBatteryHistory(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

View 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

View File

@@ -0,0 +1,40 @@
ACCESSIBILITY MANAGER (dumpsys accessibility)
User state[attributes:{id=0, currentUser=true
mIsNavBarMagnificationAssignedToAccessibilityButton = false
mIsNavBarMagnifierWindowAssignedToAccessibilityButton = false
mIsNavBarAmplifyAmbientSoundAssignedToAccessibilityButton = false
mIsAmplifyAmbientSoundEnabled = false
mIsBixbyRunning = false
mIsMagniferWindowEnabled = false
mIsFollowTypingFocusEnabled = false
mIsTapDurationEnabled = false
mIsTouchBlockingEnabled = false
mIsTextHighContrastEnabled = false
mIsDisplayMagnificationEnabled = false
mIsNavBarMagnificationEnabled = false
mIsAutoclickEnabled = false
mIsPerformGesturesEnabled = false
mIsFilterKeyEventsEnabled = false
mAccessibilityFocusOnlyInActiveWindow = true
mUserNonInteractiveUiTimeout = 0
mUserInteractiveUiTimeout = 0
mBindInstantServiceAllowed = false
mIsGestureNaviBar = false
}
installed services: {
0 : com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor
1 : com.samsung.accessibility/.universalswitch.UniversalSwitchService
2 : com.samsung.accessibility/com.samsung.android.app.talkback.TalkBackService
3 : com.sec.android.app.camera/com.samsung.android.glview.AccessibilityGestureHandler
}
enabled services: {
}
binding services: {
}
bound services:{
}
AccessibilityInputFilter:{
}]

View File

@@ -9,3 +9,52 @@ Battery History (0% used, 2720 used of 4096KB, 31 strings using 2694):
+2d23h22m24s588ms (2) 079 +usb_data +top=u0a44:"com.sec.android.app.launcher"
Daily stats:
Current start time: 2022-08-17-01-15-45
Next min deadline: 2022-08-18-01-00-00
Next max deadline: 2022-08-18-03-00-00
Current daily discharge step durations:
#0: +3h32m16s12ms to 96 (power-save-off)
#1: +2h44m44s14ms to 97 (screen-off, power-save-off, device-idle-on)
#2: +2h0m41s988ms to 98 (screen-off, power-save-off, device-idle-on)
Discharge total time: 11d 12h 30m 0s 400ms (from 3 steps)
Discharge screen off time: 9d 21h 51m 40s 100ms (from 2 steps)
Discharge screen off device idle time: 9d 21h 51m 40s 100ms (from 2 steps)
Current daily charge step durations:
#0: +5m4s541ms to 99 (screen-off, power-save-off, device-idle-off)
#1: +3m33s300ms to 98 (screen-off, power-save-off, device-idle-off)
Charge total time: 7h 11m 32s 0ms (from 2 steps)
Charge screen off time: 7h 11m 32s 0ms (from 2 steps)
Daily from 2022-08-16-15-56-39 to 2022-08-17-01-15-45:
Charge step durations:
#0: +5m15s53ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +5m35s358ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +3m43s598ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +3m33s400ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +2m32s364ms to 96 (screen-off, power-save-off, device-idle-off)
#5: +3m53s485ms to 95 (screen-off, power-save-off, device-idle-off)
#6: +3m43s317ms to 94 (screen-off, power-save-off, device-idle-off)
#7: +3m13s27ms to 93 (screen-off, power-save-off, device-idle-off)
#8: +1h9m49s978ms to 92 (power-save-off, device-idle-off)
#9: +3m43s682ms to 92 (screen-off, power-save-off, device-idle-off)
#10: +6m15s588ms to 91 (screen-off, power-save-off, device-idle-off)
Package changes:
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.services vers=385230290
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.katana vers=315814651
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279
Update com.facebook.system vers=385230279

View 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

View File

@@ -9,6 +9,32 @@ Libraries:
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:
Package [com.samsung.android.provider.filterprovider] (d64f8e0):
@@ -124,3 +150,25 @@ Packages:
Receiver Resolver Table:
Full MIME Types:
application/gmail-ls:
231cb24 com.google.android.gm/.GmailReceiver
3441042 com.google.android.gm/.widget.GoogleMailWidgetProvider (3 filters)
cf0a68d com.google.android.gm/.widget.GmailWidgetProvider (3 filters)
vnd.android.cursor.dir/voicemails:
39596b6 com.samsung.android.app.telephonyui/com.android.voicemail.impl.sync.VoicemailProviderChangeReceiver
application/*:
8e50848 com.android.vending/com.google.android.finsky.recoverymode.download.impl.RecoveryModeDownloadBroadcastReceiver
Non-Data Actions:
com.android.storagemanager.automatic.SHOW_NOTIFICATION:
417c3b3 com.android.storagemanager/.automatic.NotificationController
android.provider.action.EXTERNAL_PROVIDER_CHANGE:
b310d70 com.samsung.android.messaging/.service.syncservice.ExtProviderChangeReceiver
com.google.android.projection.gearhead.RESET_USB_FUNCTION:
da90126 com.google.android.projection.gearhead/com.google.android.apps.auto.components.connectivity.reset.ConnectionResetReceiver
com.samsung.cmh.action.CMH_SYNC:
d5c0372 com.samsung.cmh/.core.SystemBroadcastReceiver$CMHBroadcastReceiver

View File

@@ -250,5 +250,121 @@ 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
-------------------------------------------------------------------------------
DUMP OF SERVICE batterystats:
Battery History (0% used, 11KB used of 4096KB, 79 strings using 9632):
0 (19) RESET:TIME: 2023-07-27-12-34-18
0 (2) 100 c0100024 status=discharging health=good plug=none temp=260 volt=4345 current=226 ap_temp=27 -nr_connected -wifi_ap -otg misc_event=0x0 online=1 current_event=0x0 txshare_event=0x0 charge=3000 modemRailChargemAh=0 wifiRailChargemAh=0 +running +wake_lock +screen phone_signal_strength=great brightness=bright +wifi_running +wifi +usb_data wifi_signal_strength=3 wifi_suppl=disconn +ble_scan top=1000:"com.wssyncmldm"
0 (2) 100 c0100024 user=0:"0"
0 (2) 100 c0100024 userfg=0:"0"
+343ms (3) 100 80000024 -wake_lock -screen -usb_data stats=0:"get-stats"
+1s235ms (4) 100 c0000020 +wake_lock=1000:"ActivityManager-Sleep" brightness=dark stats=0:"screen-state"
+1s314ms (1) 100 80000020 -wake_lock
+1s320ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s321ms (1) 100 80000020 -wake_lock
+1s321ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s332ms (1) 100 80000020 -wake_lock
+1s332ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s334ms (1) 100 80000020 -wake_lock
+1s441ms (2) 100 c0000020 +wake_lock=1000:"startDream"
+1s751ms (1) 100 80000020 -wake_lock
+1s809ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s811ms (1) 100 80000020 -wake_lock
+1s811ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s821ms (1) 100 80000020 -wake_lock
+1s821ms (2) 100 c0000020 +wake_lock=1001:"*telephony-radio*"
+1s823ms (1) 100 80000020 -wake_lock -ble_scan
+2s042ms (2) 100 c0000020 +wake_lock=u0a12:"Wakeful StateMachine: GeofencerStateMachine"
+2s044ms (1) 100 80000020 -wake_lock
+2s050ms (2) 100 c0000020 +wake_lock=u0a12:"NlpWakeLock"
+23m32s163ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/com.google.android.libraries.internal.growth.growthkit.internal.jobs.impl.GrowthKitJobService"
+23m33s713ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/.job.ProviderCreatedJob$ProviderCreatedJobService"
+23m33s752ms (2) 100 c0000020 +job=u0a134:"com.google.android.gm/com.android.mail.widget.NotifyDatasetChangedJob$NotifyDatasetChangedJobService"
+23m33s786ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/.job.ProviderCreatedJob$ProviderCreatedJobService"
+23m33s867ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/com.google.android.libraries.internal.growth.growthkit.internal.jobs.impl.GrowthKitJobService"
+23m33s910ms (2) 100 c0000020 -job=u0a134:"com.google.android.gm/com.android.mail.widget.NotifyDatasetChangedJob$NotifyDatasetChangedJobService"
Daily stats:
Current start time: 2023-07-27-02-02-56
Next min deadline: 2023-07-28-01-00-00
Next max deadline: 2023-07-28-03-00-00
Current daily discharge step durations:
#0: +2h44m59s971ms to 98 (screen-off, power-save-off, device-idle-on)
Discharge total time: 11d 10h 59m 57s 100ms (from 1 steps)
Discharge screen off time: 11d 10h 59m 57s 100ms (from 1 steps)
Discharge screen off device idle time: 11d 10h 59m 57s 100ms (from 1 steps)
Current daily charge step durations:
#0: +2m32s269ms to 100 (power-save-off, device-idle-off)
Charge total time: 4h 13m 46s 900ms (from 1 steps)
Daily from 2023-07-26-03-02-02 to 2023-07-27-02-02-56:
Discharge step durations:
#0: +2h21m35s4ms to 75 (screen-off, power-save-off)
#1: +2h19m0s999ms to 76 (screen-off, power-save-off)
#2: +1h46m26s999ms to 77 (screen-off, power-save-off)
#3: +2h24m32s6ms to 78 (screen-off, power-save-off, device-idle-on)
#4: +2h44m58s966ms to 79 (screen-off, power-save-off, device-idle-on)
Discharge total time: 9d 16h 11m 19s 400ms (from 5 steps)
Discharge screen off time: 9d 16h 11m 19s 400ms (from 5 steps)
Discharge screen off device idle time: 10d 17h 55m 48s 600ms (from 2 steps)
Charge step durations:
#0: +5m45s118ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +1m0s998ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +2m1s894ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +1m0s973ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +3m33s239ms to 96 (screen-off, power-save-off, device-idle-off)
Charge step durations:
#0: +30s531ms to 100 (screen-off, power-save-off, device-idle-off)
#1: +30s527ms to 99 (screen-off, power-save-off, device-idle-off)
#2: +30s571ms to 98 (screen-off, power-save-off, device-idle-off)
#3: +1m1s53ms to 97 (screen-off, power-save-off, device-idle-off)
#4: +30s580ms to 96 (screen-off, power-save-off, device-idle-off)
#5: +30s568ms to 95 (screen-off, power-save-off, device-idle-off)
#6: +20s407ms to 94 (screen-off, power-save-off, device-idle-off)
#7: +7m16s300ms to 93 (screen-off, power-save-off, device-idle-off)
#8: +5m55s313ms to 92 (screen-off, power-save-off, device-idle-off)
#9: +6m35s856ms to 91 (screen-off, power-save-off, device-idle-off)
#10: +4m17s981ms to 90 (screen-off, power-save-off, device-idle-off)
#11: +3m43s342ms to 89 (screen-off, power-save-off, device-idle-off)
Charge total time: 4h 24m 18s 500ms (from 12 steps)
Charge screen off time: 4h 24m 18s 500ms (from 12 steps)
Package changes:
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update com.google.android.gm vers=63983425
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update org.mozilla.firefox vers=2015962857
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623
Update com.google.android.projection.gearhead vers=99632623

View File

@@ -62,5 +62,5 @@ class TestHashes:
assert hashes[1]["file_path"] == os.path.join(path, "dumpsys.txt")
assert (
hashes[1]["sha256"]
== "bac858001784657a43c7cfa771fd1fc4a49428eb6b7c458a1ebf2fdeef78dd86"
== "cfae0e04ef139b5a2ae1e2b3d400ce67eb98e67ff66f56ba2a580fe41bc120d0"
)

View File

@@ -0,0 +1,20 @@
# 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 mvt.common.module import run_module
from mvt.ios.modules.mixed.global_preferences import GlobalPreferences
from ..utils import get_ios_backup_folder
class TestGlobalPreferencesModule:
def test_global_preferences(self):
m = GlobalPreferences(target_path=get_ios_backup_folder())
run_module(m)
assert len(m.results) == 16
assert len(m.timeline) == 0
assert len(m.detected) == 0
assert m.results[0]["entry"] == "WebKitShowLinkPreviews"
assert m.results[0]["value"] is False

View File

@@ -12,7 +12,7 @@ from mvt.ios.modules.mixed.tcc import TCC
from ..utils import get_ios_backup_folder
class TestTCCtModule:
class TestTCCModule:
def test_tcc(self):
m = TCC(target_path=get_ios_backup_folder())
run_module(m)

View File

@@ -2,25 +2,33 @@
# 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
import pytest
from mvt.common.indicators import Indicators
from mvt.common.module import run_module
from mvt.ios.modules.fs.filesystem import Filesystem
from ..utils import get_ios_backup_folder
from ..utils import delete_tmp_db_files, get_ios_backup_folder
@pytest.fixture()
def cleanup_tmp_artifacts():
ios_backup_folder = get_ios_backup_folder()
delete_tmp_db_files(ios_backup_folder)
return
class TestFilesystem:
def test_filesystem(self):
def test_filesystem(self, cleanup_tmp_artifacts):
m = Filesystem(target_path=get_ios_backup_folder())
run_module(m)
assert len(m.results) == 14
assert len(m.timeline) == 14
assert len(m.results) == 15
assert len(m.timeline) == 15
assert len(m.detected) == 0
def test_detection(self, indicator_file):
def test_detection(self, indicator_file, cleanup_tmp_artifacts):
m = Filesystem(target_path=get_ios_backup_folder())
ind = Indicators(log=logging.getLogger())
ind.parse_stix2(indicator_file)
@@ -30,6 +38,6 @@ class TestFilesystem:
)
m.indicators = ind
run_module(m)
assert len(m.results) == 14
assert len(m.timeline) == 14
assert len(m.results) == 15
assert len(m.timeline) == 15
assert len(m.detected) == 1

View File

@@ -37,6 +37,18 @@ def get_indicator_file():
print("PYTEST env", os.getenv("PYTEST_CURRENT_TEST"))
def delete_tmp_db_files(file_path):
"""
Remove Sqlite temporary files that appear on some platforms
These can cause filesystem tests to fail depending on the OS.
"""
for file_name in ["Manifest.db-wal", "Manifest.db-shm"]:
path = os.path.join(file_path, file_name)
if os.path.isfile(path):
os.remove(path)
def list_files(path: str):
files = []
parent_path = Path(path).absolute().parent.as_posix()