Import from old repository

This commit is contained in:
Stefan
2020-04-06 18:48:34 +02:00
commit 0da6783a45
762 changed files with 103065 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
#!/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.
"""This file contains an import statement for each OLECF plugin."""
from plaso.parsers.olecf_plugins import automatic_destinations
from plaso.parsers.olecf_plugins import default
from plaso.parsers.olecf_plugins import summary
@@ -0,0 +1,204 @@
#!/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.
"""Plugin to parse .automaticDestinations-ms OLECF files."""
import logging
import re
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.parsers import olecf
from plaso.parsers import winlnk
from plaso.parsers.olecf_plugins import interface
class AutomaticDestinationsDestListEntryEvent(time_events.FiletimeEvent):
"""Convenience class for an .automaticDestinations-ms DestList entry event."""
DATA_TYPE = 'olecf:dest_list:entry'
def __init__(
self, timestamp, timestamp_description, entry_offset, dest_list_entry):
"""Initializes the event object.
Args:
timestamp: The FILETIME value for the timestamp.
timestamp_description: The usage string for the timestamp value.
entry_offset: The offset of the DestList entry relative to the start of
the DestList stream.
dest_list_entry: The DestList entry (instance of construct.Struct).
"""
super(AutomaticDestinationsDestListEntryEvent, self).__init__(
timestamp, timestamp_description)
self.offset = entry_offset
self.entry_number = dest_list_entry.entry_number
self.hostname = binary.ByteStreamCopyToString(
dest_list_entry.hostname, codepage='ascii')
self.path = binary.Ut16StreamCopyToString(dest_list_entry.path)
self.pin_status = dest_list_entry.pin_status
self.droid_volume_identifier = binary.ByteStreamCopyToGuid(
dest_list_entry.droid_volume_identifier)
self.droid_file_identifier = binary.ByteStreamCopyToGuid(
dest_list_entry.droid_file_identifier)
self.birth_droid_volume_identifier = binary.ByteStreamCopyToGuid(
dest_list_entry.birth_droid_volume_identifier)
self.birth_droid_file_identifier = binary.ByteStreamCopyToGuid(
dest_list_entry.birth_droid_file_identifier)
class AutomaticDestinationsOlecfPlugin(interface.OlecfPlugin):
"""Plugin that parses an .automaticDestinations-ms OLECF file."""
NAME = '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(
'dest_list_stream_header',
construct.ULInt32('unknown1'),
construct.ULInt32('number_of_entries'),
construct.ULInt32('number_of_pinned_entries'),
construct.LFloat32('unknown2'),
construct.ULInt32('last_entry_number'),
construct.Padding(4),
construct.ULInt32('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(
'dest_list_stream_entry',
construct.ULInt64('unknown1'),
construct.Array(16, construct.Byte('droid_volume_identifier')),
construct.Array(16, construct.Byte('droid_file_identifier')),
construct.Array(16, construct.Byte('birth_droid_volume_identifier')),
construct.Array(16, construct.Byte('birth_droid_file_identifier')),
construct.String('hostname', 16),
construct.ULInt32('entry_number'),
construct.ULInt32('unknown2'),
construct.LFloat32('unknown3'),
construct.ULInt64('last_modification_time'),
construct.ULInt32('pin_status'),
construct.ULInt16('path_size'),
construct.String('path', lambda ctx: ctx.path_size * 2))
def ParseDestList(
self, parser_context, file_entry=None, parser_chain=None,
olecf_item=None):
"""Parses the DestList OLECF item.
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.
olecf_item: An optional OLECF item (instance of pyolecf.item).
"""
if not olecf_item:
return
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 context.
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_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
entry_offset = olecf_item.get_offset()
def ParseItems(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
**unused_kwargs):
"""Parses OLECF items.
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.
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_context, file_entry=file_entry, parser_chain=parser_chain,
olecf_item=item)
elif self._RE_LNK_ITEM_NAME.match(item.name):
if file_entry:
display_name = u'{0:s} # {1:s}'.format(
parser_context.GetDisplayName(file_entry), item.name)
else:
display_name = u'# {0:s}'.format(item.name)
self._WINLNK_PARSER.ParseFileObject(
parser_context, item, file_entry=file_entry,
parser_chain=parser_chain, display_name=display_name)
# TODO: check for trailing data?
olecf.OleCfParser.RegisterPlugin(AutomaticDestinationsOlecfPlugin)
@@ -0,0 +1,101 @@
#!/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 .automaticDestinations-ms OLECF file plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import olecf as olecf_formatter
from plaso.lib import eventdata
from plaso.lib import timelib_test
from plaso.parsers.olecf_plugins import automatic_destinations
from plaso.parsers.olecf_plugins import test_lib
class TestAutomaticDestinationsOlecfPlugin(test_lib.OleCfPluginTestCase):
"""Tests for the .automaticDestinations-ms OLECF file plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = automatic_destinations.AutomaticDestinationsOlecfPlugin()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath([
u'1b4dd67f29cb1962.automaticDestinations-ms'])
event_queue_consumer = self._ParseOleCfFileWithPlugin(
test_file, self._plugin)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 44)
# Check a AutomaticDestinationsDestListEntryEvent.
event_object = event_objects[3]
self.assertEquals(event_object.offset, 32)
self.assertEquals(
event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME)
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2012-04-01 13:52:38.997538')
self.assertEquals(event_object.timestamp, expected_timestamp)
expected_msg = (
u'Entry: 11 '
u'Pin status: Unpinned '
u'Hostname: wks-win764bitb '
u'Path: C:\\Users\\nfury\\Pictures\\The SHIELD '
u'Droid volume identifier: {cf6619c2-66a8-44a6-8849-1582fcd3a338} '
u'Droid file identifier: {63eea867-7b85-11e1-8950-005056a50b40} '
u'Birth droid volume identifier: '
u'{cf6619c2-66a8-44a6-8849-1582fcd3a338} '
u'Birth droid file identifier: {63eea867-7b85-11e1-8950-005056a50b40}')
expected_msg_short = (
u'Entry: 11 '
u'Pin status: Unpinned '
u'Path: C:\\Users\\nfury\\Pictures\\The SHIELD')
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
# Check a WinLnkLinkEvent.
event_object = event_objects[1]
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2010-11-10 07:51:16.749125')
self.assertEquals(event_object.timestamp, expected_timestamp)
expected_msg = (
u'File size: 3545 '
u'File attribute flags: 0x00002020 '
u'Drive type: 3 '
u'Drive serial number: 0x24ba718b '
u'Local path: C:\\Users\\nfury\\AppData\\Roaming\\Microsoft\\Windows\\'
u'Libraries\\Documents.library-ms '
u'Link target: [Users Libraries, UNKNOWN: 0x00]')
expected_msg_short = (
u'C:\\Users\\nfury\\AppData\\Roaming\\Microsoft\\Windows\\Libraries\\'
u'Documents.library-ms')
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
if __name__ == '__main__':
unittest.main()
+162
View File
@@ -0,0 +1,162 @@
#!/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.
"""The default plugin for parsing OLE Compound Files (OLECF)."""
from plaso.events import time_events
from plaso.lib import eventdata
from plaso.parsers import olecf
from plaso.parsers.olecf_plugins import interface
class OleCfItemEvent(time_events.FiletimeEvent):
"""Convenience class for an OLECF item event."""
DATA_TYPE = 'olecf:item'
def __init__(self, timestamp, usage, olecf_item):
"""Initializes the event.
Args:
timestamp: The FILETIME timestamp value.
usage: A string describing the timestamp value.
olecf_item: The OLECF item (pyolecf.item).
"""
super(OleCfItemEvent, self).__init__(timestamp, usage)
# TODO: need a better way to express the original location of the
# original data.
self.offset = 0
self.name = olecf_item.name
# TODO: have pyolecf return the item type here.
# self.type = olecf_item.type
self.size = olecf_item.size
class DefaultOleCFPlugin(interface.OlecfPlugin):
"""Class to define the default OLECF file plugin."""
NAME = 'olecf_default'
DESCRIPTION = u'Parser for a generic OLECF item.'
def _ParseItem(
self, parser_context, file_entry=None, parser_chain=None,
olecf_item=None):
"""Parses an OLECF item.
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.
olecf_item: An optional OLECF item (instance of pyolecf.item).
Returns:
A boolean value indicating if an event object was produced.
"""
event_object = None
result = False
creation_time, modification_time = self.GetTimestamps(olecf_item)
if creation_time:
event_object = OleCfItemEvent(
creation_time, eventdata.EventTimestamp.CREATION_TIME,
olecf_item)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if modification_time:
event_object = OleCfItemEvent(
modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
olecf_item)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if event_object:
result = True
for sub_item in olecf_item.sub_items:
if self._ParseItem(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
olecf_item=sub_item):
result = True
return result
def ParseItems(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
**unused_kwargs):
"""Parses OLECF items.
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.
root_item: Optional root item of the OLECF file. The default is None.
"""
if not self._ParseItem(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
olecf_item=root_item):
# If no event object was produced, produce at least one for
# the root item.
event_object = OleCfItemEvent(
0, eventdata.EventTimestamp.CREATION_TIME, root_item)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
def Process(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
item_names=None, **kwargs):
"""Determine if this is the right plugin for this OLECF file.
This function takes a list of sub items found in the root of a
OLECF file and compares that to a list of required items defined
in this plugin.
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.
root_item: Optional root item of the OLECF file. The default is None.
item_names: Optional list of all items discovered in the root.
The default is None.
Raises:
errors.WrongPlugin: If the set of required items is not a subset
of the available items.
ValueError: If the root_item or items are not set.
"""
if root_item is None or item_names is None:
raise ValueError(u'Root item or items are not set.')
# Add ourselves to the parser chain, which will be used in all subsequent
# event creation in this parser.
parser_chain = self._BuildParserChain(parser_chain)
self.ParseItems(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
root_item=root_item)
olecf.OleCfParser.RegisterPlugin(DefaultOleCFPlugin)
@@ -0,0 +1,75 @@
#!/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.
"""Tests for the OLE Compound File (OLECF) default plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import olecf as olecf_formatter
from plaso.lib import eventdata
from plaso.lib import timelib_test
from plaso.parsers.olecf_plugins import default
from plaso.parsers.olecf_plugins import test_lib
class TestDefaultPluginOleCf(test_lib.OleCfPluginTestCase):
"""Tests for the OLECF default plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = default.DefaultOleCFPlugin()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['Document.doc'])
event_queue_consumer = self._ParseOleCfFileWithPlugin(
test_file, self._plugin)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 5)
# Check the Root Entry event.
event_object = event_objects[0]
self.assertEquals(event_object.name, u'Root Entry')
self.assertEquals(
event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME)
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2013-05-16 02:29:49.795')
self.assertEquals(event_object.timestamp, expected_timestamp)
expected_string = (
u'Name: Root Entry')
self._TestGetMessageStrings(event_object, expected_string, expected_string)
# Check one other entry.
event_object = event_objects[1]
expected_string = u'Name: MsoDataStore'
self._TestGetMessageStrings(event_object, expected_string, expected_string)
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2013-05-16 02:29:49.704')
self.assertEquals(event_object.timestamp, expected_timestamp)
if __name__ == '__main__':
unittest.main()
+150
View File
@@ -0,0 +1,150 @@
#!/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.
"""This file contains the necessary interface for OLECF plugins."""
import abc
import logging
from plaso.lib import errors
from plaso.parsers import plugins
class OlecfPlugin(plugins.BasePlugin):
"""An OLECF plugin for Plaso."""
NAME = 'olecf'
# List of tables that should be present in the database, for verification.
REQUIRED_ITEMS = frozenset([])
def GetTimestamps(self, olecf_item):
"""Takes an OLECF object and returns extracted timestamps.
Args:
olecf_item: A OLECF item (instance of pyolecf.item).
Returns:
A tuple of two timestamps: created and modified.
"""
if not olecf_item:
return None, None
try:
creation_time = olecf_item.get_creation_time_as_integer()
except OverflowError as exception:
logging.warning(
u'Unable to read the creation time with error: {0:s}'.format(
exception))
creation_time = 0
try:
modification_time = olecf_item.get_modification_time_as_integer()
except OverflowError as exception:
logging.warning(
u'Unable to read the modification time with error: {0:s}'.format(
exception))
modification_time = 0
# If no useful events, return early.
if not creation_time and not modification_time:
return None, None
# Office template documents sometimes contain a creation time
# of -1 (0xffffffffffffffff).
if creation_time == 0xffffffffffffffffL:
creation_time = 0
return creation_time, modification_time
@abc.abstractmethod
def ParseItems(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
items=None, **kwargs):
"""Parses OLECF items.
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.
root_item: Optional root item of the OLECF file. The default is None.
item_names: Optional list of all items discovered in the root.
The default is None.
"""
def Process(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
item_names=None, **kwargs):
"""Determine if this is the right plugin for this OLECF file.
This function takes a list of sub items found in the root of a
OLECF file and compares that to a list of required items defined
in this plugin.
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.
root_item: Optional root item of the OLECF file. The default is None.
item_names: Optional list of all items discovered in the root.
The default is None.
Raises:
errors.WrongPlugin: If the set of required items is not a subset
of the available items.
ValueError: If the root_item or items are not set.
"""
if root_item is None or item_names is None:
raise ValueError(u'Root item or items are not set.')
if not frozenset(item_names) >= self.REQUIRED_ITEMS:
raise errors.WrongPlugin(
u'Not the correct items for: {0:s}'.format(self.NAME))
# This will raise if unhandled keyword arguments are passed.
super(OlecfPlugin, self).Process(parser_context, **kwargs)
# Add ourselves to the parser chain, which will be used in all subsequent
# event creation in this parser.
parser_chain = self._BuildParserChain(parser_chain)
items = []
for item_string in self.REQUIRED_ITEMS:
item = root_item.get_sub_item_by_name(item_string)
if item:
items.append(item)
self.ParseItems(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
root_item=root_item, items=items)
class OleDefinitions(object):
"""Convenience class for OLE definitions."""
VT_I2 = 0x0002
VT_I4 = 0x0003
VT_BOOL = 0x000b
VT_LPSTR = 0x001e
VT_LPWSTR = 0x001e
VT_FILETIME = 0x0040
VT_CF = 0x0047
+433
View File
@@ -0,0 +1,433 @@
#!/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.
"""Plugin to parse the OLECF summary/document summary information items."""
from plaso.events import time_events
from plaso.lib import eventdata
from plaso.parsers import olecf
from plaso.parsers.olecf_plugins import interface
class OleCfSummaryInfoEvent(time_events.FiletimeEvent):
"""Convenience class for an OLECF Summary info event."""
DATA_TYPE = 'olecf:summary_info'
def __init__(self, timestamp, usage, attributes):
"""Initializes the event.
Args:
timestamp: The FILETIME timestamp value.
usage: The usage string, describing the timestamp value.
attributes: A dict object containing all extracted attributes.
"""
super(OleCfSummaryInfoEvent, self).__init__(
timestamp, usage)
self.name = u'Summary Information'
for attribute_name, attribute_value in attributes.iteritems():
setattr(self, attribute_name, attribute_value)
# TODO: Move this class to a higher level (to the interface)
# so the these functions can be shared by other plugins.
class OleCfSummaryInfo(object):
"""An OLECF Summary Info object."""
_CLASS_IDENTIFIER = 'f29f85e0-4ff9-1068-ab91-08002b27b3d9'
_PROPERTY_NAMES_INT32 = {
0x000e: 'number_of_pages', # PIDSI_PAGECOUNT
0x000f: 'number_of_words', # PIDSI_WORDCOUNT
0x0010: 'number_of_characters', # PIDSI_CHARCOUNT
0x0013: 'security', # PIDSI_SECURITY
}
_PROPERTY_NAMES_STRING = {
0x0002: 'title', # PIDSI_TITLE
0x0003: 'subject', # PIDSI_SUBJECT
0x0004: 'author', # PIDSI_AUTHOR
0x0005: 'keywords', # PIDSI_KEYWORDS
0x0006: 'comments', # PIDSI_COMMENTS
0x0007: 'template', # PIDSI_TEMPLATE
0x0008: 'last_saved_by', # PIDSI_LASTAUTHOR
0x0009: 'revision_number', # PIDSI_REVNUMBER
0x0012: 'application', # PIDSI_APPNAME
}
PIDSI_CODEPAGE = 0x0001
PIDSI_EDITTIME = 0x000a
PIDSI_LASTPRINTED = 0x000b
PIDSI_CREATE_DTM = 0x000c
PIDSI_LASTSAVE_DTM = 0x000d
PIDSI_THUMBNAIL = 0x0011
def __init__(self, olecf_item):
"""Initialize the OLECF summary object.
Args:
olecf_item: The OLECF item (instance of pyolecf.property_set_stream).
"""
super(OleCfSummaryInfo, self).__init__()
self.attributes = {}
self.events = []
self._InitFromPropertySet(olecf_item.set)
def _InitFromPropertySet(self, property_set):
"""Initializes the object from a property set.
Args:
property_set: The OLECF property set (pyolecf.property_set).
"""
# Combine the values of multiple property sections
# but do not override properties that are already set.
for property_section in property_set.sections:
if property_section.class_identifier != self._CLASS_IDENTIFIER:
continue
for property_value in property_section.properties:
self._InitFromPropertyValue(property_value)
def _InitFromPropertyValue(self, property_value):
"""Initializes the object from a property value.
Args:
property_value: The OLECF property value (pyolecf.property_value).
"""
if property_value.type == interface.OleDefinitions.VT_I2:
self._InitFromPropertyValueTypeInt16(property_value)
elif property_value.type == interface.OleDefinitions.VT_I4:
self._InitFromPropertyValueTypeInt32(property_value)
elif (property_value.type == interface.OleDefinitions.VT_LPSTR or
property_value.type == interface.OleDefinitions.VT_LPWSTR):
self._InitFromPropertyValueTypeString(property_value)
elif property_value.type == interface.OleDefinitions.VT_FILETIME:
self._InitFromPropertyValueTypeFiletime(property_value)
def _InitFromPropertyValueTypeInt16(self, property_value):
"""Initializes the object from a 16-bit int type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_I2).
"""
if property_value.identifier == self.PIDSI_CODEPAGE:
# TODO: can the codepage vary per property section?
# And is it needed to interpret the ASCII strings?
# codepage = property_value.data_as_integer
pass
def _InitFromPropertyValueTypeInt32(self, property_value):
"""Initializes the object from a 32-bit int type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_I4).
"""
property_name = self._PROPERTY_NAMES_INT32.get(
property_value.identifier, None)
if property_name and not property_name in self.attributes:
self.attributes[property_name] = property_value.data_as_integer
def _InitFromPropertyValueTypeString(self, property_value):
"""Initializes the object from a string type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_LPSTR or VT_LPWSTR).
"""
property_name = self._PROPERTY_NAMES_STRING.get(
property_value.identifier, None)
if property_name and not property_name in self.attributes:
self.attributes[property_name] = property_value.data_as_string
def _InitFromPropertyValueTypeFiletime(self, property_value):
"""Initializes the object from a filetime type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_FILETIME).
"""
if property_value.identifier == self.PIDSI_LASTPRINTED:
self.events.append(
(property_value.data_as_integer, 'Document Last Printed Time'))
elif property_value.identifier == self.PIDSI_CREATE_DTM:
self.events.append(
(property_value.data_as_integer, 'Document Creation Time'))
elif property_value.identifier == self.PIDSI_LASTSAVE_DTM:
self.events.append(
(property_value.data_as_integer, 'Document Last Save Time'))
elif property_value.identifier == self.PIDSI_EDITTIME:
# property_name = 'total_edit_time'
# TODO: handle duration.
pass
class OleCfDocumentSummaryInfoEvent(time_events.FiletimeEvent):
"""Convenience class for an OLECF Document Summary info event."""
DATA_TYPE = 'olecf:document_summary_info'
_CLASS_IDENTIFIER = 'd5cdd502-2e9c-101b-9397-08002b2cf9ae'
_PROPERTY_NAMES_BOOL = {
0x0013: 'shared_document', # PIDDSI_SHAREDDOC
}
_PROPERTY_NAMES_INT32 = {
0x0004: 'number_of_bytes', # PIDDSI_BYTECOUNT
0x0005: 'number_of_lines', # PIDDSI_LINECOUNT
0x0006: 'number_of_paragraphs', # PIDDSI_PARCOUNT
0x0007: 'number_of_slides', # PIDDSI_SLIDECOUNT
0x0008: 'number_of_notes', # PIDDSI_NOTECOUNT
0x0009: 'number_of_hidden_slides', # PIDDSI_HIDDENCOUNT
0x000a: 'number_of_clips', # PIDDSI_MMCLIPCOUNT
0x0011: 'number_of_characters_with_white_space', # PIDDSI_CCHWITHSPACES
0x0017: 'application_version', # PIDDSI_VERSION
}
_PROPERTY_NAMES_STRING = {
0x000e: 'manager', # PIDDSI_MANAGER
0x000f: 'company', # PIDDSI_COMPANY
0x001a: 'content_type', # PIDDSI_CONTENTTYPE
0x001b: 'content_status', # PIDDSI_CONTENTSTATUS
0x001c: 'language', # PIDDSI_LANGUAGE
0x001d: 'document_version', # PIDDSI_DOCVERSION
}
PIDDSI_CODEPAGE = 0x0001
PIDDSI_CATEGORY = 0x0002
PIDDSI_PRESFORMAT = 0x0003
PIDDSI_SCALE = 0x000b
PIDDSI_HEADINGPAIR = 0x000c
PIDDSI_DOCPARTS = 0x000d
PIDDSI_LINKSDIRTY = 0x0010
PIDDSI_VERSION = 0x0017
def __init__(self, timestamp, usage, olecf_item):
"""Initializes the event.
Args:
timestamp: The FILETIME timestamp value.
usage: The usage string, describing the timestamp value.
olecf_item: The OLECF item (pyolecf.property_set_stream).
"""
super(OleCfDocumentSummaryInfoEvent, self).__init__(
timestamp, usage)
self.name = u'Document Summary Information'
self._InitFromPropertySet(olecf_item.set)
def _InitFromPropertySet(self, property_set):
"""Initializes the event from a property set.
Args:
property_set: The OLECF property set (pyolecf.property_set).
"""
# Combine the values of multiple property sections
# but do not override properties that are already set.
for property_section in property_set.sections:
if property_section.class_identifier != self._CLASS_IDENTIFIER:
continue
for property_value in property_section.properties:
self._InitFromPropertyValue(property_value)
def _InitFromPropertyValue(self, property_value):
"""Initializes the event from a property value.
Args:
property_value: The OLECF property value (pyolecf.property_value).
"""
if property_value.type == interface.OleDefinitions.VT_I2:
self._InitFromPropertyValueTypeInt16(property_value)
elif property_value.type == interface.OleDefinitions.VT_I4:
self._InitFromPropertyValueTypeInt32(property_value)
elif property_value.type == interface.OleDefinitions.VT_BOOL:
self._InitFromPropertyValueTypeBool(property_value)
elif (property_value.type == interface.OleDefinitions.VT_LPSTR or
property_value.type == interface.OleDefinitions.VT_LPWSTR):
self._InitFromPropertyValueTypeString(property_value)
def _InitFromPropertyValueTypeInt16(self, property_value):
"""Initializes the event from a 16-bit int type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_I2).
"""
if property_value.identifier == self.PIDDSI_CODEPAGE:
# TODO: can the codepage vary per property section?
# And is it needed to interpret the ASCII strings?
# codepage = property_value.data_as_integer
pass
def _InitFromPropertyValueTypeInt32(self, property_value):
"""Initializes the event from a 32-bit int type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_I4).
"""
property_name = self._PROPERTY_NAMES_INT32.get(
property_value.identifier, None)
# The application version consists of 2 16-bit values that make up
# the version number. Where the upper 16-bit is the major number
# and the lower 16-bit the minor number.
if property_value.identifier == self.PIDDSI_VERSION:
application_version = property_value.data_as_integer
setattr(self, property_name, u'{0:d}.{1:d}'.format(
application_version >> 16, application_version & 0xffff))
elif property_name and not hasattr(self, property_name):
setattr(self, property_name, property_value.data_as_integer)
def _InitFromPropertyValueTypeBool(self, property_value):
"""Initializes the event from a boolean type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_BOOL).
"""
property_name = self._PROPERTY_NAMES_BOOL.get(
property_value.identifier, None)
if property_name and not hasattr(self, property_name):
setattr(self, property_name, property_value.data_as_boolean)
def _InitFromPropertyValueTypeString(self, property_value):
"""Initializes the event from a string type property value.
Args:
property_value: The OLECF property value (pyolecf.property_value
of type VT_LPSTR or VT_LPWSTR).
"""
property_name = self._PROPERTY_NAMES_STRING.get(
property_value.identifier, None)
if property_name and not hasattr(self, property_name):
setattr(self, property_name, property_value.data_as_string)
class DocumentSummaryOlecfPlugin(interface.OlecfPlugin):
"""Plugin that parses DocumentSummaryInformation item from an OLECF file."""
NAME = 'olecf_document_summary'
DESCRIPTION = u'Parser for a DocumentSummaryInformation OLECF stream.'
# pylint: disable=anomalous-backslash-in-string
REQUIRED_ITEMS = frozenset([u'\005DocumentSummaryInformation'])
def ParseItems(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
items=None, **unused_kwargs):
"""Parses a document summary information OLECF item.
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.
root_item: Optional root item of the OLECF file. The default is None.
item_names: Optional list of all items discovered in the root.
The default is None.
"""
root_creation_time, root_modification_time = self.GetTimestamps(root_item)
for item in items:
if root_creation_time:
event_object = OleCfDocumentSummaryInfoEvent(
root_creation_time, eventdata.EventTimestamp.CREATION_TIME, item)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if root_modification_time:
event_object = OleCfDocumentSummaryInfoEvent(
root_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
item)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
class SummaryInfoOlecfPlugin(interface.OlecfPlugin):
"""Plugin that parses the SummaryInformation item from an OLECF file."""
NAME = 'olecf_summary'
DESCRIPTION = u'Parser for a SummaryInformation OLECF stream.'
# pylint: disable=anomalous-backslash-in-string
REQUIRED_ITEMS = frozenset([u'\005SummaryInformation'])
def ParseItems(
self, parser_context, file_entry=None, parser_chain=None, root_item=None,
items=None, **unused_kwargs):
"""Parses a summary information OLECF item.
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.
root_item: Optional root item of the OLECF file. The default is None.
item_names: Optional list of all items discovered in the root.
The default is None.
"""
root_creation_time, root_modification_time = self.GetTimestamps(root_item)
for item in items:
summary_information_object = OleCfSummaryInfo(item)
for timestamp, timestamp_description in summary_information_object.events:
event_object = OleCfSummaryInfoEvent(
timestamp, timestamp_description,
summary_information_object.attributes)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if root_creation_time:
event_object = OleCfSummaryInfoEvent(
root_creation_time, eventdata.EventTimestamp.CREATION_TIME,
summary_information_object.attributes)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if root_modification_time:
event_object = OleCfSummaryInfoEvent(
root_modification_time, eventdata.EventTimestamp.MODIFICATION_TIME,
summary_information_object.attributes)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
olecf.OleCfParser.RegisterPlugins(
[DocumentSummaryOlecfPlugin, SummaryInfoOlecfPlugin])
+129
View File
@@ -0,0 +1,129 @@
#!/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.
"""Tests for the OLE Compound File summary and document summary plugins."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import olecf as olecf_formatter
from plaso.lib import timelib_test
from plaso.parsers.olecf_plugins import summary
from plaso.parsers.olecf_plugins import test_lib
class TestSummaryInfoOlecfPlugin(test_lib.OleCfPluginTestCase):
"""Tests for the OLECF summary information plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._summary_plugin = summary.SummaryInfoOlecfPlugin()
self._test_file = self._GetTestFilePath(['Document.doc'])
def testProcess(self):
"""Tests the Process function on a SummaryInformation stream."""
event_queue_consumer = self._ParseOleCfFileWithPlugin(
self._test_file, self._summary_plugin)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
# There is one summary info stream with three event objects.
self.assertEquals(len(event_objects), 3)
event_object = event_objects[0]
self.assertEquals(event_object.name, u'Summary Information')
self.assertEquals(event_object.title, u'Table of Context')
self.assertEquals(event_object.author, u'DAVID NIDES')
self.assertEquals(event_object.template, u'Normal.dotm')
self.assertEquals(event_object.last_saved_by, u'Nides')
self.assertEquals(event_object.revision_number, u'4')
self.assertEquals(event_object.number_of_characters, 18)
self.assertEquals(event_object.application, u'Microsoft Office Word')
self.assertEquals(event_object.security, 0)
self.assertEquals(event_object.timestamp_desc, u'Document Creation Time')
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2012-12-10 18:38:00')
self.assertEquals(event_object.timestamp, expected_timestamp)
expected_msg = (
u'Title: Table of Context '
u'Author: DAVID NIDES '
u'Template: Normal.dotm '
u'Revision number: 4 '
u'Last saved by: Nides '
u'Number of pages: 1 '
u'Number of words: 3 '
u'Number of characters: 18 '
u'Application: Microsoft Office Word '
u'Security: 0')
expected_msg_short = (
u'Title: Table of Context '
u'Author: DAVID NIDES '
u'Revision number: 4')
# TODO: add support for:
# u'Total edit time (secs): 0 '
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
class TestDocumentSummaryInfoOlecfPlugin(test_lib.OleCfPluginTestCase):
"""Tests for the OLECF document summary information plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._document_summary_plugin = summary.DocumentSummaryOlecfPlugin()
self._test_file = self._GetTestFilePath(['Document.doc'])
def testProcess(self):
"""Tests the Process function on a DocumentSummaryInformation stream."""
event_queue_consumer = self._ParseOleCfFileWithPlugin(
self._test_file, self._document_summary_plugin)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
# There should only be one summary info stream with one event.
self.assertEquals(len(event_objects), 1)
event_object = event_objects[0]
self.assertEquals(event_object.name, u'Document Summary Information')
self.assertEquals(event_object.number_of_lines, 1)
self.assertEquals(event_object.number_of_paragraphs, 1)
self.assertEquals(event_object.company, u'KPMG')
self.assertFalse(event_object.shared_document)
self.assertEquals(event_object.application_version, u'14.0')
# TODO: add support for:
# self.assertEquals(event_object.is_shared, False)
expected_msg = (
u'Number of lines: 1 '
u'Number of paragraphs: 1 '
u'Company: KPMG '
u'Shared document: False '
u'Application version: 14.0')
expected_msg_short = (
u'Company: KPMG')
self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short)
if __name__ == '__main__':
unittest.main()
+84
View File
@@ -0,0 +1,84 @@
#!/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.
"""OLECF plugin related functions and classes for testing."""
from dfvfs.lib import definitions
from dfvfs.path import factory as path_spec_factory
from dfvfs.resolver import resolver as path_spec_resolver
import pyolecf
from plaso.engine import single_process
from plaso.parsers import test_lib
class OleCfPluginTestCase(test_lib.ParserTestCase):
"""The unit test case for OLE CF based plugins."""
def _OpenOleCfFile(self, path, codepage='cp1252'):
"""Opens an OLE compound file and returns back a pyolecf.file object.
Args:
path: The path to the OLE CF test file.
codepate: Optional codepage. The default is cp1252.
"""
path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_OS, location=path)
file_entry = path_spec_resolver.Resolver.OpenFileEntry(path_spec)
file_object = file_entry.GetFileObject()
olecf_file = pyolecf.file()
olecf_file.set_ascii_codepage(codepage)
olecf_file.open_file_object(file_object)
return olecf_file
def _ParseOleCfFileWithPlugin(
self, path, plugin_object, knowledge_base_values=None):
"""Parses a file as an OLE compound file and returns an event generator.
Args:
path: The path to the OLE CF test file.
plugin_object: The plugin object that is used to extract an event
generator.
knowledge_base_values: optional dict containing the knowledge base
values. The default is None.
Returns:
An event object queue consumer object (instance of
TestEventObjectQueueConsumer).
"""
event_queue = single_process.SingleProcessQueue()
event_queue_consumer = test_lib.TestEventObjectQueueConsumer(event_queue)
parse_error_queue = single_process.SingleProcessQueue()
parser_context = self._GetParserContext(
event_queue, parse_error_queue,
knowledge_base_values=knowledge_base_values)
olecf_file = self._OpenOleCfFile(path)
# Get a list of all root items from the OLE CF file.
root_item = olecf_file.root_item
item_names = [item.name for item in root_item.sub_items]
plugin_object.Process(
parser_context, root_item=root_item, item_names=item_names)
return event_queue_consumer