mirror of
https://github.com/mvt-project/mvt.git
synced 2024-06-26 14:28:54 +00:00
Adds calendar iOS plugin
This commit is contained in:
parent
b1e5dc715f
commit
33d092692e
|
@ -3,10 +3,10 @@
|
|||
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||
# https://license.mvt.re/1.1/
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import plistlib
|
||||
import sqlite3
|
||||
import copy
|
||||
from typing import Optional, Union
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_iso
|
||||
|
|
|
@ -15,8 +15,6 @@ from ..base import IOSExtraction
|
|||
class Filesystem(IOSExtraction):
|
||||
"""This module extracts creation and modification date of files from a
|
||||
full file-system dump.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
# https://license.mvt.re/1.1/
|
||||
|
||||
from .applications import Applications
|
||||
from .calendar import Calendar
|
||||
from .calls import Calls
|
||||
from .chrome_favicon import ChromeFavicon
|
||||
from .chrome_history import ChromeHistory
|
||||
|
@ -29,4 +30,5 @@ MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
|
|||
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
||||
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
||||
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
|
||||
WebkitSessionResourceLog, Whatsapp, Shortcuts, Applications]
|
||||
WebkitSessionResourceLog, Whatsapp, Shortcuts, Applications,
|
||||
Calendar]
|
||||
|
|
|
@ -3,16 +3,15 @@
|
|||
# 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 logging
|
||||
import plistlib
|
||||
import hashlib
|
||||
import logging
|
||||
import os
|
||||
import plistlib
|
||||
from datetime import datetime, timezone
|
||||
from typing import Optional, Union, Dict, Any
|
||||
from typing import Any, Dict, Optional, Union
|
||||
|
||||
from mvt.common.utils import convert_datetime_to_iso
|
||||
from mvt.common.module import DatabaseNotFoundError
|
||||
|
||||
from mvt.common.utils import convert_datetime_to_iso
|
||||
from mvt.ios.modules.base import IOSExtraction
|
||||
|
||||
APPLICATIONS_DB_PATH = [
|
||||
|
|
136
mvt/ios/modules/mixed/calendar.py
Normal file
136
mvt/ios/modules/mixed/calendar.py
Normal file
|
@ -0,0 +1,136 @@
|
|||
# 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 sqlite3
|
||||
from typing import Optional, Union
|
||||
|
||||
from mvt.common.utils import convert_mactime_to_iso
|
||||
|
||||
from ..base import IOSExtraction
|
||||
|
||||
CALENDAR_BACKUP_IDS = [
|
||||
"2041457d5fe04d39d0ab481178355df6781e6858",
|
||||
]
|
||||
CALENDAR_ROOT_PATHS = [
|
||||
"private/var/mobile/Library/Calendar/Calendar.sqlitedb"
|
||||
]
|
||||
|
||||
|
||||
class Calendar(IOSExtraction):
|
||||
"""This module extracts all calendar entries."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
file_path: Optional[str] = None,
|
||||
target_path: Optional[str] = None,
|
||||
results_path: Optional[str] = None,
|
||||
fast_mode: Optional[bool] = False,
|
||||
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, fast_mode=fast_mode,
|
||||
log=log, results=results)
|
||||
self.timestamps = [
|
||||
"start_date",
|
||||
"end_date",
|
||||
"last_modified",
|
||||
"creation_date",
|
||||
"participant_last_modified"
|
||||
]
|
||||
|
||||
def serialize(self, record: dict) -> Union[dict, list]:
|
||||
records = []
|
||||
for timestamp in self.timestamps:
|
||||
if timestamp not in record or not record[timestamp]:
|
||||
continue
|
||||
|
||||
records.append({
|
||||
"timestamp": record[timestamp],
|
||||
"module": self.__class__.__name__,
|
||||
"event": timestamp,
|
||||
"data": f"Calendar event {record['summary']} ({record['description']}) "
|
||||
f"(invitation by {record['participant_email']})"
|
||||
})
|
||||
return records
|
||||
|
||||
def check_indicators(self) -> None:
|
||||
for result in self.results:
|
||||
if result["participant_email"]:
|
||||
ioc = self.indicators.check_email(result["participant_email"])
|
||||
if ioc:
|
||||
result["matched_indicator"] = ioc
|
||||
self.detected.append(result)
|
||||
continue
|
||||
|
||||
# Custom check for Quadream exploit
|
||||
if result["summary"] == "Meeting" and result["description"] == "Notes":
|
||||
self.log.warning("Potential Quadream exploit event identified: %s", result["uuid"])
|
||||
self.detected.append(result)
|
||||
|
||||
def _parse_calendar_db(self):
|
||||
"""
|
||||
Parse the calendar database
|
||||
"""
|
||||
conn = sqlite3.connect(self.file_path)
|
||||
cur = conn.cursor()
|
||||
|
||||
cur.execute("""
|
||||
SELECT
|
||||
CalendarItem.ROWID as "id",
|
||||
CalendarItem.summary as "summary",
|
||||
CalendarItem.description as "description",
|
||||
CalendarItem.start_date as "start_date",
|
||||
CalendarItem.end_date as "end_date",
|
||||
CalendarItem.all_day as "all_day",
|
||||
CalendarItem.calendar_id as "calendar_id",
|
||||
CalendarItem.organizer_id as "organizer_id",
|
||||
CalendarItem.url as "url",
|
||||
CalendarItem.last_modified as "last_modified",
|
||||
CalendarItem.external_id as "external_id",
|
||||
CalendarItem.external_mod_tag as "external_mod_tag",
|
||||
CalendarItem.unique_identifier as "unique_identifier",
|
||||
CalendarItem.hidden as "hidden",
|
||||
CalendarItem.UUID as "uuid",
|
||||
CalendarItem.creation_date as "creation_date",
|
||||
CalendarItem.action as "action",
|
||||
CalendarItem.created_by_id as "created_by_id",
|
||||
Participant.UUID as "participant_uuid",
|
||||
Participant.email as "participant_email",
|
||||
Participant.phone_number as "participant_phone",
|
||||
Participant.comment as "participant_comment",
|
||||
Participant.last_modified as "participant_last_modified"
|
||||
FROM CalendarItem
|
||||
LEFT JOIN Participant ON Participant.ROWID = CalendarItem.organizer_id;
|
||||
""")
|
||||
|
||||
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)
|
||||
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
def run(self) -> None:
|
||||
self._find_ios_database(backup_ids=CALENDAR_BACKUP_IDS,
|
||||
root_paths=CALENDAR_ROOT_PATHS)
|
||||
self.log.info("Found calendar database at path: %s",
|
||||
self.file_path)
|
||||
|
||||
self._parse_calendar_db()
|
||||
|
||||
self.log.info("Extracted a total of %d calendar items",
|
||||
len(self.results))
|
Binary file not shown.
34
tests/ios_backup/test_calendar.py
Normal file
34
tests/ios_backup/test_calendar.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
# 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.common.indicators import Indicators
|
||||
from mvt.common.module import run_module
|
||||
from mvt.ios.modules.mixed.calendar import Calendar
|
||||
|
||||
from ..utils import get_ios_backup_folder
|
||||
|
||||
|
||||
class TestCalendarModule:
|
||||
|
||||
def test_calendar(self):
|
||||
m = Calendar(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 0
|
||||
assert m.results[0]["summary"] == "Super interesting meeting"
|
||||
|
||||
def test_calendar_detection(self, indicator_file):
|
||||
m = Calendar(target_path=get_ios_backup_folder())
|
||||
ind = Indicators(log=logging.getLogger())
|
||||
ind.parse_stix2(indicator_file)
|
||||
ind.ioc_collections[0]["emails"].append("user@example.org")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 1
|
||||
assert len(m.timeline) == 4
|
||||
assert len(m.detected) == 1
|
|
@ -17,8 +17,8 @@ class TestFilesystem:
|
|||
def test_filesystem(self):
|
||||
m = Filesystem(target_path=get_ios_backup_folder())
|
||||
run_module(m)
|
||||
assert len(m.results) == 12
|
||||
assert len(m.timeline) == 12
|
||||
assert len(m.results) == 14
|
||||
assert len(m.timeline) == 14
|
||||
assert len(m.detected) == 0
|
||||
|
||||
def test_detection(self, indicator_file):
|
||||
|
@ -29,6 +29,6 @@ class TestFilesystem:
|
|||
ind.ioc_collections[0]["processes"].append("64d0019cb3d46bfc8cce545a8ba54b93e7ea9347")
|
||||
m.indicators = ind
|
||||
run_module(m)
|
||||
assert len(m.results) == 12
|
||||
assert len(m.timeline) == 12
|
||||
assert len(m.results) == 14
|
||||
assert len(m.timeline) == 14
|
||||
assert len(m.detected) == 1
|
||||
|
|
Loading…
Reference in New Issue
Block a user