diff --git a/ACKNOWLEDGEMENTS b/ACKNOWLEDGEMENTS index b70637dc3b..0f7bed817b 100644 --- a/ACKNOWLEDGEMENTS +++ b/ACKNOWLEDGEMENTS @@ -140,6 +140,7 @@ Copyright SANS Institute - Digital Forensics and Incident Response. * 5afe4de1b92fc382.customDestinations-ms * Catalog1.edb * example.lnk +* MFT * nfury_index.dat * Ntuser.dat (multiple instances) * SysEvent.Evt diff --git a/config/dpkg/changelog b/config/dpkg/changelog index 9d80bc16a3..d80f08dbb7 100644 --- a/config/dpkg/changelog +++ b/config/dpkg/changelog @@ -2,4 +2,4 @@ python-plaso (1.3.1-1) unstable; urgency=low * Auto-generated - -- Log2Timeline Tue, 29 Sep 2015 21:29:06 +0200 + -- Log2Timeline Tue, 29 Sep 2015 22:18:57 +0200 diff --git a/docs/plaso.events.rst b/docs/plaso.events.rst index 25fc487d6f..bed86b8dfb 100644 --- a/docs/plaso.events.rst +++ b/docs/plaso.events.rst @@ -4,6 +4,14 @@ plaso.events package Submodules ---------- +plaso.events.file_system_events module +-------------------------------------- + +.. automodule:: plaso.events.file_system_events + :members: + :undoc-members: + :show-inheritance: + plaso.events.plist_event module ------------------------------- diff --git a/docs/plaso.formatters.rst b/docs/plaso.formatters.rst index c46cb1a3cc..a601de3745 100644 --- a/docs/plaso.formatters.rst +++ b/docs/plaso.formatters.rst @@ -140,10 +140,10 @@ plaso.formatters.file_history module :undoc-members: :show-inheritance: -plaso.formatters.filestat module --------------------------------- +plaso.formatters.file_system module +----------------------------------- -.. automodule:: plaso.formatters.filestat +.. automodule:: plaso.formatters.file_system :members: :undoc-members: :show-inheritance: diff --git a/docs/plaso.parsers.rst b/docs/plaso.parsers.rst index b1df135858..8c3719663d 100644 --- a/docs/plaso.parsers.rst +++ b/docs/plaso.parsers.rst @@ -210,6 +210,14 @@ plaso.parsers.msiecf module :undoc-members: :show-inheritance: +plaso.parsers.ntfs module +------------------------- + +.. automodule:: plaso.parsers.ntfs + :members: + :undoc-members: + :show-inheritance: + plaso.parsers.olecf module -------------------------- diff --git a/plaso/dependencies.py b/plaso/dependencies.py index 75d3534575..f1ed0466ea 100644 --- a/plaso/dependencies.py +++ b/plaso/dependencies.py @@ -16,7 +16,7 @@ u'pyevt': 20120410, u'pyevtx': 20141112, u'pyewf': 20131210, - u'pyfsntfs': 20150829, + u'pyfsntfs': 20150831, u'pyfwsi': 20150606, u'pylnk': 20150830, u'pymsiecf': 20150314, @@ -39,7 +39,7 @@ (u'binplist', u'__version__', u'0.1.4', None), (u'construct', u'__version__', u'2.5.2', None), (u'dateutil', u'__version__', u'1.5', None), - (u'dfvfs', u'__version__', u'20150829', None), + (u'dfvfs', u'__version__', u'20150915', None), (u'dpkt', u'__version__', u'1.8', None), # The protobuf module does not appear to have version information. (u'google.protobuf', u'', u'', None), diff --git a/plaso/events/file_system_events.py b/plaso/events/file_system_events.py new file mode 100644 index 0000000000..db59fe50a5 --- /dev/null +++ b/plaso/events/file_system_events.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +"""This file contains the file system specific event object classes.""" + +from plaso.events import time_events + + +class FileStatEvent(time_events.TimestampEvent): + """File system stat event. + + Attributes: + file_size: the file size. + file_system_type: the file system type. + is_allocated: boolean value to indicate the file is allocated. + offset: the offset of the stat data. + """ + + DATA_TYPE = u'fs:stat' + + def __init__( + self, timestamp, timestamp_description, is_allocated, file_size, + file_system_type): + """Initializes the event object. + + Args: + timestamp: the timestamp time value. The timestamp contains the + number of microseconds since Jan 1, 1970 00:00:00 UTC + timestamp_description: a description string for the timestamp value. + is_allocated: boolean value to indicate the file entry is allocated. + file_size: an integer containing the file size in bytes. + file_system_type: a string containing the file system type. + """ + super(FileStatEvent, self).__init__(timestamp, timestamp_description) + + self.file_size = file_size + self.file_system_type = file_system_type + self.is_allocated = is_allocated + self.offset = 0 + + +class NTFSFileStatEvent(time_events.FiletimeEvent): + """NTFS file system stat event. + + Attributes: + attribute_type: the attribute type e.g. 0x00000030 which represents + $FILE_NAME. + file_attribute_flags: the NTFS file attribute flags, set to None + if not available. + file_reference: NTFS file reference. + file_system_type: the file system type. + is_allocated: boolean value to indicate the MFT entry is allocated + (marked as in use). + name: string containing the name associated with the stat event, e.g. + that of a $FILE_NAME attribute, set to None if not available. + offset: the offset of the stat data. + parent_file_reference: NTFS file reference of the parent, set to None + if not available. + """ + + DATA_TYPE = u'fs:stat:ntfs' + + def __init__( + self, timestamp, timestamp_description, file_reference, attribute_type, + file_attribute_flags=None, is_allocated=True, name=None, + parent_file_reference=None): + """Initializes the event object. + + Args: + timestamp: the FILETIME value for the timestamp. + timestamp_description: the usage string for the timestamp value. + file_reference: NTFS file reference. + attribute_type: the attribute type e.g. 0x00000030 which represents + $FILE_NAME. + file_attribute_flags: optional NTFS file attribute flags, set to None + if not available. + is_allocated: optional boolean value to indicate the MFT entry is + is allocated (marked as in use). + name: optional string containing the name associated with the stat event, + e.g. that of a $FILE_NAME attribute, set to None if not available. + parent_file_reference: optional NTFS file reference of the parent, set + to None if not available. + """ + super(NTFSFileStatEvent, self).__init__(timestamp, timestamp_description) + + self.attribute_type = attribute_type + self.file_reference = file_reference + self.file_attribute_flags = file_attribute_flags + self.file_system_type = u'NTFS' + self.is_allocated = is_allocated + self.name = name + self.offset = 0 + self.parent_file_reference = parent_file_reference diff --git a/plaso/formatters/__init__.py b/plaso/formatters/__init__.py index b2f2780a7d..130d7585aa 100644 --- a/plaso/formatters/__init__.py +++ b/plaso/formatters/__init__.py @@ -16,8 +16,8 @@ from plaso.formatters import chrome_extension_activity from plaso.formatters import chrome_preferences from plaso.formatters import cups_ipp -from plaso.formatters import filestat from plaso.formatters import file_history +from plaso.formatters import file_system from plaso.formatters import firefox from plaso.formatters import firefox_cache from plaso.formatters import firefox_cookies diff --git a/plaso/formatters/file_system.py b/plaso/formatters/file_system.py new file mode 100644 index 0000000000..cad6cc513e --- /dev/null +++ b/plaso/formatters/file_system.py @@ -0,0 +1,139 @@ +# -*- coding: utf-8 -*- +"""The file system stat event formatter.""" + +from plaso.formatters import interface +from plaso.formatters import manager +from plaso.lib import errors + + +class FileStatEventFormatter(interface.ConditionalEventFormatter): + """The file system stat event formatter.""" + + DATA_TYPE = u'fs:stat' + + FORMAT_STRING_PIECES = [ + u'{display_name}', + u'({unallocated})'] + + FORMAT_STRING_SHORT_PIECES = [ + u'{filename}'] + + SOURCE_SHORT = u'FILE' + + def GetMessages(self, unused_formatter_mediator, event_object): + """Determines the formatted message strings for an event object. + + Args: + formatter_mediator: the formatter mediator object (instance of + FormatterMediator). + event_object: the event object (instance of EventObject). + + Returns: + A tuple containing the formatted message string and short message string. + + Raises: + WrongFormatter: if the event object cannot be formatted by the formatter. + """ + if self.DATA_TYPE != event_object.data_type: + raise errors.WrongFormatter(u'Unsupported data type: {0:s}.'.format( + event_object.data_type)) + + event_values = event_object.GetValues() + + # The usage of allocated is deprecated in favor of is_allocated but + # is kept here to be backwards compatible. + if (not event_values.get(u'allocated', False) and + not event_values.get(u'is_allocated', False)): + event_values[u'unallocated'] = u'unallocated' + + return self._ConditionalFormatMessages(event_values) + + def GetSources(self, event_object): + """Determines the the short and long source for an event object. + + Args: + event_object: the event object (instance of EventObject). + + Returns: + A tuple of the short and long source string. + + Raises: + WrongFormatter: if the event object cannot be formatted by the formatter. + """ + if self.DATA_TYPE != event_object.data_type: + raise errors.WrongFormatter(u'Unsupported data type: {0:s}.'.format( + event_object.data_type)) + + file_system_type = getattr(event_object, u'file_system_type', u'UNKNOWN') + timestamp_desc = getattr(event_object, u'timestamp_desc', u'Time') + source_long = u'{0:s} {1:s}'.format(file_system_type, timestamp_desc) + + return self.SOURCE_SHORT, source_long + + +class NTFSFileStatEventFormatter(FileStatEventFormatter): + """The NTFS file system stat event formatter.""" + + DATA_TYPE = u'fs:stat:ntfs' + + FORMAT_STRING_PIECES = [ + u'{display_name}', + u'File reference: {file_reference}', + u'Attribute name: {attribute_name}', + u'Name: {name}', + u'Parent file reference: {parent_file_reference}', + u'({unallocated})'] + + FORMAT_STRING_SHORT_PIECES = [ + u'{filename}', + u'{file_reference}', + u'{attribute_name}'] + + SOURCE_SHORT = u'FILE' + + _ATTRIBUTE_NAMES = { + 0x00000010: u'$STANDARD_INFORMATION', + 0x00000030: u'$FILE_NAME' + } + + def GetMessages(self, unused_formatter_mediator, event_object): + """Determines the formatted message strings for an event object. + + Args: + formatter_mediator: the formatter mediator object (instance of + FormatterMediator). + event_object: the event object (instance of EventObject). + + Returns: + A tuple containing the formatted message string and short message string. + + Raises: + WrongFormatter: if the event object cannot be formatted by the formatter. + """ + if self.DATA_TYPE != event_object.data_type: + raise errors.WrongFormatter(u'Unsupported data type: {0:s}.'.format( + event_object.data_type)) + + event_values = event_object.GetValues() + + attribute_type = event_values.get(u'attribute_type', 0) + event_values[u'attribute_name'] = self._ATTRIBUTE_NAMES.get( + attribute_type, u'UNKNOWN') + + file_reference = event_values.get(u'file_reference', 0) + event_values[u'file_reference'] = u'{0:d}-{1:d}'.format( + file_reference & 0xffffffffffff, file_reference >> 48) + + parent_file_reference = event_values.get(u'parent_file_reference', 0) + if parent_file_reference: + event_values[u'parent_file_reference'] = u'{0:d}-{1:d}'.format( + parent_file_reference & 0xffffffffffff, parent_file_reference >> 48) + + if not event_values.get(u'is_allocated', False): + event_values[u'unallocated'] = u'unallocated' + + return self._ConditionalFormatMessages(event_values) + + +manager.FormattersManager.RegisterFormatters([ + FileStatEventFormatter, NTFSFileStatEventFormatter]) diff --git a/plaso/formatters/filestat.py b/plaso/formatters/filestat.py deleted file mode 100644 index d97b59397e..0000000000 --- a/plaso/formatters/filestat.py +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 -*- -"""The file system stat event formatter.""" - -from plaso.formatters import interface -from plaso.formatters import manager -from plaso.lib import errors - - -class FileStatFormatter(interface.ConditionalEventFormatter): - """The file system stat event formatter.""" - - DATA_TYPE = u'fs:stat' - - FORMAT_STRING_PIECES = [ - u'{display_name}', - u'({unallocated})'] - - FORMAT_STRING_SHORT_PIECES = [ - u'{filename}'] - - SOURCE_SHORT = u'FILE' - - def GetMessages(self, unused_formatter_mediator, event_object): - """Determines the formatted message strings for an event object. - - Args: - formatter_mediator: the formatter mediator object (instance of - FormatterMediator). - event_object: the event object (instance of EventObject). - - Returns: - A tuple containing the formatted message string and short message string. - - Raises: - WrongFormatter: if the event object cannot be formatted by the formatter. - """ - if self.DATA_TYPE != event_object.data_type: - raise errors.WrongFormatter(u'Unsupported data type: {0:s}.'.format( - event_object.data_type)) - - event_values = event_object.GetValues() - if not event_values.get(u'allocated', False): - event_values[u'unallocated'] = u'unallocated' - - return self._ConditionalFormatMessages(event_values) - - def GetSources(self, event_object): - """Determines the the short and long source for an event object. - - Args: - event_object: the event object (instance of EventObject). - - Returns: - A tuple of the short and long source string. - - Raises: - WrongFormatter: if the event object cannot be formatted by the formatter. - """ - if self.DATA_TYPE != event_object.data_type: - raise errors.WrongFormatter(u'Unsupported data type: {0:s}.'.format( - event_object.data_type)) - - fs_type = getattr(event_object, u'fs_type', u'Unknown FS') - timestamp_desc = getattr(event_object, u'timestamp_desc', u'Time') - source_long = u'{0:s} {1:s}'.format(fs_type, timestamp_desc) - - return self.SOURCE_SHORT, source_long - - -manager.FormattersManager.RegisterFormatter(FileStatFormatter) diff --git a/plaso/parsers/__init__.py b/plaso/parsers/__init__.py index 260f62ccee..0b49f194ca 100644 --- a/plaso/parsers/__init__.py +++ b/plaso/parsers/__init__.py @@ -22,6 +22,7 @@ from plaso.parsers import mactime from plaso.parsers import mcafeeav from plaso.parsers import msiecf +from plaso.parsers import ntfs from plaso.parsers import olecf from plaso.parsers import opera from plaso.parsers import oxml diff --git a/plaso/parsers/filestat.py b/plaso/parsers/filestat.py index 422c0d80c6..826c18b480 100644 --- a/plaso/parsers/filestat.py +++ b/plaso/parsers/filestat.py @@ -3,36 +3,12 @@ from dfvfs.lib import definitions as dfvfs_definitions -from plaso.events import time_events +from plaso.events import file_system_events from plaso.lib import timelib from plaso.parsers import interface from plaso.parsers import manager -class FileStatEvent(time_events.TimestampEvent): - """File system stat event.""" - - DATA_TYPE = u'fs:stat' - - def __init__(self, timestamp, usage, allocated, size, fs_type): - """Initializes the event. - - Args: - timestamp: The timestamp time value. The timestamp contains the - number of microseconds since Jan 1, 1970 00:00:00 UTC - usage: The usage string describing the timestamp. - allocated: Boolean value to indicate the file entry is allocated. - size: The file size in bytes. - fs_type: The filesystem this timestamp is extracted from. - """ - super(FileStatEvent, self).__init__(timestamp, usage) - - self.offset = 0 - self.size = size - self.allocated = allocated - self.fs_type = fs_type - - class FileStatParser(interface.BaseParser): """Class that defines a file system stat object parser.""" @@ -100,7 +76,7 @@ def Parse(self, parser_mediator, **kwargs): if not timestamp: continue - event_object = FileStatEvent( + event_object = file_system_events.FileStatEvent( timestamp, time_attribute, is_allocated, file_size, file_system_type) parser_mediator.ProduceEvent(event_object) diff --git a/plaso/parsers/ntfs.py b/plaso/parsers/ntfs.py new file mode 100644 index 0000000000..d2f59143f7 --- /dev/null +++ b/plaso/parsers/ntfs.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +"""Parser for NTFS metadata files.""" + +import uuid + +import pyfsntfs + +from plaso import dependencies +from plaso.events import file_system_events +from plaso.events import windows_events +from plaso.lib import errors +from plaso.lib import eventdata +from plaso.lib import specification +from plaso.parsers import interface +from plaso.parsers import manager + + +dependencies.CheckModuleVersion(u'pyfsntfs') + + +class NTFSMFTParser(interface.SingleFileBaseParser): + """Parses a NTFS $MFT metadata file.""" + + _INITIAL_FILE_OFFSET = None + + NAME = u'mft' + DESCRIPTION = u'Parser for NTFS $MFT metadata files.' + + _MFT_ATTRIBUTE_STANDARD_INFORMATION = 0x00000010 + _MFT_ATTRIBUTE_FILE_NAME = 0x00000030 + _MFT_ATTRIBUTE_OBJECT_ID = 0x00000040 + + @classmethod + def GetFormatSpecification(cls): + """Retrieves the format specification.""" + format_specification = specification.FormatSpecification(cls.NAME) + format_specification.AddNewSignature(b'BAAD', offset=0) + format_specification.AddNewSignature(b'FILE', offset=0) + return format_specification + + def _ParseMFTAttribute(self, parser_mediator, mft_entry, mft_attribute): + """Extract data from a NFTS $MFT attribute. + + Args: + parser_mediator: A parser mediator object (instance of ParserMediator). + mft_entry: The MFT entry (instance of pyfsntfs.file_entry). + mft_attribute: The MFT attribute (instance of pyfsntfs.attribute). + """ + if mft_entry.is_empty() or mft_entry.base_record_file_reference != 0: + return + + if mft_attribute.attribute_type in [ + self._MFT_ATTRIBUTE_STANDARD_INFORMATION, + self._MFT_ATTRIBUTE_FILE_NAME]: + + file_attribute_flags = getattr( + mft_attribute, u'file_attribute_flags', None) + name = getattr(mft_attribute, u'name', None) + parent_file_reference = getattr( + mft_attribute, u'parent_file_reference', None) + + try: + creation_time = mft_attribute.get_creation_time_as_integer() + except OverflowError as exception: + parser_mediator.ProduceParseError(( + u'unable to read the creation timestamp from MFT attribute: ' + u'0x{0:08x} with error: {1:s}').format( + mft_attribute.attribute_type, exception)) + creation_time = None + + if creation_time is not None: + event_object = file_system_events.NTFSFileStatEvent( + creation_time, eventdata.EventTimestamp.CREATION_TIME, + mft_entry.file_reference, mft_attribute.attribute_type, + file_attribute_flags=file_attribute_flags, + is_allocated=mft_entry.is_allocated, name=name, + parent_file_reference=parent_file_reference) + parser_mediator.ProduceEvent(event_object) + + try: + modification_time = mft_attribute.get_modification_time_as_integer() + except OverflowError as exception: + parser_mediator.ProduceParseError(( + u'unable to read the modification timestamp from MFT attribute: ' + u'0x{0:08x} with error: {1:s}').format( + mft_attribute.attribute_type, exception)) + modification_time = None + + if modification_time is not None: + event_object = file_system_events.NTFSFileStatEvent( + modification_time, eventdata.EventTimestamp.MODIFICATION_TIME, + mft_entry.file_reference, mft_attribute.attribute_type, + file_attribute_flags=file_attribute_flags, + is_allocated=mft_entry.is_allocated, name=name, + parent_file_reference=parent_file_reference) + parser_mediator.ProduceEvent(event_object) + + try: + access_time = mft_attribute.get_access_time_as_integer() + except OverflowError as exception: + parser_mediator.ProduceParseError(( + u'unable to read the access timestamp from MFT attribute: ' + u'0x{0:08x} with error: {1:s}').format( + exception, mft_attribute.attribute_type)) + access_time = None + + if access_time is not None: + event_object = file_system_events.NTFSFileStatEvent( + access_time, eventdata.EventTimestamp.ACCESS_TIME, + mft_entry.file_reference, mft_attribute.attribute_type, + file_attribute_flags=file_attribute_flags, + is_allocated=mft_entry.is_allocated, name=name, + parent_file_reference=parent_file_reference) + parser_mediator.ProduceEvent(event_object) + + try: + entry_modification_time = ( + mft_attribute.get_entry_modification_time_as_integer()) + except OverflowError as exception: + parser_mediator.ProduceParseError(( + u'unable to read the entry modification timestamp from MFT ' + u'attribute: 0x{0:08x} with error: {1:s}').format( + mft_attribute.attribute_type, exception)) + entry_modification_time = None + + if entry_modification_time is not None: + event_object = file_system_events.NTFSFileStatEvent( + entry_modification_time, + eventdata.EventTimestamp.ENTRY_MODIFICATION_TIME, + mft_entry.file_reference, mft_attribute.attribute_type, + file_attribute_flags=file_attribute_flags, + is_allocated=mft_entry.is_allocated, name=name, + parent_file_reference=parent_file_reference) + parser_mediator.ProduceEvent(event_object) + + elif mft_attribute.attribute_type == self._MFT_ATTRIBUTE_OBJECT_ID: + display_name = u'$MFT: {0:d}-{1:d}'.format( + mft_entry.file_reference & 0xffffffffffff, + mft_entry.file_reference >> 48) + + if mft_attribute.droid_file_identifier: + try: + uuid_object = uuid.UUID(mft_attribute.droid_file_identifier) + if uuid_object.version == 1: + event_object = ( + windows_events.WindowsDistributedLinkTrackingCreationEvent( + uuid_object, display_name)) + parser_mediator.ProduceEvent(event_object) + + except (TypeError, ValueError) as exception: + parser_mediator.ProduceParseError(( + u'unable to read droid file identifier from attribute: 0x{0:08x} ' + u'with error: {1:s}').format( + mft_attribute.attribute_type, exception)) + + if mft_attribute.birth_droid_file_identifier: + try: + uuid_object = uuid.UUID(mft_attribute.birth_droid_file_identifier) + if uuid_object.version == 1: + event_object = ( + windows_events.WindowsDistributedLinkTrackingCreationEvent( + uuid_object, display_name)) + parser_mediator.ProduceEvent(event_object) + + except (TypeError, ValueError) as exception: + parser_mediator.ProduceParseError(( + u'unable to read birth droid file identifier from attribute: ' + u'0x{0:08x} with error: {1:s}').format( + mft_attribute.attribute_type, exception)) + + def _ParseMFTEntry(self, parser_mediator, mft_entry): + """Extract data from a NFTS $MFT entry. + + Args: + parser_mediator: A parser mediator object (instance of ParserMediator). + mft_entry: The MFT entry (instance of pyfsntfs.file_entry). + """ + for attribute_index in range(0, mft_entry.number_of_attributes): + try: + mft_attribute = mft_entry.get_attribute(attribute_index) + self._ParseMFTAttribute(parser_mediator, mft_entry, mft_attribute) + + except IOError as exception: + parser_mediator.ProduceParseError(( + u'unable to parse MFT attribute: {0:d} with error: {1:s}').format( + attribute_index, exception)) + + def ParseFileObject(self, parser_mediator, file_object, **kwargs): + """Parses a NTFS $MFT metadata file-like object. + + Args: + parser_mediator: A parser mediator object (instance of ParserMediator). + file_object: A file-like object. + + Raises: + UnableToParseFile: when the file cannot be parsed. + """ + mft_metadata_file = pyfsntfs.mft_metadata_file() + + try: + mft_metadata_file.open_file_object(file_object) + except IOError as exception: + display_name = parser_mediator.GetDisplayName() + raise errors.UnableToParseFile( + u'[{0:s}] unable to parse file {1:s} with error: {2:s}'.format( + self.NAME, display_name, exception)) + + for entry_index in range(0, mft_metadata_file.number_of_file_entries): + try: + mft_entry = mft_metadata_file.get_file_entry(entry_index) + self._ParseMFTEntry(parser_mediator, mft_entry) + + except IOError as exception: + parser_mediator.ProduceParseError(( + u'unable to parse MFT entry: {0:d} with error: {1:s}').format( + entry_index, exception)) + + mft_metadata_file.close() + + +manager.ParsersManager.RegisterParser(NTFSMFTParser) diff --git a/test_data/MFT b/test_data/MFT new file mode 100644 index 0000000000..70e2ad9efb Binary files /dev/null and b/test_data/MFT differ diff --git a/tests/engine/collector.py b/tests/engine/collector.py index 17a2395d8a..1b276b791f 100644 --- a/tests/engine/collector.py +++ b/tests/engine/collector.py @@ -278,6 +278,7 @@ def _TestImageWithPartitionsCollections(self, collect_directory_metadata): collect_directory_metadata: boolean value to indicate to collect directory metadata. """ + # Note that the source file is a RAW (VMDK flat) image. test_file = self._GetTestFilePath([u'multi_partition_image.vmdk']) image_path_spec = path_spec_factory.Factory.NewPathSpec( diff --git a/tests/formatters/file_system.py b/tests/formatters/file_system.py new file mode 100644 index 0000000000..6dc7083c61 --- /dev/null +++ b/tests/formatters/file_system.py @@ -0,0 +1,57 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Tests for the file system stat event formatter.""" + +import unittest + +from plaso.formatters import file_system + +from tests.formatters import test_lib + + +class FileStatEventFormatterTest(test_lib.EventFormatterTestCase): + """Tests for the file system stat event formatter.""" + + def testInitialization(self): + """Tests the initialization.""" + event_formatter = file_system.FileStatEventFormatter() + self.assertNotEqual(event_formatter, None) + + def testGetFormatStringAttributeNames(self): + """Tests the GetFormatStringAttributeNames function.""" + event_formatter = file_system.FileStatEventFormatter() + + expected_attribute_names = [u'display_name', u'unallocated'] + + self._TestGetFormatStringAttributeNames( + event_formatter, expected_attribute_names) + + # TODO: add test for GetMessages. + # TODO: add test for GetSources. + + +class NTFSFileStatEventFormatterTest(test_lib.EventFormatterTestCase): + """Tests for the NFTS file system stat event formatter.""" + + def testInitialization(self): + """Tests the initialization.""" + event_formatter = file_system.NTFSFileStatEventFormatter() + self.assertNotEqual(event_formatter, None) + + def testGetFormatStringAttributeNames(self): + """Tests the GetFormatStringAttributeNames function.""" + event_formatter = file_system.NTFSFileStatEventFormatter() + + expected_attribute_names = [ + u'attribute_name', u'display_name', u'file_reference', u'name', + u'parent_file_reference', u'unallocated'] + + self._TestGetFormatStringAttributeNames( + event_formatter, expected_attribute_names) + + # TODO: add test for GetMessages. + # TODO: add test for GetSources. + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/formatters/filestat.py b/tests/formatters/filestat.py deleted file mode 100644 index ed74616c1b..0000000000 --- a/tests/formatters/filestat.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/python -# -*- coding: utf-8 -*- -"""Tests for the file system stat event formatter.""" - -import unittest - -from plaso.formatters import filestat - -from tests.formatters import test_lib - - -class FileStatFormatterTest(test_lib.EventFormatterTestCase): - """Tests for the file system stat event formatter.""" - - def testInitialization(self): - """Tests the initialization.""" - event_formatter = filestat.FileStatFormatter() - self.assertNotEqual(event_formatter, None) - - def testGetFormatStringAttributeNames(self): - """Tests the GetFormatStringAttributeNames function.""" - event_formatter = filestat.FileStatFormatter() - - expected_attribute_names = [u'display_name', u'unallocated'] - - self._TestGetFormatStringAttributeNames( - event_formatter, expected_attribute_names) - - # TODO: add test for GetMessages. - # TODO: add test for GetSources. - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/parsers/filestat.py b/tests/parsers/filestat.py index 39524b8bdb..c208ae0d1e 100644 --- a/tests/parsers/filestat.py +++ b/tests/parsers/filestat.py @@ -9,7 +9,7 @@ from dfvfs.path import factory as path_spec_factory # pylint: disable=unused-import -from plaso.formatters import filestat as filestat_formatter +from plaso.formatters import file_system as file_system_formatter from plaso.parsers import filestat from tests.parsers import test_lib diff --git a/tests/parsers/ntfs.py b/tests/parsers/ntfs.py new file mode 100644 index 0000000000..09083ed76f --- /dev/null +++ b/tests/parsers/ntfs.py @@ -0,0 +1,165 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +"""Tests for the NTFS metadata file parser.""" + +import unittest + +from dfvfs.lib import definitions as dfvfs_definitions +from dfvfs.path import factory as path_spec_factory + +# pylint: disable=unused-import +from plaso.formatters import file_system as file_system_formatter +from plaso.lib import eventdata +from plaso.lib import timelib +from plaso.parsers import ntfs + +from tests.parsers import test_lib + + +class NTFSMFTParserTest(test_lib.ParserTestCase): + """Tests for NTFS $MFT metadata file parser.""" + + def setUp(self): + """Sets up the needed objects used throughout the test.""" + self._parser = ntfs.NTFSMFTParser() + + def testParseFile(self): + """Tests the Parse function on a stand-alone $MFT file.""" + test_path = self._GetTestFilePath([u'MFT']) + os_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_OS, location=test_path) + + event_queue_consumer = self._ParseFileByPathSpec( + self._parser, os_path_spec) + event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) + + self.assertEqual(len(event_objects), 126352) + + # A distributed link tracking event. + event_object = event_objects[3684] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2007-06-30 12:58:40.500004') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + expected_message = ( + u'9fe44b69-2709-11dc-a06b-db3099beae3c ' + u'MAC address: db:30:99:be:ae:3c ' + u'Origin: $MFT: 462-1') + + expected_short_message = ( + u'9fe44b69-2709-11dc-a06b-db3099beae3c ' + u'Origin: $MFT: 462-1') + + self._TestGetMessageStrings( + event_object, expected_message, expected_short_message) + + def testParseImage(self): + """Tests the Parse function on a storage media image.""" + test_path = self._GetTestFilePath([u'vsstest.qcow2']) + os_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_OS, location=test_path) + qcow_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_QCOW, parent=os_path_spec) + tsk_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_TSK, inode=0, location=u'/$MFT', + parent=qcow_path_spec) + + event_queue_consumer = self._ParseFileByPathSpec( + self._parser, tsk_path_spec) + event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) + + self.assertEqual(len(event_objects), 284) + + # The creation timestamp. + event_object = event_objects[0] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2013-12-03 06:30:41.807907') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + # The last modification timestamp. + event_object = event_objects[1] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2013-12-03 06:30:41.807907') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.MODIFICATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + # The last accessed timestamp. + event_object = event_objects[2] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2013-12-03 06:30:41.807907') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.ACCESS_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + # The entry modification timestamp. + event_object = event_objects[3] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2013-12-03 06:30:41.807907') + self.assertEqual( + event_object.timestamp_desc, + eventdata.EventTimestamp.ENTRY_MODIFICATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + expected_message = ( + u'TSK:/$MFT ' + u'File reference: 0-1 ' + u'Attribute name: $STANDARD_INFORMATION') + + expected_short_message = ( + u'/$MFT 0-1 $STANDARD_INFORMATION') + + self._TestGetMessageStrings( + event_object, expected_message, expected_short_message) + + # The creation timestamp. + event_object = event_objects[4] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2013-12-03 06:30:41.807907') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + expected_message = ( + u'TSK:/$MFT ' + u'File reference: 0-1 ' + u'Attribute name: $FILE_NAME ' + u'Name: $MFT ' + u'Parent file reference: 5-5') + + expected_short_message = ( + u'/$MFT 0-1 $FILE_NAME') + + self._TestGetMessageStrings( + event_object, expected_message, expected_short_message) + + # Note that the source file is a RAW (VMDK flat) image. + test_path = self._GetTestFilePath([u'multi_partition_image.vmdk']) + os_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_OS, location=test_path) + p2_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_TSK_PARTITION, location=u'/p2', + part_index=3, start_offset=0x00510000, parent=os_path_spec) + tsk_path_spec = path_spec_factory.Factory.NewPathSpec( + dfvfs_definitions.TYPE_INDICATOR_TSK, inode=0, location=u'/$MFT', + parent=p2_path_spec) + + event_queue_consumer = self._ParseFileByPathSpec( + self._parser, tsk_path_spec) + event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) + + self.assertEqual(len(event_objects), 184) + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/log2timeline_test.py b/tools/log2timeline_test.py index 3cf6c64ce7..ea3a91d80f 100644 --- a/tools/log2timeline_test.py +++ b/tools/log2timeline_test.py @@ -91,6 +91,7 @@ def testProcessSourcesImage(self): def testProcessSourcesPartitionedImage(self): """Tests the ProcessSources function on a multi partition image.""" + # Note that the source file is a RAW (VMDK flat) image. test_source = self._GetTestFilePath([u'multi_partition_image.vmdk']) with shared_test_lib.TempDirectory() as temp_directory: diff --git a/tools/psort_test.py b/tools/psort_test.py index 8ed1643d1c..bb2097e107 100644 --- a/tools/psort_test.py +++ b/tools/psort_test.py @@ -137,7 +137,7 @@ def testProcessStorageWithMissingParameters(self): self.assertEqual(TestOutputModuleMissingParameters.missing, u'foobar') self.assertEqual(TestOutputModuleMissingParameters.parameters, u'foobar') - self.assertIn(u'FILE/Unknown FS ctime OS:syslog', lines) + self.assertIn(u'FILE/UNKNOWN ctime OS:syslog', lines) output_manager.OutputManager.DeregisterOutput( TestOutputModuleMissingParameters) helpers_manager.ArgumentHelperManager.DeregisterHelper(