Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 67 additions & 96 deletions scripts/artifacts/appItunesmeta.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,121 +3,92 @@
"name": "Apps - Itunes Metadata",
"description": "iTunes & Bundle ID Metadata contents for apps",
"author": "@AlexisBrignoni",
"version": "0.2",
"date": "2020-10-04",
"creation_date": "2020-10-04",
"last_update_date": "2025-12-16",
"requirements": "none",
"category": "Installed Apps",
"notes": "",
"paths": ('*/iTunesMetadata.plist', '**/BundleMetadata.plist',),
"function": "get_appItunesmeta",
"paths": ('*/iTunesMetadata.plist', '*/BundleMetadata.plist',),
"output_types": "standard"
}
}

import biplist
import pathlib
import os
import nska_deserialize as nd
import plistlib
import sys
from datetime import *
import pytz

#from scripts.artifact_report import ArtifactHtmlReport
#from scripts.ilapfuncs import logfunc, tsv, timeline, is_platform_windows
from scripts.ilapfuncs import artifact_processor, convert_ts_human_to_timezone_offset, convert_time_obj_to_utc

def convert_plist_date_to_timezone_offset_b(plist_date, timezone_offset):
if plist_date:
plist_date = datetime.strptime(plist_date, '%Y-%m-%dT%H:%M:%SZ')
iso_date = plist_date.strftime("%Y-%m-%d %H:%M:%S")

return convert_ts_human_to_timezone_offset(iso_date, timezone_offset)
else:
return plist_date

from scripts.ilapfuncs import artifact_processor, convert_time_obj_to_utc, convert_plist_date_to_utc, get_plist_file_content

@artifact_processor
def get_appItunesmeta(files_found, report_folder, seeker, wrap_text, timezone_offset):
def get_appItunesmeta(context):
data_list = []
for file_found in files_found:
for file_found in context.get_files_found():
file_found = str(file_found)

if file_found.endswith('iTunesMetadata.plist'):
with open(file_found, "rb") as fp:
if sys.version_info >= (3, 9):
plist = plistlib.load(fp)
else:
plist = biplist.readPlist(fp)

purchasedate = plist.get('com.apple.iTunesStore.downloadInfo', {}).get('purchaseDate', '')
#print(purchasedate, timezone_offset)
purchasedate = convert_plist_date_to_timezone_offset_b(purchasedate, timezone_offset)
# with open(file_found, "rb") as fp:
# if sys.version_info >= (3, 9):
# plist = plistlib.load(fp)
# else:
# plist = biplist.readPlist(fp)

plist = get_plist_file_content(file_found)

# Check if plist is a valid parseable object
if not plist or not isinstance(plist, dict):
continue

bundleid = plist.get('softwareVersionBundleId', '')
itemname = plist.get('itemName', '')
artistname = plist.get('artistName', '')
versionnum = plist.get('bundleShortVersionString', '')
downloadedby = plist.get('com.apple.iTunesStore.downloadInfo', {}) .get('accountInfo', {}).get('AppleID', '')
genre = plist.get('genre', '')
factoryinstall = plist.get('isFactoryInstall', '')
appreleasedate = plist.get('releaseDate', '')
appreleasedate = convert_plist_date_to_timezone_offset_b(appreleasedate, timezone_offset)
sourceapp = plist.get('sourceApp', '')
sideloaded = plist.get('sideLoadedDeviceBasedVPP', '')
variantid = plist.get('variantID', '')

p = pathlib.Path(file_found)
parent = p.parent
parent = str(parent)
purchasedate = plist.get('com.apple.iTunesStore.downloadInfo', {}).get('purchaseDate', '')
#print(purchasedate, timezone_offset)
purchasedate = convert_plist_date_to_utc(purchasedate)

bundleid = plist.get('softwareVersionBundleId', '')
itemname = plist.get('itemName', '')
artistname = plist.get('artistName', '')
versionnum = plist.get('bundleShortVersionString', '')
downloadedby = plist.get('com.apple.iTunesStore.downloadInfo', {}) .get('accountInfo', {}).get('AppleID', '')
genre = plist.get('genre', '')
factoryinstall = plist.get('isFactoryInstall', '')
appreleasedate = plist.get('releaseDate', '')
appreleasedate = convert_plist_date_to_utc(appreleasedate)
sourceapp = plist.get('sourceApp', '')
sideloaded = plist.get('sideLoadedDeviceBasedVPP', '')
variantid = plist.get('variantID', '')

p = pathlib.Path(file_found)
parent = p.parent
parent = str(parent)

