mirror of
https://github.com/mvt-project/mvt
synced 2025-10-21 22:42:15 +02:00
Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cf5cf3b85d | ||
|
|
f0dbe0bfa6 | ||
|
|
555e49fda7 | ||
|
|
a6d32e1c88 | ||
|
|
f155146f1e | ||
|
|
9d47acc228 | ||
|
|
cbd41b2aff | ||
|
|
0509eaa162 | ||
|
|
59e6dff1e1 | ||
|
|
f1821d1a02 | ||
|
|
6c7ad0ac95 | ||
|
|
3a997d30d2 | ||
|
|
6f56939dd7 |
4
.github/workflows/python-package.yml
vendored
4
.github/workflows/python-package.yml
vendored
@@ -40,7 +40,9 @@ jobs:
|
||||
- name: Safety checks
|
||||
run: safety check
|
||||
- name: Test with pytest and coverage
|
||||
run: pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
|
||||
run: |
|
||||
set -o pipefail
|
||||
pytest --junitxml=pytest.xml --cov-report=term-missing:skip-covered --cov=mvt tests/ | tee pytest-coverage.txt
|
||||
- name: Pytest coverage comment
|
||||
continue-on-error: true # Workflows running on a fork can't post comments
|
||||
uses: MishaKav/pytest-coverage-comment@main
|
||||
|
||||
9
.safety-policy.yml
Normal file
9
.safety-policy.yml
Normal file
@@ -0,0 +1,9 @@
|
||||
# Safety Security and License Configuration file
|
||||
# We recommend checking this file into your source control in the root of your Python project
|
||||
# If this file is named .safety-policy.yml and is in the same directory where you run `safety check` it will be used by default.
|
||||
# Otherwise, you can use the flag `safety check --policy-file <path-to-this-file>` to specify a custom location and name for the file.
|
||||
# To validate and review your policy file, run the validate command: `safety validate policy_file --path <path-to-this-file>`
|
||||
security: # configuration for the `safety check` command
|
||||
ignore-vulnerabilities: # Here you can list multiple specific vulnerabilities you want to ignore (optionally for a time period)
|
||||
67599: # Example vulnerability ID
|
||||
reason: disputed, inapplicable
|
||||
@@ -4,6 +4,7 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .artifact import AndroidArtifact
|
||||
import re
|
||||
|
||||
|
||||
class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
@@ -25,6 +26,8 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
|
||||
:param content: content of the accessibility section (string)
|
||||
"""
|
||||
|
||||
# "Old" syntax
|
||||
in_services = False
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("installed services:"):
|
||||
@@ -35,6 +38,7 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
continue
|
||||
|
||||
if line.strip() == "}":
|
||||
# At end of installed services
|
||||
break
|
||||
|
||||
service = line.split(":")[1].strip()
|
||||
@@ -45,3 +49,19 @@ class DumpsysAccessibilityArtifact(AndroidArtifact):
|
||||
"service": service,
|
||||
}
|
||||
)
|
||||
|
||||
# "New" syntax - AOSP >= 14 (?)
|
||||
# Looks like:
|
||||
# Enabled services:{{com.azure.authenticator/com.microsoft.brooklyn.module.accessibility.BrooklynAccessibilityService}, {com.agilebits.onepassword/com.agilebits.onepassword.filling.accessibility.FillingAccessibilityService}}
|
||||
|
||||
for line in content.splitlines():
|
||||
if line.strip().startswith("Enabled services:"):
|
||||
matches = re.finditer(r"{([^{]+?)}", line)
|
||||
|
||||
for match in matches:
|
||||
# Each match is in format: <package_name>/<service>
|
||||
package_name, _, service = match.group(1).partition("/")
|
||||
|
||||
self.results.append(
|
||||
{"package_name": package_name, "service": service}
|
||||
)
|
||||
|
||||
@@ -51,6 +51,11 @@ ANDROID_DANGEROUS_SETTINGS = [
|
||||
"key": "install_non_market_apps",
|
||||
"safe_value": "0",
|
||||
},
|
||||
{
|
||||
"description": "enabled accessibility services",
|
||||
"key": "accessibility_enabled",
|
||||
"safe_value": "0",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -85,6 +85,15 @@ class Command:
|
||||
)
|
||||
file_handler.setLevel(logging.DEBUG)
|
||||
file_handler.setFormatter(formatter)
|
||||
|
||||
# MVT can be run in a loop
|
||||
# Old file handlers stick around in subsequent loops
|
||||
# Remove any existing logging.FileHandler instances
|
||||
for handler in logger.handlers:
|
||||
if isinstance(handler, logging.FileHandler):
|
||||
logger.removeHandler(handler)
|
||||
|
||||
# And finally add the new one
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
def _store_timeline(self) -> None:
|
||||
|
||||
@@ -53,20 +53,23 @@ def convert_chrometime_to_datetime(timestamp: int) -> datetime.datetime:
|
||||
def convert_datetime_to_iso(date_time: datetime.datetime) -> str:
|
||||
"""Converts datetime to ISO string.
|
||||
|
||||
:param datetime: datetime.
|
||||
:param datetime: datetime, naive or timezone aware
|
||||
:type datetime: datetime.datetime
|
||||
:returns: ISO datetime string in YYYY-mm-dd HH:MM:SS.ms format.
|
||||
:rtype: str
|
||||
|
||||
"""
|
||||
try:
|
||||
return date_time.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
except Exception:
|
||||
if not date_time:
|
||||
return ""
|
||||
|
||||
if date_time.tzinfo:
|
||||
# Timezone aware object - convert to UTC
|
||||
date_time = date_time.astimezone(tz=datetime.timezone.utc)
|
||||
return date_time.strftime("%Y-%m-%d %H:%M:%S.%f")
|
||||
|
||||
|
||||
def convert_unix_to_utc_datetime(
|
||||
timestamp: Union[int, float, str]
|
||||
timestamp: Union[int, float, str],
|
||||
) -> datetime.datetime:
|
||||
"""Converts a unix epoch timestamp to UTC datetime.
|
||||
|
||||
|
||||
@@ -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.5.1"
|
||||
MVT_VERSION = "2.5.4"
|
||||
|
||||
@@ -988,6 +988,10 @@
|
||||
"version": "16.7.7",
|
||||
"build": "20H330"
|
||||
},
|
||||
{
|
||||
"version": "16.7.8",
|
||||
"build": "20H343"
|
||||
},
|
||||
{
|
||||
"version": "17.0",
|
||||
"build": "21A327"
|
||||
@@ -1055,5 +1059,13 @@
|
||||
{
|
||||
"version": "17.4.1",
|
||||
"build": "21E237"
|
||||
},
|
||||
{
|
||||
"version": "17.5",
|
||||
"build": "21F79"
|
||||
},
|
||||
{
|
||||
"version": "17.5.1",
|
||||
"build": "21F90"
|
||||
}
|
||||
]
|
||||
@@ -66,8 +66,11 @@ class SMS(IOSExtraction):
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
for message in self.results:
|
||||
alert = "ALERT: State-sponsored attackers may be targeting your iPhone"
|
||||
if message.get("text", "").startswith(alert):
|
||||
alert_old = "ALERT: State-sponsored attackers may be targeting your iPhone"
|
||||
alert_new = "ALERT: Apple detected a targeted mercenary spyware attack against your iPhone"
|
||||
if message.get("text", "").startswith(alert_old) or message.get(
|
||||
"text", ""
|
||||
).startswith(alert_new):
|
||||
self.log.warning(
|
||||
"Apple warning about state-sponsored attack received on the %s",
|
||||
message["isodate"],
|
||||
|
||||
@@ -31,7 +31,7 @@ install_requires =
|
||||
iOSbackup >=0.9.923
|
||||
adb-shell >=0.4.3
|
||||
libusb1 >=3.0.0
|
||||
cryptography >=38.0.1
|
||||
cryptography >=42.0.5
|
||||
pyyaml >=6.0
|
||||
pyahocorasick >= 2.0.0
|
||||
|
||||
|
||||
@@ -26,6 +26,18 @@ class TestDumpsysAccessibilityArtifact:
|
||||
== "com.android.settings/com.samsung.android.settings.development.gpuwatch.GPUWatchInterceptor"
|
||||
)
|
||||
|
||||
def test_parsing_v14_aosp_format(self):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
file = get_artifact("android_data/dumpsys_accessibility_v14_or_later.txt")
|
||||
with open(file) as f:
|
||||
data = f.read()
|
||||
|
||||
assert len(da.results) == 0
|
||||
da.parse(data)
|
||||
assert len(da.results) == 1
|
||||
assert da.results[0]["package_name"] == "com.malware.accessibility"
|
||||
assert da.results[0]["service"] == "com.malware.service.malwareservice"
|
||||
|
||||
def test_ioc_check(self, indicator_file):
|
||||
da = DumpsysAccessibilityArtifact()
|
||||
file = get_artifact("android_data/dumpsys_accessibility.txt")
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -42,6 +42,14 @@ class TestDateConversions:
|
||||
converted = convert_unix_to_utc_datetime(TEST_DATE_EPOCH)
|
||||
assert convert_datetime_to_iso(converted) == TEST_DATE_ISO
|
||||
|
||||
def test_convert_timezone_aware_to_iso(self):
|
||||
assert (
|
||||
convert_datetime_to_iso(
|
||||
datetime.strptime("2024-09-30 11:21:20+0200", "%Y-%m-%d %H:%M:%S%z")
|
||||
)
|
||||
== "2024-09-30 09:21:20.000000"
|
||||
)
|
||||
|
||||
|
||||
class TestHashes:
|
||||
def test_hash_from_file(self):
|
||||
|
||||
Reference in New Issue
Block a user