From 33d092692e758562d0b5804b846379fdd127e246 Mon Sep 17 00:00:00 2001 From: tek Date: Wed, 12 Apr 2023 10:21:17 +0200 Subject: [PATCH] Adds calendar iOS plugin --- mvt/ios/modules/fs/analytics.py | 2 +- mvt/ios/modules/fs/filesystem.py | 2 - mvt/ios/modules/mixed/__init__.py | 4 +- mvt/ios/modules/mixed/applications.py | 11 +- mvt/ios/modules/mixed/calendar.py | 136 ++++++++++++++++++ .../2041457d5fe04d39d0ab481178355df6781e6858 | Bin 0 -> 16384 bytes tests/ios_backup/test_calendar.py | 34 +++++ tests/ios_fs/test_filesystem.py | 8 +- 8 files changed, 183 insertions(+), 14 deletions(-) create mode 100644 mvt/ios/modules/mixed/calendar.py create mode 100644 tests/artifacts/ios_backup/20/2041457d5fe04d39d0ab481178355df6781e6858 create mode 100644 tests/ios_backup/test_calendar.py diff --git a/mvt/ios/modules/fs/analytics.py b/mvt/ios/modules/fs/analytics.py index ec02610..742418d 100644 --- a/mvt/ios/modules/fs/analytics.py +++ b/mvt/ios/modules/fs/analytics.py @@ -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 diff --git a/mvt/ios/modules/fs/filesystem.py b/mvt/ios/modules/fs/filesystem.py index 4aff021..7c5d6f3 100644 --- a/mvt/ios/modules/fs/filesystem.py +++ b/mvt/ios/modules/fs/filesystem.py @@ -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__( diff --git a/mvt/ios/modules/mixed/__init__.py b/mvt/ios/modules/mixed/__init__.py index 86c2a11..70cb2f4 100644 --- a/mvt/ios/modules/mixed/__init__.py +++ b/mvt/ios/modules/mixed/__init__.py @@ -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] diff --git a/mvt/ios/modules/mixed/applications.py b/mvt/ios/modules/mixed/applications.py index 7b255bb..0e0d23a 100644 --- a/mvt/ios/modules/mixed/applications.py +++ b/mvt/ios/modules/mixed/applications.py @@ -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 = [ diff --git a/mvt/ios/modules/mixed/calendar.py b/mvt/ios/modules/mixed/calendar.py new file mode 100644 index 0000000..4813800 --- /dev/null +++ b/mvt/ios/modules/mixed/calendar.py @@ -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)) diff --git a/tests/artifacts/ios_backup/20/2041457d5fe04d39d0ab481178355df6781e6858 b/tests/artifacts/ios_backup/20/2041457d5fe04d39d0ab481178355df6781e6858 new file mode 100644 index 0000000000000000000000000000000000000000..0931e92ce5339140d0519ed8e164100a5d76d2c6 GIT binary patch literal 16384 zcmeI3y>Ht_6u?Q_NaDe9ht{475O54ch!xnfW!Y0A+p=NQUsYtZo(?DSBwbA&nd4E8 zvK1Y=b?wl(Q;Yr;o%<)WQ`ew>Lf=s&b^36CEJXu6LKew;_wnxcK2EfBuf9CviaDK7 z22?rQ)+d&2TQ42QvaE&d4^HVZ2k*KpKkWSCJ?91Mxoa;$h4}}+SdUkheuontNB{{S z0VIF~K7_yzEAtOm*VpZzLPgs?JET4n9+jtx1?Kg_VY5+hHJn!c;H=@866b02@~hJ$ z=k%i0IB7JUtLEu>y?Nt&(YSHy*R9Lb3ut=YxM*!SaS{Yn4xCowYq;}6mnt3#!o9rl zhU5C22}NegJQll>N4ibcMXab)#G|m*f_i5#LRz5SW^$N{Db=@yjQT#v2FB3Y)W`G* zCGQeg%Y*4sW7tr3so-y!9IF*GzeA`}Ff3!^5=B)KkA~)=&!I@efEkR5XrFSQwz!P(`(h9g-Vg(7Ce4TAuBG8x1Jn6XFhsnX*f3_XI;qcK!<6Dc()W6oNTo6;!0 zz6Q_F^nw|v0a1g9jrs#Sfd`qE^I*D$=jhwQCH2j;TZTd8v(z_?cCKVYe=*9aYlH@k zBI0v#n<4Ue9Qk16P!^99l8sRRF%n&T1$26Cl z=ZIKeWtN99fG8x22ZrUdLhAK-9LfP{vmWh(GmMlO=Bk;QPA96Ff;j1R8R+g22z5SUIT zqktwc%acRLr(GkE^TvXL5p_V9uI3)BK6_^0q|0Lb8uBlL=EDp+aXUY@I8F$k>Ml2+ zW$C-F{cv?{&Hnzgw9l*D>4Y*a=f?Un1Rt=@>SQ{VipCSub^_0khv34LY9Giv@vpb*3*=o3N)p zWBt272-55VKH1Jco6M*>!wJ*HhD;At($D7h@~;-W@IV4c00|%gB!C2v01`j~NB{{S z0VIF~{s#n__DZ3}b@}t+nr>D%+tsaFd3U!~D_6I+_RISet}0$-x83e+gSeK&Oul4q zsQwS_bNCWqtFpaQ-L38E=l{9ozpUkd;0+HXfCP{L5$~(2=qw?Nf W^{`yu+}k;-ZdQ)B4|ky6Z~p=ekBDdh literal 0 HcmV?d00001 diff --git a/tests/ios_backup/test_calendar.py b/tests/ios_backup/test_calendar.py new file mode 100644 index 0000000..75383c6 --- /dev/null +++ b/tests/ios_backup/test_calendar.py @@ -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 diff --git a/tests/ios_fs/test_filesystem.py b/tests/ios_fs/test_filesystem.py index a8d8b89..c313c99 100644 --- a/tests/ios_fs/test_filesystem.py +++ b/tests/ios_fs/test_filesystem.py @@ -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