itunes_metadata_path = (os.path.join(parent, "BundleMetadata.plist"))
if os.path.exists(itunes_metadata_path):
with open(itunes_metadata_path, 'rb') as f:
deserialized_plist = nd.deserialize_plist(f)
install_date = deserialized_plist.get('installDate', '')
#print(install_date, type(install_date))
install_date = convert_time_obj_to_utc(install_date)
else:
itunes_metadata_path = (os.path.join(parent, "BundleMetadata.plist"))
if os.path.exists(itunes_metadata_path):
#with open(itunes_metadata_path, 'rb') as f:
deserialized_plist = get_plist_file_content(itunes_metadata_path)
# Check if deserialized_plist is a valid parseable object
if not deserialized_plist or not isinstance(deserialized_plist, dict):
install_date = ''

data_list.append((install_date, purchasedate, bundleid, itemname, artistname, versionnum, downloadedby, genre, factoryinstall, appreleasedate, sourceapp, sideloaded, variantid, parent))
else:
install_date = deserialized_plist.get('installDate', '')
#print(install_date, type(install_date))
install_date = convert_time_obj_to_utc(install_date)
else:
install_date = ''

data_list.append((install_date, purchasedate, bundleid, itemname, artistname, versionnum, downloadedby, genre, factoryinstall, appreleasedate, sourceapp, sideloaded, variantid, parent))

if len(data_list) > 0:
fileloc = 'See source file location column'
"""
description = 'iTunes & Bundle ID Metadata contents for apps'
report = ArtifactHtmlReport('Apps - Itunes & Bundle Metadata')
report.start_artifact_report(report_folder, 'Apps - Itunes Metadata', description)
report.add_script()
data_headers = ('Installed Date', 'App Purchase Date','Bundle ID', 'Item Name', 'Artist Name', 'Version Number', 'Downloaded by', 'Genre', 'Factory Install', 'App Release Date', 'Source App', 'Sideloaded?', 'Variant ID', 'Source File Location')
report.write_artifact_data_table(data_headers, data_list, fileloc)
report.end_artifact_report()

tsvname = 'Apps - Itunes Bundle Metadata'
tsv(report_folder, data_headers, data_list, tsvname)

tlactivity = 'Apps - Itunes Bundle Metadata'
timeline(report_folder, tlactivity, data_list, data_headers)
else:
logfunc('No data on Apps - Itunes Bundle Metadata')
"""
data_headers = (
('Installed Date','datetime'),
('App Purchase Date','datetime'),
'Bundle ID',
'Item Name',
'Artist Name',
'Version Number',
'Downloaded by',
'Genre',
'Factory Install',
'App Release Date',
'Source App',
'Sideloaded?',
'Variant ID',
'Source File Location')
return data_headers, data_list, fileloc
data_headers = (
('Installed Date','datetime'),
('App Purchase Date','datetime'),
'Bundle ID',
'Item Name',
'Artist Name',
'Version Number',
'Downloaded by',
'Genre',
'Factory Install',
'App Release Date',
'Source App',
'Sideloaded?',
'Variant ID',
'Source File Location')
return data_headers, data_list, 'see Source File Location'



Expand Down
18 changes: 13 additions & 5 deletions scripts/artifacts/appleAlarms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Extraction of alarms set",
"author": "Anna-Mariya Mateyna",
"creation_date": "2021-01-17",
"last_update_date": "2024-12-22",
"last_update_date": "2025-12-16",
"requirements": "none",
"category": "Clock",
"notes": "",
Expand All @@ -14,7 +14,7 @@
}
}

from scripts.ilapfuncs import artifact_processor, get_file_path, get_plist_file_content, convert_plist_date_to_utc
from scripts.ilapfuncs import artifact_processor, get_file_path, get_plist_file_content, convert_plist_date_to_utc, logfunc

