From 4201b1c20157b966559caf38fc5439d26adb1291 Mon Sep 17 00:00:00 2001 From: rick Date: Thu, 7 Mar 2024 11:07:37 -0500 Subject: [PATCH] implemented biome parser and app_launch biom parser plugin implemented ApplicationInstallBiomePluging --- plaso/data/formatters/ios.yaml | 10 ++ plaso/data/presets.yaml | 4 + plaso/data/timeliner.yaml | 24 +++ plaso/parsers/__init__.py | 2 + plaso/parsers/apple_biome.py | 180 +++++++++++++++++++++ plaso/parsers/apple_biome.yaml | 81 ++++++++++ plaso/parsers/biome_plugins/__init__.py | 5 + plaso/parsers/biome_plugins/app_install.py | 118 ++++++++++++++ plaso/parsers/biome_plugins/app_launch.py | 76 +++++++++ plaso/parsers/biome_plugins/interface.py | 48 ++++++ requirements.txt | 1 + test_data/apple_biome/appInstall-segb | Bin 0 -> 1048576 bytes test_data/apple_biome/applaunch-segb | Bin 0 -> 1048576 bytes tests/cli/extraction_tool.py | 2 +- tests/parsers/apple_biome.py | 66 ++++++++ tests/parsers/biome_plugins/__init__.py | 1 + tests/parsers/biome_plugins/app_install.py | 49 ++++++ tests/parsers/biome_plugins/app_launch.py | 49 ++++++ tests/parsers/biome_plugins/interface.py | 67 ++++++++ tests/parsers/biome_plugins/test_lib.py | 75 +++++++++ 20 files changed, 857 insertions(+), 1 deletion(-) create mode 100644 plaso/parsers/apple_biome.py create mode 100644 plaso/parsers/apple_biome.yaml create mode 100644 plaso/parsers/biome_plugins/__init__.py create mode 100644 plaso/parsers/biome_plugins/app_install.py create mode 100644 plaso/parsers/biome_plugins/app_launch.py create mode 100644 plaso/parsers/biome_plugins/interface.py create mode 100644 test_data/apple_biome/appInstall-segb create mode 100644 test_data/apple_biome/applaunch-segb create mode 100644 tests/parsers/apple_biome.py create mode 100644 tests/parsers/biome_plugins/__init__.py create mode 100644 tests/parsers/biome_plugins/app_install.py create mode 100644 tests/parsers/biome_plugins/app_launch.py create mode 100644 tests/parsers/biome_plugins/interface.py create mode 100644 tests/parsers/biome_plugins/test_lib.py diff --git a/plaso/data/formatters/ios.yaml b/plaso/data/formatters/ios.yaml index bd3c3b005c..6b6923ae7d 100644 --- a/plaso/data/formatters/ios.yaml +++ b/plaso/data/formatters/ios.yaml @@ -1,6 +1,16 @@ # Plaso iOS related event formatters. --- type: 'conditional' +data_type: 'apple:biome:app_launch' +message: +- 'Launcher: {launcher}' +- 'Launched Application: {launched_application}' +short_message: +- 'Launched Application: {launched_application}' +short_source: 'LOG' +source: 'Apple biome application launch' +--- +type: 'conditional' data_type: 'ios:app_privacy:access' message: - 'Accessor Identifier: {accessor_identifier}' diff --git a/plaso/data/presets.yaml b/plaso/data/presets.yaml index 19d4a6fe2b..05e5638f55 100644 --- a/plaso/data/presets.yaml +++ b/plaso/data/presets.yaml @@ -19,6 +19,8 @@ parsers: name: ios description: Preset for iOS. parsers: +- biome/app_install +- biome/app_launch - jsonl/ios_application_privacy - plist/ios_identityservices - sqlite/imessage @@ -70,6 +72,8 @@ operating_systems: - {family: MacOS} parsers: - asl_log +- biome/app_install +- biome/app_launch - bencode - bsm_log - cups_ipp diff --git a/plaso/data/timeliner.yaml b/plaso/data/timeliner.yaml index 2022e8a8c4..24fb1e6b42 100644 --- a/plaso/data/timeliner.yaml +++ b/plaso/data/timeliner.yaml @@ -95,6 +95,30 @@ attribute_mappings: description: 'Recorded Time' place_holder_event: true --- +data_type: 'apple:biome:app_launch' +attribute_mappings: +- name: 'event_time' + description: 'Event Time' +- name: 'launcher' + description: 'process that launched the application' +- name: 'launched application' + description': 'name of the launched application' +place_holder_event: true +--- +data_type: 'apple:biome:app_linstall' +attribute_mappings: +- name: 'event_time' + description: 'Event Time' +- name: 'action_guid' + description: 'GUID for the action of installing the application' +- name: 'action_name' + description: 'name of the action' +- name: 'application_name' + description: 'name of the application' +- name: 'bundle_identifier' + description: 'bundle identifier of the application' +place_holder_event: true +--- data_type: 'av:defender:detection_history' attribute_mappings: - name: 'recorded_time' diff --git a/plaso/parsers/__init__.py b/plaso/parsers/__init__.py index 30394c553f..d56e8c2133 100644 --- a/plaso/parsers/__init__.py +++ b/plaso/parsers/__init__.py @@ -2,6 +2,7 @@ """This file imports Python modules that register parsers.""" from plaso.parsers import android_app_usage +from plaso.parsers import apple_biome from plaso.parsers import asl from plaso.parsers import bencode_parser from plaso.parsers import bodyfile @@ -53,6 +54,7 @@ from plaso.parsers import winrestore # Register parser plugins. +from plaso.parsers import biome_plugins from plaso.parsers import bencode_plugins from plaso.parsers import czip_plugins from plaso.parsers import esedb_plugins diff --git a/plaso/parsers/apple_biome.py b/plaso/parsers/apple_biome.py new file mode 100644 index 0000000000..5e68bb5db7 --- /dev/null +++ b/plaso/parsers/apple_biome.py @@ -0,0 +1,180 @@ +"""A parser for Apple biome files, aka SEGB files.""" +import os + +from plaso.lib import dtfabric_helper +from plaso.lib import errors +from plaso.lib import specification +from plaso.parsers import interface +from plaso.parsers import manager + + +class AppleBiomeFile(dtfabric_helper.DtFabricHelper): + """Apple biome (aka SEGB) file. + + Attributes: + header (segb_header): Header of the file. + records (list[segb_record]): All the records recovered from the file. + version (str): file version number. + """ + + _DEFINITION_FILE = os.path.join( + os.path.dirname(__file__), 'apple_biome.yaml') + + def __init__(self): + """Initializes an Apple biome file.""" + super(AppleBiomeFile, self).__init__() + self.header = None + self.records = [] + self.version = None + + def _ReadAllRecords(self, file_object, starting_offset): + """Iterates over all the records in the Apple biome file. + + Args: + file_object (dfvfs.FileIO): file-like object. + starting_offset (int): offset from which to start reading records. + """ + data_type_map = self._GetDataTypeMap(self.version) + file_size = file_object.get_size() + file_offset = starting_offset + + while file_offset < file_size: + record, record_size = self._ReadStructureFromFileObject( + file_object, file_offset, data_type_map) + + file_offset += record_size + + # Padding + _, alignment = divmod(file_offset, 8) + if alignment > 0: + alignment = 8 - alignment + + file_offset += alignment + + # Case where the record has a blank header and no content + # record_size includes the record header, record.size only counts content + # This signals the end of records. + if record_size == 32 and record.size == 0: + break + + # Case where the record has a valid header but the content is all nulls. + # These can be at the top of the file. + if set(record.protobuf) == {0}: + continue + + self.records.append(record) + + def _ReadFileHeader(self, file_object): + """Determines the version of the Apple biome file and returns its header. + + Args: + file_object (dfvfs.FileIO): file-like object. + + Returns: + File header and data size of the header. + """ + data_type_map = self._GetDataTypeMap('segb_header_v1') + + header, header_size = self._ReadStructureFromFileObject( + file_object, 0, data_type_map) + + if header.segb_magic == b'SEGB': + return header, header_size + + return None, 0 + + def Open(self, file_object): + """Opens an Apple biome file. + + Args: + file_object (dfvfs.FileIO): file-like object. + + Raises: + ValueError: if the file object is missing. + errors.WrongParser: if the segb_record version is not recognized. + """ + if not file_object: + raise ValueError('Missing file_object.') + + self.header, header_size = self._ReadFileHeader(file_object) + + if header_size == 56: + self.version = 'segb_record_v1' + else: + raise errors.WrongParser('File could not be parsed.') + + self._ReadAllRecords(file_object, header_size) + + +class AppleBiomeParser(interface.FileObjectParser): + """Parses Apple biome file-like objects.""" + + NAME = 'biome' + DATA_FORMAT = 'Apple biome' + + _plugin_classes = {} + + def __int__(self): + """Initializes a parser.""" + super(AppleBiomeParser, self).__init__() + + @classmethod + def GetFormatSpecification(cls): + """Retrieves the format specification. + + Returns: + FormatSpecification: format specification. + """ + format_specification = specification.FormatSpecification(cls.NAME) + format_specification.AddNewSignature(b'SEGB', offset=52) + return format_specification + + def ParseFileObject(self, parser_mediator, file_object): + """Parses an Apple biome files. + + Args: + parser_mediator (ParserMediator): mediates interactions between parsers + and other components, such as storage and dfVFS. + file_object (dfvfs.FileIO): file-like object. + + Raises: + WrongParser: when the file cannot be parsed. + """ + biome_file = AppleBiomeFile() + biome_file.Open(file_object) + + for plugin_name, plugin in self._plugins_per_name.items(): + if parser_mediator.abort: + break + + profiling_name = '/'.join([self.NAME, plugin.NAME]) + parser_mediator.SampleFormatCheckStartTiming(profiling_name) + + try: + result = False + # Some of the records may have missing fields + for record in biome_file.records: + result = plugin.CheckRequiredSchema(record.protobuf) + if result: + break + finally: + parser_mediator.SampleFormatCheckStopTiming(profiling_name) + + if not result: + continue + + parser_mediator.SampleStartTiming(profiling_name) + + try: + plugin.UpdateChainAndProcess(parser_mediator, biome_file=biome_file) + + except Exception as exception: # pylint: disable=broad-except + parser_mediator.ProduceExtractionWarning(( + 'plugin: {0:s} unable to parse Apple biome file with error: ' + '{1!s}').format(plugin_name, exception)) + + finally: + parser_mediator.SampleStopTiming(profiling_name) + + +manager.ParsersManager.RegisterParser(AppleBiomeParser) diff --git a/plaso/parsers/apple_biome.yaml b/plaso/parsers/apple_biome.yaml new file mode 100644 index 0000000000..c694eebdea --- /dev/null +++ b/plaso/parsers/apple_biome.yaml @@ -0,0 +1,81 @@ +# dtFabric format specification. +--- +name: apple_biome +type: format +description: Apple biome file (aka SEGB file) +--- +name: byte +type: integer +attributes: + format: unsigned + size: 1 + units: bytes +--- +name: uint32 +type: integer +attributes: + format: unsigned + size: 4 + units: bytes +--- +name: float64 +aliases: [double, DOUBLE] +type: floating-point +description: 64-bit double precision floating-point type +attributes: + size: 8 + units: bytes +--- +name: segb_header_v1 +type: structure +attributes: + byte_order: little-endian +members: +- name: unknown0 + data_type: uint32 +- name: unknown1 + type: stream + element_data_type: byte + number_of_elements: 4 +- name: unknown2 + type: stream + element_data_type: byte + number_of_elements: 8 +- name: unknown3 + data_type: uint32 +- name: filename + type: stream + element_data_type: byte + number_of_elements: 16 +- name: unknown4 + type: stream + element_data_type: byte + number_of_elements: 16 +- name: segb_magic + type: stream + element_data_type: byte + number_of_elements: 4 +--- +name: segb_record_v1 +type: structure +attributes: + byte_order: little-endian +members: + - name: size + data_type: uint32 + - name: unknown1 + data_type: uint32 + - name: timestamp1 + data_type: float64 + - name: timestamp2 + data_type: float64 + - name: unknown2 + data_type: uint32 + - name: unknown3 + data_type: uint32 + - name: protobuf + type: stream + element_data_type: byte + number_of_elements: segb_record_v1.size + + diff --git a/plaso/parsers/biome_plugins/__init__.py b/plaso/parsers/biome_plugins/__init__.py new file mode 100644 index 0000000000..0e602c6b18 --- /dev/null +++ b/plaso/parsers/biome_plugins/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +"""Imports for the apple biome parser plugins.""" + +from plaso.parsers.biome_plugins import app_launch +from plaso.parsers.biome_plugins import app_install diff --git a/plaso/parsers/biome_plugins/app_install.py b/plaso/parsers/biome_plugins/app_install.py new file mode 100644 index 0000000000..d77bfd2ef8 --- /dev/null +++ b/plaso/parsers/biome_plugins/app_install.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +"""Apple biome file parser plugin for App Install.""" +import blackboxprotobuf +from dfdatetime import cocoa_time as dfdatetime_cocoa_time + +from plaso.containers import events +from plaso.parsers.biome_plugins import interface +from plaso.parsers import apple_biome + + +class ApplicationInstallAppleBiomeEvent(events.EventData): + """Application install entry in Apple biome file. + + Attributes: + event_time (dfdatetime.DateTimeValues): date and time when the application + was launched. + action_guid (str): GUID for the action of installing the application. + action_name (str): name of the action. + application_name (str): name of the application. + bundle_identifier (str): bundle identifier of the application. + event_time (dfdatetime.DateTimeValues): date and time when the application + was installed. + """ + + DATA_TYPE = 'apple:biome:app_install' + + def __init__(self): + """Initializes the event data.""" + super( + ApplicationInstallAppleBiomeEvent, + self).__init__(data_type=self.DATA_TYPE) + self.action_guid = None + self.action_name = None + self.application_name = None + self.bundle_identifier = None + self.event_time = None + + +class ApplicationInstallBiomePlugin(interface.AppleBiomePlugin): + """Parses an Application Install Apple biome file.""" + + NAME = 'application_launcher_biome' + DATA_FORMAT = 'Biome application install' + + REQUIRED_SCHEMA = { + '1': { + 'type': 'message', 'message_typedef': { + '1': {'type': 'string'}, + '2': { + 'type': 'message', 'message_typedef': { + '1': {'type': 'int'}, + '2': {'type': 'int'}}, + 'field_order': ['1', '2']}}, + 'field_order': ['1', '2']}, + '2': {'type': 'fixed64'}, + '3': {'type': 'fixed64'}, + '4': { + 'type': 'message', 'message_typedef': { + '1': { + 'type': 'message', 'message_typedef': { + '1': {'type': 'int'}, + '2': {'type': 'int'}}, + 'field_order': ['1', '2']}, + '3': {'type': 'string'}}, 'field_order': ['1', '3']}, + '5': {'type': 'string'}, + '7': { + 'type': 'message', 'message_typedef': { + '1': { + 'type': 'message', 'message_typedef': {}, 'field_order': []}, + '2': { + 'type': 'message', 'message_typedef': { + '1': { + 'type': 'message', 'message_typedef': { + '1': {'type': 'int'}, + '2': {'type': 'int'}}, + 'field_order': ['1', '2']}, + '3': {'type': 'string'}, + '4': {'type': 'int'}}, + 'field_order': ['1', '4']}, + '3': {'type': 'int'}}, + 'field_order': ['1', '2', '3']}, + '8': {'type': 'fixed64'}, + '10': {'type': 'int'}} + + # pylint: disable=unused-argument + def Process(self, parser_mediator, biome_file=None, **unused_kwargs): + """Extracts information from an Apple biome file. This is the main method + that an Apple biome file plugin needs to implement. + + Args: + parser_mediator (ParserMediator): parser mediator. + biome_file (Optional[AppleBiomeFile]): Biome file. + """ + if biome_file is None: + raise ValueError('Missing biome file.') + + for record in biome_file.records: + content, _ = blackboxprotobuf.decode_message(record.protobuf) + + event_data = ApplicationInstallAppleBiomeEvent() + event_data.action_guid = content.get('5') + event_data.action_name = content.get('1', {}).get('1') + + application_information = content.get('7', []) + if application_information and isinstance(application_information, list): + event_data.application_name = application_information[0].get( + '2', {}).get('3') + if not isinstance(event_data.application_name, str): + event_data.application_name = None + + event_data.bundle_identifier = content.get('4', {}).get('3') + event_data.event_time = dfdatetime_cocoa_time.CocoaTime( + timestamp=record.timestamp1) + + parser_mediator.ProduceEventData(event_data) + + +apple_biome.AppleBiomeParser.RegisterPlugin(ApplicationInstallBiomePlugin) diff --git a/plaso/parsers/biome_plugins/app_launch.py b/plaso/parsers/biome_plugins/app_launch.py new file mode 100644 index 0000000000..6c7b453e47 --- /dev/null +++ b/plaso/parsers/biome_plugins/app_launch.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +"""Apple biome file parser plugin for App Launch.""" +import blackboxprotobuf +from dfdatetime import cocoa_time as dfdatetime_cocoa_time + +from plaso.containers import events +from plaso.parsers.biome_plugins import interface +from plaso.parsers import apple_biome + + +class ApplicationLaunchAppleBiomeEvent(events.EventData): + """Application launch entry in Apple biome file. + + Attributes: + event_time (dfdatetime.DateTimeValues): date and time when the application + was launched. + launcher (str): which process launched the application. + launched_application (str): name of the launched application. + """ + + DATA_TYPE = 'apple:biome:app_launch' + + def __init__(self): + """Initializes the event data.""" + super( + ApplicationLaunchAppleBiomeEvent, + self).__init__(data_type=self.DATA_TYPE) + self.event_time = None + self.launcher = None + self.launched_application = None + + +class ApplicationLaunchBiomePlugin(interface.AppleBiomePlugin): + """Parses an Application launch Apple biome file.""" + + NAME = 'application_launch_biome' + DATA_FORMAT = 'Biome application launch' + + REQUIRED_SCHEMA = { + '1': {'type': 'string'}, + '2': {'type': 'int'}, + '3': {'type': 'int'}, + '4': {'type': 'fixed64'}, + '5': {'type': 'fixed64'}, + '6': {'type': 'string'}, + '9': {'type': 'string'}, + '10': {'type': 'string'}} + + # pylint: disable=unused-argument + def Process(self, parser_mediator, biome_file=None, **unused_kwargs): + """Extracts information from an Apple biome file. This is the main method + that an Apple biome file plugin needs to implement. + + Args: + parser_mediator (ParserMediator): parser mediator. + biome_file (Optional[AppleBiomeFile]): Biome file. + + Raises: + ValueError: If the file value is missing. + """ + if biome_file is None: + raise ValueError('Missing biome file.') + + for record in biome_file.records: + content, _ = blackboxprotobuf.decode_message(record.protobuf) + + event_data = ApplicationLaunchAppleBiomeEvent() + event_data.launcher = content.get('1') + event_data.launched_application = content.get('6') + event_data.event_time = dfdatetime_cocoa_time.CocoaTime( + timestamp=record.timestamp1) + + parser_mediator.ProduceEventData(event_data) + + +apple_biome.AppleBiomeParser.RegisterPlugin(ApplicationLaunchBiomePlugin) diff --git a/plaso/parsers/biome_plugins/interface.py b/plaso/parsers/biome_plugins/interface.py new file mode 100644 index 0000000000..aa100f82da --- /dev/null +++ b/plaso/parsers/biome_plugins/interface.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +"""Interface for Apple biome file parser plugins.""" +import abc +import blackboxprotobuf + +from plaso.parsers import plugins + + +class AppleBiomePlugin(plugins.BasePlugin): + """Apple biome (aka SEGB) file parser plugin.""" + + NAME = 'biome_plugins' + DATA_FORMAT = 'apple biome' + + REQUIRED_SCHEMA = None + + def CheckRequiredSchema(self, protobuf): + """Checks if the record of an Aple biome file has the required schema by the + plugin. + + Args: + protobuf (bytes): Content of the protobuf field in the Apple biome record. + Returns: + bool: True if the record's protobuf's schema is valid. + """ + if not self.REQUIRED_SCHEMA: + return False + + _, schema = blackboxprotobuf.decode_message(protobuf) + + if schema.items() <= self.REQUIRED_SCHEMA.items(): + return True + + return False + + # pylint: disable=arguments-differ + @abc.abstractmethod + def Process(self, parser_mediator, biome_file=None, **unused_kwargs): + """Extracts information from an Apple biome file. This is the main method + that an Apple biome file plugin needs to implement. + + Args: + parser_mediator (ParserMediator): parser mediator. + biome_file (Optional[AppleBiomeFile]): Apple biome file. + + Raises: + ValueError: If the file_object value is missing. + """ diff --git a/requirements.txt b/requirements.txt index 7629330d63..e4e4b71f49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ PyYAML >= 3.10 XlsxWriter >= 0.9.3 acstore >= 20240407 artifacts >= 20220219 +bbpb >= 1.1 bencode.py certifi >= 2016.9.26 cffi >= 1.9.1 diff --git a/test_data/apple_biome/appInstall-segb b/test_data/apple_biome/appInstall-segb new file mode 100644 index 0000000000000000000000000000000000000000..817a2a89fc8b5d026b96724ce677919801607447 GIT binary patch literal 1048576 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 z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 z2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N z0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBly zK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk z1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZ zfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U wAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7e?|5t(k0eueD5dZ)H literal 0 HcmV?d00001 diff --git a/tests/cli/extraction_tool.py b/tests/cli/extraction_tool.py index f749fd113d..c18c173a50 100644 --- a/tests/cli/extraction_tool.py +++ b/tests/cli/extraction_tool.py @@ -282,7 +282,7 @@ def testListParsersAndPlugins(self): lines = frozenset(lines) - self.assertEqual(number_of_tables, 11) + self.assertEqual(number_of_tables, 12) expected_line = 'filestat : Parser for file system stat information.' self.assertIn(expected_line, lines) diff --git a/tests/parsers/apple_biome.py b/tests/parsers/apple_biome.py new file mode 100644 index 0000000000..e323c3ed0e --- /dev/null +++ b/tests/parsers/apple_biome.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Tests for the Apple biome parser.""" + +import unittest + +from plaso.parsers import apple_biome + +from tests.parsers import test_lib + + +class AppleBiomeFileTest(test_lib.ParserTestCase): + """Tests for the Apple biome file.""" + + def testReadFileHeader(self): + """Tests the _ReadFileHeader function.""" + biome_file = apple_biome.AppleBiomeFile() + # pylint: disable=protected-access + file_entry = self._GetTestFileEntry([ + 'apple_biome', 'applaunch-segb-magfor']) + file_object = file_entry.GetFileObject() + + header, header_size = biome_file._ReadFileHeader(file_object) + + self.assertEqual(header.segb_magic, b'SEGB') + self.assertEqual(header.filename, b'690684028683239\x00') + self.assertEqual(header_size, 56) + + def testOpen(self): + """Tests the Open function.""" + biome_file = apple_biome.AppleBiomeFile() + file_entry = self._GetTestFileEntry([ + 'apple_biome', 'applaunch-segb-magfor']) + file_object = file_entry.GetFileObject() + + biome_file.Open(file_object) + + self.assertEqual(biome_file.header.segb_magic, b'SEGB') + self.assertEqual(biome_file.header.filename, b'690684028683239\x00') + self.assertEqual(biome_file.records[0].size, 0x65) + + +class AppleBiomeParserTest(test_lib.ParserTestCase): + """Apple Biome parser test case.""" + def testParseFileObject(self): + """Tests the ParseFileObject function.""" + parser = apple_biome.AppleBiomeParser() + + storage_writer = self._ParseFile( + ['apple_biome', 'applaunch-segb-magfor'], parser) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 558) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'extraction_warning') + self.assertEqual(number_of_warnings, 0) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'recovery_warning') + self.assertEqual(number_of_warnings, 0) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/parsers/biome_plugins/__init__.py b/tests/parsers/biome_plugins/__init__.py new file mode 100644 index 0000000000..40a96afc6f --- /dev/null +++ b/tests/parsers/biome_plugins/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/tests/parsers/biome_plugins/app_install.py b/tests/parsers/biome_plugins/app_install.py new file mode 100644 index 0000000000..1cf87bdcc6 --- /dev/null +++ b/tests/parsers/biome_plugins/app_install.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Tests for the Apple biome file parser plugin for Application Launch.""" + +import unittest + +from plaso.parsers.biome_plugins import app_install + +from tests.parsers.biome_plugins import test_lib + + +class ApplicationInstallBiomePluginTest(test_lib.AppleBiomeTestCase): + """Tests for the Application install Apple biome parser plugin.""" + + def testProcess(self): + """Tests the Process function.""" + plugin = app_install.ApplicationInstallBiomePlugin() + storage_writer = self._ParseAppleBiomeFileWithPlugin( + ['apple_biome', 'appInstall-segb'], plugin) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 41) + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 2) + + self.assertEqual( + event_data.action_guid, 'C1B7E595-401E-4331-A805-6D745F078B4C') + self.assertEqual(event_data.action_name, '/app/install') + self.assertEqual( + event_data.application_name, None) + self.assertEqual(event_data.bundle_identifier, 'com.apple.stocks') + self.assertEqual( + event_data.event_time.GetDateWithTimeOfDay(), (2024, 4, 3, 15, 6, 15)) + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 31) + + self.assertEqual( + event_data.action_guid, '790F0F66-3021-46CF-8440-086022E7E1E8') + self.assertEqual(event_data.action_name, '/app/install') + self.assertEqual( + event_data.application_name, 'X') + self.assertEqual(event_data.bundle_identifier, 'com.atebits.Tweetie2') + self.assertEqual( + event_data.event_time.GetDateWithTimeOfDay(), (2024, 4, 3, 15, 10, 55)) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/parsers/biome_plugins/app_launch.py b/tests/parsers/biome_plugins/app_launch.py new file mode 100644 index 0000000000..14e5c8c8ea --- /dev/null +++ b/tests/parsers/biome_plugins/app_launch.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Tests for the Apple biome file parser plugin for Application Launch.""" + +import unittest + +from plaso.parsers.biome_plugins import app_launch + +from tests.parsers.biome_plugins import test_lib + + +class ApplicationLaunchBiomePluginTest(test_lib.AppleBiomeTestCase): + """Tests for the Application launch Apple biome parser plugin.""" + + def testProcess(self): + """Tests the Process function.""" + plugin = app_launch.ApplicationLaunchBiomePlugin() + storage_writer = self._ParseAppleBiomeFileWithPlugin( + ['apple_biome', 'applaunch-segb'], plugin) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 85) + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 0) + + self.assertEqual( + event_data.event_time.GetDateWithTimeOfDay(), (2024, 4, 3, 15, 4, 27)) + self.assertEqual( + event_data.launcher, + None) + self.assertEqual( + event_data.launched_application, 'com.apple.purplebuddy') + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 84) + + self.assertEqual( + event_data.event_time.GetDateWithTimeOfDay(), (2024, 4, 17, 13, 27, 19)) + self.assertEqual( + event_data.launcher, + 'com.apple.SpringBoard.backlight.transitionReason.idleTimer') + self.assertEqual( + event_data.launched_application, 'org.coolstar.SileoStore') + + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/parsers/biome_plugins/interface.py b/tests/parsers/biome_plugins/interface.py new file mode 100644 index 0000000000..50a014805e --- /dev/null +++ b/tests/parsers/biome_plugins/interface.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +"""Tests for the Apple biome plugin interface.""" +import abc +import unittest +import os + +from plaso.parsers.biome_plugins import interface +from tests.parsers.biome_plugins import test_lib + + +class TestAppleBiomePlugin(interface.AppleBiomePlugin): + """Apple biome plugin for test purposes.""" + + NAME = 'test' + DATA_FORMAT = 'Apple biome test file' + + _DEFINITION_FILE = os.path.join( + 'plaso', 'parsers', 'apple_biome.yaml') + + REQUIRED_SCHEMA = { + '1': {'type': 'string'}, + '2': {'type': 'int'}, + '3': {'type': 'int'}, + '4': {'type': 'fixed64'}, + '5': {'type': 'fixed64'}, + '6': {'type': 'string'}, + '9': {'type': 'string'}, + '10': {'type': 'string'}} + + def __int__(self): + """Initializes the test plugin.""" + super(TestAppleBiomePlugin, self).__init__() + + # pylint: disable=arguments-differ + @abc.abstractmethod + def Process(self, parser_mediator, biome_file=None, **unused_kwargs): + """Extracts information from an Apple biome file. This is the main method + that an Apple biome file plugin needs to implement. + + Args: + parser_mediator (ParserMediator): parser mediator. + biome_file (Optional[AppleBiomeFile]): Apple biome file. + + Raises: + ValueError: If the file_object value is missing. + """ + + +class AppleBiomeInterfaceTest(test_lib.AppleBiomeTestCase): + """Tests for the Apple biome plugin interface.""" + + def testCheckRequiredSchema(self): + """Tests the CheckRequiredSchema function.""" + plugin = TestAppleBiomePlugin() + + biome_file = self._OpenAppleBiomeFile([ + 'apple_biome', 'applaunch-segb']) + + raw_protobuf = biome_file.records[1].protobuf + + required_schema_present = plugin.CheckRequiredSchema(raw_protobuf) + self.assertTrue(required_schema_present) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/parsers/biome_plugins/test_lib.py b/tests/parsers/biome_plugins/test_lib.py new file mode 100644 index 0000000000..852e9b50e8 --- /dev/null +++ b/tests/parsers/biome_plugins/test_lib.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +"""Apple biome file plugin related functions and classes for testing.""" + +from plaso.containers import events +from plaso.parsers import mediator as parsers_mediator +from plaso.parsers import apple_biome + +from tests.parsers import test_lib + + +class AppleBiomeTestCase(test_lib.ParserTestCase): + """Apple biome plugin test case.""" + def _OpenAppleBiomeFile(self, path_segments): + """Opens an Apple biome log file. + Args: + path_segments (list[str]): path segments inside the test data directory. + Returns: + AppleBiomeFile: Apple biome file. + Raises: + SkipTest: if the path inside the test data directory does not exist and + the test should be skipped. + """ + file_entry = self._GetTestFileEntry(path_segments) + + file_object = file_entry.GetFileObject() + + apple_biome_file = apple_biome.AppleBiomeFile() + apple_biome_file.Open(file_object) + + return apple_biome_file + + def _ParseAppleBiomeFileWithPlugin(self, path_segments, plugin): + """Parses a file as an Apple biome file and returns an event generator. + This method will first test if the Apple biome file has the required schema + using plugin.CheckRequiredSchema() and then extracts events using + plugin.Process(). + Args: + path_segments (list[str]): path segments inside the test data directory. + plugin (AppleBiomePlugin): Apple biome file plugin. + Returns: + FakeStorageWriter: storage writer. + Raises: + SkipTest: if the path inside the test data directory does not exist and + the test should be skipped. + """ + parser_mediator = parsers_mediator.ParserMediator() + + storage_writer = self._CreateStorageWriter() + parser_mediator.SetStorageWriter(storage_writer) + + file_entry = self._GetTestFileEntry(path_segments) + parser_mediator.SetFileEntry(file_entry) + + if file_entry: + event_data_stream = events.EventDataStream() + event_data_stream.path_spec = file_entry.path_spec + + parser_mediator.ProduceEventDataStream(event_data_stream) + + # AppendToParserChain needs to be run after SetFileEntry. + parser_mediator.AppendToParserChain('apple_biome') + + biome_file = self._OpenAppleBiomeFile(path_segments) + + result = False + for record in biome_file.records: + result = plugin.CheckRequiredSchema(record.protobuf) + if result: + break + + self.assertTrue(result) + + plugin.UpdateChainAndProcess(parser_mediator, biome_file=biome_file) + + return storage_writer