mirror of
https://github.com/mvt-project/mvt.git
synced 2026-09-28 12:12:10 +02:00
Added modules to extract details on configuration profiles from backup
This commit is contained in:
@@ -92,9 +92,9 @@ class MVTModule(object):
|
|||||||
if self.results:
|
if self.results:
|
||||||
results_file_name = f"{name}.json"
|
results_file_name = f"{name}.json"
|
||||||
results_json_path = os.path.join(self.output_folder, results_file_name)
|
results_json_path = os.path.join(self.output_folder, results_file_name)
|
||||||
with open(results_json_path, "w") as handle:
|
with io.open(results_json_path, "w", encoding="utf-8") as handle:
|
||||||
try:
|
try:
|
||||||
json.dump(self.results, handle, indent=4)
|
json.dump(self.results, handle, indent=4, default=str)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.log.error("Unable to store results of module %s to file %s: %s",
|
self.log.error("Unable to store results of module %s to file %s: %s",
|
||||||
self.__class__.__name__, results_file_name, e)
|
self.__class__.__name__, results_file_name, e)
|
||||||
@@ -102,8 +102,8 @@ class MVTModule(object):
|
|||||||
if self.detected:
|
if self.detected:
|
||||||
detected_file_name = f"{name}_detected.json"
|
detected_file_name = f"{name}_detected.json"
|
||||||
detected_json_path = os.path.join(self.output_folder, detected_file_name)
|
detected_json_path = os.path.join(self.output_folder, detected_file_name)
|
||||||
with open(detected_json_path, "w") as handle:
|
with io.open(detected_json_path, "w", encoding="utf-8") as handle:
|
||||||
json.dump(self.detected, handle, indent=4)
|
json.dump(self.detected, handle, indent=4, default=str)
|
||||||
|
|
||||||
def serialize(self, record):
|
def serialize(self, record):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@@ -193,8 +193,8 @@ def save_timeline(timeline, timeline_path):
|
|||||||
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
csvoutput.writerow(["UTC Timestamp", "Plugin", "Event", "Description"])
|
||||||
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):
|
for event in sorted(timeline, key=lambda x: x["timestamp"] if x["timestamp"] is not None else ""):
|
||||||
csvoutput.writerow([
|
csvoutput.writerow([
|
||||||
event["timestamp"],
|
event.get("timestamp"),
|
||||||
event["module"],
|
event.get("module"),
|
||||||
event["event"],
|
event.get("event"),
|
||||||
event["data"],
|
event.get("data"),
|
||||||
])
|
])
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
# https://license.mvt.re/1.1/
|
# https://license.mvt.re/1.1/
|
||||||
|
|
||||||
from .backup_info import BackupInfo
|
from .backup_info import BackupInfo
|
||||||
|
from .configuration_profiles import ConfigurationProfiles
|
||||||
from .manifest import Manifest
|
from .manifest import Manifest
|
||||||
|
from .profile_events import ProfileEvents
|
||||||
|
|
||||||
BACKUP_MODULES = [BackupInfo, Manifest,]
|
BACKUP_MODULES = [BackupInfo, ConfigurationProfiles, Manifest, ProfileEvents]
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021 The MVT Project 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 os
|
||||||
|
import plistlib
|
||||||
|
from base64 import b64encode
|
||||||
|
|
||||||
|
from ..base import IOSExtraction
|
||||||
|
|
||||||
|
CONF_PROFILES_DOMAIN = "SysSharedContainerDomain-systemgroup.com.apple.configurationprofiles"
|
||||||
|
|
||||||
|
class ConfigurationProfiles(IOSExtraction):
|
||||||
|
"""This module extracts the full plist data from configuration profiles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||||
|
fast_mode=False, log=None, results=[]):
|
||||||
|
super().__init__(file_path=file_path, base_folder=base_folder,
|
||||||
|
output_folder=output_folder, fast_mode=fast_mode,
|
||||||
|
log=log, results=results)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
for conf_file in self._get_files_from_manifest(domain=CONF_PROFILES_DOMAIN):
|
||||||
|
conf_file_path = self._get_backup_file_from_id(conf_file["file_id"])
|
||||||
|
if not conf_file_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(conf_file_path, "rb") as handle:
|
||||||
|
conf_plist = plistlib.load(handle)
|
||||||
|
|
||||||
|
if "SignerCerts" in conf_plist:
|
||||||
|
conf_plist["SignerCerts"] = [b64encode(x) for x in conf_plist["SignerCerts"]]
|
||||||
|
|
||||||
|
self.results.append(dict(
|
||||||
|
file_id=conf_file["file_id"],
|
||||||
|
relative_path=conf_file["relative_path"],
|
||||||
|
domain=conf_file["domain"],
|
||||||
|
plist=conf_plist,
|
||||||
|
))
|
||||||
|
|
||||||
|
self.log.info("Extracted details about %d configuration profiles", len(self.results))
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Mobile Verification Toolkit (MVT)
|
||||||
|
# Copyright (c) 2021 The MVT Project 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 plistlib
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from mvt.common.utils import convert_timestamp_to_iso
|
||||||
|
|
||||||
|
from ..base import IOSExtraction
|
||||||
|
|
||||||
|
CONF_PROFILES_EVENTS_RELPATH = "Library/ConfigurationProfiles/MCProfileEvents.plist"
|
||||||
|
|
||||||
|
class ProfileEvents(IOSExtraction):
|
||||||
|
"""This module extracts events related to the installation of configuration
|
||||||
|
profiles.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, file_path=None, base_folder=None, output_folder=None,
|
||||||
|
fast_mode=False, log=None, results=[]):
|
||||||
|
super().__init__(file_path=file_path, base_folder=base_folder,
|
||||||
|
output_folder=output_folder, fast_mode=fast_mode,
|
||||||
|
log=log, results=results)
|
||||||
|
|
||||||
|
def serialize(self, record):
|
||||||
|
return {
|
||||||
|
"timestamp": record.get("timestamp"),
|
||||||
|
"module": self.__class__.__name__,
|
||||||
|
"event": "profile_operation",
|
||||||
|
"data": f"Process {record.get('process')} started operation {record.get('operation')} of profile {record.get('profile_id')}"
|
||||||
|
}
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
for events_file in self._get_files_from_manifest(relative_path=CONF_PROFILES_EVENTS_RELPATH):
|
||||||
|
events_file_path = self._get_backup_file_from_id(events_file["file_id"])
|
||||||
|
if not events_file_path:
|
||||||
|
continue
|
||||||
|
|
||||||
|
with open(events_file_path, "rb") as handle:
|
||||||
|
events_plist = plistlib.load(handle)
|
||||||
|
|
||||||
|
if "ProfileEvents" not in events_plist:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for event in events_plist["ProfileEvents"]:
|
||||||
|
key = list(event.keys())[0]
|
||||||
|
self.log.info("On %s process \"%s\" started operation \"%s\" of profile \"%s\"",
|
||||||
|
event[key].get("timestamp"), event[key].get("process"),
|
||||||
|
event[key].get("operation"), key)
|
||||||
|
|
||||||
|
self.results.append({
|
||||||
|
"profile_id": key,
|
||||||
|
"timestamp": convert_timestamp_to_iso(event[key].get("timestamp")),
|
||||||
|
"operation": event[key].get("operation"),
|
||||||
|
"process": event[key].get("process"),
|
||||||
|
})
|
||||||
|
|
||||||
|
self.log.info("Extracted %d profile events", len(self.results))
|
||||||
+14
-4
@@ -94,7 +94,18 @@ class IOSExtraction(MVTModule):
|
|||||||
raise Exception("Query to Manifest.db failed: %s", e)
|
raise Exception("Query to Manifest.db failed: %s", e)
|
||||||
|
|
||||||
for row in cur:
|
for row in cur:
|
||||||
yield dict(file_id=row[0], domain=row[1], relative_path=row[2])
|
yield {
|
||||||
|
"file_id": row[0],
|
||||||
|
"domain": row[1],
|
||||||
|
"relative_path": row[2],
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_backup_file_from_id(self, file_id):
|
||||||
|
file_path = os.path.join(self.base_folder, file_id[0:2], file_id)
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
return file_path
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _find_ios_database(self, backup_ids=None, root_paths=[]):
|
def _find_ios_database(self, backup_ids=None, root_paths=[]):
|
||||||
"""Try to locate the module's database file from either an iTunes
|
"""Try to locate the module's database file from either an iTunes
|
||||||
@@ -110,9 +121,8 @@ class IOSExtraction(MVTModule):
|
|||||||
# folder structure, if we have a valid ID.
|
# folder structure, if we have a valid ID.
|
||||||
if backup_ids:
|
if backup_ids:
|
||||||
for backup_id in backup_ids:
|
for backup_id in backup_ids:
|
||||||
file_path = os.path.join(self.base_folder, backup_id[0:2], backup_id)
|
file_path = self._get_backup_file_from_id(backup_id)
|
||||||
# If we found the correct backup file, then we stop searching.
|
if file_path:
|
||||||
if os.path.exists(file_path):
|
|
||||||
break
|
break
|
||||||
|
|
||||||
# If this file does not exist we might be processing a full
|
# If this file does not exist we might be processing a full
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class Filesystem(IOSExtraction):
|
|||||||
return {
|
return {
|
||||||
"timestamp": record["modified"],
|
"timestamp": record["modified"],
|
||||||
"module": self.__class__.__name__,
|
"module": self.__class__.__name__,
|
||||||
"event": f"file_modified",
|
"event": "file_modified",
|
||||||
"data": record["file_path"],
|
"data": record["file_path"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ class WebkitResourceLoadStatistics(IOSExtraction):
|
|||||||
output_folder=output_folder, fast_mode=fast_mode,
|
output_folder=output_folder, fast_mode=fast_mode,
|
||||||
log=log, results=results)
|
log=log, results=results)
|
||||||
|
|
||||||
|
self.results = {}
|
||||||
|
|
||||||
def check_indicators(self):
|
def check_indicators(self):
|
||||||
if not self.indicators:
|
if not self.indicators:
|
||||||
return
|
return
|
||||||
@@ -72,8 +74,6 @@ class WebkitResourceLoadStatistics(IOSExtraction):
|
|||||||
self.log.info("Extracted a total of %d records from %s", len(self.results[key]), db_path)
|
self.log.info("Extracted a total of %d records from %s", len(self.results[key]), db_path)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.results = {}
|
|
||||||
|
|
||||||
if self.is_backup:
|
if self.is_backup:
|
||||||
try:
|
try:
|
||||||
for backup_file in self._get_files_from_manifest(relative_path=WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH):
|
for backup_file in self._get_files_from_manifest(relative_path=WEBKIT_RESOURCELOADSTATICS_BACKUP_RELPATH):
|
||||||
|
|||||||
Reference in New Issue
Block a user