# -*- coding: utf-8 -*-
"""Plist parser plugin for Mac OS login items plist files."""
import os
from dfdatetime import hfs_time as dfdatetime_hfs_time
from dtfabric.runtime import data_maps as dtfabric_data_maps
from plaso.containers import events
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
[docs]
class MacOSLoginItemEventData(events.EventData):
"""Mac OS login item event data.
Attributes:
hidden (bool): whether this login item is hidden.
name (str): name.
target_creation_time (dfdatetime.DateTimeValues): date and time the target
was created.
target_path (str): path of the target.
volume_creation_time (dfdatetime.DateTimeValues): date and time the (target)
volume was created.
volume_flags (int): volume flags.
volume_mount_point (str): location the volume is mounted on the file system.
volume_name (str): name of the volume containing the target.
"""
DATA_TYPE = 'macos:login_items:entry'
[docs]
def __init__(self):
"""Initializes event data."""
super(MacOSLoginItemEventData, self).__init__(data_type=self.DATA_TYPE)
self.hidden = None
self.name = None
self.target_creation_time = None
self.target_path = None
self.volume_creation_time = None
self.volume_flags = None
self.volume_mount_point = None
self.volume_name = None
[docs]
class MacOSLoginItemsPlistPlugin(
interface.PlistPlugin, dtfabric_helper.DtFabricHelper):
"""Plist parser plugin for Mac OS login items."""
NAME = 'macos_login_items_plist'
DATA_FORMAT = 'Mac OS com.apple.loginitems.plist file'
PLIST_PATH_FILTERS = frozenset([
interface.PlistPathFilter('com.apple.loginitems.plist')])
PLIST_KEYS = frozenset(['SessionItems'])
_DEFINITION_FILE = os.path.join(
os.path.dirname(__file__), 'alias_data.yaml')
def _ParseAliasData(self, alias_data, event_data):
"""Parses alias data.
Args:
alias_data (bytes): alias data.
event_data (MacOSLoginItemEventData): event data.
Raises:
ParseError: if the value cannot be parsed.
"""
data_type_map = self._GetDataTypeMap('alias_data_record_header')
record_header = self._ReadStructureFromByteStream(
alias_data, 0, data_type_map)
data_offset = 8
if record_header.application_information != b'\x00\x00\x00\x00':
raise errors.ParseError('Unsupported alias application information')
if record_header.record_size != len(alias_data):
raise errors.ParseError('Unsupported alias record size')
# TODO: add format version 2 support, but need test data.
data_type_map = self._GetDataTypeMap('alias_data_record_v3')
record_data = self._ReadStructureFromByteStream(
alias_data[data_offset:], data_offset, data_type_map)
data_offset += 50
hfs_timestamp, _ = divmod(record_data.target_creation_time, 65536)
event_data.target_creation_time = dfdatetime_hfs_time.HFSTime(
timestamp=hfs_timestamp)
event_data.volume_flags = record_data.volume_flags
hfs_timestamp, _ = divmod(record_data.volume_creation_time, 65536)
event_data.volume_creation_time = dfdatetime_hfs_time.HFSTime(
timestamp=hfs_timestamp)
relative_target_path = None
while data_offset < record_header.record_size:
data_type_map = self._GetDataTypeMap('alias_data_tagged_value')
context = dtfabric_data_maps.DataTypeMapContext()
tagged_value = self._ReadStructureFromByteStream(
alias_data[data_offset:], data_offset, data_type_map,
context=context)
data_offset += context.byte_size
if tagged_value.value_tag == 0xffff:
break
if tagged_value.value_tag == 0x000f:
event_data.volume_name = tagged_value.string
elif tagged_value.value_tag == 0x0012:
relative_target_path = tagged_value.string
elif tagged_value.value_tag == 0x0013:
volume_mount_point = tagged_value.string
if relative_target_path:
relative_target_path = ''.join([
volume_mount_point, relative_target_path])
event_data.volume_mount_point = volume_mount_point
if relative_target_path:
event_data.target_path = relative_target_path
# pylint: disable=arguments-differ
def _ParsePlist(self, parser_mediator, top_level=None, **unused_kwargs):
"""Extracts login item information from the plist.
Args:
parser_mediator (ParserMediator): mediates interactions between parsers
and other components, such as storage and dfVFS.
top_level (Optional[dict[str, object]]): plist top-level item.
"""
session_items = top_level.get('SessionItems')
if not session_items:
return
for custom_list_item in session_items.get('CustomListItems'):
alias_data = custom_list_item.get('Alias')
properties = custom_list_item.get('CustomItemProperties', {})
event_data = MacOSLoginItemEventData()
event_data.name = custom_list_item.get('Name')
event_data.hidden = properties.get(
'com.apple.LSSharedFileList.ItemIsHidden', False)
if alias_data is not None:
self._ParseAliasData(alias_data, event_data)
parser_mediator.ProduceEventData(event_data)
plist.PlistParser.RegisterPlugin(MacOSLoginItemsPlistPlugin)