Skip to content

Commit

Permalink
Code review: 185450043: Adding a file history ESE database parser.
Browse files Browse the repository at this point in the history
  • Loading branch information
kiddinn authored and joachimmetz committed Dec 31, 2015
1 parent 6cdae2c commit d5fa22f
Show file tree
Hide file tree
Showing 11 changed files with 274 additions and 5 deletions.
1 change: 1 addition & 0 deletions ACKNOWLEDGEMENTS
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Copied with permission granted by Rob Lee.
Copyright SANS Institute - Digital Forensics and Incident Response.
* 1b4dd67f29cb1962.automaticDestinations-ms
* 5afe4de1b92fc382.customDestinations-ms
* Catalog1.edb
* example.lnk
* SysEvent.Evt
* System.evtx
Expand Down
4 changes: 2 additions & 2 deletions plaso/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = '1.2.0'
__version__ = '1.2.1'

VERSION_DEV = False
VERSION_DEV = True
VERSION_DATE = '20141220'


Expand Down
1 change: 1 addition & 0 deletions plaso/formatters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from plaso.formatters import chrome_extension_activity
from plaso.formatters import cups_ipp
from plaso.formatters import filestat
from plaso.formatters import file_history
from plaso.formatters import firefox
from plaso.formatters import firefox_cache
from plaso.formatters import firefox_cookies
Expand Down
39 changes: 39 additions & 0 deletions plaso/formatters/file_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Formatters for the file history ESE database events."""

from plaso.formatters import interface


class FileHistoryNamespaceEventFormatter(interface.ConditionalEventFormatter):
"""Formatter for a file history ESE database namespace table record."""

DATA_TYPE = 'file_history:namespace:event'

FORMAT_STRING_PIECES = [
u'Filename: {original_filename}',
u'Identifier: {identifier}',
u'Parent Identifier: {parent_identifier}',
u'Attributes: {file_attribute}',
u'USN number: {usn_number}']

FORMAT_STRING_SHORT_PIECES = [
u'Filename: {original_filename}']

SOURCE_LONG = 'File History Namespace'
SOURCE_SHORT = 'LOG'
4 changes: 2 additions & 2 deletions plaso/frontend/presets.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
'winxp_slow': [
'hachoir', 'winxp'],
'win7': [
'recycle_bin', 'custom_destinations', 'olecf_automatic_destinations',
'win_gen', 'winevtx'],
'recycle_bin', 'custom_destinations', 'esedb_file_history',
'olecf_automatic_destinations', 'win_gen', 'winevtx'],
'win7_slow': [
'hachoir', 'win7'],
'webhist': [
Expand Down
9 changes: 9 additions & 0 deletions plaso/parsers/esedb.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,15 @@
class EseDbCache(plugins.BasePluginCache):
"""A cache storing query results for ESEDB plugins."""

def StoreDictInCache(self, attribute_name, dict_object):
"""Store a dict object in cache.
Args:
attribute_name: The name of the attribute.
dict_object: A dict object.
"""
setattr(self, attribute_name, dict_object)


class EseDbParser(interface.BasePluginsParser):
"""Parses Extensible Storage Engine (ESE) database files (EDB)."""
Expand Down
1 change: 1 addition & 0 deletions plaso/parsers/esedb_plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@
# limitations under the License.
"""This file contains import statements for the ESE database plugins."""

from plaso.parsers.esedb_plugins import file_history
from plaso.parsers.esedb_plugins import msie_webcache
145 changes: 145 additions & 0 deletions plaso/parsers/esedb_plugins/file_history.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the Microsoft File History ESE database."""

import logging

from plaso.events import time_events
from plaso.lib import eventdata
from plaso.parsers import esedb
from plaso.parsers.esedb_plugins import interface


class FileHistoryNamespaceEventObject(time_events.FiletimeEvent):
"""Convenience class for a file history namespace table event."""

DATA_TYPE = 'file_history:namespace:event'

def __init__(self, timestamp, usage, filename, record_values):
"""Initializes the event.
Args:
timestamp: The FILETIME timestamp value.
usage: The usage string, describing the timdestamp value.
filename: The name of the file.
record_values: A dict object containing the record values.
"""
super(FileHistoryNamespaceEventObject, self).__init__(timestamp, usage)

self.file_attribute = record_values.get(u'fileAttrib')
self.identifier = record_values.get(u'id')
self.original_filename = filename
self.parent_identifier = record_values.get(u'parentId')
self.usn_number = record_values.get(u'usn')


class FileHistoryEseDbPlugin(interface.EseDbPlugin):
"""Parses a File History ESE database file."""

