From bea92a3150ff1bb1da89f9302634b3b2642fc62b Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Thu, 3 Sep 2015 08:27:39 +0200 Subject: [PATCH] Code review: 259660043: Added LNK distributed link tracking support #316 --- config/dpkg/changelog | 2 +- plaso/__init__.py | 2 +- plaso/dependencies.py | 2 +- plaso/events/time_events.py | 85 +++++++++++++------ plaso/events/windows_events.py | 40 ++++++--- plaso/formatters/windows.py | 23 ++++- plaso/lib/timelib.py | 27 +++++- plaso/parsers/winlnk.py | 56 +++++++++++- tests/lib/timelib.py | 54 ++++++++---- tests/parsers/custom_destinations.py | 28 ++++-- .../olecf_plugins/automatic_destinations.py | 4 +- tests/parsers/winlnk.py | 17 +++- 12 files changed, 272 insertions(+), 68 deletions(-) diff --git a/config/dpkg/changelog b/config/dpkg/changelog index d915cb4b8a..d996e0c570 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, 01 Sep 2015 23:10:22 +0200 + -- Log2Timeline Thu, 03 Sep 2015 08:27:39 +0200 diff --git a/plaso/__init__.py b/plaso/__init__.py index e0bde4b9d8..a1e8cf9f8e 100644 --- a/plaso/__init__.py +++ b/plaso/__init__.py @@ -3,7 +3,7 @@ __version__ = '1.3.1' VERSION_DEV = True -VERSION_DATE = '20150901' +VERSION_DATE = '20150903' def GetVersion(): diff --git a/plaso/dependencies.py b/plaso/dependencies.py index 15b1366091..3e94474fb7 100644 --- a/plaso/dependencies.py +++ b/plaso/dependencies.py @@ -17,7 +17,7 @@ u'pyevtx': 20141112, u'pyewf': 20131210, u'pyfwsi': 20150606, - u'pylnk': 20141026, + u'pylnk': 20150830, u'pymsiecf': 20150314, u'pyolecf': 20150413, u'pyqcow': 20131204, diff --git a/plaso/events/time_events.py b/plaso/events/time_events.py index f37bebe803..95e01b4b26 100644 --- a/plaso/events/time_events.py +++ b/plaso/events/time_events.py @@ -6,14 +6,21 @@ class TimestampEvent(event.EventObject): - """Convenience class for a timestamp-based event.""" + """Convenience class for a timestamp-based event. + + Attributes: + data_type: the event data type. + timestamp: the timestamp, contains the number of microseconds from + January 1, 1970 00:00:00 UTC. + timestamp_desc: the description of the usage of the timestamp. + """ def __init__(self, timestamp, usage, data_type=None): """Initializes an event object. Args: - timestamp: The timestamp value. - usage: The description of the usage of the time value. + timestamp: the timestamp value. + usage: the description of the usage of the time value. data_type: Optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ @@ -32,9 +39,9 @@ def __init__(self, cocoa_time, usage, data_type=None): """Initializes an event object. Args: - cocoa_time: The Cocoa time value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + cocoa_time: the Cocoa time value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(CocoaTimeEvent, self).__init__( @@ -49,9 +56,9 @@ def __init__(self, fat_date_time, usage, data_type=None): """Initializes an event object. Args: - fat_date_time: The FAT date time value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + fat_date_time: the FAT date time value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(FatDateTimeEvent, self).__init__( @@ -66,9 +73,9 @@ def __init__(self, filetime, usage, data_type=None): """Initializes an event object. Args: - filetime: The FILETIME timestamp value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + filetime: the FILETIME timestamp value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(FiletimeEvent, self).__init__( @@ -82,9 +89,9 @@ def __init__(self, java_time, usage, data_type=None): """Initializes an event object. Args: - java_time: The Java time value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + java_time: the Java time value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(JavaTimeEvent, self).__init__( @@ -98,9 +105,9 @@ def __init__(self, posix_time, usage, data_type=None): """Initializes an event object. Args: - posix_time: The POSIX time value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + posix_time: the POSIX time value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(PosixTimeEvent, self).__init__( @@ -114,9 +121,9 @@ def __init__(self, datetime_time, usage, data_type=None): """Initializes an event object. Args: - datetime_time: The datetime object (instance of datetime.datetime). - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + datetime_time: the datetime object (instance of datetime.datetime). + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(PythonDatetimeEvent, self).__init__( @@ -124,6 +131,36 @@ def __init__(self, datetime_time, usage, data_type=None): data_type=data_type) +class UUIDTimeEvent(TimestampEvent): + """Convenience class for an UUID version time-based event. + + Attributes: + mac_address: the MAC address stored in the UUID. + """ + + def __init__(self, uuid, usage): + """Initializes an event object. + + Args: + uuid: a uuid object (instance of uuid.UUID). + usage: the description of the usage of the time value. + + Raises: + ValueError: if the UUID version is not supported. + """ + if not uuid.version == 1: + raise ValueError(u'Unsupported UUID version.') + + timestamp = timelib.Timestamp.FromUUIDTime(uuid.time) + mac_address = u'{0:s}:{1:s}:{2:s}:{3:s}:{4:s}:{5:s}'.format( + uuid.hex[20:22], uuid.hex[22:24], uuid.hex[24:26], uuid.hex[26:28], + uuid.hex[28:30], uuid.hex[30:32]) + super(UUIDTimeEvent, self).__init__(timestamp, usage) + + self.mac_address = mac_address + self.uuid = u'{0!s}'.format(uuid) + + class WebKitTimeEvent(TimestampEvent): """Convenience class for a WebKit time-based event.""" @@ -131,9 +168,9 @@ def __init__(self, webkit_time, usage, data_type=None): """Initializes an event object. Args: - webkit_time: The WebKit time value. - usage: The description of the usage of the time value. - data_type: Optional event data type. If not set data_type is + webkit_time: the WebKit time value. + usage: the description of the usage of the time value. + data_type: optional event data type. If not set data_type is derived from the DATA_TYPE attribute. """ super(WebKitTimeEvent, self).__init__( diff --git a/plaso/events/windows_events.py b/plaso/events/windows_events.py index 9dd8050357..faaf0852c2 100644 --- a/plaso/events/windows_events.py +++ b/plaso/events/windows_events.py @@ -5,25 +5,21 @@ from plaso.lib import eventdata -class WindowsVolumeCreationEvent(time_events.FiletimeEvent): - """Convenience class for a Windows volume creation event.""" +class WindowsDistributedLinkTrackingCreationEvent(time_events.UUIDTimeEvent): + """Convenience class for a Windows distributed link creation event.""" - DATA_TYPE = 'windows:volume:creation' + DATA_TYPE = 'windows:distributed_link_tracking:creation' - def __init__(self, filetime, device_path, serial_number, origin): + def __init__(self, uuid, origin): """Initializes an event object. Args: - filetime: The FILETIME timestamp value. - device_path: A string containing the volume device path. - serial_number: A string containing the volume serial number. + uuid: A uuid object (instance of uuid.UUID). origin: A string containing the origin of the event (event source). """ - super(WindowsVolumeCreationEvent, self).__init__( - filetime, eventdata.EventTimestamp.CREATION_TIME) + super(WindowsDistributedLinkTrackingCreationEvent, self).__init__( + uuid, eventdata.EventTimestamp.CREATION_TIME) - self.device_path = device_path - self.serial_number = serial_number self.origin = origin @@ -78,3 +74,25 @@ def __init__( class WindowsRegistryServiceEvent(WindowsRegistryEvent): """Convenience class for service entries retrieved from the registry.""" DATA_TYPE = 'windows:registry:service' + + +class WindowsVolumeCreationEvent(time_events.FiletimeEvent): + """Convenience class for a Windows volume creation event.""" + + DATA_TYPE = 'windows:volume:creation' + + def __init__(self, filetime, device_path, serial_number, origin): + """Initializes an event object. + + Args: + filetime: The FILETIME timestamp value. + device_path: A string containing the volume device path. + serial_number: A string containing the volume serial number. + origin: A string containing the origin of the event (event source). + """ + super(WindowsVolumeCreationEvent, self).__init__( + filetime, eventdata.EventTimestamp.CREATION_TIME) + + self.device_path = device_path + self.serial_number = serial_number + self.origin = origin diff --git a/plaso/formatters/windows.py b/plaso/formatters/windows.py index 82dc2beb7b..ff60fa6b58 100644 --- a/plaso/formatters/windows.py +++ b/plaso/formatters/windows.py @@ -5,6 +5,25 @@ from plaso.formatters import manager +class WindowsDistributedLinkTrackingCreationEventFormatter( + interface.ConditionalEventFormatter): + """Formatter for a Windows distributed link creation event.""" + + DATA_TYPE = u'windows:distributed_link_tracking:creation' + + FORMAT_STRING_PIECES = [ + u'{uuid}', + u'MAC address: {mac_address}', + u'Origin: {origin}'] + + FORMAT_STRING_SHORT_PIECES = [ + u'{uuid}', + u'Origin: {origin}'] + + SOURCE_LONG = u'System' + SOURCE_SHORT = u'LOG' + + class WindowsVolumeCreationEventFormatter(interface.ConditionalEventFormatter): """Formatter for a Windows volume creation event.""" @@ -23,4 +42,6 @@ class WindowsVolumeCreationEventFormatter(interface.ConditionalEventFormatter): SOURCE_SHORT = u'LOG' -manager.FormattersManager.RegisterFormatter(WindowsVolumeCreationEventFormatter) +manager.FormattersManager.RegisterFormatters([ + WindowsDistributedLinkTrackingCreationEventFormatter, + WindowsVolumeCreationEventFormatter]) diff --git a/plaso/lib/timelib.py b/plaso/lib/timelib.py index 93f5759ad6..2a08d8b537 100644 --- a/plaso/lib/timelib.py +++ b/plaso/lib/timelib.py @@ -77,9 +77,12 @@ class Timestamp(object): # The difference between Jan 1, 1601 and Jan 1, 1970 in micro seconds WEBKIT_TIME_TO_POSIX_BASE = 11644473600L * 1000000 - # The difference between Jan 1, 1601 and Jan 1, 1970 in 100s of nanoseconds. + # The difference between Jan 1, 1601 and Jan 1, 1970 in 100 nanoseconds. FILETIME_TO_POSIX_BASE = 11644473600L * 10000000 + # The difference between Nov 10, 1582 and Jan 1, 1970 in 100 nanoseconds. + UUID_TIME_TO_POSIX_BASE = 12219292800L * 10000000 + # The number of seconds between January 1, 1904 and Jan 1, 1970. # Value confirmed with sleuthkit: # http://svn.sleuthkit.org/repos/sleuthkit/trunk/tsk3/fs/tsk_hfs.h @@ -669,6 +672,28 @@ def FromTimeString( return cls.FromPythonDatetime(datetime_object) + @classmethod + def FromUUIDTime(cls, uuid_time): + """Converts a UUID verion 1 time into a timestamp. + + The UUID version 1 time is a 60-bit value containing: + 100th nano seconds since 1582-10-15 00:00:00 + + Args: + uuid_time: The 60-bit UUID version 1 timestamp. + + Returns: + An integer containing the timestamp or 0 on error. + """ + # TODO: Add a handling for if the timestamp equals to zero. + if uuid_time < 0: + return 0 + timestamp = (uuid_time - cls.UUID_TIME_TO_POSIX_BASE) / 10 + + if timestamp > cls.TIMESTAMP_MAX_MICRO_SECONDS: + return 0 + return timestamp + @classmethod def FromWebKitTime(cls, webkit_time): """Converts a WebKit time into a timestamp. diff --git a/plaso/parsers/winlnk.py b/plaso/parsers/winlnk.py index 0b2397e3e1..05e131c6e1 100644 --- a/plaso/parsers/winlnk.py +++ b/plaso/parsers/winlnk.py @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- """Parser for Windows Shortcut (LNK) files.""" +import uuid + import pylnk from plaso import dependencies from plaso.events import time_events +from plaso.events import windows_events from plaso.lib import errors from plaso.lib import eventdata from plaso.lib import specification @@ -17,7 +20,33 @@ class WinLnkLinkEvent(time_events.FiletimeEvent): - """Convenience class for a Windows Shortcut (LNK) link event.""" + """Convenience class for a Windows Shortcut (LNK) link event. + + Attributes: + birth_droid_file_identifier: the distributed link tracking brith droid + file identifier. + birth_droid_volume_identifier: the distributed link tracking brith droid + volume identifier. + command_line_arguments: the command line arguments. + description: the description of the linked item. + drive_serial_number: the drive serial number where the linked item resides. + drive_type: the drive type where the linked item resided. + droid_file_identifier: the distributed link tracking droid file + identifier. + droid_volume_identifier: the distributed link tracking droid volume + identifier. + env_var_location: the evironment variables loction. + file_attribute_flags: the file attribute flags of the linked item. + file_size: the size of the linked item. + icon_location: the icon location.. + link_target: shell item list of the link target. + local_path: the local path of the linked item. + network_path: the local path of the linked item. + offset: the data offset of the event. + relative_path: the relative path. + volume_label: the volume label where the linked item resided. + working_directory: the working directory. + """ DATA_TYPE = u'windows:lnk:link' @@ -51,6 +80,11 @@ def __init__(self, timestamp, timestamp_description, lnk_file, link_target): if link_target: self.link_target = link_target + self.droid_volume_identifier = lnk_file.droid_volume_identifier + self.droid_file_identifier = lnk_file.droid_file_identifier + self.birth_droid_volume_identifier = lnk_file.birth_droid_volume_identifier + self.birth_droid_file_identifier = lnk_file.birth_droid_file_identifier + class WinLnkParser(interface.SingleFileBaseParser): """Parses Windows Shortcut (LNK) files.""" @@ -119,7 +153,25 @@ def ParseFileObject( eventdata.EventTimestamp.MODIFICATION_TIME, lnk_file, link_target)]) - # TODO: add support for the distributed link tracker. + try: + uuid_object = uuid.UUID(lnk_file.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): + pass + + try: + uuid_object = uuid.UUID(lnk_file.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): + pass lnk_file.close() diff --git a/tests/lib/timelib.py b/tests/lib/timelib.py index b3f22b298e..0cc6a3621a 100644 --- a/tests/lib/timelib.py +++ b/tests/lib/timelib.py @@ -4,6 +4,7 @@ import datetime import unittest +import uuid from plaso.lib import errors from plaso.lib import timelib @@ -232,23 +233,6 @@ def testTimestampFromFatDateTime(self): fat_date_time = (0xa8d03d0c & ~(0x0f << 5)) | ((13 & 0x0f) << 5) self.assertEqual(timelib.Timestamp.FromFatDateTime(fat_date_time), 0) - def testTimestampFromWebKitTime(self): - """Test the WebKit time conversion.""" - timestamp = timelib.Timestamp.FromWebKitTime(0x2dec3d061a9bfb) - expected_timestamp = timelib.Timestamp.CopyFromString( - u'2010-08-12 21:06:31.546875') - self.assertEqual(timestamp, expected_timestamp) - - webkit_time = 86400 * 1000000 - timestamp = timelib.Timestamp.FromWebKitTime(webkit_time) - expected_timestamp = timelib.Timestamp.CopyFromString( - u'1601-01-02 00:00:00') - self.assertEqual(timestamp, expected_timestamp) - - # WebKit time that exceeds lower bound. - webkit_time = -((1 << 63L) - 1) - self.assertEqual(timelib.Timestamp.FromWebKitTime(webkit_time), 0) - def testTimestampFromFiletime(self): """Test the FILETIME conversion.""" timestamp = timelib.Timestamp.FromFiletime(0x01cb3a623d0a17ce) @@ -284,6 +268,42 @@ def testTimestampFromPosixTime(self): # POSIX time that exceeds lower bound. self.assertEqual(timelib.Timestamp.FromPosixTime(-9223372036855), 0) + def testTimestampFromUUIDTime(self): + """Test the UUID time conversion.""" + uuid_object = uuid.UUID(u'00911b54-9ef4-11e1-be53-525400123456') + + timestamp = timelib.Timestamp.FromUUIDTime(uuid_object.time) + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2012-05-16 01:11:01.654408') + self.assertEqual(timestamp, expected_timestamp) + + uuid_time = 86400 * 10000000 + timestamp = timelib.Timestamp.FromUUIDTime(uuid_time) + expected_timestamp = timelib.Timestamp.CopyFromString( + u'1582-10-16 00:00:00') + self.assertEqual(timestamp, expected_timestamp) + + # UUID time that exceeds lower bound. + uuid_time = -1 + self.assertEqual(timelib.Timestamp.FromUUIDTime(uuid_time), 0) + + def testTimestampFromWebKitTime(self): + """Test the WebKit time conversion.""" + timestamp = timelib.Timestamp.FromWebKitTime(0x2dec3d061a9bfb) + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2010-08-12 21:06:31.546875') + self.assertEqual(timestamp, expected_timestamp) + + webkit_time = 86400 * 1000000 + timestamp = timelib.Timestamp.FromWebKitTime(webkit_time) + expected_timestamp = timelib.Timestamp.CopyFromString( + u'1601-01-02 00:00:00') + self.assertEqual(timestamp, expected_timestamp) + + # WebKit time that exceeds lower bound. + webkit_time = -((1 << 63L) - 1) + self.assertEqual(timelib.Timestamp.FromWebKitTime(webkit_time), 0) + def testMonthDict(self): """Test the month dict, both inside and outside of scope.""" self.assertEqual(timelib.MONTH_DICT[u'nov'], 11) diff --git a/tests/parsers/custom_destinations.py b/tests/parsers/custom_destinations.py index a616530d5a..d2a40888bf 100644 --- a/tests/parsers/custom_destinations.py +++ b/tests/parsers/custom_destinations.py @@ -26,11 +26,11 @@ def testParse(self): event_queue_consumer = self._ParseFile(self._parser, test_file) event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) - self.assertEqual(len(event_objects), 108) + self.assertEqual(len(event_objects), 126) # A shortcut event object. # The last accessed timestamp. - event_object = event_objects[105] + event_object = event_objects[121] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-13 23:55:56.248103') @@ -39,7 +39,7 @@ def testParse(self): self.assertEqual(event_object.timestamp, expected_timestamp) # The creation timestamp. - event_object = event_objects[106] + event_object = event_objects[122] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-13 23:55:56.248103') @@ -48,7 +48,7 @@ def testParse(self): self.assertEqual(event_object.timestamp, expected_timestamp) # The last modification timestamp. - event_object = event_objects[107] + event_object = event_objects[123] expected_timestamp = timelib.Timestamp.CopyFromString( u'2009-07-14 01:39:11.388000') @@ -76,7 +76,7 @@ def testParse(self): self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) # A shell item event object. - event_object = event_objects[16] + event_object = event_objects[18] expected_timestamp = timelib.Timestamp.CopyFromString( u'2010-11-10 07:41:04') @@ -96,6 +96,24 @@ def testParse(self): self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) + # A distributed link tracking event object. + event_object = event_objects[12] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2010-11-10 19:08:32.656259') + self.assertEqual(event_object.timestamp, expected_timestamp) + + expected_msg = ( + u'e9215b24-ecfd-11df-a81c-000c29031e1e ' + u'MAC address: 00:0c:29:03:1e:1e ' + u'Origin: 5afe4de1b92fc382.customDestinations-ms') + + expected_msg_short = ( + u'e9215b24-ecfd-11df-a81c-000c29031e1e ' + u'Origin: 5afe4de1b92fc382.customDestinati...') + + self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) + if __name__ == '__main__': unittest.main() diff --git a/tests/parsers/olecf_plugins/automatic_destinations.py b/tests/parsers/olecf_plugins/automatic_destinations.py index cf91d13d39..f32adc6602 100644 --- a/tests/parsers/olecf_plugins/automatic_destinations.py +++ b/tests/parsers/olecf_plugins/automatic_destinations.py @@ -27,10 +27,10 @@ def testProcess(self): test_file, self._plugin) event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) - self.assertEqual(len(event_objects), 44) + self.assertEqual(len(event_objects), 66) # Check a AutomaticDestinationsDestListEntryEvent. - event_object = event_objects[3] + event_object = event_objects[5] self.assertEqual(event_object.offset, 32) diff --git a/tests/parsers/winlnk.py b/tests/parsers/winlnk.py index bf14e37374..a3290da00b 100644 --- a/tests/parsers/winlnk.py +++ b/tests/parsers/winlnk.py @@ -35,7 +35,7 @@ def testParse(self): # Icon location : %windir%\system32\migwiz\migwiz.exe # Environment variables location : %windir%\system32\migwiz\migwiz.exe - self.assertEqual(len(event_objects), 3) + self.assertEqual(len(event_objects), 5) # A shortcut event object. event_object = event_objects[0] @@ -92,13 +92,26 @@ def testParse(self): self._TestGetMessageStrings(event_object, expected_msg, expected_msg_short) + # A distributed link tracking event object. + event_object = event_objects[4] + + expected_timestamp = timelib.Timestamp.CopyFromString( + u'2009-07-14 05:45:20.500012') + self.assertEqual( + event_object.timestamp_desc, eventdata.EventTimestamp.CREATION_TIME) + self.assertEqual(event_object.timestamp, expected_timestamp) + + expected_uuid = u'846ee3bb-7039-11de-9d20-001d09fa5a1c' + self.assertEqual(event_object.uuid, expected_uuid) + self.assertEqual(event_object.mac_address, u'00:1d:09:fa:5a:1c') + def testParseLinkTargetIdentifier(self): """Tests the Parse function on an LNK with a link target identifier.""" test_file = self._GetTestFilePath([u'NeroInfoTool.lnk']) event_queue_consumer = self._ParseFile(self._parser, test_file) event_objects = self._GetEventObjectsFromQueue(event_queue_consumer) - self.assertEqual(len(event_objects), 18) + self.assertEqual(len(event_objects), 20) # A shortcut event object. event_object = event_objects[16]