def decode_repeat_schedule(repeat_schedule_value):
days_list = {
Expand Down Expand Up @@ -43,15 +43,21 @@ def decode_repeat_schedule(repeat_schedule_value):


@artifact_processor
def alarms(files_found, report_folder, seeker, wrap_text, timezone_offset):
def alarms(context):
files_found = context.get_files_found()
source_path = get_file_path(files_found, "com.apple.mobiletimerd.plist")
data_list = []

pl = get_plist_file_content(source_path)

# Check if plist is valid before processing
if not pl or not isinstance(pl, dict):
return (), [], ''

if 'MTAlarms' in pl:
if 'MTAlarms' in pl['MTAlarms']:
for alarms in pl['MTAlarms']['MTAlarms']:
alarms_dict = alarms['$MTAlarm']
for alarm in pl['MTAlarms']['MTAlarms']:
alarms_dict = alarm['$MTAlarm']

alarm_title = alarms_dict.get('MTAlarmTitle', 'Alarm')
alarm_hour = alarms_dict.get('MTAlarmHour', '')
Expand Down Expand Up @@ -82,6 +88,7 @@ def alarms(files_found, report_folder, seeker, wrap_text, timezone_offset):

if 'MTSleepAlarm' in pl['MTAlarms']:
for sleep_alarms in pl['MTAlarms']['MTSleepAlarm']:
logfunc(sleep_alarms)
sleep_alarm_dict = pl['MTAlarms']['MTSleepAlarm'][sleep_alarms]

alarm_title = sleep_alarm_dict.get('MTAlarmTitle', 'Bedtime')
Expand All @@ -95,6 +102,7 @@ def alarms(files_found, report_folder, seeker, wrap_text, timezone_offset):

data_list.append(
(fire_date,
None, # alarm time
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch on this data misalignment. i am not super familiar with this artifact, is the 'MTSleepAlarm' condition never having a time? did you find any examples to validate in test data?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just put None in there to fix the alignment. I thought about getting the hour and minute to build the time string, but I have no idea what the internal structure for that artifact looks like and didn't want to assume those fields exist. I couldn't find any test data.

alarm_title,
sleep_alarm_dict['MTAlarmEnabled'],
dismiss_date,
Expand Down
10 changes: 8 additions & 2 deletions scripts/artifacts/appleLocationd.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Extracts location services settings",
"author": "@AlexisBrignoni",
"creation_date": "2023-10-03",
"last_update_date": "2024-12-20",
"last_update_date": "2025-10-08",
"requirements": "none",
"category": "Identifiers",
"notes": "",
Expand All @@ -17,11 +17,17 @@
from scripts.ilapfuncs import artifact_processor, get_file_path, get_plist_file_content, device_info, convert_cocoa_core_data_ts_to_utc

@artifact_processor
def appleLocationd(files_found, report_folder, seeker, wrap_text, timezone_offset):
def appleLocationd(context):
files_found = context.get_files_found()
source_path = get_file_path(files_found, "com.apple.locationd.plist")
data_list = []

pl = get_plist_file_content(source_path)

# Check if plist is valid before processing
if not pl or not isinstance(pl, dict):
return (), [], ''

for key, val in pl.items():
if key == 'LocationServicesEnabledIn8.0':
data_list.append(('Location Services Enabled', val))
Expand Down
9 changes: 7 additions & 2 deletions scripts/artifacts/appleMapsApplication.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"author": "@AlexisBrignoni",
"creation_date": "2020-08-03",
"last_update_date": "2024-12-20",
"last_update_date": "2025-10-08",
"requirements": "none",
"category": "Locations",
"notes": "",
Expand All @@ -19,12 +19,17 @@
from scripts.ilapfuncs import artifact_processor, get_file_path, get_plist_file_content

@artifact_processor
def appleMapsApplication(files_found, report_folder, seeker, wrap_text, timezone_offset):
def appleMapsApplication(context):
files_found = context.get_files_found()
source_path = get_file_path(files_found, "com.apple.Maps.plist")
data_list = []

plist = get_plist_file_content(source_path)

# Check if plist is valid before processing
if not plist or not isinstance(plist, dict):
return (), [], ''

types = {'1': {'type': 'double', 'name': 'Latitude'},
'2': {'type': 'double', 'name': 'Longitude'},
'3': {'type': 'double', 'name': ''},
Expand Down
10 changes: 7 additions & 3 deletions scripts/artifacts/appleMapsGroup.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "",
"author": "@AlexisBrignoni",
"creation_date": "2020-08-03",
"last_update_date": "2025-01-21",
"last_update_date": "2025-12-16",
"requirements": "none",
"category": "Locations",
"notes": "",
Expand All @@ -18,11 +18,15 @@
from scripts.ilapfuncs import artifact_processor, get_file_path, get_plist_file_content

@artifact_processor
def appleMapsGroup(files_found, report_folder, seeker, wrap_text, timezone_offset):
def appleMapsGroup(context):
files_found = context.get_files_found()
source_path = get_file_path(files_found, "group.com.apple.Maps.plist")
data_list = []

pl = get_plist_file_content(source_path)
# Check if plist is valid before processing
if not pl or not isinstance(pl, dict):
return (), [], ''
maps_activity = pl.get('MapsActivity', None)
if maps_activity:
types = {'1': {'type': 'message', 'message_typedef':
Expand All @@ -38,7 +42,7 @@ def appleMapsGroup(files_found, report_folder, seeker, wrap_text, timezone_offse
'7': {'type': 'int', 'name': ''}},
'name': ''}
}
internal_deserialized_plist, di = blackboxprotobuf.decode_message(maps_activity, types)
internal_deserialized_plist, _ = blackboxprotobuf.decode_message(maps_activity, types)
latitude = (internal_deserialized_plist['1']['5']['Latitude'])
longitude = (internal_deserialized_plist['1']['5']['Longitude'])
data_list.append((latitude, longitude))
Expand Down