mirror of
https://github.com/mvt-project/mvt.git
synced 2024-06-29 07:39:00 +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
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import plistlib
|
import plistlib
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import copy
|
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from mvt.common.utils import convert_mactime_to_iso
|
from mvt.common.utils import convert_mactime_to_iso
|
||||||
|
|
|
@ -15,8 +15,6 @@ from ..base import IOSExtraction
|
||||||
class Filesystem(IOSExtraction):
|
class Filesystem(IOSExtraction):
|
||||||
"""This module extracts creation and modification date of files from a
|
"""This module extracts creation and modification date of files from a
|
||||||
full file-system dump.
|
full file-system dump.
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .applications import Applications
|
from .applications import Applications
|
||||||
|
from .calendar import Calendar
|
||||||
from .calls import Calls
|
from .calls import Calls
|
||||||
from .chrome_favicon import ChromeFavicon
|
from .chrome_favicon import ChromeFavicon
|
||||||
from .chrome_history import ChromeHistory
|
from .chrome_history import ChromeHistory
|
||||||
|
@ -29,4 +30,5 @@ MIXED_MODULES = [Calls, ChromeFavicon, ChromeHistory, Contacts, FirefoxFavicon,
|
||||||
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
FirefoxHistory, IDStatusCache, InteractionC, LocationdClients,
|
||||||
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
OSAnalyticsADDaily, Datausage, SafariBrowserState, SafariHistory,
|
||||||
TCC, SMS, SMSAttachments, WebkitResourceLoadStatistics,
|
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
|
# Use of this software is governed by the MVT License 1.1 that can be found at
|
||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
import os
|
|
||||||
import logging
|
|
||||||
import plistlib
|
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import plistlib
|
||||||
from datetime import datetime, timezone
|
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.module import DatabaseNotFoundError
|
||||||
|
from mvt.common.utils import convert_datetime_to_iso
|
||||||
from mvt.ios.modules.base import IOSExtraction
|
from mvt.ios.modules.base import IOSExtraction
|
||||||
|
|
||||||
APPLICATIONS_DB_PATH = [
|
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):
|
def test_filesystem(self):
|
||||||
m = Filesystem(target_path=get_ios_backup_folder())
|
m = Filesystem(target_path=get_ios_backup_folder())
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 12
|
assert len(m.results) == 14
|
||||||
assert len(m.timeline) == 12
|
assert len(m.timeline) == 14
|
||||||
assert len(m.detected) == 0
|
assert len(m.detected) == 0
|
||||||
|
|
||||||
def test_detection(self, indicator_file):
|
def test_detection(self, indicator_file):
|
||||||
|
@ -29,6 +29,6 @@ class TestFilesystem:
|
||||||
ind.ioc_collections[0]["processes"].append("64d0019cb3d46bfc8cce545a8ba54b93e7ea9347")
|
ind.ioc_collections[0]["processes"].append("64d0019cb3d46bfc8cce545a8ba54b93e7ea9347")
|
||||||
m.indicators = ind
|
m.indicators = ind
|
||||||
run_module(m)
|
run_module(m)
|
||||||
assert len(m.results) == 12
|
assert len(m.results) == 14
|
||||||
assert len(m.timeline) == 12
|
assert len(m.timeline) == 14
|
||||||
assert len(m.detected) == 1
|
assert len(m.detected) == 1
|
||||||
|
|
Loading…
Reference in New Issue
Block a user