434 lines
16 KiB
Python
Raw Permalink Normal View History

2020-04-06 18:48:34 +02:00
#!/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])