"""Plist parser plugin for Mac OS background items plist files."""
import os
from dfdatetime import cocoa_time as dfdatetime_cocoa_time
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 MacOSBackgroundItemEventData(events.EventData):
"""Mac OS background item event data.
Attributes:
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:background_items:entry"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
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 MacOSBackgroundItemsPlistPlugin(
interface.PlistPlugin, dtfabric_helper.DtFabricHelper
):
"""Plist parser plugin for Mac OS background items."""
NAME = "macos_background_items_plist"
DATA_FORMAT = "Mac OS backgrounditems.btm or BackgroundItems-v[3-9].btm plist file"
PLIST_PATH_FILTERS = frozenset(
[
interface.PlistPathFilter("backgrounditems.btm"),
interface.PrefixPlistPathFilter("BackgroundItems-v"),
]
)
PLIST_KEYS = frozenset(["$objects"])
_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), "bookmark_data.yaml")
[docs]
def __init__(self):
"""Initializes a plist parser plugin for Mac OS background items."""
super().__init__()
self._decoder = interface.NSKeyedArchiverDecoder()
def _ParseBookmarkData(self, bookmark_data, event_data):
"""Parses bookmark data.
Args:
bookmark_data (bytes): bookmark data.
event_data (MacOSBackgroundItemEventData): event data.
Raises:
ParseError: if the value cannot be parsed.
"""
data_type_map = self._GetDataTypeMap("bookmark_data_header")
header = self._ReadStructureFromByteStream(bookmark_data, 0, data_type_map)
if header.signature not in (b"alis", b"book"):
raise errors.ParseError("Unsupported bookmark signature")
if header.size != len(bookmark_data):
raise errors.ParseError("Unsupported bookmark size")
if header.data_area_offset != 48:
raise errors.ParseError("Unsupported bookmark data area offset")
data_type_map = self._GetDataTypeMap("uint32le")
data_area_size = self._ReadStructureFromByteStream(
bookmark_data[header.data_area_offset :],
header.data_area_offset,
data_type_map,
)
data_type_map = self._GetDataTypeMap("bookmark_data_toc")
toc_offset = header.data_area_offset + data_area_size
table_of_contents = self._ReadStructureFromByteStream(
bookmark_data[toc_offset:], toc_offset, data_type_map
)
if table_of_contents.next_toc_offset != 0:
raise errors.ParseError("Unsupported next TOC offset")
relative_target_path = None
for tagged_value in table_of_contents.tagged_values:
data_type_map = self._GetDataTypeMap("bookmark_data_record")
data_record_offset = (
header.data_area_offset + tagged_value.value_data_record_offset
)
data_record = self._ReadStructureFromByteStream(
bookmark_data[data_record_offset:], data_record_offset, data_type_map
)
if tagged_value.value_tag == 0x00001004:
strings_array = self._ParseStringsArray(
bookmark_data, header.data_area_offset, data_record.integers
)
relative_target_path = "/".join(strings_array)
elif tagged_value.value_tag == 0x00001040:
data_type_map = self._GetDataTypeMap("float64be")
cocoa_timestamp = self._ReadStructureFromByteStream(
data_record.data, 0, data_type_map
)
event_data.target_creation_time = dfdatetime_cocoa_time.CocoaTime(
timestamp=cocoa_timestamp
)
elif tagged_value.value_tag == 0x00002002:
volume_mount_point = data_record.string
if relative_target_path:
relative_target_path = "".join(
[volume_mount_point, relative_target_path]
)
event_data.volume_mount_point = volume_mount_point
elif tagged_value.value_tag == 0x00002010:
event_data.volume_name = data_record.string
elif tagged_value.value_tag == 0x00002013:
data_type_map = self._GetDataTypeMap("float64be")
cocoa_timestamp = self._ReadStructureFromByteStream(
data_record.data, 0, data_type_map
)
event_data.volume_creation_time = dfdatetime_cocoa_time.CocoaTime(
timestamp=cocoa_timestamp
)
elif tagged_value.value_tag == 0x00002020:
data_type_map = self._GetDataTypeMap("bookmark_data_property_flags")
property_flags = self._ReadStructureFromByteStream(
data_record.data, 0, data_type_map
)
event_data.volume_flags = (
property_flags.flags & property_flags.valid_flags_bitmask
)
elif tagged_value.value_tag == 0x0000F017:
event_data.name = data_record.string
if relative_target_path:
event_data.target_path = relative_target_path
def _ParseIntegersArray(self, bookmark_data, data_area_offset, data_record_offsets):
"""Parses an integers array.
Args:
bookmark_data (bytes): bookmark data.
data_area_offset (int): offset of the data area relative to the start of
the file.
data_record_offsets (list[int]): offsets of the entry data records.
Returns:
list[int]: integers.
"""
data_type_map = self._GetDataTypeMap("bookmark_data_record")
integers_array = []
for data_record_offset in data_record_offsets:
data_record_offset += data_area_offset
data_record = self._ReadStructureFromByteStream(
bookmark_data[data_record_offset:], data_record_offset, data_type_map
)
integers_array.append(data_record.integer)
return integers_array
def _ParseStringsArray(self, bookmark_data, data_area_offset, data_record_offsets):
"""Parses a strings array.
Args:
bookmark_data (bytes): bookmark data.
data_area_offset (int): offset of the data area relative to the start of
the file.
data_record_offsets (list[int]): offsets of the entry data records.
Returns:
list[str]: strings.
"""
data_type_map = self._GetDataTypeMap("bookmark_data_record")
strings_array = []
for data_record_offset in data_record_offsets:
data_record_offset += data_area_offset
data_record = self._ReadStructureFromByteStream(
bookmark_data[data_record_offset:], data_record_offset, data_type_map
)
strings_array.append(data_record.string)
return strings_array
# pylint: disable=arguments-differ
def _ParsePlist(self, parser_mediator, top_level=None, **unused_kwargs):
"""Extracts background 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.
"""
if not self._decoder.IsEncoded(top_level):
parser_mediator.ProduceExtractionWarning(
"unsupported background items plist - unsupported encoding."
)
return
decoded_plist = self._decoder.Decode(top_level)
# Format version 2 is known to use the "backgroundItems" property.
background_items = decoded_plist.get("backgroundItems")
# Format version 4, 7 and 8 are known to use the "store" property.
store = decoded_plist.get("store")
if not background_items and not store:
parser_mediator.ProduceExtractionWarning(
"unsupported background items plist - missing items."
)
return
if background_items:
for container in background_items.get("allContainers") or []:
event_data = MacOSBackgroundItemEventData()
bookmark = container.get("bookmark") or {}
bookmark_data = bookmark.get("data")
if bookmark_data:
try:
self._ParseBookmarkData(bookmark_data, event_data)
except errors.ParseError as exception:
parser_mediator.ProduceExtractionWarning(
f"unable to parse bookmark data with error: {exception!s}"
)
# For now generate an additional event data container if container
# level bookmark data is present.
parser_mediator.ProduceEventData(event_data)
event_data = MacOSBackgroundItemEventData()
internal_items = container.get("internalItems") or {}
bookmark = internal_items.get("bookmark") or {}
bookmark_data = bookmark.get("data")
if bookmark_data:
try:
self._ParseBookmarkData(bookmark_data, event_data)
except errors.ParseError as exception:
parser_mediator.ProduceExtractionWarning(
f"unable to parse internal items bookmark data with error: "
f"{exception!s}"
)
parser_mediator.ProduceEventData(event_data)
else:
items_by_user_identifier = store.get("itemsByUserIdentifier") or {}
for container in items_by_user_identifier.values():
for internal_item in container:
event_data = MacOSBackgroundItemEventData()
bookmark_data = internal_item.get("bookmark")
if bookmark_data:
try:
self._ParseBookmarkData(bookmark_data, event_data)
except errors.ParseError as exception:
parser_mediator.ProduceExtractionWarning(
f"unable to parse bookmark data with error: "
f"{exception!s}"
)
parser_mediator.ProduceEventData(event_data)
plist.PlistParser.RegisterPlugin(MacOSBackgroundItemsPlistPlugin)