def testParseLinkTargetIdentifier(self): """Tests the Parse function on an LNK with a link target identifier.""" parser = winlnk.WinLnkParser() storage_writer = self._ParseFile(['NeroInfoTool.lnk'], parser) self.assertEqual(storage_writer.number_of_warnings, 0) self.assertEqual(storage_writer.number_of_events, 20) events = list(storage_writer.GetEvents()) # A shortcut event. event = events[16] expected_message = ( '[Nero InfoTool provides you with information about the most ' 'important features of installed drives, inserted discs, installed ' 'software and much more. With Nero InfoTool you can find out all ' 'about your drive and your system configuration.] ' 'File size: 4635160 ' 'File attribute flags: 0x00000020 ' 'Drive type: 3 ' 'Drive serial number: 0x70ecfa33 ' 'Volume label: OS ' 'Local path: C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool\\' 'InfoTool.exe ' 'cmd arguments: -ScParameter=30002 ' 'Relative path: ..\\..\\..\\..\\..\\..\\..\\..\\Program Files (x86)\\' 'Nero\\Nero 9\\Nero InfoTool\\InfoTool.exe ' 'Working dir: C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool ' 'Icon location: %ProgramFiles%\\Nero\\Nero 9\\Nero InfoTool\\' 'InfoTool.exe ' 'Link target: <My Computer> C:\\Program Files (x86)\\Nero\\Nero 9\\' 'Nero InfoTool\\InfoTool.exe') expected_short_message = ( '[Nero InfoTool provides you with information about the most ' 'important feature...') self._TestGetMessageStrings(event, expected_message, expected_short_message) # A shell item event. event = events[12] self.CheckTimestamp(event.timestamp, '2009-06-05 20:13:20.000000') expected_message = ( 'Name: InfoTool.exe ' 'Long name: InfoTool.exe ' 'NTFS file reference: 81349-1 ' 'Shell item path: <My Computer> C:\\Program Files (x86)\\Nero\\' 'Nero 9\\Nero InfoTool\\InfoTool.exe ' 'Origin: NeroInfoTool.lnk') expected_short_message = ('Name: InfoTool.exe ' 'NTFS file reference: 81349-1 ' 'Origin: NeroInfoTool.lnk') self._TestGetMessageStrings(event, expected_message, expected_short_message)
def testParseLinkTargetIdentifier(self): """Tests the Parse function on an LNK with a link target identifier.""" parser = winlnk.WinLnkParser() storage_writer = self._ParseFile(['NeroInfoTool.lnk'], parser) number_of_events = storage_writer.GetNumberOfAttributeContainers('event') self.assertEqual(number_of_events, 20) 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) events = list(storage_writer.GetEvents()) # A shortcut creation event. expected_event_values = { 'date_time': '2009-06-05 20:13:20.0000000', 'data_type': 'windows:lnk:link', 'description': ( 'Nero InfoTool provides you with information about the most ' 'important features of installed drives, inserted discs, installed ' 'software and much more. With Nero InfoTool you can find out all ' 'about your drive and your system configuration.'), 'drive_serial_number': 0x70ecfa33, 'drive_type': 3, 'file_attribute_flags': 0x00000020, 'file_size': 4635160, 'icon_location': ( '%ProgramFiles%\\Nero\\Nero 9\\Nero InfoTool\\InfoTool.exe'), 'local_path': ( 'C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool\\' 'InfoTool.exe'), 'relative_path': ( '..\\..\\..\\..\\..\\..\\..\\..\\Program Files (x86)\\' 'Nero\\Nero 9\\Nero InfoTool\\InfoTool.exe'), 'timestamp_desc': definitions.TIME_DESCRIPTION_CREATION, 'volume_label': 'OS', 'working_directory': ( 'C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool')} self.CheckEventValues(storage_writer, events[16], expected_event_values) # A shell item event. expected_event_values = { 'date_time': '2009-06-05 20:13:20', 'data_type': 'windows:shell_item:file_entry', 'file_reference': '81349-1', 'long_name': 'InfoTool.exe', 'name': 'InfoTool.exe', 'origin': 'NeroInfoTool.lnk', 'shell_item_path': ( '<My Computer> C:\\Program Files (x86)\\Nero\\Nero 9\\' 'Nero InfoTool\\InfoTool.exe'), 'timestamp_desc': definitions.TIME_DESCRIPTION_CREATION} self.CheckEventValues(storage_writer, events[12], expected_event_values)
class CustomDestinationsParser(interface.FileObjectParser): """Parses .customDestinations-ms files.""" _INITIAL_FILE_OFFSET = None NAME = u'custom_destinations' DESCRIPTION = u'Parser for *.customDestinations-ms files.' # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _LNK_GUID = ( b'\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46') _FOOTER_SIGNATURE = 0xbabffbab _FILE_HEADER = construct.Struct(u'file_header', construct.ULInt32(u'unknown1'), construct.ULInt32(u'unknown2'), construct.ULInt32(u'unknown3'), construct.ULInt32(u'header_values_type')) _HEADER_VALUE_TYPE_0 = construct.Struct( u'header_value_type_0', construct.ULInt32(u'number_of_characters'), construct.String(u'string', lambda ctx: ctx.number_of_characters * 2), construct.ULInt32(u'unknown1')) _HEADER_VALUE_TYPE_1_OR_2 = construct.Struct( u'header_value_type_1_or_2', construct.ULInt32(u'unknown1')) _ENTRY_HEADER = construct.Struct(u'entry_header', construct.String(u'guid', 16)) _FILE_FOOTER = construct.Struct(u'file_footer', construct.ULInt32(u'signature')) def _ParseLNKFile(self, parser_mediator, file_entry, file_offset, remaining_file_size): """Parses a LNK file stored within the .customDestinations-ms file. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_entry: A file entry object (instance of dfvfs.FileEntry). file_offset: The offset of the LNK file data. remaining_file_size: The size of the data remaining in the .customDestinations-ms file. Returns: The size of the LNK file data or 0 if the LNK file could not be read. """ path_spec = path_spec_factory.Factory.NewPathSpec( definitions.TYPE_INDICATOR_DATA_RANGE, range_offset=file_offset, range_size=remaining_file_size, parent=file_entry.path_spec) display_name = u'{0:s} # 0x{1:08x}'.format(file_entry.name, file_offset) try: lnk_file_object = resolver.Resolver.OpenFileObject(path_spec) except (dfvfs_errors.BackEndError, RuntimeError) as exception: message = ( u'Unable to open LNK file: {0:s} with error {1:s}').format( display_name, exception) parser_mediator.ProduceParseError(message) return 0 self._WINLNK_PARSER.Parse(parser_mediator, lnk_file_object, display_name=display_name) # We cannot trust the file size in the LNK data so we get the last offset # that was read instead. lnk_file_size = lnk_file_object.get_offset() lnk_file_object.close() return lnk_file_size def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a .customDestinations-ms file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_entry = parser_mediator.GetFileEntry() display_name = parser_mediator.GetDisplayName() file_object.seek(0, os.SEEK_SET) try: file_header = self._FILE_HEADER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( (u'Invalid Custom Destination: {0:s} - unable to parse ' u'file header with error: {1:s}').format( display_name, exception)) if file_header.unknown1 != 2: raise errors.UnableToParseFile(( u'Unsupported Custom Destination file: {0:s} - invalid unknown1: ' u'{1:d}.').format(display_name, file_header.unknown1)) if file_header.header_values_type > 2: raise errors.UnableToParseFile(( u'Unsupported Custom Destination file: {0:s} - invalid header value ' u'type: {1:d}.').format(display_name, file_header.header_values_type)) if file_header.header_values_type == 0: data_structure = self._HEADER_VALUE_TYPE_0 else: data_structure = self._HEADER_VALUE_TYPE_1_OR_2 try: _ = data_structure.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( (u'Invalid Custom Destination file: {0:s} - unable to parse ' u'header value with error: {1:s}').format( display_name, exception)) file_size = file_object.get_size() file_offset = file_object.get_offset() remaining_file_size = file_size - file_offset # The Custom Destination file does not have a unique signature in # the file header that is why we use the first LNK class identifier (GUID) # as a signature. first_guid_checked = False while remaining_file_size > 4: try: entry_header = self._ENTRY_HEADER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: error_message = ( u'Invalid Custom Destination file: {0:s} - unable to parse ' u'entry header with error: {1:s}').format( display_name, exception) if not first_guid_checked: raise errors.UnableToParseFile(error_message) logging.warning(error_message) break if entry_header.guid != self._LNK_GUID: error_message = ( u'Unsupported Custom Destination file: {0:s} - invalid entry ' u'header.').format(display_name) if not first_guid_checked: raise errors.UnableToParseFile(error_message) file_object.seek(-16, os.SEEK_CUR) try: file_footer = self._FILE_FOOTER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise IOError( (u'Unable to parse file footer at offset: 0x{0:08x} ' u'with error: {1:s}').format(file_offset, exception)) if file_footer.signature != self._FOOTER_SIGNATURE: logging.warning(error_message) file_object.seek(-4, os.SEEK_CUR) # TODO: add support for Jump List LNK file recovery. break first_guid_checked = True file_offset += 16 remaining_file_size -= 16 lnk_file_size = self._ParseLNKFile(parser_mediator, file_entry, file_offset, remaining_file_size) file_offset += lnk_file_size remaining_file_size -= lnk_file_size file_object.seek(file_offset, os.SEEK_SET) try: file_footer = self._FILE_FOOTER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: logging.warning( (u'Invalid Custom Destination file: {0:s} - unable to parse ' u'footer with error: {1:s}').format(display_name, exception)) if file_footer.signature != self._FOOTER_SIGNATURE: logging.warning(( u'Unsupported Custom Destination file: {0:s} - invalid footer ' u'signature.').format(display_name))
def setUp(self): """Sets up the needed objects used throughout the test.""" self._parser = winlnk.WinLnkParser()
class CustomDestinationsParser(interface.SingleFileBaseParser): """Parses .customDestinations-ms files.""" _INITIAL_FILE_OFFSET = None NAME = 'custom_destinations' DESCRIPTION = u'Parser for *.customDestinations-ms files.' # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _LNK_GUID = '\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46' _FILE_HEADER = construct.Struct(u'file_header', construct.ULInt32(u'unknown1'), construct.ULInt32(u'unknown2'), construct.ULInt32(u'unknown3'), construct.ULInt32(u'header_values_type')) _HEADER_VALUE_TYPE_0 = construct.Struct( u'header_value_type_0', construct.ULInt32(u'number_of_characters'), construct.String(u'string', lambda ctx: ctx.number_of_characters * 2), construct.ULInt32(u'unknown1')) _HEADER_VALUE_TYPE_1_OR_2 = construct.Struct( u'header_value_type_1_or_2', construct.ULInt32(u'unknown1')) _ENTRY_HEADER = construct.Struct(u'entry_header', construct.String(u'guid', 16)) _FILE_FOOTER = construct.Struct(u'file_footer', construct.ULInt32(u'signature')) def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a *.customDestinations-ms file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_object.seek(0, os.SEEK_SET) try: file_header = self._FILE_HEADER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( (u'Unable to parse Custom Destination file header with error: ' u'{0:s}').format(exception)) if file_header.unknown1 != 2: raise errors.UnableToParseFile( (u'Unsupported Custom Destination file - invalid unknown1: ' u'{0:d}.').format(file_header.unknown1)) if file_header.header_values_type > 2: raise errors.UnableToParseFile(( u'Unsupported Custom Destination file - invalid header value type: ' u'{0:d}.').format(file_header.header_values_type)) if file_header.header_values_type == 0: data_structure = self._HEADER_VALUE_TYPE_0 else: data_structure = self._HEADER_VALUE_TYPE_1_OR_2 try: _ = data_structure.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile(( u'Unable to parse Custom Destination file header value with error: ' u'{0:s}').format(exception)) file_size = file_object.get_size() file_offset = file_object.get_offset() remaining_file_size = file_size - file_offset # The Custom Destination file does not have a unique signature in # the file header that is why we use the first LNK class identifier (GUID) # as a signature. first_guid_checked = False while remaining_file_size > 4: try: entry_header = self._ENTRY_HEADER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: if not first_guid_checked: raise errors.UnableToParseFile(( u'Unable to parse Custom Destination file entry header with ' u'error: {0:s}').format(exception)) else: logging.warning(( u'Unable to parse Custom Destination file entry header with ' u'error: {0:s}').format(exception)) break if entry_header.guid != self._LNK_GUID: if not first_guid_checked: raise errors.UnableToParseFile( u'Unsupported Custom Destination file - invalid entry header.' ) else: logging.warning( u'Unsupported Custom Destination file - invalid entry header.' ) break first_guid_checked = True file_offset += 16 remaining_file_size -= 16 path_spec = path_spec_factory.Factory.NewPathSpec( definitions.TYPE_INDICATOR_DATA_RANGE, range_offset=file_offset, range_size=remaining_file_size, parent=parser_mediator.GetFileEntry().path_spec) try: lnk_file_object = resolver.Resolver.OpenFileObject(path_spec) except RuntimeError as exception: message = u'Unable to open LNK file with error'.format( exception) parser_mediator.ProduceParseError(message) return display_name = u'{0:s} # 0x{1:08x}'.format( parser_mediator.GetFileEntry().name, file_offset) self._WINLNK_PARSER.UpdateChainAndParseFileObject( parser_mediator, lnk_file_object, display_name=display_name) # We cannot trust the file size in the LNK data so we get the last offset # that was read instead. lnk_file_size = lnk_file_object.get_offset() lnk_file_object.close() file_offset += lnk_file_size remaining_file_size -= lnk_file_size file_object.seek(file_offset, os.SEEK_SET) try: file_footer = self._FILE_FOOTER.parse_stream(file_object) except (IOError, construct.FieldError) as exception: logging.warning( (u'Unable to parse Custom Destination file footer with error: ' u'{0:s}').format(exception)) if file_footer.signature != 0xbabffbab: logging.warning( u'Unsupported Custom Destination file - invalid footer signature.' )
class AutomaticDestinationsOLECFPlugin( interface.OLECFPlugin, dtfabric_helper.DtFabricHelper): """Plugin that parses an .automaticDestinations-ms OLECF file.""" NAME = 'olecf_automatic_destinations' DATA_FORMAT = ( 'Automatic destinations jump list OLE compound file ' '(.automaticDestinations-ms)') REQUIRED_ITEMS = frozenset(['DestList']) _DEFINITION_FILE = os.path.join( os.path.dirname(__file__), 'automatic_destinations.yaml') _RE_LNK_ITEM_NAME = re.compile(r'^[1-9a-f][0-9a-f]*$') # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() def _ParseDistributedTrackingIdentifier( self, parser_mediator, uuid_object, origin): """Extracts data from a Distributed Tracking identifier. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. uuid_object (uuid.UUID): UUID of the Distributed Tracking identifier. origin (str): origin of the event (event source). Returns: str: UUID string of the Distributed Tracking identifier. """ if uuid_object.version == 1: event_data = windows_events.WindowsDistributedLinkTrackingEventData( uuid_object, origin) date_time = dfdatetime_uuid_time.UUIDTime(timestamp=uuid_object.time) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_CREATION) parser_mediator.ProduceEventWithEventData(event, event_data) return '{{{0!s}}}'.format(uuid_object) def ParseDestList(self, parser_mediator, olecf_item): """Parses the DestList OLECF item. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. olecf_item (pyolecf.item): OLECF item. Raises: UnableToParseFile: if the DestList cannot be parsed. """ if olecf_item.size == 0: parser_mediator.ProduceExtractionWarning('empty DestList stream') return header_map = self._GetDataTypeMap('dest_list_header') try: header, entry_offset = self._ReadStructureFromFileObject( olecf_item, 0, header_map) except (ValueError, errors.ParseError) as exception: raise errors.UnableToParseFile( 'Unable to parse DestList header with error: {0!s}'.format( exception)) if header.format_version == 1: entry_map = self._GetDataTypeMap('dest_list_entry_v1') elif header.format_version in (3, 4): entry_map = self._GetDataTypeMap('dest_list_entry_v3') else: parser_mediator.ProduceExtractionWarning( 'unsupported format version: {0:d}.'.format(header.format_version)) return while entry_offset < olecf_item.size: try: entry, entry_data_size = self._ReadStructureFromFileObject( olecf_item, entry_offset, entry_map) except (ValueError, errors.ParseError) as exception: raise errors.UnableToParseFile( 'Unable to parse DestList entry with error: {0!s}'.format( exception)) display_name = 'DestList entry at offset: 0x{0:08x}'.format(entry_offset) try: droid_volume_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.droid_volume_identifier, display_name) except (TypeError, ValueError) as exception: droid_volume_identifier = '' parser_mediator.ProduceExtractionWarning( 'unable to read droid volume identifier with error: {0!s}'.format( exception)) try: droid_file_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.droid_file_identifier, display_name) except (TypeError, ValueError) as exception: droid_file_identifier = '' parser_mediator.ProduceExtractionWarning( 'unable to read droid file identifier with error: {0!s}'.format( exception)) try: birth_droid_volume_identifier = ( self._ParseDistributedTrackingIdentifier( parser_mediator, entry.birth_droid_volume_identifier, display_name)) except (TypeError, ValueError) as exception: birth_droid_volume_identifier = '' parser_mediator.ProduceExtractionWarning(( 'unable to read birth droid volume identifier with error: ' '{0:s}').format( exception)) try: birth_droid_file_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.birth_droid_file_identifier, display_name) except (TypeError, ValueError) as exception: birth_droid_file_identifier = '' parser_mediator.ProduceExtractionWarning(( 'unable to read birth droid file identifier with error: ' '{0:s}').format( exception)) if entry.last_modification_time == 0: date_time = dfdatetime_semantic_time.NotSet() else: date_time = dfdatetime_filetime.Filetime( timestamp=entry.last_modification_time) event_data = AutomaticDestinationsDestListEntryEventData() event_data.birth_droid_file_identifier = birth_droid_file_identifier event_data.birth_droid_volume_identifier = birth_droid_volume_identifier event_data.droid_file_identifier = droid_file_identifier event_data.droid_volume_identifier = droid_volume_identifier event_data.entry_number = entry.entry_number event_data.hostname = entry.hostname.rstrip('\x00') event_data.offset = entry_offset event_data.path = entry.path.rstrip('\x00') event_data.pin_status = entry.pin_status event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_MODIFICATION) parser_mediator.ProduceEventWithEventData(event, event_data) entry_offset += entry_data_size def Process(self, parser_mediator, root_item=None, **kwargs): """Extracts events from an OLECF file. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. root_item (Optional[pyolecf.item]): root item of the OLECF file. Raises: ValueError: If the root_item is not set. """ # This will raise if unhandled keyword arguments are passed. super(AutomaticDestinationsOLECFPlugin, self).Process( parser_mediator, **kwargs) if not root_item: raise ValueError('Root item not set.') for item in root_item.sub_items: if item.name == 'DestList': self.ParseDestList(parser_mediator, item) elif self._RE_LNK_ITEM_NAME.match(item.name): display_name = parser_mediator.GetDisplayName() if display_name: display_name = '{0:s} # {1:s}'.format(display_name, item.name) else: display_name = '# {0:s}'.format(item.name) parser_mediator.AppendToParserChain(self._WINLNK_PARSER) try: item.seek(0, os.SEEK_SET) self._WINLNK_PARSER.ParseFileLNKFile( parser_mediator, item, display_name) finally: parser_mediator.PopFromParserChain()
def testParse(self): """Tests the Parse function.""" parser = winlnk.WinLnkParser() storage_writer = self._ParseFile(['example.lnk'], parser) # Link information: # Creation time : Jul 13, 2009 23:29:02.849131000 UTC # Modification time : Jul 14, 2009 01:39:18.220000000 UTC # Access time : Jul 13, 2009 23:29:02.849131000 UTC # Description : @%windir%\system32\migwiz\wet.dll,-590 # Relative path : .\migwiz\migwiz.exe # Working directory : %windir%\system32\migwiz # Icon location : %windir%\system32\migwiz\migwiz.exe # Environment variables location : %windir%\system32\migwiz\migwiz.exe self.assertEqual(storage_writer.number_of_events, 5) events = list(storage_writer.GetEvents()) # A shortcut event. event = events[0] expected_string = '@%windir%\\system32\\migwiz\\wet.dll,-590' self.assertEqual(event.description, expected_string) expected_string = '.\\migwiz\\migwiz.exe' self.assertEqual(event.relative_path, expected_string) expected_string = '%windir%\\system32\\migwiz' self.assertEqual(event.working_directory, expected_string) expected_string = '%windir%\\system32\\migwiz\\migwiz.exe' self.assertEqual(event.icon_location, expected_string) self.assertEqual(event.env_var_location, expected_string) # The last accessed timestamp. expected_timestamp = timelib.Timestamp.CopyFromString( '2009-07-13 23:29:02.849131') self.assertEqual(event.timestamp_desc, definitions.TIME_DESCRIPTION_LAST_ACCESS) self.assertEqual(event.timestamp, expected_timestamp) # The creation timestamp. event = events[1] expected_timestamp = timelib.Timestamp.CopyFromString( '2009-07-13 23:29:02.849131') self.assertEqual(event.timestamp_desc, definitions.TIME_DESCRIPTION_CREATION) self.assertEqual(event.timestamp, expected_timestamp) # The last modification timestamp. event = events[2] expected_timestamp = timelib.Timestamp.CopyFromString( '2009-07-14 01:39:18.220000') self.assertEqual(event.timestamp_desc, definitions.TIME_DESCRIPTION_MODIFICATION) self.assertEqual(event.timestamp, expected_timestamp) expected_message = ( '[@%windir%\\system32\\migwiz\\wet.dll,-590] ' 'File size: 544768 ' 'File attribute flags: 0x00000020 ' 'env location: %windir%\\system32\\migwiz\\migwiz.exe ' 'Relative path: .\\migwiz\\migwiz.exe ' 'Working dir: %windir%\\system32\\migwiz ' 'Icon location: %windir%\\system32\\migwiz\\migwiz.exe') expected_short_message = ( '[@%windir%\\system32\\migwiz\\wet.dll,-590] ' '%windir%\\system32\\migwiz\\.\\migwiz\\mi...') self._TestGetMessageStrings(event, expected_message, expected_short_message) # A distributed link tracking event. event = events[4] expected_timestamp = timelib.Timestamp.CopyFromString( '2009-07-14 05:45:20.500012') self.assertEqual(event.timestamp_desc, definitions.TIME_DESCRIPTION_CREATION) self.assertEqual(event.timestamp, expected_timestamp) expected_uuid = '846ee3bb-7039-11de-9d20-001d09fa5a1c' self.assertEqual(event.uuid, expected_uuid) self.assertEqual(event.mac_address, '00:1d:09:fa:5a:1c')
def setUp(self): """Sets up the needed objects used throughout the test.""" pre_obj = event.PreprocessObject() self._parser = winlnk.WinLnkParser(pre_obj, None)
class CustomDestinationsParser(dtfabric_parser.DtFabricBaseParser): """Parses custom destinations jump list (.customDestinations-ms) files.""" NAME = 'custom_destinations' DATA_FORMAT = 'Custom destinations jump list (.customDestinations-ms) file' _INITIAL_FILE_OFFSET = None _DEFINITION_FILE = 'custom_destinations.yaml' # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _LNK_GUID = ( b'\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46') def _ParseLNKFile(self, parser_mediator, file_entry, file_offset, remaining_file_size): """Parses a LNK file stored within the .customDestinations-ms file. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_entry (dfvfs.FileEntry): a file entry. file_offset (int): offset to the LNK file, relative to the start of the .customDestinations-ms file. remaining_file_size (int): size of the data remaining in the .customDestinations-ms file. Returns: int: size of the LNK file data or 0 if the LNK file could not be read. """ path_spec = path_spec_factory.Factory.NewPathSpec( definitions.TYPE_INDICATOR_DATA_RANGE, range_offset=file_offset, range_size=remaining_file_size, parent=file_entry.path_spec) display_name = '{0:s} # 0x{1:08x}'.format(file_entry.name, file_offset) try: lnk_file_object = resolver.Resolver.OpenFileObject( path_spec, resolver_context=parser_mediator.resolver_context) except (dfvfs_errors.BackEndError, RuntimeError) as exception: message = ( 'unable to open LNK file: {0:s} with error: {1!s}').format( display_name, exception) parser_mediator.ProduceExtractionWarning(message) return 0 parser_mediator.AppendToParserChain(self._WINLNK_PARSER) try: lnk_file_object.seek(0, os.SEEK_SET) self._WINLNK_PARSER.ParseFileLNKFile(parser_mediator, lnk_file_object, display_name) finally: parser_mediator.PopFromParserChain() # We cannot trust the file size in the LNK data so we get the last offset # that was read instead. return lnk_file_object.get_offset() @classmethod def GetFormatSpecification(cls): """Retrieves the format specification. Returns: FormatSpecification: format specification. """ format_specification = specification.FormatSpecification(cls.NAME) format_specification.AddNewSignature(b'\xab\xfb\xbf\xba', offset=-4) return format_specification def ParseFileObject(self, parser_mediator, file_object): """Parses a .customDestinations-ms file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_entry = parser_mediator.GetFileEntry() display_name = parser_mediator.GetDisplayName() file_header_map = self._GetDataTypeMap('custom_file_header') try: file_header, file_offset = self._ReadStructureFromFileObject( file_object, 0, file_header_map) except (ValueError, errors.ParseError) as exception: raise errors.UnableToParseFile(( 'Invalid Custom Destination: {0:s} - unable to parse file header ' 'with error: {1!s}').format(display_name, exception)) if file_header.unknown1 != 2: raise errors.UnableToParseFile(( 'Unsupported Custom Destination file: {0:s} - invalid unknown1: ' '{1:d}.').format(display_name, file_header.unknown1)) if file_header.header_values_type > 2: raise errors.UnableToParseFile(( 'Unsupported Custom Destination file: {0:s} - invalid header value ' 'type: {1:d}.').format(display_name, file_header.header_values_type)) if file_header.header_values_type == 0: data_map_name = 'custom_file_header_value_type_0' else: data_map_name = 'custom_file_header_value_type_1_or_2' file_header_value_map = self._GetDataTypeMap(data_map_name) try: _, value_data_size = self._ReadStructureFromFileObject( file_object, file_offset, file_header_value_map) except (ValueError, errors.ParseError) as exception: raise errors.UnableToParseFile(( 'Invalid Custom Destination: {0:s} - unable to parse file header ' 'value with error: {1!s}').format(display_name, exception)) file_offset += value_data_size file_size = file_object.get_size() remaining_file_size = file_size - file_offset entry_header_map = self._GetDataTypeMap('custom_entry_header') file_footer_map = self._GetDataTypeMap('custom_file_footer') # The Custom Destination file does not have a unique signature in # the file header that is why we use the first LNK class identifier (GUID) # as a signature. first_guid_checked = False while remaining_file_size > 4: try: entry_header, entry_data_size = self._ReadStructureFromFileObject( file_object, file_offset, entry_header_map) except (ValueError, errors.ParseError) as exception: if not first_guid_checked: raise errors.UnableToParseFile(( 'Invalid Custom Destination file: {0:s} - unable to parse ' 'entry header with error: {1!s}').format( display_name, exception)) parser_mediator.ProduceExtractionWarning( 'unable to parse entry header with error: {0!s}'.format( exception)) break if entry_header.guid != self._LNK_GUID: if not first_guid_checked: raise errors.UnableToParseFile(( 'Unsupported Custom Destination file: {0:s} - invalid entry ' 'header signature offset: 0x{1:08x}.').format( display_name, file_offset)) try: # Check if we found the footer instead of an entry header. self._ReadStructureFromFileObject(file_object, file_offset, file_footer_map) except (ValueError, errors.ParseError) as exception: parser_mediator.ProduceExtractionWarning(( 'unable to parse footer at offset: 0x{0:08x} with error: ' '{1!s}').format(file_offset, exception)) break # TODO: add support for Jump List LNK file recovery. break first_guid_checked = True file_offset += entry_data_size remaining_file_size -= entry_data_size lnk_file_size = self._ParseLNKFile(parser_mediator, file_entry, file_offset, remaining_file_size) file_offset += lnk_file_size remaining_file_size -= lnk_file_size try: self._ReadStructureFromFileObject(file_object, file_offset, file_footer_map) except (ValueError, errors.ParseError) as exception: parser_mediator.ProduceExtractionWarning( ('unable to parse footer at offset: 0x{0:08x} with error: ' '{1!s}').format(file_offset, exception))
class AutomaticDestinationsOlecfPlugin(interface.OlecfPlugin): """Plugin that parses an .automaticDestinations-ms OLECF file.""" NAME = u'olecf_automatic_destinations' DESCRIPTION = u'Parser for *.automaticDestinations-ms OLECF files.' REQUIRED_ITEMS = frozenset([u'DestList']) _RE_LNK_ITEM_NAME = re.compile(r'^[1-9a-f][0-9a-f]*$') # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _DEST_LIST_STREAM_HEADER = construct.Struct( u'dest_list_stream_header', construct.ULInt32(u'unknown1'), construct.ULInt32(u'number_of_entries'), construct.ULInt32(u'number_of_pinned_entries'), construct.LFloat32(u'unknown2'), construct.ULInt32(u'last_entry_number'), construct.Padding(4), construct.ULInt32(u'last_revision_number'), construct.Padding(4)) _DEST_LIST_STREAM_HEADER_SIZE = _DEST_LIST_STREAM_HEADER.sizeof() # Using Construct's utf-16 encoding here will create strings with their # end-of-string characters exposed. Instead the strings are read as # binary strings and converted using ReadUtf16(). _DEST_LIST_STREAM_ENTRY = construct.Struct( u'dest_list_stream_entry', construct.ULInt64(u'unknown1'), construct.Array(16, construct.Byte(u'droid_volume_identifier')), construct.Array(16, construct.Byte(u'droid_file_identifier')), construct.Array(16, construct.Byte(u'birth_droid_volume_identifier')), construct.Array(16, construct.Byte(u'birth_droid_file_identifier')), construct.String(u'hostname', 16), construct.ULInt32(u'entry_number'), construct.ULInt32(u'unknown2'), construct.LFloat32(u'unknown3'), construct.ULInt64(u'last_modification_time'), construct.ULInt32(u'pin_status'), construct.ULInt16(u'path_size'), construct.String(u'path', lambda ctx: ctx.path_size * 2)) def ParseDestList(self, parser_mediator, olecf_item): """Parses the DestList OLECF item. Args: parser_mediator: A parser mediator object (instance of ParserMediator). olecf_item: An OLECF item (instance of pyolecf.item). """ try: header = self._DEST_LIST_STREAM_HEADER.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList header with error: {0:s}'.format( exception)) if header.unknown1 != 1: # TODO: add format debugging notes to parser mediator. logging.debug(u'[{0:s}] unknown1 value: {1:d}.'.format( self.NAME, header.unknown1)) entry_offset = olecf_item.get_offset() while entry_offset < olecf_item.size: try: entry = self._DEST_LIST_STREAM_ENTRY.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList entry with error: {0:s}'.format( exception)) if not entry: break event_object = AutomaticDestinationsDestListEntryEvent( entry.last_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME, entry_offset, entry) parser_mediator.ProduceEvent(event_object) entry_offset = olecf_item.get_offset() def ParseItems(self, parser_mediator, file_entry=None, root_item=None, **unused_kwargs): """Parses OLECF items. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_entry: Optional file entry object (instance of dfvfs.FileEntry). The default is None. root_item: Optional root item of the OLECF file. The default is None. Raises: ValueError: If the root_item is not set. """ if root_item is None: raise ValueError(u'Root item not set.') for item in root_item.sub_items: if item.name == u'DestList': self.ParseDestList(parser_mediator, item) elif self._RE_LNK_ITEM_NAME.match(item.name): if file_entry: display_name = u'{0:s} # {1:s}'.format( parser_mediator.GetDisplayName(), item.name) else: display_name = u'# {0:s}'.format(item.name) self._WINLNK_PARSER.UpdateChainAndParseFileObject( parser_mediator, item, display_name=display_name)
def setUp(self): """Makes preparations before running an individual test.""" self._parser = winlnk.WinLnkParser()
def testParse(self): """Tests the Parse function.""" parser_object = winlnk.WinLnkParser() test_file = self._GetTestFilePath([u'example.lnk']) event_queue_consumer = self._ParseFile(parser_object, test_file) event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) # Link information: # Creation time : Jul 13, 2009 23:29:02.849131000 UTC # Modification time : Jul 14, 2009 01:39:18.220000000 UTC # Access time : Jul 13, 2009 23:29:02.849131000 UTC # Description : @%windir%\system32\migwiz\wet.dll,-590 # Relative path : .\migwiz\migwiz.exe # Working directory : %windir%\system32\migwiz # Icon location : %windir%\system32\migwiz\migwiz.exe # Environment variables location : %windir%\system32\migwiz\migwiz.exe self.assertEqual(len(event_objects), 5) # A shortcut event object. event_object = event_objects[0] expected_string = u'@%windir%\\system32\\migwiz\\wet.dll,-590' self.assertEqual(event_object.description, expected_string) expected_string = u'.\\migwiz\\migwiz.exe' self.assertEqual(event_object.relative_path, expected_string) expected_string = u'%windir%\\system32\\migwiz' self.assertEqual(event_object.working_directory, expected_string) expected_string = u'%windir%\\system32\\migwiz\\migwiz.exe' self.assertEqual(event_object.icon_location, expected_string) self.assertEqual(event_object.env_var_location, expected_string) # The last accessed timestamp. expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-13 23:29:02.849131') self.assertEqual(event_object.timestamp_desc, eventdata.EventTimestamp.ACCESS_TIME) self.assertEqual(event_object.timestamp, expected_timestamp) # The creation timestamp. event_object = event_objects[1] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-13 23:29:02.849131') self.assertEqual(event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) self.assertEqual(event_object.timestamp, expected_timestamp) # The last modification timestamp. event_object = event_objects[2] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-14 01:39:18.220000') self.assertEqual(event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME) self.assertEqual(event_object.timestamp, expected_timestamp) expected_msg = ( u'[@%windir%\\system32\\migwiz\\wet.dll,-590] ' u'File size: 544768 ' u'File attribute flags: 0x00000020 ' u'env location: %windir%\\system32\\migwiz\\migwiz.exe ' u'Relative path: .\\migwiz\\migwiz.exe ' u'Working dir: %windir%\\system32\\migwiz ' u'Icon location: %windir%\\system32\\migwiz\\migwiz.exe') expected_msg_short = (u'[@%windir%\\system32\\migwiz\\wet.dll,-590]') self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) # A distributed link tracking event object. event_object = event_objects[4] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-14 05:45:20.500012') self.assertEqual(event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) self.assertEqual(event_object.timestamp, expected_timestamp) expected_uuid = u'846ee3bb-7039-11de-9d20-001d09fa5a1c' self.assertEqual(event_object.uuid, expected_uuid) self.assertEqual(event_object.mac_address, u'00:1d:09:fa:5a:1c')
def testParseLinkTargetIdentifier(self): """Tests the Parse function on an LNK with a link target identifier.""" parser_object = winlnk.WinLnkParser() test_file = self._GetTestFilePath([u'NeroInfoTool.lnk']) event_queue_consumer = self._ParseFile(parser_object, test_file) event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) self.assertEqual(len(event_objects), 20) # A shortcut event object. event_object = event_objects[16] expected_msg = ( u'[Nero InfoTool provides you with information about the most ' u'important features of installed drives, inserted discs, installed ' u'software and much more. With Nero InfoTool you can find out all ' u'about your drive and your system configuration.] ' u'File size: 4635160 ' u'File attribute flags: 0x00000020 ' u'Drive type: 3 ' u'Drive serial number: 0x70ecfa33 ' u'Volume label: OS ' u'Local path: C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool\\' u'InfoTool.exe ' u'cmd arguments: -ScParameter=30002 ' u'Relative path: ..\\..\\..\\..\\..\\..\\..\\..\\Program Files (x86)\\' u'Nero\\Nero 9\\Nero InfoTool\\InfoTool.exe ' u'Working dir: C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool ' u'Icon location: %ProgramFiles%\\Nero\\Nero 9\\Nero InfoTool\\' u'InfoTool.exe ' u'Link target: <My Computer> C:\\Program Files (x86)\\Nero\\Nero 9\\' u'Nero InfoTool\\InfoTool.exe') expected_msg_short = ( u'[Nero InfoTool provides you with information about the most ' u'important feature...') self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) # A shell item event object. event_object = event_objects[12] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-06-05 20:13:20') self.assertEqual(event_object.timestamp, expected_timestamp) expected_msg = ( u'Name: InfoTool.exe ' u'Long name: InfoTool.exe ' u'NTFS file reference: 81349-1 ' u'Shell item path: <My Computer> C:\\Program Files (x86)\\Nero\\' u'Nero 9\\Nero InfoTool\\InfoTool.exe ' u'Origin: NeroInfoTool.lnk') expected_msg_short = (u'Name: InfoTool.exe ' u'NTFS file reference: 81349-1 ' u'Origin: NeroInfoTool.lnk') self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
class AutomaticDestinationsOLECFPlugin(interface.OLECFPlugin): """Plugin that parses an .automaticDestinations-ms OLECF file.""" NAME = u'olecf_automatic_destinations' DESCRIPTION = u'Parser for *.automaticDestinations-ms OLECF files.' REQUIRED_ITEMS = frozenset([u'DestList']) _RE_LNK_ITEM_NAME = re.compile(r'^[1-9a-f][0-9a-f]*$') # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _DEST_LIST_STREAM_HEADER = construct.Struct( u'dest_list_stream_header', construct.ULInt32(u'format_version'), construct.ULInt32(u'number_of_entries'), construct.ULInt32(u'number_of_pinned_entries'), construct.Padding(4), construct.ULInt32(u'last_entry_number'), construct.Padding(4), construct.ULInt32(u'last_revision_number'), construct.Padding(4)) _DEST_LIST_STREAM_HEADER_SIZE = _DEST_LIST_STREAM_HEADER.sizeof() # Using Construct's utf-16 encoding here will create strings with their # end-of-string characters exposed. Instead the strings are read as # binary strings and converted using ReadUTF16(). _DEST_LIST_STREAM_ENTRY_V1 = construct.Struct( u'dest_list_stream_entry_v1', construct.Padding(8), construct.Bytes(u'droid_volume_identifier', 16), construct.Bytes(u'droid_file_identifier', 16), construct.Bytes(u'birth_droid_volume_identifier', 16), construct.Bytes(u'birth_droid_file_identifier', 16), construct.String(u'hostname', 16), construct.ULInt32(u'entry_number'), construct.Padding(8), construct.ULInt64(u'last_modification_time'), construct.ULInt32(u'pin_status'), construct.ULInt16(u'path_size'), construct.String(u'path', lambda ctx: ctx.path_size * 2)) _DEST_LIST_STREAM_ENTRY_V3 = construct.Struct( u'dest_list_stream_entry_v3', construct.Padding(8), construct.Bytes(u'droid_volume_identifier', 16), construct.Bytes(u'droid_file_identifier', 16), construct.Bytes(u'birth_droid_volume_identifier', 16), construct.Bytes(u'birth_droid_file_identifier', 16), construct.String(u'hostname', 16), construct.ULInt32(u'entry_number'), construct.Padding(8), construct.ULInt64(u'last_modification_time'), construct.ULInt32(u'pin_status'), construct.Padding(16), construct.ULInt16(u'path_size'), construct.String(u'path', lambda ctx: ctx.path_size * 2), construct.Padding(4)) def _ParseDistributedTrackingIdentifier(self, parser_mediator, uuid_data, origin): """Extracts data from a Distributed Tracking identifier. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. uuid_data (bytes): UUID data of the Distributed Tracking identifier. origin (str): origin of the event (event source). Returns: str: UUID string of the Distributed Tracking identifier. """ uuid_object = uuid.UUID(bytes_le=uuid_data) if uuid_object.version == 1: event_data = windows_events.WindowsDistributedLinkTrackingEventData( uuid_object, origin) date_time = dfdatetime_uuid_time.UUIDTime( timestamp=uuid_object.time) event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_CREATION) parser_mediator.ProduceEventWithEventData(event, event_data) return u'{{{0!s}}}'.format(uuid_object) def ParseDestList(self, parser_mediator, olecf_item): """Parses the DestList OLECF item. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. olecf_item (pyolecf.item): OLECF item. """ try: header = self._DEST_LIST_STREAM_HEADER.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList header with error: {0:s}'.format( exception)) if header.format_version not in (1, 3, 4): parser_mediator.ProduceExtractionError( u'unsupported format version: {0:d}.'.format( header.format_version)) if header.format_version == 1: dest_list_stream_entry = self._DEST_LIST_STREAM_ENTRY_V1 elif header.format_version in (3, 4): dest_list_stream_entry = self._DEST_LIST_STREAM_ENTRY_V3 entry_offset = olecf_item.get_offset() while entry_offset < olecf_item.size: try: entry = dest_list_stream_entry.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList entry with error: {0:s}'.format( exception)) if not entry: break display_name = u'DestList entry at offset: 0x{0:08x}'.format( entry_offset) try: droid_volume_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.droid_volume_identifier, display_name) except (TypeError, ValueError) as exception: droid_volume_identifier = u'' parser_mediator.ProduceExtractionError( u'unable to read droid volume identifier with error: {0:s}' .format(exception)) try: droid_file_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.droid_file_identifier, display_name) except (TypeError, ValueError) as exception: droid_file_identifier = u'' parser_mediator.ProduceExtractionError( u'unable to read droid file identifier with error: {0:s}'. format(exception)) try: birth_droid_volume_identifier = ( self._ParseDistributedTrackingIdentifier( parser_mediator, entry.birth_droid_volume_identifier, display_name)) except (TypeError, ValueError) as exception: birth_droid_volume_identifier = u'' parser_mediator.ProduceExtractionError(( u'unable to read birth droid volume identifier with error: ' u'{0:s}').format(exception)) try: birth_droid_file_identifier = self._ParseDistributedTrackingIdentifier( parser_mediator, entry.birth_droid_file_identifier, display_name) except (TypeError, ValueError) as exception: birth_droid_file_identifier = u'' parser_mediator.ProduceExtractionError( (u'unable to read birth droid file identifier with error: ' u'{0:s}').format(exception)) if entry.last_modification_time == 0: date_time = dfdatetime_semantic_time.SemanticTime(u'Not set') else: date_time = dfdatetime_filetime.Filetime( timestamp=entry.last_modification_time) event = AutomaticDestinationsDestListEntryEvent( date_time, definitions.TIME_DESCRIPTION_MODIFICATION, entry_offset, entry, droid_volume_identifier, droid_file_identifier, birth_droid_volume_identifier, birth_droid_file_identifier) parser_mediator.ProduceEvent(event) entry_offset = olecf_item.get_offset() def Process(self, parser_mediator, root_item=None, **kwargs): """Parses an OLECF file. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. root_item (Optional[pyolecf.item]): root item of the OLECF file. Raises: ValueError: If the root_item is not set. """ # This will raise if unhandled keyword arguments are passed. super(AutomaticDestinationsOLECFPlugin, self).Process(parser_mediator, **kwargs) if not root_item: raise ValueError(u'Root item not set.') for item in root_item.sub_items: if item.name == u'DestList': self.ParseDestList(parser_mediator, item) elif self._RE_LNK_ITEM_NAME.match(item.name): display_name = parser_mediator.GetDisplayName() if display_name: display_name = u'{0:s} # {1:s}'.format( display_name, item.name) else: display_name = u'# {0:s}'.format(item.name) self._WINLNK_PARSER.Parse(parser_mediator, item, display_name=display_name)
def testParse(self): """Tests the Parse function.""" parser = winlnk.WinLnkParser() storage_writer = self._ParseFile(['example.lnk'], parser) # Link information: # Creation time : Jul 13, 2009 23:29:02.849131000 UTC # Modification time : Jul 14, 2009 01:39:18.220000000 UTC # Access time : Jul 13, 2009 23:29:02.849131000 UTC # Description : @%windir%\system32\migwiz\wet.dll,-590 # Relative path : .\migwiz\migwiz.exe # Working directory : %windir%\system32\migwiz # Icon location : %windir%\system32\migwiz\migwiz.exe # Environment variables location : %windir%\system32\migwiz\migwiz.exe number_of_events = storage_writer.GetNumberOfAttributeContainers('event') self.assertEqual(number_of_events, 5) 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) events = list(storage_writer.GetEvents()) # A shortcut last accessed event. expected_event_values = { 'date_time': '2009-07-13 23:29:02.8491310', 'data_type': 'windows:lnk:link', 'description': '@%windir%\\system32\\migwiz\\wet.dll,-590', 'env_var_location': '%windir%\\system32\\migwiz\\migwiz.exe', 'file_attribute_flags': 0x00000020, 'file_size': 544768, 'icon_location': '%windir%\\system32\\migwiz\\migwiz.exe', 'relative_path': '.\\migwiz\\migwiz.exe', 'timestamp_desc': definitions.TIME_DESCRIPTION_LAST_ACCESS, 'working_directory': '%windir%\\system32\\migwiz'} self.CheckEventValues(storage_writer, events[0], expected_event_values) # A shortcut creation event. expected_event_values = { 'date_time': '2009-07-13 23:29:02.8491310', 'data_type': 'windows:lnk:link', 'timestamp_desc': definitions.TIME_DESCRIPTION_CREATION} self.CheckEventValues(storage_writer, events[1], expected_event_values) # A shortcut last modification event. expected_event_values = { 'date_time': '2009-07-14 01:39:18.2200000', 'data_type': 'windows:lnk:link', 'description': '@%windir%\\system32\\migwiz\\wet.dll,-590', 'env_var_location': '%windir%\\system32\\migwiz\\migwiz.exe', 'file_attribute_flags': 0x00000020, 'file_size': 544768, 'icon_location': '%windir%\\system32\\migwiz\\migwiz.exe', 'relative_path': '.\\migwiz\\migwiz.exe', 'timestamp_desc': definitions.TIME_DESCRIPTION_MODIFICATION, 'working_directory': '%windir%\\system32\\migwiz'} self.CheckEventValues(storage_writer, events[2], expected_event_values) # A distributed link tracking creation event. expected_event_values = { 'date_time': '2009-07-14 05:45:20.5000123', 'data_type': 'windows:distributed_link_tracking:creation', 'mac_address': '00:1d:09:fa:5a:1c', 'timestamp_desc': definitions.TIME_DESCRIPTION_CREATION, 'uuid': '846ee3bb-7039-11de-9d20-001d09fa5a1c'} self.CheckEventValues(storage_writer, events[4], expected_event_values)
class AutomaticDestinationsOlecfPlugin(interface.OlecfPlugin): """Plugin that parses an .automaticDestinations-ms OLECF file.""" NAME = u'olecf_automatic_destinations' DESCRIPTION = u'Parser for *.automaticDestinations-ms OLECF files.' REQUIRED_ITEMS = frozenset([u'DestList']) _RE_LNK_ITEM_NAME = re.compile(r'^[1-9a-f][0-9a-f]*$') # We cannot use the parser registry here since winlnk could be disabled. # TODO: see if there is a more elegant solution for this. _WINLNK_PARSER = winlnk.WinLnkParser() _DEST_LIST_STREAM_HEADER = construct.Struct( u'dest_list_stream_header', construct.ULInt32(u'format_version'), construct.ULInt32(u'number_of_entries'), construct.ULInt32(u'number_of_pinned_entries'), construct.Padding(4), construct.ULInt32(u'last_entry_number'), construct.Padding(4), construct.ULInt32(u'last_revision_number'), construct.Padding(4)) _DEST_LIST_STREAM_HEADER_SIZE = _DEST_LIST_STREAM_HEADER.sizeof() # Using Construct's utf-16 encoding here will create strings with their # end-of-string characters exposed. Instead the strings are read as # binary strings and converted using ReadUTF16(). _DEST_LIST_STREAM_ENTRY_V1 = construct.Struct( u'dest_list_stream_entry_v1', construct.Padding(8), construct.Bytes(u'droid_volume_identifier', 16), construct.Bytes(u'droid_file_identifier', 16), construct.Bytes(u'birth_droid_volume_identifier', 16), construct.Bytes(u'birth_droid_file_identifier', 16), construct.String(u'hostname', 16), construct.ULInt32(u'entry_number'), construct.Padding(8), construct.ULInt64(u'last_modification_time'), construct.ULInt32(u'pin_status'), construct.ULInt16(u'path_size'), construct.String(u'path', lambda ctx: ctx.path_size * 2)) _DEST_LIST_STREAM_ENTRY_V3 = construct.Struct( u'dest_list_stream_entry_v3', construct.Padding(8), construct.Bytes(u'droid_volume_identifier', 16), construct.Bytes(u'droid_file_identifier', 16), construct.Bytes(u'birth_droid_volume_identifier', 16), construct.Bytes(u'birth_droid_file_identifier', 16), construct.String(u'hostname', 16), construct.ULInt32(u'entry_number'), construct.Padding(8), construct.ULInt64(u'last_modification_time'), construct.ULInt32(u'pin_status'), construct.Padding(16), construct.ULInt16(u'path_size'), construct.String(u'path', lambda ctx: ctx.path_size * 2), construct.Padding(4)) def ParseDestList(self, parser_mediator, olecf_item): """Parses the DestList OLECF item. Args: parser_mediator: A parser mediator object (instance of ParserMediator). olecf_item: An OLECF item (instance of pyolecf.item). """ try: header = self._DEST_LIST_STREAM_HEADER.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList header with error: {0:s}'.format( exception)) if header.format_version not in (1, 3, 4): parser_mediator.ProduceParseError( u'unsupported format version: {0:d}.'.format( header.format_version)) if header.format_version == 1: dest_list_stream_entry = self._DEST_LIST_STREAM_ENTRY_V1 elif header.format_version in (3, 4): dest_list_stream_entry = self._DEST_LIST_STREAM_ENTRY_V3 entry_offset = olecf_item.get_offset() while entry_offset < olecf_item.size: try: entry = dest_list_stream_entry.parse_stream(olecf_item) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse DestList entry with error: {0:s}'.format( exception)) if not entry: break display_name = u'Dest list entry at offset: 0x{0:08x}'.format( entry_offset) try: uuid_object = uuid.UUID(bytes_le=entry.droid_volume_identifier) droid_volume_identifier = u'{{{0!s}}}'.format(uuid_object) if uuid_object.version == 1: event_object = ( windows_events. WindowsDistributedLinkTrackingCreationEvent( uuid_object, display_name)) parser_mediator.ProduceEvent(event_object) except (TypeError, ValueError) as exception: droid_volume_identifier = u'' parser_mediator.ProduceParseError( u'unable to read droid volume identifier with error: {0:s}' .format(exception)) try: uuid_object = uuid.UUID(bytes_le=entry.droid_file_identifier) droid_file_identifier = u'{{{0!s}}}'.format(uuid_object) if uuid_object.version == 1: event_object = ( windows_events. WindowsDistributedLinkTrackingCreationEvent( uuid_object, display_name)) parser_mediator.ProduceEvent(event_object) except (TypeError, ValueError) as exception: droid_file_identifier = u'' parser_mediator.ProduceParseError( u'unable to read droid file identifier with error: {0:s}'. format(exception)) try: uuid_object = uuid.UUID( bytes_le=entry.birth_droid_volume_identifier) birth_droid_volume_identifier = u'{{{0!s}}}'.format( uuid_object) if uuid_object.version == 1: event_object = ( windows_events. WindowsDistributedLinkTrackingCreationEvent( uuid_object, display_name)) parser_mediator.ProduceEvent(event_object) except (TypeError, ValueError) as exception: birth_droid_volume_identifier = u'' parser_mediator.ProduceParseError(( u'unable to read birth droid volume identifier with error: ' u'{0:s}').format(exception)) try: uuid_object = uuid.UUID( bytes_le=entry.birth_droid_file_identifier) birth_droid_file_identifier = u'{{{0!s}}}'.format(uuid_object) if uuid_object.version == 1: event_object = ( windows_events. WindowsDistributedLinkTrackingCreationEvent( uuid_object, display_name)) parser_mediator.ProduceEvent(event_object) except (TypeError, ValueError) as exception: birth_droid_file_identifier = u'' parser_mediator.ProduceParseError( (u'unable to read birth droid file identifier with error: ' u'{0:s}').format(exception)) event_object = AutomaticDestinationsDestListEntryEvent( entry.last_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME, entry_offset, entry, droid_volume_identifier, droid_file_identifier, birth_droid_volume_identifier, birth_droid_file_identifier) parser_mediator.ProduceEvent(event_object) entry_offset = olecf_item.get_offset() def ParseItems(self, parser_mediator, file_entry=None, root_item=None, **unused_kwargs): """Parses OLECF items. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_entry: Optional file entry object (instance of dfvfs.FileEntry). root_item: Optional root item of the OLECF file. Raises: ValueError: If the root_item is not set. """ if root_item is None: raise ValueError(u'Root item not set.') for item in root_item.sub_items: if item.name == u'DestList': self.ParseDestList(parser_mediator, item) elif self._RE_LNK_ITEM_NAME.match(item.name): if file_entry: display_name = u'{0:s} # {1:s}'.format( parser_mediator.GetDisplayName(), item.name) else: display_name = u'# {0:s}'.format(item.name) self._WINLNK_PARSER.Parse(parser_mediator, item, display_name=display_name)