mirror of
https://github.com/mvt-project/mvt.git
synced 2026-09-03 08:30:51 +02:00
fix(ios): preserve complete artifact records
This commit is contained in:
@@ -18,6 +18,7 @@ from mvt.common.utils import (
|
||||
generate_hashes_from_path,
|
||||
get_sha256_from_file_path,
|
||||
init_logging,
|
||||
sanitize_json_data,
|
||||
set_verbose_logging,
|
||||
)
|
||||
|
||||
@@ -108,6 +109,18 @@ class TestCustomJSONEncoder:
|
||||
)
|
||||
|
||||
|
||||
def test_sanitize_json_data_preserves_nested_binary_and_dates():
|
||||
value = {
|
||||
"blob": b"\x00\xff",
|
||||
"nested": [datetime(2023, 11, 13, 12, 21, 49, 727467)],
|
||||
}
|
||||
|
||||
assert sanitize_json_data(value) == {
|
||||
"blob": "AP8=",
|
||||
"nested": ["2023-11-13 12:21:49.727467"],
|
||||
}
|
||||
|
||||
|
||||
class TestInitLogging:
|
||||
def test__init_logging_is_idempotent(self):
|
||||
# Loaded module packages may import an MVT CLI module, which calls
|
||||
|
||||
@@ -21,6 +21,8 @@ class TestCalendarModule:
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
assert m.results[0]["summary"] == "Super interesting meeting"
|
||||
assert m.results[0]["record"]["calendar_app_link"] is None
|
||||
assert "participant_proposed_start_date" in m.results[0]["record"]
|
||||
|
||||
def test_calendar_with_explicit_file_path(self):
|
||||
backup_path = get_ios_backup_folder()
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2026 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 sqlite3
|
||||
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.mixed.calls import Calls
|
||||
|
||||
|
||||
def test_calls_preserves_complete_source_record(tmp_path):
|
||||
db_path = tmp_path / "CallHistory.storedata"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE ZCALLRECORD (
|
||||
ZDATE REAL, ZDURATION REAL, ZLOCATION TEXT, ZADDRESS BLOB,
|
||||
ZSERVICE_PROVIDER TEXT, ZORIGINATED INTEGER, ZANSWERED INTEGER,
|
||||
ZMETADATA BLOB
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.execute(
|
||||
"INSERT INTO ZCALLRECORD VALUES (?, ?, ?, ?, ?, ?, ?, ?);",
|
||||
(700000000, 42, "Berlin", b"+491234", "carrier", 1, 0, b"\x00\xff"),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
module = Calls(file_path=str(db_path))
|
||||
run_module(module)
|
||||
|
||||
assert len(module.results) == 1
|
||||
assert module.results[0]["number"] == "+491234"
|
||||
assert module.results[0]["call"]["ZORIGINATED"] == 1
|
||||
assert module.results[0]["call"]["ZANSWERED"] == 0
|
||||
assert module.results[0]["call"]["ZMETADATA"] == "AP8="
|
||||
@@ -0,0 +1,42 @@
|
||||
# Mobile Verification Toolkit (MVT)
|
||||
# Copyright (c) 2021-2026 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 sqlite3
|
||||
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.mixed.contacts import Contacts
|
||||
|
||||
|
||||
def test_contacts_preserves_complete_rows_and_people_without_values(tmp_path):
|
||||
db_path = tmp_path / "AddressBook.sqlitedb"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE ABPerson (
|
||||
ROWID INTEGER PRIMARY KEY, First TEXT, Middle TEXT, Last TEXT,
|
||||
Organization TEXT, Note TEXT
|
||||
);
|
||||
CREATE TABLE ABMultiValue (
|
||||
ROWID INTEGER PRIMARY KEY, record_id INTEGER, value TEXT,
|
||||
label INTEGER
|
||||
);
|
||||
INSERT INTO ABPerson VALUES (1, 'Alice', NULL, 'Example', NULL, 'note');
|
||||
INSERT INTO ABPerson VALUES (2, 'Bob', NULL, 'NoValue', NULL, 'retained');
|
||||
INSERT INTO ABMultiValue VALUES (10, 1, '+491234', 3);
|
||||
"""
|
||||
)
|
||||
conn.close()
|
||||
|
||||
module = Contacts(file_path=str(db_path))
|
||||
run_module(module)
|
||||
|
||||
assert len(module.results) == 2
|
||||
alice = next(result for result in module.results if result["first"] == "Alice")
|
||||
bob = next(result for result in module.results if result["first"] == "Bob")
|
||||
assert alice["contact"]["person_Note"] == "note"
|
||||
assert alice["contact"]["multivalue_label"] == 3
|
||||
assert alice["First"] == "Alice"
|
||||
assert bob["value"] is None
|
||||
assert bob["contact"]["person_Note"] == "retained"
|
||||
@@ -20,6 +20,8 @@ class TestDatausageModule:
|
||||
assert m.results[0]["isodate"][0:19] == "2019-08-27 15:08:09"
|
||||
assert len(m.results) == 42
|
||||
assert len(m.timeline) == 60
|
||||
assert "live_ZBILLCYCLEEND" in m.results[0]["record"]
|
||||
assert "process_Z_ENT" in m.results[0]["record"]
|
||||
assert (
|
||||
len(m.alertstore.alerts) == 1
|
||||
) # We now have a detection for missing processes.
|
||||
|
||||
@@ -20,6 +20,10 @@ class TestInteractionCModule:
|
||||
run_module(m)
|
||||
|
||||
assert len(m.results) == 3
|
||||
assert all(
|
||||
result["interaction"]["Z_PK"] == result["table_id"] for result in m.results
|
||||
)
|
||||
assert all("ZMECHANISM" in result["interaction"] for result in m.results)
|
||||
|
||||
incoming = next(
|
||||
r for r in m.results if r["sender_identifier"] == "100000000000001@lid"
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import sqlite3
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -52,6 +53,7 @@ class TestSafariBrowserStateModule:
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 1
|
||||
assert len(m.alertstore.alerts) == 0
|
||||
assert "tab" in m.results[0]
|
||||
|
||||
def test_parsing_backup_with_profile(self, backup_with_safari_profile):
|
||||
m = SafariBrowserState(target_path=backup_with_safari_profile)
|
||||
@@ -74,3 +76,30 @@ class TestSafariBrowserStateModule:
|
||||
assert len(m.alertstore.alerts) == 1
|
||||
assert len(m.results) == 1
|
||||
assert m.results[0]["tab_url"] == "https://en.wikipedia.org/wiki/NSO_Group"
|
||||
|
||||
def test_tabs_without_session_rows_are_not_dropped(self, tmp_path):
|
||||
db_path = tmp_path / "BrowserState.db"
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.executescript(
|
||||
"""
|
||||
CREATE TABLE tabs (
|
||||
uuid TEXT, title TEXT, url TEXT, user_visible_url TEXT,
|
||||
last_viewed_time REAL, private_browsing INTEGER
|
||||
);
|
||||
CREATE TABLE tab_sessions (tab_uuid TEXT, session_data BLOB);
|
||||
INSERT INTO tabs VALUES (
|
||||
'tab-1', 'Example', 'https://example.test',
|
||||
'https://example.test', 700000000, 1
|
||||
);
|
||||
"""
|
||||
)
|
||||
conn.close()
|
||||
module = SafariBrowserState(target_path=str(tmp_path))
|
||||
|
||||
module._process_browser_state_db(str(db_path))
|
||||
|
||||
assert len(module.results) == 1
|
||||
assert module.results[0]["tab_url"] == "https://example.test"
|
||||
assert module.results[0]["session_data"] == []
|
||||
assert module.results[0]["tab"]["private_browsing"] == 1
|
||||
assert module.results[0]["tab"]["session_data"] is None
|
||||
|
||||
@@ -105,6 +105,7 @@ class TestSafariHistoryModule:
|
||||
assert len(m.results) == 2
|
||||
assert {result["url"] for result in m.results} == {DEFAULT_URL, PROFILE_URL}
|
||||
assert len({result["safari_history_db"] for result in m.results}) == 2
|
||||
assert all("visit_history_item" in result["record"] for result in m.results)
|
||||
|
||||
def test_parsing_fs_dump_with_profile(self, fs_dump_with_safari_profile):
|
||||
m = SafariHistory(target_path=fs_dump_with_safari_profile)
|
||||
|
||||
@@ -22,6 +22,9 @@ class TestTCCModule:
|
||||
assert m.results[0]["service"] == "kTCCServiceUbiquity"
|
||||
assert m.results[0]["client"] == "com.apple.Preferences"
|
||||
assert m.results[0]["auth_value"] == "allowed"
|
||||
assert m.results[0]["auth_value_value"] == 2
|
||||
assert "policy_id" in m.results[0]
|
||||
assert "csreq" in m.results[0]
|
||||
|
||||
def test_tcc_detection(self, indicator_file):
|
||||
m = TCC(target_path=get_ios_backup_folder())
|
||||
|
||||
@@ -34,6 +34,7 @@ class TestWebkitResourceLoadStatisticsModule:
|
||||
"most_recent_web_push_interaction_time" not in result
|
||||
for result in m.results
|
||||
)
|
||||
assert all("record" in result for result in m.results)
|
||||
|
||||
def test_webkit_full_timestamp_schema(self, tmp_path):
|
||||
db_path = tmp_path / "observations.db"
|
||||
@@ -68,3 +69,4 @@ class TestWebkitResourceLoadStatisticsModule:
|
||||
assert "most_recent_user_interaction_time_isodate" in result
|
||||
assert result["most_recent_web_push_interaction_time"] == -1.0
|
||||
assert "most_recent_web_push_interaction_time_isodate" not in result
|
||||
assert result["record"]["domainID"] == 1
|
||||
|
||||
Reference in New Issue
Block a user