plaso-rubanetra/plaso/parsers/recycler.py
2020-04-06 18:48:34 +02:00

223 lines
7.7 KiB
Python

#!/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)