mirror of
https://github.com/mvt-project/mvt.git
synced 2026-11-05 20:23:37 +01:00
Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22ed92f4b2 | ||
|
|
3222bcd98f | ||
|
|
5169a46b59 | ||
|
|
5a129430ae | ||
|
|
190a5f119f | ||
|
|
02c52f43c8 |
No files matched your search
+1
-1
@@ -66,4 +66,4 @@ docker run --rm -it \
|
||||
ghcr.io/mvt-project/mvt:latest-ios lockdown info
|
||||
```
|
||||
|
||||
If you built the image from source, replace `ghcr.io/mvt-project/mvt` with `mvt`.
|
||||
If you built the image from source, replace `ghcr.io/mvt-project/mvt` with `mvt`.
|
||||
@@ -145,7 +145,7 @@ class TombstoneCrashArtifact(AndroidArtifact):
|
||||
|
||||
# Add some extra metadata
|
||||
tombstone_dict["timestamp"] = self._parse_timestamp_string(
|
||||
tombstone_pb.timestamp
|
||||
tombstone_pb.timestamp, file_timestamp
|
||||
)
|
||||
tombstone_dict["file_name"] = file_name
|
||||
tombstone_dict["file_timestamp"] = convert_datetime_to_iso(file_timestamp)
|
||||
@@ -288,11 +288,21 @@ class TombstoneCrashArtifact(AndroidArtifact):
|
||||
|
||||
def _load_timestamp_line(self, line: str, tombstone: dict) -> bool:
|
||||
timestamp = line.split(":", 1)[1].strip()
|
||||
tombstone["timestamp"] = self._parse_timestamp_string(timestamp)
|
||||
tombstone["timestamp"] = self._parse_timestamp_string(timestamp, None)
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _parse_timestamp_string(timestamp: str) -> str:
|
||||
def _parse_timestamp_string(
|
||||
timestamp: str, fallback_timestamp: Optional[datetime.datetime]
|
||||
) -> str:
|
||||
"""Parse timestamp string, using fallback if timestamp is empty."""
|
||||
# Handle empty or whitespace-only timestamps
|
||||
if not timestamp or not timestamp.strip():
|
||||
if fallback_timestamp:
|
||||
return convert_datetime_to_iso(fallback_timestamp)
|
||||
else:
|
||||
raise ValueError("Empty timestamp with no fallback provided")
|
||||
|
||||
timestamp_parsed = parser.parse(timestamp)
|
||||
return convert_datetime_to_iso(timestamp_parsed)
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ class SMS(BackupModule):
|
||||
|
||||
def collect_url_results(self) -> None:
|
||||
for message in self.results:
|
||||
for url in message.get("links") or []:
|
||||
for url in message.get("links", []):
|
||||
self.add_url_result(url, message.get("isodate"), "sms")
|
||||
|
||||
def run(self) -> None:
|
||||
|
||||
@@ -7,6 +7,7 @@ from mvt.common.module import MVTModule
|
||||
|
||||
from .dumpsys_accessibility import DumpsysAccessibility
|
||||
from .dumpsys_activities import DumpsysActivities
|
||||
from .dumpsys_adb_state import DumpsysADBState
|
||||
from .dumpsys_appops import DumpsysAppops
|
||||
from .dumpsys_battery_daily import DumpsysBatteryDaily
|
||||
from .dumpsys_battery_history import DumpsysBatteryHistory
|
||||
@@ -15,7 +16,6 @@ from .dumpsys_getprop import DumpsysGetProp
|
||||
from .dumpsys_packages import DumpsysPackages
|
||||
from .dumpsys_platform_compat import DumpsysPlatformCompat
|
||||
from .dumpsys_receivers import DumpsysReceivers
|
||||
from .dumpsys_adb_state import DumpsysADBState
|
||||
from .fs_timestamps import BugReportTimestamps
|
||||
from .tombstones import Tombstones
|
||||
from .mounts import Mounts
|
||||
|
||||
@@ -68,6 +68,11 @@ class Tombstones(TombstoneCrashArtifact, BugReportModule):
|
||||
}
|
||||
try:
|
||||
data = self._get_file_content(file_path)
|
||||
if not data:
|
||||
self.log.debug("Skipping empty tombstone file %s (0 bytes)", file_path)
|
||||
source_info["error"] = "empty tombstone"
|
||||
parsed_sources[source] = source_info
|
||||
continue
|
||||
if source == "protobuf":
|
||||
record = self.parse_protobuf_record(
|
||||
file_name, file_timestamp, data
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
# Copyright (c) 2021-2023 The MVT Authors.
|
||||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
import os
|
||||
import datetime
|
||||
import os
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -130,3 +130,83 @@ class TestTombstoneCrashArtifact:
|
||||
|
||||
# Tombstones include an explicit offset, so normalize them to UTC.
|
||||
assert tombstone_result.get("timestamp") == "2023-04-12 10:32:40.518290"
|
||||
|
||||
def test_tombstone_pb_empty_timestamp(self):
|
||||
"""Test parsing a protobuf tombstone with an empty timestamp."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_timestamp.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 1, 15, 10, 30, 45, 123456)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# When tombstone has empty timestamp, should use file modification time
|
||||
assert result.get("timestamp") == "2024-01-15 10:30:45.123456"
|
||||
assert result.get("pid") == 12345
|
||||
assert result.get("uid") == 1000
|
||||
assert result.get("signal_info", {}).get("name") == "SIGSEGV"
|
||||
|
||||
def test_tombstone_pb_empty_timestamp_with_threads(self):
|
||||
"""Test parsing a protobuf tombstone with empty timestamp and thread info."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_timestamp_with_threads.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 2, 20, 14, 15, 30, 0)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# Verify timestamp fallback
|
||||
assert result.get("timestamp") == "2024-02-20 14:15:30.000000"
|
||||
assert result.get("pid") == 9876
|
||||
assert result.get("uid") == 10001
|
||||
assert result.get("signal_info", {}).get("name") == "SIGABRT"
|
||||
assert result.get("process_name") == "ExampleThread"
|
||||
|
||||
def test_tombstone_pb_whitespace_timestamp(self):
|
||||
"""Test parsing a protobuf tombstone with whitespace-only timestamp."""
|
||||
tombstone_artifact = TombstoneCrashArtifact()
|
||||
artifact_path = (
|
||||
"android_data/bugreport_tombstones/tombstone_whitespace_timestamp.pb"
|
||||
)
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
file_name = os.path.basename(artifact_path)
|
||||
file_timestamp = datetime.datetime(2024, 3, 10, 8, 0, 0, 0)
|
||||
tombstone_artifact.parse_protobuf(file_name, file_timestamp, data)
|
||||
|
||||
assert len(tombstone_artifact.results) == 1
|
||||
result = tombstone_artifact.results[0]
|
||||
|
||||
# Verify whitespace timestamp is treated as empty
|
||||
assert result.get("timestamp") == "2024-03-10 08:00:00.000000"
|
||||
assert result.get("pid") == 11111
|
||||
assert result.get("uid") == 2000
|
||||
assert result.get("signal_info", {}).get("name") == "SIGILL"
|
||||
|
||||
def test_tombstone_pb_empty_file(self):
|
||||
"""Test that empty (0 bytes) tombstone files are handled gracefully."""
|
||||
artifact_path = "android_data/bugreport_tombstones/tombstone_empty_file.pb"
|
||||
file = get_artifact(artifact_path)
|
||||
with open(file, "rb") as f:
|
||||
data = f.read()
|
||||
|
||||
# Verify the file is actually empty
|
||||
assert len(data) == 0, "Test file should be empty (0 bytes)"
|
||||
|
||||
# Empty files should be skipped in the module (not parsed)
|
||||
# The actual skipping happens in the Tombstones module's run() method
|
||||
# This test verifies that empty data is detectable
|
||||
@@ -4,118 +4,17 @@
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import io
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import tarfile
|
||||
import zlib
|
||||
|
||||
import pytest
|
||||
|
||||
from mvt.android.modules.backup.sms import SMS
|
||||
from mvt.android.parsers.backup import parse_backup_file
|
||||
from mvt.common.config import settings
|
||||
from mvt.common.module import run_module
|
||||
|
||||
from ..utils import get_android_backup_folder
|
||||
|
||||
|
||||
class TestBackupModule:
|
||||
@pytest.mark.parametrize("link_fields", [{}, {"links": None}, {"links": []}])
|
||||
def test_collect_urls_with_empty_links(self, link_fields):
|
||||
mod = SMS()
|
||||
mod.results = [
|
||||
{"links": ["https://example.org/first"], "isodate": "2026-11-04 12:00:00"},
|
||||
{"body": "", **link_fields},
|
||||
{"links": ["https://example.org/last"], "isodate": "2026-11-04 13:00:00"},
|
||||
]
|
||||
|
||||
mod.collect_url_results()
|
||||
|
||||
assert mod.url_results == [
|
||||
{
|
||||
"url": "https://example.org/first",
|
||||
"expanded_url": None,
|
||||
"timestamp": "2026-11-04 12:00:00",
|
||||
"source": "sms",
|
||||
},
|
||||
{
|
||||
"url": "https://example.org/last",
|
||||
"expanded_url": None,
|
||||
"timestamp": "2026-11-04 13:00:00",
|
||||
"source": "sms",
|
||||
},
|
||||
]
|
||||
|
||||
@pytest.mark.parametrize("message_type", ["sms", "mms"])
|
||||
@pytest.mark.parametrize("with_indicators", [False, True])
|
||||
def test_blank_message_backup(
|
||||
self,
|
||||
tmp_path,
|
||||
caplog,
|
||||
monkeypatch,
|
||||
indicators_factory,
|
||||
message_type,
|
||||
with_indicators,
|
||||
):
|
||||
monkeypatch.setattr(settings, "NETWORK_ACCESS_ALLOWED", False)
|
||||
backup_path = tmp_path / "backup"
|
||||
relative_path = (
|
||||
f"apps/com.android.providers.telephony/d_f/000000_{message_type}_backup"
|
||||
)
|
||||
message_file = backup_path / relative_path
|
||||
message_file.parent.mkdir(parents=True)
|
||||
body_key = "body" if message_type == "sms" else "mms_body"
|
||||
messages = [
|
||||
{
|
||||
body_key: body,
|
||||
"date": str(1735689600000 + index * 1000),
|
||||
"date_sent": "0",
|
||||
}
|
||||
for index, body in enumerate(
|
||||
[
|
||||
"",
|
||||
"https://suspicious.example.org/first",
|
||||
"ordinary message",
|
||||
" \t ",
|
||||
"",
|
||||
"https://suspicious.example.org/last",
|
||||
]
|
||||
)
|
||||
]
|
||||
message_file.write_bytes(zlib.compress(json.dumps(messages).encode()))
|
||||
results_path = tmp_path / "results"
|
||||
results_path.mkdir()
|
||||
mod = SMS(target_path=str(backup_path), results_path=str(results_path))
|
||||
mod.from_dir(str(backup_path), [relative_path])
|
||||
if with_indicators:
|
||||
mod.indicators = indicators_factory(domains=["suspicious.example.org"])
|
||||
|
||||
with caplog.at_level(logging.ERROR):
|
||||
run_module(mod)
|
||||
|
||||
assert not [
|
||||
record for record in caplog.records if record.levelno == logging.ERROR
|
||||
]
|
||||
assert len(mod.results) == len(messages)
|
||||
assert mod.results[0]["links"] is None
|
||||
assert mod.url_results == [
|
||||
{
|
||||
"url": f"https://suspicious.example.org/{suffix}",
|
||||
"expanded_url": None,
|
||||
"timestamp": mod.results[index]["isodate"],
|
||||
"source": "sms",
|
||||
}
|
||||
for index, suffix in [(1, "first"), (5, "last")]
|
||||
]
|
||||
assert len(mod.alertstore.alerts) == (2 if with_indicators else 0)
|
||||
if with_indicators:
|
||||
assert [alert.event for alert in mod.alertstore.alerts] == [
|
||||
mod.results[1],
|
||||
mod.results[5],
|
||||
]
|
||||
assert json.loads((results_path / "sms.json").read_text()) == mod.results
|
||||
|
||||
def test_module_folder(self):
|
||||
backup_path = get_android_backup_folder()
|
||||
mod = SMS(target_path=backup_path)
|
||||
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
5test/build/fingerprint:12/TEST/V1.0:user/release-keystest-rev-001(¹`0º`8èBu:r:system_app:s0J/system/bin/test_process <RSIGSEGV"SEGV_MAPERR
|
||||
+1
@@ -0,0 +1 @@
|
||||
5test/build/fingerprint:13/TEST/V2.0:user/release-keystest-rev-002(”M0•M8‘NBu:r:untrusted_app:s0J-/data/app/com.example.app/lib/arm64/libapp.so xRSIGABRT"SI_TKILL‚•M•M
ExampleThread
|
||||
@@ -0,0 +1,2 @@
|
||||
5test/build/fingerprint:11/TEST/V3.0:user/release-keystest-rev-003" (çV0èV8ÐBu:r:shell:s0J/system/bin/app_process64 RSIGILL"
|
||||
ILL_ILLOPN
|
||||
Reference in new issue
Block a user