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 0000000000..817a2a89fc Binary files /dev/null and b/test_data/apple_biome/appInstall-segb differ diff --git a/test_data/apple_biome/applaunch-segb b/test_data/apple_biome/applaunch-segb new file mode 100644 index 0000000000..379d0f2181 Binary files /dev/null and b/test_data/apple_biome/applaunch-segb differ 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