NAME = 'esedb_file_history'
DESCRIPTION = u'Parser for File History ESE database files.'

# TODO: Add support for other tables as well, backupset, file, library, etc.
REQUIRED_TABLES = {
'backupset': '',
'file': '',
'library': '',
'namespace': 'ParseNameSpace'}

def _GetDictFromStringsTable(self, table):
"""Build a dict for the strings table.
Args:
table: A table object for the strings table (instance of pyesedb.table).
Returns:
A dict that contains the identification field as key and filename as
value.
"""
return_dict = {}

if not table:
return return_dict

for record in table.records:
if record.get_number_of_values() != 2:
continue

identification = self._GetRecordValue(record, 0)
filename = self._GetRecordValue(record, 1)

if not identification:
continue
return_dict[identification] = filename

return return_dict

def ParseNameSpace(
self, parser_context, file_entry=None, parser_chain=None, database=None,
cache=None, table=None, **unused_kwargs):
"""Parses the namespace table.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
database: Optional database object (instance of pyesedb.file).
The default is None.
cache: Optional cache object (instance of EseDbCache).
table: Optional table object (instance of pyesedb.table).
The default is None.
"""
if database is None:
logging.warning(u'[{0:s}] invalid database'.format(self.NAME))
return

if table is None:
logging.warning(u'[{0:s}] invalid Containers table'.format(self.NAME))
return

strings = cache.GetResults('strings')
if not strings:
strings = self._GetDictFromStringsTable(
database.get_table_by_name(u'string'))
cache.StoreDictInCache(u'strings', strings)

for esedb_record in table.records:
record_values = self._GetRecordValues(table.name, esedb_record)

filename = strings.get(record_values.get('id', -1), u'')
created_timestamp = record_values.get(u'fileCreated')

if created_timestamp:
event_object = FileHistoryNamespaceEventObject(
created_timestamp, eventdata.EventTimestamp.CREATION_TIME,
filename, record_values)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)

modified_timestamp = record_values.get(u'fileModified')
if modified_timestamp:
event_object = FileHistoryNamespaceEventObject(
modified_timestamp, eventdata.EventTimestamp.MODIFICATION_TIME,
filename, record_values)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)


esedb.EseDbParser.RegisterPlugin(FileHistoryEseDbPlugin)
71 changes: 71 additions & 0 deletions plaso/parsers/esedb_plugins/file_history_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the File History ESE database file."""

import unittest

# pylint: disable=unused-import
from plaso.formatters import file_history as file_history_formatter
from plaso.lib import eventdata
from plaso.lib import timelib_test
from plaso.parsers.esedb_plugins import file_history
from plaso.parsers.esedb_plugins import test_lib


class FileHistoryEseDbPluginTest(test_lib.EseDbPluginTestCase):
"""Tests for the File History ESE database plugin."""

def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = file_history.FileHistoryEseDbPlugin()

def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['Catalog1.edb'])
event_queue_consumer = self._ParseEseDbFileWithPlugin(
test_file, self._plugin)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)

self.assertEquals(len(event_objects), 2680)

event_object = event_objects[693]

self.assertEquals(event_object.usn_number, 9251162904)
self.assertEquals(event_object.identifier, 356)

expected_timestamp = timelib_test.CopyStringToTimestamp(
'2013-10-12 17:34:36.688580')

self.assertEquals(event_object.timestamp, expected_timestamp)
self.assertEquals(
event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME)

filename = u'?UP\\Favorites\\Links\\Lenovo'
self.assertEquals(event_object.original_filename, filename)

expected_msg = (
u'Filename: {0:s} Identifier: 356 Parent Identifier: 230 Attributes: '
u'16 USN number: 9251162904').format(filename)

expected_msg_short = u'Filename: {0:s}'.format(filename)

self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)


if __name__ == '__main__':
unittest.main()
4 changes: 3 additions & 1 deletion plaso/parsers/esedb_plugins/test_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from dfvfs.resolver import resolver as path_spec_resolver

from plaso.engine import single_process
from plaso.parsers import esedb
from plaso.parsers import test_lib


Expand Down Expand Up @@ -71,6 +72,7 @@ def _ParseEseDbFileWithPlugin(
event_queue, parse_error_queue,
knowledge_base_values=knowledge_base_values)
esedb_file = self._OpenEseDbFile(path)
plugin_object.Process(parser_context, database=esedb_file)
cache = esedb.EseDbCache()
plugin_object.Process(parser_context, database=esedb_file, cache=cache)

return event_queue_consumer
Binary file added test_data/Catalog1.edb
Binary file not shown.

0 comments on commit d5fa22f

Please sign in to comment.