223 lines
7.7 KiB
Python
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)
|