#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright 2013 The Plaso Project Authors. # Please see the AUTHORS file for details on individual authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Parser for Windows Recycle files, INFO2 and $I/$R pairs.""" import logging import construct from plaso.events import time_events from plaso.lib import binary from plaso.lib import errors from plaso.lib import eventdata from plaso.lib import utils from plaso.parsers import interface from plaso.parsers import manager class WinRecycleEvent(time_events.FiletimeEvent): """Convenience class for a Windows Recycle bin EventObject.""" DATA_TYPE = 'windows:metadata:deleted_item' def __init__( self, filename_ascii, filename_utf, record_information, record_size): """Initializes the event object.""" timestamp = record_information.get('filetime', 0) super(WinRecycleEvent, self).__init__( timestamp, eventdata.EventTimestamp.DELETED_TIME) if 'index' in record_information: self.index = record_information.get('index', 0) self.offset = record_size * self.index else: self.offset = 0 self.drive_number = record_information.get('drive', None) self.file_size = record_information.get('filesize', 0) if filename_utf: self.orig_filename = filename_utf else: self.orig_filename = filename_ascii # The unicode cast is done on the ASCII string to make # comparison work better (sometimes a warning that a comparison # could not be made due to the objects being of different type). if filename_ascii and unicode(filename_ascii) != filename_utf: self.orig_filename_legacy = filename_ascii class WinRecycleBinParser(interface.BaseParser): """Parses the Windows $Recycle.Bin $I files.""" NAME = 'recycle_bin' DESCRIPTION = u'Parser for Windows $Recycle.Bin $I files.' # Define a list of all structs needed. # Struct read from: # https://code.google.com/p/rifiuti2/source/browse/trunk/src/rifiuti-vista.h RECORD_STRUCT = construct.Struct( 'record', construct.ULInt64('filesize'), construct.ULInt64('filetime')) MAGIC_STRUCT = construct.ULInt64('magic') def Parse(self, parser_context, file_entry, parser_chain=None): """Extract entries from a Windows RecycleBin $Ixx file. Args: parser_context: A parser context object (instance of ParserContext). file_entry: A file entry object (instance of dfvfs.FileEntry). parser_chain: Optional string containing the parsing chain up to this point. The default is None. """ file_object = file_entry.GetFileObject() try: magic_header = self.MAGIC_STRUCT.parse_stream(file_object) except (construct.FieldError, IOError) as exception: raise errors.UnableToParseFile( u'Unable to parse $Ixxx file with error: {0:s}'.format(exception)) if magic_header is not 1: raise errors.UnableToParseFile( u'Not an $Ixxx file, wrong magic header.') # Add ourselves to the parser chain, which will be used in all subsequent # event creation in this parser. parser_chain = self._BuildParserChain(parser_chain) # We may have to rely on filenames since this header is very generic. # TODO: Rethink this and potentially make a better test. base_filename = utils.GetBaseName(file_entry.name) if not base_filename.startswith('$I'): raise errors.UnableToParseFile( u'Not an $Ixxx file, filename doesn\'t start with $I.') record = self.RECORD_STRUCT.parse_stream(file_object) filename_utf = binary.ReadUtf16Stream(file_object) file_object.close() event_object = WinRecycleEvent(u'', filename_utf, record, 0) parser_context.ProduceEvent( event_object, parser_chain=parser_chain, file_entry=file_entry) class WinRecycleInfo2Parser(interface.BaseParser): """Parses the Windows Recycler INFO2 file.""" NAME = 'recycle_bin_info2' DESCRIPTION = u'Parser for Windows Recycler INFO2 files.' # Define a list of all structs used. INT32_LE = construct.ULInt32('my_int') FILE_HEADER_STRUCT = construct.Struct( 'file_header', construct.Padding(8), construct.ULInt32('record_size')) # Struct based on (-both unicode and legacy string): # https://code.google.com/p/rifiuti2/source/browse/trunk/src/rifiuti.h RECORD_STRUCT = construct.Struct( 'record', construct.ULInt32('index'), construct.ULInt32('drive'), construct.ULInt64('filetime'), construct.ULInt32('filesize')) STRING_STRUCT = construct.CString('legacy_filename') # Define a list of needed variables. UNICODE_FILENAME_OFFSET = 0x11C RECORD_INDEX_OFFSET = 0x108 def Parse(self, parser_context, file_entry, parser_chain=None): """Extract entries from Windows Recycler INFO2 file. Args: parser_context: A parser context object (instance of ParserContext). file_entry: A file entry object (instance of dfvfs.FileEntry). parser_chain: Optional string containing the parsing chain up to this point. The default is None. """ file_object = file_entry.GetFileObject() try: magic_header = self.INT32_LE.parse_stream(file_object) except (construct.FieldError, IOError) as exception: raise errors.UnableToParseFile( u'Unable to parse INFO2 file with error: {0:s}'.format(exception)) if magic_header is not 5: raise errors.UnableToParseFile( u'Not an INFO2 file, wrong magic header.') # Add ourselves to the parser chain, which will be used in all subsequent # event creation in this parser. parser_chain = self._BuildParserChain(parser_chain) # Since this header value is really generic it is hard not to use filename # as an indicator too. # TODO: Rethink this and potentially make a better test. base_filename = utils.GetBaseName(file_entry.name) if not base_filename.startswith('INFO2'): raise errors.UnableToParseFile( u'Not an INFO2 file, filename isn\'t INFO2.') file_header = self.FILE_HEADER_STRUCT.parse_stream(file_object) # Limit recrodsize to 65536 to be on the safe side. record_size = file_header['record_size'] if record_size > 65536: logging.error(( u'Record size: {0:d} is too large for INFO2 reducing to: ' u'65535').format(record_size)) record_size = 65535 # If recordsize is 0x320 then we have UTF/unicode names as well. read_unicode_names = False if record_size is 0x320: read_unicode_names = True data = file_object.read(record_size) while data: if len(data) != record_size: break filename_ascii = self.STRING_STRUCT.parse(data[4:]) record_information = self.RECORD_STRUCT.parse( data[self.RECORD_INDEX_OFFSET:]) if read_unicode_names: filename_utf = binary.ReadUtf16( data[self.UNICODE_FILENAME_OFFSET:]) else: filename_utf = u'' event_object = WinRecycleEvent( filename_ascii, filename_utf, record_information, record_size) parser_context.ProduceEvent( event_object, parser_chain=parser_chain, file_entry=file_entry) data = file_object.read(record_size) file_object.close() manager.ParsersManager.RegisterParser(WinRecycleBinParser)