mirror of
https://github.com/mvt-project/mvt.git
synced 2026-07-02 19:25:48 +02:00
b8331ddac8
* Replace split("\n") with splitlines() for platform compatibility
* Remove dead commented-out code in webkit_session_resource_log
* Remove stale FIXME comment in command.py
* Narrow bare except to specific exception types in convert_mactime_to_datetime
* Fix typo in aqf_files.py comment
* Refactor b64 encoding in configuration_profiles into helper methods
* Pass branch parameter to GitHub commits API in update checker
* Replace bare KeyError catch with explicit key check in net_base
* Remove confirmed Chrome database path TODOs
Backup IDs verified via SHA-1 of AppDomain-com.google.chrome.ios paths.
* Extract additional timestamps from WebKit ObservedDomains table
Query mostRecentUserInteractionTime and mostRecentWebPushInteractionTime
with fallback to the original 4-column query for older iOS versions.
* Clarify command_line list format matches protobuf schema in tombstone parser
* Support SHA1 and MD5 hash matching in AQF files module
* Remove resolved TODO about --output requirement in download-apks
* Clean up code TODOs and type checks
* Fix WebKit timestamp schema handling
329 lines
15 KiB
Python
329 lines
15 KiB
Python
# Mobile Verification Toolkit (MVT)
|
|
# 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 logging
|
|
import sqlite3
|
|
from typing import Optional
|
|
|
|
from mvt.common.module_types import (
|
|
ModuleAtomicResult,
|
|
ModuleResults,
|
|
ModuleSerializedResult,
|
|
)
|
|
from mvt.common.utils import convert_mactime_to_iso
|
|
|
|
from ..base import IOSExtraction
|
|
|
|
INTERACTIONC_BACKUP_IDS = [
|
|
"1f5a521220a3ad80ebfdc196978df8e7a2e49dee",
|
|
]
|
|
INTERACTIONC_ROOT_PATHS = [
|
|
"private/var/mobile/Library/CoreDuet/People/interactionC.db",
|
|
]
|
|
# Taken from APOLLO
|
|
# https://github.com/mac4n6/APOLLO/blob/master/modules/interaction_contact_interactions.txt
|
|
QUERIES = [
|
|
"""SELECT
|
|
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
|
ZINTERACTIONS.ZENDDATE AS "end_date",
|
|
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
|
ZINTERACTIONS.ZACCOUNT AS "account",
|
|
ZINTERACTIONS.ZTARGETBUNDLEID AS "target_bundle_id",
|
|
CASE ZINTERACTIONS.ZDIRECTION
|
|
WHEN '0' THEN 'INCOMING'
|
|
WHEN '1' THEN 'OUTGOING'
|
|
END 'DIRECTION' AS "direction",
|
|
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
|
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
|
ZCONTACTS.ZPERSONID AS "sender_personid",
|
|
RECEIPIENTCONACT.ZDISPLAYNAME AS "recipient_display_name",
|
|
RECEIPIENTCONACT.ZIDENTIFIER AS "recipient_identifier",
|
|
RECEIPIENTCONACT.ZPERSONID AS "recipient_personid",
|
|
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
|
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
|
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
|
ZATTACHMENT.ZCONTENTTEXT AS "content",
|
|
ZATTACHMENT.ZUTI AS "uti",
|
|
ZATTACHMENT.ZCONTENTURL AS "attachment_content_url",
|
|
ZATTACHMENT.ZSIZEINBYTES AS "size",
|
|
ZATTACHMENT.ZPHOTOLOCALIDENTIFIER AS "photo_local_id",
|
|
HEX(ZATTACHMENT.ZIDENTIFIER) AS "attachment_id",
|
|
ZATTACHMENT.ZCLOUDIDENTIFIER AS "cloud_id",
|
|
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
|
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
|
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
|
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
|
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
|
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
|
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
|
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE AS "last_incoming_recipient_date",
|
|
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
|
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
|
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
|
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
|
ZINTERACTIONS.ZGROUPNAME AS "group_name",
|
|
ZINTERACTIONS.ZDERIVEDINTENTIDENTIFIER AS "derivied_intent_id",
|
|
ZINTERACTIONS.Z_PK AS "table_id"
|
|
FROM ZINTERACTIONS
|
|
LEFT JOIN ZCONTACTS
|
|
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
|
LEFT JOIN Z_1INTERACTIONS
|
|
ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
|
LEFT JOIN ZATTACHMENT
|
|
ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
|
LEFT JOIN Z_2INTERACTIONRECIPIENT
|
|
ON ZINTERACTIONS.Z_PK == Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
|
LEFT JOIN ZCONTACTS RECEIPIENTCONACT
|
|
ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS == RECEIPIENTCONACT.Z_PK;
|
|
""",
|
|
""" SELECT
|
|
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
|
ZINTERACTIONS.ZENDDATE AS "end_date",
|
|
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
|
ZINTERACTIONS.ZACCOUNT AS "account",
|
|
ZINTERACTIONS.ZTARGETBUNDLEID AS "target_bundle_id",
|
|
CASE ZINTERACTIONS.ZDIRECTION
|
|
WHEN '0' THEN 'INCOMING'
|
|
WHEN '1' THEN 'OUTGOING'
|
|
END 'DIRECTION' AS "direction",
|
|
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
|
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
|
ZCONTACTS.ZPERSONID AS "sender_personid",
|
|
RECEIPIENTCONACT.ZDISPLAYNAME AS "recipient_display_name",
|
|
RECEIPIENTCONACT.ZIDENTIFIER AS "recipient_identifier",
|
|
RECEIPIENTCONACT.ZPERSONID AS "recipient_personid",
|
|
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
|
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
|
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
|
ZATTACHMENT.ZCONTENTTEXT AS "content",
|
|
ZATTACHMENT.ZUTI AS "uti",
|
|
ZATTACHMENT.ZCONTENTURL AS "attachment_content_url",
|
|
ZATTACHMENT.ZSIZEINBYTES AS "size",
|
|
HEX(ZATTACHMENT.ZIDENTIFIER) AS "attachment_id",
|
|
ZATTACHMENT.ZCLOUDIDENTIFIER AS "cloud_id",
|
|
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
|
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
|
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
|
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
|
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
|
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
|
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
|
CASE ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
|
WHEN '0' THEN '0'
|
|
ELSE ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
|
END 'LAST INCOMING RECIPIENT DATE' AS "last_incoming_recipient_date",
|
|
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
|
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
|
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
|
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
|
ZINTERACTIONS.Z_PK AS "table_id"
|
|
FROM
|
|
ZINTERACTIONS
|
|
LEFT JOIN
|
|
ZCONTACTS
|
|
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
|
LEFT JOIN Z_1INTERACTIONS ON ZINTERACTIONS.Z_PK == Z_1INTERACTIONS.Z_3INTERACTIONS
|
|
LEFT JOIN ZATTACHMENT ON Z_1INTERACTIONS.Z_1ATTACHMENTS == ZATTACHMENT.Z_PK
|
|
LEFT JOIN Z_2INTERACTIONRECIPIENT ON ZINTERACTIONS.Z_PK== Z_2INTERACTIONRECIPIENT.Z_3INTERACTIONRECIPIENT
|
|
LEFT JOIN ZCONTACTS RECEIPIENTCONACT ON Z_2INTERACTIONRECIPIENT.Z_2RECIPIENTS== RECEIPIENTCONACT.Z_PK
|
|
""",
|
|
""" SELECT
|
|
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
|
ZINTERACTIONS.ZENDDATE AS "end_date",
|
|
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
|
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
|
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
|
ZCONTACTS.ZPERSONID AS "sender_personid",
|
|
ZINTERACTIONS.ZDIRECTION AS "direction",
|
|
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
|
ZINTERACTIONS.ZMECHANISM AS "mechanism",
|
|
ZINTERACTIONS.ZRECIPIENTCOUNT AS "recipient_count",
|
|
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
|
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
|
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
|
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
|
CASE
|
|
ZLASTINCOMINGRECIPIENTDATE
|
|
WHEN
|
|
'0'
|
|
THEN
|
|
'0'
|
|
ELSE
|
|
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
|
END AS "last_incoming_recipient_date",
|
|
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
|
ZINTERACTIONS.ZACCOUNT AS 'account',
|
|
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
|
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
|
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
|
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
|
ZCONTACTS.ZCUSTOMIDENTIFIER AS "custom_id",
|
|
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
|
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
|
ZINTERACTIONS.Z_PK AS "table_id"
|
|
FROM
|
|
ZINTERACTIONS
|
|
LEFT JOIN
|
|
ZCONTACTS
|
|
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
|
""",
|
|
""" SELECT
|
|
ZINTERACTIONS.ZSTARTDATE AS "start_date",
|
|
ZINTERACTIONS.ZENDDATE AS "end_date",
|
|
ZINTERACTIONS.ZCREATIONDATE AS "interactions_creation_date",
|
|
ZINTERACTIONS.ZBUNDLEID AS "bundle_id",
|
|
ZCONTACTS.ZDISPLAYNAME AS "sender_display_name",
|
|
ZCONTACTS.ZIDENTIFIER AS "sender_identifier",
|
|
ZCONTACTS.ZPERSONID AS "sender_personid",
|
|
ZINTERACTIONS.ZDIRECTION AS "direction",
|
|
ZINTERACTIONS.ZISRESPONSE AS "is_response",
|
|
ZINTERACTIONS.ZMECHANISM AS "mechanism",
|
|
ZCONTACTS.ZCREATIONDATE AS "contacts_creation_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGRECIPIENTDATE AS "first_incoming_recipient_date",
|
|
ZCONTACTS.ZFIRSTINCOMINGSENDERDATE AS "first_incoming_sender_date",
|
|
ZCONTACTS.ZFIRSTOUTGOINGRECIPIENTDATE AS "first_outgoing_recipient_date",
|
|
ZCONTACTS.ZLASTINCOMINGSENDERDATE AS "last_incoming_sender_date",
|
|
CASE
|
|
ZLASTINCOMINGRECIPIENTDATE
|
|
WHEN
|
|
'0'
|
|
THEN
|
|
'0'
|
|
ELSE
|
|
ZCONTACTS.ZLASTINCOMINGRECIPIENTDATE
|
|
END AS "last_incoming_recipient_date",
|
|
ZCONTACTS.ZLASTOUTGOINGRECIPIENTDATE AS "last_outgoing_recipient_date",
|
|
ZINTERACTIONS.ZACCOUNT AS "account",
|
|
ZINTERACTIONS.ZDOMAINIDENTIFIER AS "domain_identifier",
|
|
ZCONTACTS.ZINCOMINGRECIPIENTCOUNT AS "incoming_recipient_count",
|
|
ZCONTACTS.ZINCOMINGSENDERCOUNT AS "incoming_sender_count",
|
|
ZCONTACTS.ZOUTGOINGRECIPIENTCOUNT AS "outgoing_recipient_count",
|
|
ZINTERACTIONS.ZCONTENTURL AS "interaction_content_url",
|
|
ZINTERACTIONS.ZLOCATIONUUID AS "location_uuid",
|
|
ZINTERACTIONS.Z_PK AS "table_id"
|
|
FROM
|
|
ZINTERACTIONS
|
|
LEFT JOIN
|
|
ZCONTACTS
|
|
ON ZINTERACTIONS.ZSENDER = ZCONTACTS.Z_PK
|
|
""",
|
|
]
|
|
|
|
|
|
class InteractionC(IOSExtraction):
|
|
"""This module extracts data from InteractionC db."""
|
|
|
|
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[ModuleResults] = None,
|
|
) -> None:
|
|
super().__init__(
|
|
file_path=file_path,
|
|
target_path=target_path,
|
|
results_path=results_path,
|
|
module_options=module_options,
|
|
log=log,
|
|
results=results,
|
|
)
|
|
|
|
self.timestamps = [
|
|
"start_date",
|
|
"end_date",
|
|
"interactions_creation_date",
|
|
"contacts_creation_date",
|
|
"first_incoming_recipient_date",
|
|
"first_incoming_sender_date",
|
|
"first_outgoing_recipient_date",
|
|
"last_incoming_sender_date",
|
|
"last_incoming_recipient_date",
|
|
"last_outgoing_recipient_date",
|
|
]
|
|
|
|
def serialize(self, record: ModuleAtomicResult) -> ModuleSerializedResult:
|
|
records = []
|
|
processed = []
|
|
for timestamp in self.timestamps:
|
|
# Check if the record has the current timestamp.
|
|
if timestamp not in record or not record[timestamp]:
|
|
continue
|
|
|
|
# Check if the timestamp was already processed.
|
|
if record[timestamp] in processed:
|
|
continue
|
|
|
|
records.append(
|
|
{
|
|
"timestamp": record[timestamp],
|
|
"module": self.__class__.__name__,
|
|
"event": timestamp,
|
|
"data": f"[{record['bundle_id']}] {record['account']} - "
|
|
f"from {record['sender_display_name']} ({record['sender_identifier']}) "
|
|
f"to {record.get('recipient_display_name', '')} ({record.get('recipient_identifier', '')}):"
|
|
f" {record.get('content', '')}",
|
|
}
|
|
)
|
|
processed.append(record[timestamp])
|
|
|
|
return records
|
|
|
|
def run(self) -> None:
|
|
self._find_ios_database(
|
|
backup_ids=INTERACTIONC_BACKUP_IDS, root_paths=INTERACTIONC_ROOT_PATHS
|
|
)
|
|
self.log.info("Found InteractionC database at path: %s", self.file_path)
|
|
|
|
if not self.file_path:
|
|
return
|
|
conn = self._open_sqlite_db(self.file_path)
|
|
cur = conn.cursor()
|
|
|
|
try:
|
|
try:
|
|
cur.execute(QUERIES[0])
|
|
except sqlite3.OperationalError:
|
|
try:
|
|
cur.execute(QUERIES[1])
|
|
except sqlite3.OperationalError:
|
|
try:
|
|
cur.execute(QUERIES[2])
|
|
except sqlite3.OperationalError:
|
|
try:
|
|
cur.execute(QUERIES[3])
|
|
except sqlite3.OperationalError as e:
|
|
self.log.info(
|
|
"Error while reading the InteractionC table: %s", e
|
|
)
|
|
return None
|
|
|
|
names = [description[0] for description in cur.description]
|
|
for item in cur:
|
|
entry = {}
|
|
for index, value in enumerate(item):
|
|
if names[index] in self.timestamps:
|
|
if value is None or isinstance(value, str):
|
|
entry[names[index]] = value
|
|
else:
|
|
entry[names[index]] = convert_mactime_to_iso(value)
|
|
else:
|
|
entry[names[index]] = value
|
|
|
|
self.results.append(entry)
|
|
finally:
|
|
cur.close()
|
|
conn.close()
|
|
|
|
self.log.info("Extracted a total of %d InteractionC events", len(self.results))
|