From ea82986659ee9249a2a0b0113fd998689e36ba1e Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Sun, 16 Jul 2023 14:31:45 +0200 Subject: [PATCH] Changes to handle unpaired surrogates in LNK --- .github/workflows/test_docker.yml | 2 +- config/dpkg/control | 2 +- config/linux/ubuntu_install_plaso.sh | 1 + dependencies.ini | 4 +- plaso/dependencies.py | 4 +- plaso/lib/definitions.py | 86 ++++++++++++++++++--------- plaso/parsers/shared/shell_items.py | 36 ++++++++--- plaso/parsers/winlnk.py | 61 ++++++++++++++----- requirements.txt | 4 +- setup.cfg | 4 +- test_data/unpaired_surrogate.lnk | Bin 0 -> 1103 bytes tests/parsers/winlnk.py | 74 +++++++++++++++++++---- 12 files changed, 204 insertions(+), 74 deletions(-) create mode 100755 test_data/unpaired_surrogate.lnk diff --git a/.github/workflows/test_docker.yml b/.github/workflows/test_docker.yml index d359269a4b..3be74707c1 100644 --- a/.github/workflows/test_docker.yml +++ b/.github/workflows/test_docker.yml @@ -58,7 +58,7 @@ jobs: run: | add-apt-repository -y ppa:gift/dev apt-get update -q - apt-get install -y build-essential python3 python3-dev libbde-python3 libcaes-python3 libcreg-python3 libesedb-python3 libevt-python3 libevtx-python3 libewf-python3 libfsapfs-python3 libfsext-python3 libfsfat-python3 libfshfs-python3 libfsntfs-python3 libfsxfs-python3 libfvde-python3 libfwnt-python3 libfwsi-python3 liblnk-python3 libluksde-python3 libmodi-python3 libmsiecf-python3 libolecf-python3 libphdi-python3 libqcow-python3 libregf-python3 libscca-python3 libsigscan-python3 libsmdev-python3 libsmraw-python3 libvhdi-python3 libvmdk-python3 libvsgpt-python3 libvshadow-python3 libvslvm-python3 python3-acstore python3-artifacts python3-bencode python3-certifi python3-cffi-backend python3-chardet python3-cryptography python3-dateutil python3-defusedxml python3-dfdatetime python3-dfvfs python3-dfwinreg python3-distutils python3-dtfabric python3-fakeredis python3-flor python3-future python3-idna python3-lz4 python3-mock python3-opensearch python3-pefile python3-psutil python3-pyparsing python3-pytsk3 python3-pyxattr python3-redis python3-requests python3-setuptools python3-six python3-tz python3-urllib3 python3-xlsxwriter python3-yaml python3-yara python3-zmq + apt-get install -y build-essential python3 python3-dev libbde-python3 libcaes-python3 libcreg-python3 libesedb-python3 libevt-python3 libevtx-python3 libewf-python3 libfsapfs-python3 libfsext-python3 libfsfat-python3 libfshfs-python3 libfsntfs-python3 libfsxfs-python3 libfvde-python3 libfwnt-python3 libfwsi-python3 liblnk-python3 libluksde-python3 libmodi-python3 libmsiecf-python3 libolecf-python3 libphdi-python3 libqcow-python3 libregf-python3 libscca-python3 libsigscan-python3 libsmdev-python3 libsmraw-python3 libssl-dev libvhdi-python3 libvmdk-python3 libvsgpt-python3 libvshadow-python3 libvslvm-python3 python3-acstore python3-artifacts python3-bencode python3-certifi python3-cffi-backend python3-chardet python3-cryptography python3-dateutil python3-defusedxml python3-dfdatetime python3-dfvfs python3-dfwinreg python3-distutils python3-dtfabric python3-fakeredis python3-flor python3-future python3-idna python3-lz4 python3-mock python3-opensearch python3-pefile python3-psutil python3-pyparsing python3-pytsk3 python3-pyxattr python3-redis python3-requests python3-setuptools python3-six python3-tz python3-urllib3 python3-xlsxwriter python3-yaml python3-yara python3-zmq - name: Run tests env: LANG: en_US.UTF-8 diff --git a/config/dpkg/control b/config/dpkg/control index 380919a9c3..c66e3af748 100644 --- a/config/dpkg/control +++ b/config/dpkg/control @@ -17,7 +17,7 @@ Description: Data files for plaso (log2timeline) Package: python3-plaso Architecture: all -Depends: plaso-data (>= ${binary:Version}), libbde-python3 (>= 20220121), libcaes-python3 (>= 20221127), libcreg-python3 (>= 20200725), libesedb-python3 (>= 20220806), libevt-python3 (>= 20191104), libevtx-python3 (>= 20220724), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20220112), libfsfat-python3 (>= 20220816), libfshfs-python3 (>= 20220115), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220113), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libfwsi-python3 (>= 20150606), liblnk-python3 (>= 20230205), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libmsiecf-python3 (>= 20150314), libolecf-python3 (>= 20151223), libphdi-python3 (>= 20220110), libqcow-python3 (>= 20201213), libregf-python3 (>= 20201002), libscca-python3 (>= 20190605), libsigscan-python3 (>= 20230109), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-acstore (>= 20230519), python3-artifacts (>= 20220219), python3-bencode, python3-certifi (>= 2016.9.26), python3-cffi-backend (>= 1.9.1), python3-chardet (>= 2.0.1), python3-cryptography (>= 2.0.2), python3-dateutil (>= 1.5), python3-defusedxml (>= 0.5.0), python3-dfdatetime (>= 20221112), python3-dfvfs (>= 20230407), python3-dfwinreg (>= 20211207), python3-dtfabric (>= 20230518), python3-flor (>= 1.1.3), python3-future (>= 0.16.0), python3-idna (>= 2.5), python3-lz4 (>= 0.10.0), python3-opensearch, python3-pefile (>= 2021.5.24), python3-psutil (>= 5.4.3), python3-pyparsing (>= 2.4.2), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-redis (>= 3.4), python3-requests (>= 2.18.0), python3-six (>= 1.1.0), python3-tz, python3-urllib3 (>= 1.21.1), python3-xlsxwriter (>= 0.9.3), python3-yaml (>= 3.10), python3-yara (>= 3.4.0), python3-zmq (>= 2.1.11), ${misc:Depends} +Depends: plaso-data (>= ${binary:Version}), libbde-python3 (>= 20220121), libcaes-python3 (>= 20221127), libcreg-python3 (>= 20200725), libesedb-python3 (>= 20220806), libevt-python3 (>= 20191104), libevtx-python3 (>= 20220724), libewf-python3 (>= 20131210), libfsapfs-python3 (>= 20201107), libfsext-python3 (>= 20220112), libfsfat-python3 (>= 20220816), libfshfs-python3 (>= 20220115), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220113), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libfwsi-python3 (>= 20230710), liblnk-python3 (>= 20230716), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libmsiecf-python3 (>= 20150314), libolecf-python3 (>= 20151223), libphdi-python3 (>= 20220110), libqcow-python3 (>= 20201213), libregf-python3 (>= 20201002), libscca-python3 (>= 20190605), libsigscan-python3 (>= 20230109), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-acstore (>= 20230519), python3-artifacts (>= 20220219), python3-bencode, python3-certifi (>= 2016.9.26), python3-cffi-backend (>= 1.9.1), python3-chardet (>= 2.0.1), python3-cryptography (>= 2.0.2), python3-dateutil (>= 1.5), python3-defusedxml (>= 0.5.0), python3-dfdatetime (>= 20221112), python3-dfvfs (>= 20230407), python3-dfwinreg (>= 20211207), python3-dtfabric (>= 20230518), python3-flor (>= 1.1.3), python3-future (>= 0.16.0), python3-idna (>= 2.5), python3-lz4 (>= 0.10.0), python3-opensearch, python3-pefile (>= 2021.5.24), python3-psutil (>= 5.4.3), python3-pyparsing (>= 2.4.2), python3-pytsk3 (>= 20210419), python3-pyxattr (>= 0.7.2), python3-redis (>= 3.4), python3-requests (>= 2.18.0), python3-six (>= 1.1.0), python3-tz, python3-urllib3 (>= 1.21.1), python3-xlsxwriter (>= 0.9.3), python3-yaml (>= 3.10), python3-yara (>= 3.4.0), python3-zmq (>= 2.1.11), ${misc:Depends} Description: Python 3 module of plaso (log2timeline) Plaso (log2timeline) is a framework to create super timelines. Its purpose is to extract timestamps from various files found on typical diff --git a/config/linux/ubuntu_install_plaso.sh b/config/linux/ubuntu_install_plaso.sh index 88f1034f3b..07b97e1d0b 100755 --- a/config/linux/ubuntu_install_plaso.sh +++ b/config/linux/ubuntu_install_plaso.sh @@ -43,6 +43,7 @@ PYTHON_DEPENDENCIES="libbde-python3 libsigscan-python3 libsmdev-python3 libsmraw-python3 + libssl-dev libvhdi-python3 libvmdk-python3 libvsgpt-python3 diff --git a/dependencies.ini b/dependencies.ini index 021b8c8254..6f36a58a36 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -252,7 +252,7 @@ version_property: get_version() [pyfwsi] dpkg_name: libfwsi-python3 l2tbinaries_name: libfwsi -minimum_version: 20150606 +minimum_version: 20230710 pypi_name: libfwsi-python rpm_name: libfwsi-python3 version_property: get_version() @@ -260,7 +260,7 @@ version_property: get_version() [pylnk] dpkg_name: liblnk-python3 l2tbinaries_name: liblnk -minimum_version: 20230205 +minimum_version: 20230716 pypi_name: liblnk-python rpm_name: liblnk-python3 version_property: get_version() diff --git a/plaso/dependencies.py b/plaso/dependencies.py index bbc7e0119f..88110d0a9c 100644 --- a/plaso/dependencies.py +++ b/plaso/dependencies.py @@ -48,8 +48,8 @@ 'pyfsxfs': ('get_version()', '20220113', None, True), 'pyfvde': ('get_version()', '20220121', None, True), 'pyfwnt': ('get_version()', '20210717', None, True), - 'pyfwsi': ('get_version()', '20150606', None, True), - 'pylnk': ('get_version()', '20230205', None, True), + 'pyfwsi': ('get_version()', '20230710', None, True), + 'pylnk': ('get_version()', '20230716', None, True), 'pyluksde': ('get_version()', '20220121', None, True), 'pymodi': ('get_version()', '20210405', None, True), 'pymsiecf': ('get_version()', '20150314', None, True), diff --git a/plaso/lib/definitions.py b/plaso/lib/definitions.py index 0454b673ab..97bdce65a2 100644 --- a/plaso/lib/definitions.py +++ b/plaso/lib/definitions.py @@ -7,8 +7,25 @@ MICROSECONDS_PER_MINUTE = 60000000 NANOSECONDS_PER_SECOND = 1000000000 -SOURCE_TYPE_ARCHIVE = 'archive' +# Characters that are considered non-printable Unicode characters. +NON_PRINTABLE_CHARACTERS = {} + +# Escape C0 control characters as \x## +NON_PRINTABLE_CHARACTERS.update({ + value: f'\\x{value:02x}' for value in range(0, 0x20)}) + +# Escape C1 control character as \x## +NON_PRINTABLE_CHARACTERS.update({ + value: f'\\x{value:02x}' for value in range(0x7f, 0xa0)}) + +# Escape Unicode surrogate characters as \U######## +NON_PRINTABLE_CHARACTERS.update({ + value: f'\\U{value:08x}' for value in range(0xd800, 0xe000)}) + +NON_PRINTABLE_CHARACTER_TRANSLATION_TABLE = str.maketrans( + NON_PRINTABLE_CHARACTERS) +# Compression formats. COMPRESSION_FORMAT_NONE = 'none' COMPRESSION_FORMAT_ZLIB = 'zlib' @@ -16,17 +33,7 @@ COMPRESSION_FORMAT_NONE, COMPRESSION_FORMAT_ZLIB]) -# Default worker process memory limit of 2 GiB. -DEFAULT_WORKER_MEMORY_LIMIT = 2048 * 1024 * 1024 - -# Consider a worker process inactive after 15 minutes of no status updates. -DEFAULT_WORKER_TIMEOUT = 15.0 * 60.0 - -FAILURE_MODE_EXHAUST_MEMORY = 'exhaust_memory' -FAILURE_MODE_NOT_RESPONDING = 'not_responding' -FAILURE_MODE_TERMINATED = 'terminated' -FAILURE_MODE_TIME_OUT = 'time_out' - +# Operating system families. OPERATING_SYSTEM_FAMILY_LINUX = 'Linux' OPERATING_SYSTEM_FAMILY_MACOS = 'MacOS' OPERATING_SYSTEM_FAMILY_UNKNOWN = 'Unknown' @@ -40,10 +47,47 @@ OPERATING_SYSTEM_FAMILY_WINDOWS_9x, OPERATING_SYSTEM_FAMILY_WINDOWS_NT]) +# Serialization formats. SERIALIZER_FORMAT_JSON = 'json' SERIALIZER_FORMATS = frozenset([SERIALIZER_FORMAT_JSON]) +# Source types. +SOURCE_TYPE_ARCHIVE = 'archive' + +# Storage formats. +STORAGE_FORMAT_SQLITE = 'sqlite' +STORAGE_FORMAT_REDIS = 'redis' + +SESSION_STORAGE_FORMATS = frozenset([STORAGE_FORMAT_SQLITE]) +TASK_STORAGE_FORMATS = frozenset([STORAGE_FORMAT_SQLITE, STORAGE_FORMAT_REDIS]) + +DEFAULT_STORAGE_FORMAT = STORAGE_FORMAT_SQLITE + +# Storage types. + +# The session storage contains the results of one or more sessions. +# A typical session is a single run of a tool (log2timeline.py). +# The task storage contains the results of one or more tasks. Tasks +# are used to split work within a session. A typical task is a single +# run of a worker process. + +STORAGE_TYPE_SESSION = 'session' +STORAGE_TYPE_TASK = 'task' + +STORAGE_TYPES = frozenset([STORAGE_TYPE_SESSION, STORAGE_TYPE_TASK]) + +# Default worker process memory limit of 2 GiB. +DEFAULT_WORKER_MEMORY_LIMIT = 2048 * 1024 * 1024 + +# Consider a worker process inactive after 15 minutes of no status updates. +DEFAULT_WORKER_TIMEOUT = 15.0 * 60.0 + +FAILURE_MODE_EXHAUST_MEMORY = 'exhaust_memory' +FAILURE_MODE_NOT_RESPONDING = 'not_responding' +FAILURE_MODE_TERMINATED = 'terminated' +FAILURE_MODE_TIME_OUT = 'time_out' + STATUS_INDICATOR_ABORTED = 'aborted' STATUS_INDICATOR_ANALYZING = 'analyzing' STATUS_INDICATOR_COLLECTING = 'collecting' @@ -69,23 +113,7 @@ STATUS_INDICATOR_NOT_RESPONDING, STATUS_INDICATOR_KILLED]) -STORAGE_FORMAT_SQLITE = 'sqlite' -STORAGE_FORMAT_REDIS = 'redis' - -SESSION_STORAGE_FORMATS = frozenset([STORAGE_FORMAT_SQLITE]) -TASK_STORAGE_FORMATS = frozenset([STORAGE_FORMAT_SQLITE, STORAGE_FORMAT_REDIS]) - -DEFAULT_STORAGE_FORMAT = STORAGE_FORMAT_SQLITE - -# The session storage contains the results of one or more sessions. -# A typical session is a single run of a tool (log2timeline.py). -# The task storage contains the results of one or more tasks. Tasks -# are used to split work within a session. A typical task is a single -# run of a worker process. -STORAGE_TYPE_SESSION = 'session' -STORAGE_TYPE_TASK = 'task' - -STORAGE_TYPES = frozenset([STORAGE_TYPE_SESSION, STORAGE_TYPE_TASK]) +# Time descriptions. TIME_DESCRIPTION_ADDED = 'Added Time' TIME_DESCRIPTION_BACKUP = 'Backup Time' diff --git a/plaso/parsers/shared/shell_items.py b/plaso/parsers/shared/shell_items.py index 3a319b2895..f101910d48 100644 --- a/plaso/parsers/shared/shell_items.py +++ b/plaso/parsers/shared/shell_items.py @@ -7,6 +7,7 @@ from plaso.containers import windows_events from plaso.helpers.windows import shell_folders +from plaso.lib import definitions class ShellItemsParser(object): @@ -14,6 +15,9 @@ class ShellItemsParser(object): NAME = 'shell_items' + _PATH_ESCAPE_CHARACTERS = {'\\': '\\\\'} + _PATH_ESCAPE_CHARACTERS.update(definitions.NON_PRINTABLE_CHARACTERS) + def __init__(self, origin): """Initializes the parser. @@ -22,6 +26,7 @@ def __init__(self, origin): """ super(ShellItemsParser, self).__init__() self._origin = origin + self._path_escape_characters = str.maketrans(self._PATH_ESCAPE_CHARACTERS) self._path_segments = [] def _GetDateTime(self, fat_date_time): @@ -38,6 +43,20 @@ def _GetDateTime(self, fat_date_time): return dfdatetime_fat_date_time.FATDateTime(fat_date_time=fat_date_time) + def _GetSanitizedPathString(self, path): + """Retrieves a sanitize path string. + + Args: + path (str): path. + + Returns: + str: sanitized path string. + """ + if not path: + return None + + return path.translate(self._path_escape_characters) + def _ParseShellItem(self, parser_mediator, shell_item): """Parses a shell item. @@ -54,7 +73,7 @@ def _ParseShellItem(self, parser_mediator, shell_item): event_data = windows_events.WindowsShellItemFileEntryEventData() event_data.modification_time = self._GetDateTime( shell_item.get_modification_time_as_integer()) - event_data.name = shell_item.name + event_data.name = self._GetSanitizedPathString(shell_item.name) event_data.origin = self._origin event_data.shell_item_path = self.CopyToPath() @@ -72,7 +91,8 @@ def _ParseShellItem(self, parser_mediator, shell_item): extension_block.get_creation_time_as_integer()) event_data.file_reference = file_reference event_data.localized_name = extension_block.localized_name - event_data.long_name = extension_block.long_name + event_data.long_name = self._GetSanitizedPathString( + extension_block.long_name) # TODO: change to generate an event_data for each extension block. if (event_data.access_time or event_data.creation_time or @@ -109,7 +129,7 @@ def _ParseShellItemPathSegment(self, shell_item): elif isinstance(shell_item, pyfwsi.volume): if shell_item.name: - path_segment = shell_item.name + path_segment = self._GetSanitizedPathString(shell_item.name) elif shell_item.identifier: path_segment = '{{{0:s}}}'.format(shell_item.identifier) @@ -117,12 +137,12 @@ def _ParseShellItemPathSegment(self, shell_item): long_name = '' for extension_block in shell_item.extension_blocks: if isinstance(extension_block, pyfwsi.file_entry_extension): - long_name = extension_block.long_name + long_name = self._GetSanitizedPathString(extension_block.long_name) if long_name: path_segment = long_name elif shell_item.name: - path_segment = shell_item.name + path_segment = self._GetSanitizedPathString(shell_item.name) elif isinstance(shell_item, pyfwsi.network_location): if shell_item.location: @@ -151,8 +171,8 @@ def CopyToPath(self): number_of_path_segments -= 1 for path_segment in self._path_segments[1:]: # Remove a trailing \ except for the last path segment. - if path_segment.endswith('\\') and number_of_path_segments > 1: - path_segment = path_segment[:-1] + if path_segment.endswith('\\\\') and number_of_path_segments > 1: + path_segment = path_segment[:-2] if ((path_segment.startswith('<') and path_segment.endswith('>')) or len(strings) == 1): @@ -160,7 +180,7 @@ def CopyToPath(self): elif path_segment.startswith('\\'): strings.append('{0:s}'.format(path_segment)) else: - strings.append('\\{0:s}'.format(path_segment)) + strings.append('\\\\{0:s}'.format(path_segment)) number_of_path_segments -= 1 return ''.join(strings) diff --git a/plaso/parsers/winlnk.py b/plaso/parsers/winlnk.py index cae96caebc..47b5aa5dda 100644 --- a/plaso/parsers/winlnk.py +++ b/plaso/parsers/winlnk.py @@ -9,6 +9,7 @@ from plaso.containers import events from plaso.containers import windows_events +from plaso.lib import definitions from plaso.lib import specification from plaso.parsers import interface from plaso.parsers import manager @@ -36,7 +37,7 @@ class WinLnkLinkEventData(events.EventData): identifier. droid_volume_identifier (str): distributed link tracking droid volume identifier. - env_var_location (str): environment variables loction. + env_var_location (str): environment variables location. file_attribute_flags (int): file attribute flags of the linked item. file_size (int): size of the linked item. icon_location (str): icon location. @@ -83,9 +84,17 @@ class WinLnkParser(interface.FileObjectParser): _INITIAL_FILE_OFFSET = None + _PATH_ESCAPE_CHARACTERS = {'\\': '\\\\'} + _PATH_ESCAPE_CHARACTERS.update(definitions.NON_PRINTABLE_CHARACTERS) + NAME = 'lnk' DATA_FORMAT = 'Windows Shortcut (LNK) file' + def __init__(self): + """Initializes a Windows Shortcut (LNK) file parser.""" + super(WinLnkParser, self).__init__() + self._path_escape_characters = str.maketrans(self._PATH_ESCAPE_CHARACTERS) + def _GetDateTime(self, filetime): """Retrieves the date and time from a FILETIME timestamp. @@ -100,6 +109,20 @@ def _GetDateTime(self, filetime): return dfdatetime_filetime.Filetime(timestamp=filetime) + def _GetSanitizedPathString(self, path): + """Retrieves a sanitize path string. + + Args: + path (str): path. + + Returns: + str: sanitized path string. + """ + if not path: + return None + + return path.translate(self._path_escape_characters) + @classmethod def GetFormatSpecification(cls): """Retrieves the format specification. @@ -175,33 +198,41 @@ def ParseFileLNKFile( link_target = shell_items_parser.CopyToPath() + access_time = lnk_file.get_file_access_time_as_integer() + creation_time = lnk_file.get_file_creation_time_as_integer() + modification_time = lnk_file.get_file_modification_time_as_integer() + event_data = WinLnkLinkEventData() - event_data.access_time = self._GetDateTime( - lnk_file.get_file_access_time_as_integer()) + event_data.access_time = self._GetDateTime(access_time) event_data.birth_droid_file_identifier = ( lnk_file.birth_droid_file_identifier) event_data.birth_droid_volume_identifier = ( lnk_file.birth_droid_volume_identifier) - event_data.command_line_arguments = lnk_file.command_line_arguments - event_data.creation_time = self._GetDateTime( - lnk_file.get_file_creation_time_as_integer()) - event_data.description = lnk_file.description + event_data.command_line_arguments = self._GetSanitizedPathString( + lnk_file.command_line_arguments) + event_data.creation_time = self._GetDateTime(creation_time) + event_data.description = self._GetSanitizedPathString( + lnk_file.description) event_data.drive_serial_number = lnk_file.drive_serial_number event_data.drive_type = lnk_file.drive_type event_data.droid_file_identifier = lnk_file.droid_file_identifier event_data.droid_volume_identifier = lnk_file.droid_volume_identifier - event_data.env_var_location = lnk_file.environment_variables_location + event_data.env_var_location = self._GetSanitizedPathString( + lnk_file.environment_variables_location) event_data.file_attribute_flags = lnk_file.file_attribute_flags event_data.file_size = lnk_file.file_size - event_data.icon_location = lnk_file.icon_location + event_data.icon_location = self._GetSanitizedPathString( + lnk_file.icon_location) event_data.link_target = link_target - event_data.local_path = lnk_file.local_path - event_data.modification_time = self._GetDateTime( - lnk_file.get_file_modification_time_as_integer()) - event_data.network_path = lnk_file.network_path - event_data.relative_path = lnk_file.relative_path + event_data.local_path = self._GetSanitizedPathString(lnk_file.local_path) + event_data.modification_time = self._GetDateTime(modification_time) + event_data.network_path = self._GetSanitizedPathString( + lnk_file.network_path) + event_data.relative_path = self._GetSanitizedPathString( + lnk_file.relative_path) event_data.volume_label = lnk_file.volume_label - event_data.working_directory = lnk_file.working_directory + event_data.working_directory = self._GetSanitizedPathString( + lnk_file.working_directory) parser_mediator.ProduceEventData(event_data) diff --git a/requirements.txt b/requirements.txt index 503c7493a9..9012881667 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,8 +29,8 @@ libfsntfs-python >= 20211229 libfsxfs-python >= 20220113 libfvde-python >= 20220121 libfwnt-python >= 20210717 -libfwsi-python >= 20150606 -liblnk-python >= 20230205 +libfwsi-python >= 20230710 +liblnk-python >= 20230716 libluksde-python >= 20220121 libmodi-python >= 20210405 libmsiecf-python >= 20150314 diff --git a/setup.cfg b/setup.cfg index aeacf242c5..c1c15579b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,8 +32,8 @@ requires = libbde-python3 >= 20220121 libfsxfs-python3 >= 20220113 libfvde-python3 >= 20220121 libfwnt-python3 >= 20210717 - libfwsi-python3 >= 20150606 - liblnk-python3 >= 20230205 + libfwsi-python3 >= 20230710 + liblnk-python3 >= 20230716 libluksde-python3 >= 20220121 libmodi-python3 >= 20210405 libmsiecf-python3 >= 20150314 diff --git a/test_data/unpaired_surrogate.lnk b/test_data/unpaired_surrogate.lnk new file mode 100755 index 0000000000000000000000000000000000000000..8dae11607aaaec8e53fc46c29d400e800ec3e308 GIT binary patch literal 1103 zcmb_bUr3Wt6#va8sYGpLl2Yy~5{!+{Z$V%miOOa!C^xbV9|p6wNz>{LA8TS$*SBU^|jj$5yhzDlyY zBk@>xI1&x{*)UCuv}#(YU5%!raIqu7(m8`DzcxwJ0F^A|#;*S>dq0_2{Y0NoQVM^p zV2gfezLax2-+5-2hF+YjLEXzCB|P7_Y@Pgelmw#@ruRim!OSA zfwK6>I6qCuLGBKQo7=ABtWVw|<6YdE_0!)-7|c63{-0|C>?4;Xd{lSJeSbXRnf7$3 ZQ*G(7?ew1@;30mDzn9BS@yW^lkY9u0(Fgzl literal 0 HcmV?d00001 diff --git a/tests/parsers/winlnk.py b/tests/parsers/winlnk.py index 2f409473be..9b63fcb7be 100644 --- a/tests/parsers/winlnk.py +++ b/tests/parsers/winlnk.py @@ -43,15 +43,15 @@ def testParse(self): expected_event_values = { 'access_time': '2009-07-13T23:29:02.8491310+00:00', 'data_type': 'windows:lnk:link', - 'description': '@%windir%\\system32\\migwiz\\wet.dll,-590', + 'description': '@%windir%\\\\system32\\\\migwiz\\\\wet.dll,-590', 'creation_time': '2009-07-13T23:29:02.8491310+00:00', - 'env_var_location': '%windir%\\system32\\migwiz\\migwiz.exe', + 'env_var_location': '%windir%\\\\system32\\\\migwiz\\\\migwiz.exe', 'file_attribute_flags': 0x00000020, 'file_size': 544768, - 'icon_location': '%windir%\\system32\\migwiz\\migwiz.exe', + 'icon_location': '%windir%\\\\system32\\\\migwiz\\\\migwiz.exe', 'modification_time': '2009-07-14T01:39:18.2200000+00:00', - 'relative_path': '.\\migwiz\\migwiz.exe', - 'working_directory': '%windir%\\system32\\migwiz'} + 'relative_path': '.\\\\migwiz\\\\migwiz.exe', + 'working_directory': '%windir%\\\\system32\\\\migwiz'} event_data = storage_writer.GetAttributeContainerByIndex('event_data', 0) self.CheckEventData(event_data, expected_event_values) @@ -97,17 +97,18 @@ def testParseLinkTargetIdentifier(self): 'file_attribute_flags': 0x00000020, 'file_size': 4635160, 'icon_location': ( - 'C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool\\' + 'C:\\\\Program Files (x86)\\\\Nero\\\\Nero 9\\\\Nero InfoTool\\\\' 'InfoTool.exe'), 'local_path': ( - 'C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool\\' + 'C:\\\\Program Files (x86)\\\\Nero\\\\Nero 9\\\\Nero InfoTool\\\\' 'InfoTool.exe'), 'relative_path': ( - '..\\..\\..\\..\\..\\..\\..\\..\\Program Files (x86)\\' - 'Nero\\Nero 9\\Nero InfoTool\\InfoTool.exe'), + '..\\\\..\\\\..\\\\..\\\\..\\\\..\\\\..\\\\..\\\\' + 'Program Files (x86)\\\\Nero\\\\Nero 9\\\\Nero InfoTool\\\\' + 'InfoTool.exe'), 'volume_label': 'OS', 'working_directory': ( - 'C:\\Program Files (x86)\\Nero\\Nero 9\\Nero InfoTool')} + 'C:\\\\Program Files (x86)\\\\Nero\\\\Nero 9\\\\Nero InfoTool')} event_data = storage_writer.GetAttributeContainerByIndex('event_data', 5) self.CheckEventData(event_data, expected_event_values) @@ -123,12 +124,61 @@ def testParseLinkTargetIdentifier(self): 'name': 'InfoTool.exe', 'origin': 'NeroInfoTool.lnk', 'shell_item_path': ( - ' C:\\Program Files (x86)\\Nero\\Nero 9\\' - 'Nero InfoTool\\InfoTool.exe')} + ' C:\\\\Program Files (x86)\\\\Nero\\\\Nero 9\\\\' + 'Nero InfoTool\\\\InfoTool.exe')} event_data = storage_writer.GetAttributeContainerByIndex('event_data', 4) self.CheckEventData(event_data, expected_event_values) + def testParseUnpairedSurrogate(self): + """Tests the Parse function on an LNK with an unpaired surrogate.""" + parser = winlnk.WinLnkParser() + storage_writer = self._ParseFile(['unpaired_surrogate.lnk'], parser) + + number_of_event_data = storage_writer.GetNumberOfAttributeContainers( + 'event_data') + self.assertEqual(number_of_event_data, 5) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'extraction_warning') + self.assertEqual(number_of_warnings, 0) + + number_of_warnings = storage_writer.GetNumberOfAttributeContainers( + 'recovery_warning') + self.assertEqual(number_of_warnings, 0) + + # Test shortcut event data. + expected_event_values = { + 'creation_time': '2023-07-10T04:01:20.7971076+00:00', + 'data_type': 'windows:lnk:link', + 'description': None, + 'drive_serial_number': 0x2ca3d1ae, + 'drive_type': 3, + 'file_attribute_flags': 0x00000020, + 'file_size': 11264, + 'local_path': 'C:\\\\test\\\\unicode_U+0000d800_\\U0000d800.exe', + 'relative_path': '.\\\\unicode_U+0000d800_\\U0000d800.exe', + 'working_directory': 'C:\\\\test'} + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 2) + self.CheckEventData(event_data, expected_event_values) + + # Test shell item event data. + expected_event_values = { + 'access_time': '2023-07-10T04:01:28+00:00', + 'creation_time': '2023-07-10T04:01:22+00:00', + 'data_type': 'windows:shell_item:file_entry', + 'file_reference': '386618-63', + 'long_name': 'unicode_U+0000d800_\\U0000d800.exe', + 'modification_time': '2019-12-06T21:29:00+00:00', + 'name': 'UNICOD~1.EXE', + 'origin': 'unpaired_surrogate.lnk', + 'shell_item_path': ( + ' C:\\\\test\\\\unicode_U+0000d800_\\U0000d800.exe')} + + event_data = storage_writer.GetAttributeContainerByIndex('event_data', 1) + self.CheckEventData(event_data, expected_event_values) + if __name__ == '__main__': unittest.main()