From faa3e6488f88bd95da227e8e51b3ecbb91443c0a Mon Sep 17 00:00:00 2001 From: Joachim Metz Date: Mon, 18 Mar 2024 19:48:33 +0100 Subject: [PATCH] Worked on scripts and documentation --- config/dpkg/control | 2 +- dependencies.ini | 2 +- .../Control-panel-item-identifiers.md | 17 ++ docs/sources/explorer-keys/Shell-folders.md | 12 ++ docs/sources/explorer-keys/index.rst | 1 + requirements.txt | 2 +- scripts/controlpanel_items.py | 157 ++++++++++++++++++ scripts/knownfolders.py | 110 ++++++++---- scripts/mru.py | 31 +++- scripts/shellfolders.py | 67 +++++--- setup.cfg | 56 +------ winregrc/controlpanel_items.py | 62 +++++++ winregrc/mru.py | 4 +- winregrc/shellfolders.py | 59 +++++-- winregrc/versions.py | 52 ++++++ winregrc/volume_scanner.py | 11 +- 16 files changed, 506 insertions(+), 139 deletions(-) create mode 100644 docs/sources/explorer-keys/Control-panel-item-identifiers.md create mode 100755 scripts/controlpanel_items.py create mode 100644 winregrc/controlpanel_items.py create mode 100644 winregrc/versions.py diff --git a/config/dpkg/control b/config/dpkg/control index bc7afc2..d57fbfe 100644 --- a/config/dpkg/control +++ b/config/dpkg/control @@ -9,7 +9,7 @@ Homepage: https://github.com/libyal/winreg-kb Package: python3-winregrc Architecture: all -Depends: libbde-python3 (>= 20220121), libcaes-python3 (>= 20240114), libcreg-python3 (>= 20200725), libewf-python3 (>= 20131210), libfcrypto-python3 (>= 20240114), libfsapfs-python3 (>= 20220709), libfsext-python3 (>= 20220829), libfsfat-python3 (>= 20220925), libfshfs-python3 (>= 20220831), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220829), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libfwps-python3 (>= 20240225), libfwsi-python3 (>= 20240225), libhmac-python3 (>= 20230205), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220228), libqcow-python3 (>= 20201213), libregf-python3 (>= 20201002), libsigscan-python3 (>= 20230109), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsapm-python3 (>= 20230506), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-acstore (>= 20230101), python3-artifacts (>= 20220219), python3-cffi-backend (>= 1.9.1), python3-dfdatetime (>= 20221112), python3-dfimagetools (>= 20240301), python3-dfvfs (>= 20240115), python3-dfwinreg (>= 20240229), python3-dtfabric (>= 20230518), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-xattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} +Depends: libbde-python3 (>= 20220121), libcaes-python3 (>= 20240114), libcreg-python3 (>= 20200725), libewf-python3 (>= 20131210), libfcrypto-python3 (>= 20240114), libfsapfs-python3 (>= 20220709), libfsext-python3 (>= 20220829), libfsfat-python3 (>= 20220925), libfshfs-python3 (>= 20220831), libfsntfs-python3 (>= 20211229), libfsxfs-python3 (>= 20220829), libfvde-python3 (>= 20220121), libfwnt-python3 (>= 20210717), libfwps-python3 (>= 20240225), libfwsi-python3 (>= 20240315), libhmac-python3 (>= 20230205), libluksde-python3 (>= 20220121), libmodi-python3 (>= 20210405), libphdi-python3 (>= 20220228), libqcow-python3 (>= 20201213), libregf-python3 (>= 20201002), libsigscan-python3 (>= 20230109), libsmdev-python3 (>= 20140529), libsmraw-python3 (>= 20140612), libvhdi-python3 (>= 20201014), libvmdk-python3 (>= 20140421), libvsapm-python3 (>= 20230506), libvsgpt-python3 (>= 20211115), libvshadow-python3 (>= 20160109), libvslvm-python3 (>= 20160109), python3-acstore (>= 20230101), python3-artifacts (>= 20220219), python3-cffi-backend (>= 1.9.1), python3-dfdatetime (>= 20221112), python3-dfimagetools (>= 20240301), python3-dfvfs (>= 20240115), python3-dfwinreg (>= 20240229), python3-dtfabric (>= 20230518), python3-idna (>= 2.5), python3-pytsk3 (>= 20210419), python3-xattr (>= 0.7.2), python3-yaml (>= 3.10), ${misc:Depends} Description: Python 3 module of Windows Registry resources (winregrc) winregrc is a Python module part of winreg-kb to allow reuse of Windows Registry resources. diff --git a/dependencies.ini b/dependencies.ini index 2ae1a8a..b88fa5d 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -170,7 +170,7 @@ version_property: get_version() [pyfwsi] dpkg_name: libfwsi-python3 l2tbinaries_name: libfwsi -minimum_version: 20240225 +minimum_version: 20240315 pypi_name: libfwsi-python rpm_name: libfwsi-python3 version_property: get_version() diff --git a/docs/sources/explorer-keys/Control-panel-item-identifiers.md b/docs/sources/explorer-keys/Control-panel-item-identifiers.md new file mode 100644 index 0000000..681d8f5 --- /dev/null +++ b/docs/sources/explorer-keys/Control-panel-item-identifiers.md @@ -0,0 +1,17 @@ +# Control panel item identifiers + +A control panel item identifier is a GUID that identifies a specific control +panel item. + +``` +HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Explorer\ControlPanel\NameSpace\{%GUID%} +``` + +Values: + +Name | Data type | Description +--- | --- | --- +Category | REG_DWORD | +(default) | REG_SZ | Module name of the control panel item +PreferredPlan | REG_SZ | + diff --git a/docs/sources/explorer-keys/Shell-folders.md b/docs/sources/explorer-keys/Shell-folders.md index 81816b1..2fae142 100644 --- a/docs/sources/explorer-keys/Shell-folders.md +++ b/docs/sources/explorer-keys/Shell-folders.md @@ -1,5 +1,17 @@ # Shell folders +Shell Folder identifiers are class identifiers with Shell Folder sub key. In +the Windows Registry Some Class identifiers (CLSID) have a ShellFolder sub key +for example: + +``` +HKEY_LOCAL_MACHINE\Software\CLSID\{%GUID%}\ShellFolder +``` + +Where {%GUID%} is a GUID in the form: {00000000-0000-0000-0000-000000000000}. + +A shell folder can be system or user specific. + System shell folders: ``` diff --git a/docs/sources/explorer-keys/index.rst b/docs/sources/explorer-keys/index.rst index 20c8bc6..3e5cc5b 100644 --- a/docs/sources/explorer-keys/index.rst +++ b/docs/sources/explorer-keys/index.rst @@ -6,6 +6,7 @@ Windows explorer keys :maxdepth: 1 Bit bucket + Control panel item identifiers Delegate folders Known folder identifiers Mount points diff --git a/requirements.txt b/requirements.txt index 7ea23b6..d55c311 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ libfsxfs-python >= 20220829 libfvde-python >= 20220121 libfwnt-python >= 20210717 libfwps-python >= 20240225 -libfwsi-python >= 20240225 +libfwsi-python >= 20240315 libhmac-python >= 20230205 libluksde-python >= 20220121 libmodi-python >= 20210405 diff --git a/scripts/controlpanel_items.py b/scripts/controlpanel_items.py new file mode 100755 index 0000000..493a0cc --- /dev/null +++ b/scripts/controlpanel_items.py @@ -0,0 +1,157 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""Script to extract Windows control panel items from the Windows Registry.""" + +import argparse +import logging +import sys +import yaml + +from winregrc import controlpanel_items +from winregrc import output_writers +from winregrc import volume_scanner +from winregrc import versions + + +class StdoutWriter(output_writers.StdoutOutputWriter): + """Stdout output writer.""" + + _WINDOWS_VERSIONS_KEY_FUNCTION = versions.WindowsVersions.KeyFunction + + def WriteHeader(self): + """Writes the header to stdout.""" + print('# winreg-kb controlpanel items definitions') + print('---') + + def WriteKnownFolder(self, control_panel_item, windows_versions): + """Writes the control panel item to stdout. + + Args: + control_panel_item (KnownFolder): the control panel item. + windows_versions (list[str]): the Windows versions. + """ + print(f'identifier: "{control_panel_item.identifier:s}"') + if control_panel_item.module_name: + print(f'module_name: "{control_panel_item.module_name:s}"') + + windows_versions = ', '.join([f'"{version:s}"' for version in sorted( + windows_versions, key=self._WINDOWS_VERSIONS_KEY_FUNCTION)]) + print(f'windows_versions: [{windows_versions:s}]') + print('---') + + +def Main(): + """Entry point of console script to extract control panel items. + + Returns: + int: exit code that is provided to sys.exit(). + """ + argument_parser = argparse.ArgumentParser(description=( + 'Extracts Windows control panel items from the Windows Registry.')) + + argument_parser.add_argument( + '-d', '--debug', dest='debug', action='store_true', default=False, + help='enable debug output.') + + argument_parser.add_argument( + '-w', '--windows_version', '--windows-version', dest='windows_version', + action='store', metavar='VERSION', default=None, + help='string that identifies the Windows version.') + + argument_parser.add_argument( + 'source', nargs='?', action='store', metavar='PATH', default=None, + help=( + 'path of the volume containing C:\\Windows, the filename of ' + 'a storage media image containing the C:\\Windows directory, ' + 'or the path of a SOFTWARE Registry file.')) + + options = argument_parser.parse_args() + + if not options.source: + print('Source value is missing.') + print('') + argument_parser.print_help() + print('') + return 1 + + logging.basicConfig( + level=logging.INFO, format='[%(levelname)s] %(message)s') + + try: + with open(options.source, 'r', encoding='utf-8') as file_object: + source_definitions = list(yaml.safe_load_all(file_object)) + + except (SyntaxError, UnicodeDecodeError, yaml.parser.ParserError): + source_definitions = [{ + 'source': options.source, 'windows_version': options.windows_version}] + + mediator = volume_scanner.WindowsRegistryVolumeScannerMediator() + scanner = volume_scanner.WindowsRegistryVolumeScanner(mediator=mediator) + + volume_scanner_options = volume_scanner.VolumeScannerOptions() + volume_scanner_options.partitions = ['all'] + volume_scanner_options.snapshots = ['none'] + volume_scanner_options.username = ['none'] + volume_scanner_options.volumes = ['none'] + + control_panel_item_per_identifier = {} + windows_versions_per_control_panel_item = {} + + for source_definition in source_definitions: + source_path = source_definition['source'] + logging.info(f'Processing: {source_path:s}') + + if not scanner.ScanForWindowsVolume( + source_path, options=volume_scanner_options): + logging.error(( + f'Unable to retrieve the volume with the Windows directory from: ' + f'{source_path:s}.')) + continue + + collector_object = controlpanel_items.ControlPanelItemsCollector( + debug=options.debug) + + # TODO: determine Windows version from source. + windows_version = source_definition['windows_version'] + + for control_panel_item in collector_object.Collect(scanner.registry): + # TODO: compare existing control panel item. + control_panel_item_per_identifier[ + control_panel_item.identifier] = control_panel_item + + if control_panel_item.identifier not in ( + windows_versions_per_control_panel_item): + windows_versions_per_control_panel_item[ + control_panel_item.identifier] = [] + + if windows_version: + windows_versions_per_control_panel_item[ + control_panel_item.identifier].append(windows_version) + + if not control_panel_item_per_identifier: + print('No control panel items found.') + return 0 + + output_writer_object = StdoutWriter() + + if not output_writer_object.Open(): + print('Unable to open output writer.') + print('') + return 1 + + try: + output_writer_object.WriteHeader() + for identifier, windows_versions in sorted( + windows_versions_per_control_panel_item.items()): + control_panel_item = control_panel_item_per_identifier[identifier] + output_writer_object.WriteKnownFolder( + control_panel_item, windows_versions) + + finally: + output_writer_object.Close() + + return 0 + + +if __name__ == '__main__': + sys.exit(Main()) diff --git a/scripts/knownfolders.py b/scripts/knownfolders.py index 69cf5c6..24aae02 100755 --- a/scripts/knownfolders.py +++ b/scripts/knownfolders.py @@ -5,8 +5,7 @@ import argparse import logging import sys - -from dfvfs.helpers import volume_scanner as dfvfs_volume_scanner +import yaml from winregrc import knownfolders from winregrc import output_writers @@ -16,26 +15,37 @@ class StdoutWriter(output_writers.StdoutOutputWriter): """Stdout output writer.""" - def WriteKnownFolder(self, known_folder): - """Writes a known folder to the output. + def WriteHeader(self): + """Writes the header to stdout.""" + print('# winreg-kb knownfolder definitions') + print('---') + + def WriteKnownFolder(self, known_folder, windows_versions): + """Writes the known folder to stdout. Args: - known_folder (KnownFolder): known folder. + known_folder (KnownFolder): the known folder. + windows_versions (list[str]): the Windows versions. """ - self.WriteValue('Identifier', known_folder.identifier) - self.WriteValue('Name', known_folder.name) + print(f'identifier: "{known_folder.identifier:s}"') + # TODO: escape \ in name + print(f'name: "{known_folder.name:s}"') if known_folder.localized_name: - self.WriteValue('Localized name', known_folder.localized_name) + # TODO: escape \ in localize name + print(f'localized_name: "{known_folder.localized_name:s}"') - self.WriteText('\n') + windows_versions = ', '.join([ + f'"{version:s}"' for version in sorted(windows_versions)]) + print(f'windows_versions: [{windows_versions:s}]') + print('---') def Main(): - """The main program function. + """Entry point of console script to extract known folders. Returns: - bool: True if successful or False if not. + int: exit code that is provided to sys.exit(). """ argument_parser = argparse.ArgumentParser(description=( 'Extracts Windows known folders from the Windows Registry.')) @@ -44,6 +54,11 @@ def Main(): '-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug output.') + argument_parser.add_argument( + '-w', '--windows_version', '--windows-version', dest='windows_version', + action='store', metavar='VERSION', default=None, + help='string that identifies the Windows version.') + argument_parser.add_argument( 'source', nargs='?', action='store', metavar='PATH', default=None, help=( @@ -58,52 +73,81 @@ def Main(): print('') argument_parser.print_help() print('') - return False + return 1 logging.basicConfig( level=logging.INFO, format='[%(levelname)s] %(message)s') + try: + with open(options.source, 'r', encoding='utf-8') as file_object: + source_definitions = list(yaml.safe_load_all(file_object)) + + except (SyntaxError, UnicodeDecodeError, yaml.parser.ParserError): + source_definitions = [{ + 'source': options.source, 'windows_version': options.windows_version}] + mediator = volume_scanner.WindowsRegistryVolumeScannerMediator() scanner = volume_scanner.WindowsRegistryVolumeScanner(mediator=mediator) - volume_scanner_options = dfvfs_volume_scanner.VolumeScannerOptions() + volume_scanner_options = volume_scanner.VolumeScannerOptions() volume_scanner_options.partitions = ['all'] volume_scanner_options.snapshots = ['none'] + volume_scanner_options.username = ['none'] volume_scanner_options.volumes = ['none'] - if not scanner.ScanForWindowsVolume( - options.source, options=volume_scanner_options): - print((f'Unable to retrieve the volume with the Windows directory from: ' - f'{options.source:s}.')) - print('') - return False + known_folder_per_identifier = {} + windows_versions_per_known_folder = {} + + for source_definition in source_definitions: + source_path = source_definition['source'] + logging.info(f'Processing: {source_path:s}') - collector_object = knownfolders.KnownFoldersCollector(debug=options.debug) + if not scanner.ScanForWindowsVolume( + source_path, options=volume_scanner_options): + logging.error(( + f'Unable to retrieve the volume with the Windows directory from: ' + f'{source_path:s}.')) + continue + + collector_object = knownfolders.KnownFoldersCollector(debug=options.debug) + + # TODO: determine Windows version from source. + windows_version = source_definition['windows_version'] + + for known_folder in collector_object.Collect(scanner.registry): + # TODO: compare existing known folder + known_folder_per_identifier[known_folder.identifier] = known_folder + + if known_folder.identifier not in windows_versions_per_known_folder: + windows_versions_per_known_folder[known_folder.identifier] = [] + + if windows_version: + windows_versions_per_known_folder[known_folder.identifier].append( + windows_version) + + if not known_folder_per_identifier: + print('No known folders found.') + return 0 output_writer_object = StdoutWriter() if not output_writer_object.Open(): print('Unable to open output writer.') print('') - return False + return 1 try: - has_results = False - for known_folder in collector_object.Collect(scanner.registry): - output_writer_object.WriteKnownFolder(known_folder) - has_results = True + output_writer_object.WriteHeader() + for identifier, windows_versions in sorted( + windows_versions_per_known_folder.items()): + known_folder = known_folder_per_identifier[identifier] + output_writer_object.WriteKnownFolder(known_folder, windows_versions) finally: output_writer_object.Close() - if not has_results: - print('No Windows known folders found.') - - return True + return 0 if __name__ == '__main__': - if not Main(): - sys.exit(1) - else: - sys.exit(0) + sys.exit(Main()) diff --git a/scripts/mru.py b/scripts/mru.py index f82db64..c5aa6e5 100755 --- a/scripts/mru.py +++ b/scripts/mru.py @@ -74,7 +74,9 @@ def _WriteShellItem(self, fwsi_item): Args: fwsi_item (pyfwsi.item): Shell item. """ - if isinstance(fwsi_item, pyfwsi.control_panel_category): + if isinstance(fwsi_item, pyfwsi.compressed_folder): + shell_item_type = 'Compressed Folder' + elif isinstance(fwsi_item, pyfwsi.control_panel_category): shell_item_type = 'Control Panel Category' elif isinstance(fwsi_item, pyfwsi.control_panel_item): shell_item_type = 'Control Panel Item' @@ -97,7 +99,10 @@ def _WriteShellItem(self, fwsi_item): self.WriteValue( '\tDelegate folder', fwsi_item.delegate_folder_identifier) - if isinstance(fwsi_item, pyfwsi.control_panel_category): + if isinstance(fwsi_item, pyfwsi.compressed_folder): + self._WriteShellItemCompressedFolder(fwsi_item) + + elif isinstance(fwsi_item, pyfwsi.control_panel_category): self._WriteShellItemControlPanelCategory(fwsi_item) elif isinstance(fwsi_item, pyfwsi.control_panel_item): @@ -160,11 +165,21 @@ def _WriteShellItem(self, fwsi_item): # TODO: add support for other extension blocks self.WriteText('\n') + + def _WriteShellItemCompressedFolder(self, fwsi_item): + """Writes a compressed folder shell item to stdout. + + Args: + fwsi_item (pyfwsi.compressed_folder): compressed folder shell item. + """ + self.WriteValue('\tName', fwsi_item.name) + def _WriteShellItemControlPanelCategory(self, fwsi_item): """Writes a control panel category shell item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.control_panel_category): control panel category shell + item. """ self.WriteValue( '\tControl panel category identifier', f'{fwsi_item.identifier:d}') @@ -173,7 +188,7 @@ def _WriteShellItemControlPanelItem(self, fwsi_item): """Writes a control panel item shell item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.control_panel_item): control panel item shell item. """ self.WriteValue('\tControl panel item identifier', fwsi_item.identifier) @@ -181,7 +196,7 @@ def _WriteShellItemFileEntry(self, fwsi_item): """Writes a file entry shell item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.file_entry): File entry shell item. """ self.WriteValue('\tFile size', f'{fwsi_item.file_size:d}') @@ -199,7 +214,7 @@ def _WriteShellItemNetworkLocation(self, fwsi_item): """Writes a network location shell item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.network_location): network location shell item. """ self.WriteValue('\tNetwork location', fwsi_item.location) @@ -213,7 +228,7 @@ def _WriteShellItemUsersPropertyView(self, fwsi_item): """Writes an users property view item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.users_property_view): users property view shell item. """ if fwsi_item.property_store_data: fwps_store = pyfwps.store() @@ -225,7 +240,7 @@ def _WriteShellItemVolume(self, fwsi_item): """Writes a volume shell item to stdout. Args: - fwsi_item (pyfwsi.item): Shell item. + fwsi_item (pyfwsi.volume): volume shell item. """ if fwsi_item.name: self.WriteValue('\tVolume name', fwsi_item.name) diff --git a/scripts/shellfolders.py b/scripts/shellfolders.py index 7dbb53b..59e8a66 100755 --- a/scripts/shellfolders.py +++ b/scripts/shellfolders.py @@ -1,14 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -"""Script to extract shell folder class identifiers.""" +"""Script to extract shell folder identifiers.""" import argparse import logging import sys import yaml -from dfvfs.helpers import volume_scanner as dfvfs_volume_scanner - from winregrc import output_writers from winregrc import shellfolders from winregrc import volume_scanner @@ -34,8 +32,17 @@ def WriteShellFolder(self, shell_folder, windows_versions): if shell_folder.class_name: print(f'class_name: {shell_folder.class_name:s}') + name = shell_folder.name + if '\\' in name: + name = name.replace('\\', '\\\\') + if shell_folder.name: - print(f'name: "{shell_folder.name:s}"') + print(f'name: "{name:s}"') + + if shell_folder.alternate_names: + alternate_names = ', '.join([ + f'"{name:s}"' for name in shell_folder.alternate_names]) + print(f'alternate_names: [{alternate_names:s}]') windows_versions = ', '.join([ f'"{version:s}"' for version in sorted(windows_versions)]) @@ -44,13 +51,13 @@ def WriteShellFolder(self, shell_folder, windows_versions): def Main(): - """The main program function. + """Entry point of console script to extract shell folder identifiers. Returns: - bool: True if successful or False if not. + int: exit code that is provided to sys.exit(). """ argument_parser = argparse.ArgumentParser(description=( - 'Extracts the shell folder class identifiers from the Windows Registry.')) + 'Extracts the shell folder identifiers from the Windows Registry.')) argument_parser.add_argument( '-d', '--debug', dest='debug', action='store_true', default=False, @@ -58,7 +65,7 @@ def Main(): argument_parser.add_argument( '-w', '--windows_version', '--windows-version', dest='windows_version', - action='store', metavar='Windows XP', default=None, + action='store', metavar='VERSION', default=None, help='string that identifies the Windows version.') argument_parser.add_argument( @@ -74,7 +81,7 @@ def Main(): print('') argument_parser.print_help() print('') - return False + return 1 logging.basicConfig( level=logging.INFO, format='[%(levelname)s] %(message)s') @@ -83,16 +90,17 @@ def Main(): with open(options.source, 'r', encoding='utf-8') as file_object: source_definitions = list(yaml.safe_load_all(file_object)) - except (SyntaxError, UnicodeDecodeError): + except (SyntaxError, UnicodeDecodeError, yaml.parser.ParserError): source_definitions = [{ 'source': options.source, 'windows_version': options.windows_version}] mediator = volume_scanner.WindowsRegistryVolumeScannerMediator() scanner = volume_scanner.WindowsRegistryVolumeScanner(mediator=mediator) - volume_scanner_options = dfvfs_volume_scanner.VolumeScannerOptions() + volume_scanner_options = volume_scanner.VolumeScannerOptions() volume_scanner_options.partitions = ['all'] volume_scanner_options.snapshots = ['none'] + volume_scanner_options.username = ['none'] volume_scanner_options.volumes = ['none'] shell_folder_per_identifier = {} @@ -118,24 +126,36 @@ def Main(): for shell_folder in collector_object.Collect(scanner.registry): # TODO: compare existing shell folder - shell_folder_per_identifier[shell_folder.identifier] = shell_folder - - if shell_folder.identifier not in windows_versions_per_shell_folder: - windows_versions_per_shell_folder[shell_folder.identifier] = [] - - windows_versions_per_shell_folder[shell_folder.identifier].append( - windows_version) + # TODO: track multiple names + existing_shell_folder = shell_folder_per_identifier.get( + shell_folder.identifier, None) + + if not existing_shell_folder: + shell_folder_per_identifier[shell_folder.identifier] = shell_folder + elif not existing_shell_folder.name: + existing_shell_folder.name = shell_folder.name + elif (shell_folder.name and + shell_folder.name != existing_shell_folder.name and + shell_folder.name not in existing_shell_folder.alternate_names): + existing_shell_folder.alternate_names.append(shell_folder.name) + + if windows_version: + if shell_folder.identifier not in windows_versions_per_shell_folder: + windows_versions_per_shell_folder[shell_folder.identifier] = [] + + windows_versions_per_shell_folder[shell_folder.identifier].append( + windows_version) if not shell_folder_per_identifier: print('No shell folder identifiers found.') - return True + return 0 output_writer_object = StdoutWriter() if not output_writer_object.Open(): print('Unable to open output writer.') print('') - return False + return 1 try: output_writer_object.WriteHeader() @@ -147,11 +167,8 @@ def Main(): finally: output_writer_object.Close() - return True + return 0 if __name__ == '__main__': - if not Main(): - sys.exit(1) - else: - sys.exit(0) + sys.exit(Main()) diff --git a/setup.cfg b/setup.cfg index fb8aecf..f9cf32b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = winregrc -version = 20240301 +version = 20240318 description = Windows Registry resources (winregrc) long_description = winregrc is a Python module part of winreg-kb to allow reuse of Windows Registry resources. long_description_content_type = text/plain @@ -28,6 +28,7 @@ scripts = scripts/application_identifiers.py scripts/cached_credentials.py scripts/catalog.py + scripts/controlpanel_items.py scripts/delegatefolders.py scripts/environment_variables.py scripts/eventlog_providers.py @@ -70,58 +71,5 @@ manifest = MANIFEST template = MANIFEST.test_data.in manifest = MANIFEST.test_data -[bdist_rpm] -release = 1 -packager = Joachim Metz -doc_files = - ACKNOWLEDGEMENTS - AUTHORS - LICENSE - README -build_requires = python3-setuptools -requires = - libbde-python3 >= 20220121 - libcaes-python3 >= 20240114 - libcreg-python3 >= 20200725 - libewf-python3 >= 20131210 - libfcrypto-python3 >= 20240114 - libfsapfs-python3 >= 20220709 - libfsext-python3 >= 20220829 - libfsfat-python3 >= 20220925 - libfshfs-python3 >= 20220831 - libfsntfs-python3 >= 20211229 - libfsxfs-python3 >= 20220829 - libfvde-python3 >= 20220121 - libfwnt-python3 >= 20210717 - libfwps-python3 >= 20240225 - libfwsi-python3 >= 20240225 - libhmac-python3 >= 20230205 - libluksde-python3 >= 20220121 - libmodi-python3 >= 20210405 - libphdi-python3 >= 20220228 - libqcow-python3 >= 20201213 - libregf-python3 >= 20201002 - libsigscan-python3 >= 20230109 - libsmdev-python3 >= 20140529 - libsmraw-python3 >= 20140612 - libvhdi-python3 >= 20201014 - libvmdk-python3 >= 20140421 - libvsapm-python3 >= 20230506 - libvsgpt-python3 >= 20211115 - libvshadow-python3 >= 20160109 - libvslvm-python3 >= 20160109 - python3-acstore >= 20230101 - python3-artifacts >= 20220219 - python3-cffi >= 1.9.1 - python3-dfdatetime >= 20221112 - python3-dfimagetools >= 20240301 - python3-dfvfs >= 20240115 - python3-dfwinreg >= 20240229 - python3-dtfabric >= 20230518 - python3-idna >= 2.5 - python3-pytsk3 >= 20210419 - python3-pyyaml >= 3.10 - python3-xattr >= 0.7.2 - [bdist_wheel] universal = 1 diff --git a/winregrc/controlpanel_items.py b/winregrc/controlpanel_items.py new file mode 100644 index 0000000..6f61102 --- /dev/null +++ b/winregrc/controlpanel_items.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +"""Windows control panel items collector.""" + +from winregrc import interface + + +class ControlPanelItem(object): + """Control panel item. + + Attributes: + identifier (str): identifier. + module_name (str): module name. + """ + + def __init__(self, identifier, module_name): + """Initializes a control panel item. + + Args: + identifier (str): identifier. + module_name (str): module name. + """ + super(ControlPanelItem, self).__init__() + self.identifier = identifier + self.module_name = module_name + + +class ControlPanelItemsCollector(interface.WindowsRegistryKeyCollector): + """Windows control panel items collector.""" + + _CONTROL_PANEL_NAMESPACE_KEY_PATH = ( + 'HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows\\CurrentVersion\\' + 'Explorer\\ControlPanel\\NameSpace') + + def _CollectControlPanelItems(self, control_panel_namespace_key): + """Collects Windows control panel items. + + Args: + control_panel_namespace_key (dfwinreg.WinRegistryKey): control panel + namespace Windows Registry key. + + Yields: + ControlPanelItem: a control panel item. + """ + for subkey in control_panel_namespace_key.GetSubkeys(): + if subkey.name[0] == '{' and subkey.name[-1] == '}': + identifier = subkey.name.lower() + module_name = self._GetValueFromKey(subkey, '') + yield ControlPanelItem(identifier, module_name) + + def Collect(self, registry): + """Collects Windows control panel items. + + Args: + registry (dfwinreg.WinRegistry): Windows Registry. + + Yields: + ControlPanelItem: a control panel item. + """ + control_panel_namespace_key = registry.GetKeyByPath( + self._CONTROL_PANEL_NAMESPACE_KEY_PATH) + if control_panel_namespace_key: + yield from self._CollectControlPanelItems(control_panel_namespace_key) diff --git a/winregrc/mru.py b/winregrc/mru.py index e2ee0ce..1af265a 100644 --- a/winregrc/mru.py +++ b/winregrc/mru.py @@ -77,7 +77,9 @@ class MostRecentlyUsedCollector(data_format.BinaryDataFormat): ('HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\' 'Explorer\\ComDlg32\\OpenSavePidlMRU'), ('HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\' - 'Explorer\\StreamMRU')] + 'Explorer\\StreamMRU'), + ('HKEY_CURRENT_USER\\Software\\Classes\\Software\\Microsoft\\Windows\\' + 'CurrentVersion\\Explorer\\StreamMRU')] _SHELL_ITEM_LIST_MRU_KEY_PATHS = [ key_path.upper() for key_path in _SHELL_ITEM_LIST_MRU_KEY_PATHS] diff --git a/winregrc/shellfolders.py b/winregrc/shellfolders.py index 7548ba3..6b801c2 100644 --- a/winregrc/shellfolders.py +++ b/winregrc/shellfolders.py @@ -20,6 +20,7 @@ class WindowsShellFolder(containers_interface.AttributeContainer): CONTAINER_TYPE = 'windows_shell_folder' SCHEMA = { + 'alternate_names': 'List[str]', 'class_name': 'str', 'identifier': 'str', 'localized_string': 'str', @@ -33,6 +34,7 @@ def __init__(self, identifier=None, localized_string=None): localized_string (Optional[str]): localized string of the name. """ super(WindowsShellFolder, self).__init__() + self.alternate_names = [] self.class_name = None self.identifier = identifier self.localized_string = localized_string @@ -44,6 +46,15 @@ class ShellFoldersCollector(interface.WindowsRegistryKeyCollector): _CLASS_IDENTIFIERS_KEY_PATH = 'HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID' + def __init__(self, debug=False): + """Initializes a Windows Registry key and value collector. + + Args: + debug (Optional[bool]): True if debug information should be printed. + """ + super(ShellFoldersCollector, self).__init__(debug=debug) + self._ascii_codepage = 'cp1252' + def _CollectShellFolders(self, class_identifiers_key): """Collects Windows Shell folders. @@ -54,20 +65,11 @@ def _CollectShellFolders(self, class_identifiers_key): ShellFolder: a Windows Shell folder. """ for class_identifier_key in class_identifiers_key.GetSubkeys(): - guid = class_identifier_key.name.lower() + shell_folder_identifier = class_identifier_key.name.lower() shell_folder_key = class_identifier_key.GetSubkeyByName('ShellFolder') if shell_folder_key: - value = class_identifier_key.GetValueByName('') - if value: - # The value data type does not have to be a string therefore try to - # decode the data as an UTF-16 little-endian string and strip - # the trailing end-of-string character - name = value.data.decode('utf-16-le').rstrip('\x00') - else: - name = None - - # TODO: resolve name MUI paths. + name = self._GetShellFolderName(class_identifier_key) value = class_identifier_key.GetValueByName('LocalizedString') if value: @@ -79,7 +81,8 @@ def _CollectShellFolders(self, class_identifiers_key): localized_string = None shell_folder = WindowsShellFolder( - identifier=guid, localized_string=localized_string) + identifier=shell_folder_identifier, + localized_string=localized_string) if name and name.startswith('CLSID_'): shell_folder.class_name = name else: @@ -87,6 +90,36 @@ def _CollectShellFolders(self, class_identifiers_key): yield shell_folder + def _GetShellFolderName(self, class_identifier_key): + """Retrieves the shell folder name. + + Args: + class_identifier_key (dfwinreg.RegistryKey): class identifier Windows + Registry key. + + Returns: + str: shell folder name or None if not available. + """ + value = class_identifier_key.GetValueByName('') + if not value or not value.data: + return None + + # First try to decode the value data as an UTF-16 little-endian string with + # end-of-string character + try: + return value.data.decode('utf-16-le').rstrip('\x00') + except UnicodeDecodeError: + pass + + # Next try to decode the value data as an ASCII string with a specific + # codepage and end-of-string character. + try: + return value.data.decode(self._ascii_codepage).rstrip('\x00') + except UnicodeDecodeError: + pass + + return None + def Collect(self, registry): """Collects Windows Shell folders. @@ -96,6 +129,8 @@ def Collect(self, registry): Yields: WindowsShellFolder: a Windows Shell folder. """ + # TODO: Add support for per-user shell folders + class_identifiers_key = registry.GetKeyByPath( self._CLASS_IDENTIFIERS_KEY_PATH) if class_identifiers_key: diff --git a/winregrc/versions.py b/winregrc/versions.py new file mode 100644 index 0000000..e672c6b --- /dev/null +++ b/winregrc/versions.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +"""Windows versions.""" + + +class WindowsVersions(object): + """Windows versions.""" + + _SORT_KEY_PER_VERSION = { + # TODO: update the dates of the Windows 10 releases. + 'Windows 10 (1511)': 2015, + 'Windows 10 (1607)': 2015, + 'Windows 10 (1703)': 2015, + 'Windows 10 (1709)': 2015, + 'Windows 10 (1803)': 2015, + 'Windows 10 (1809)': 2015, + 'Windows 10 (1903)': 2015, + 'Windows 10 (1909)': 2015, + 'Windows 10 (2004)': 2015, + 'Windows 10 (20H2)': 2015, + 'Windows 11 (21H2)': 2021, + 'Windows 2000': 2000, + 'Windows 2003': 2003, + 'Windows 2003 R2': 2005, + 'Windows 2008': 2008, + 'Windows 2008 R2': 2009, + 'Windows 2012': 2012, + 'Windows 2012 R2': 2013, + 'Windows 2016': 2016, + 'Windows 2019': 2019, + 'Windows 7': 2009, + 'Windows 8.0': 2012, + 'Windows 8.1': 2013, + 'Windows 95': 1995, + 'Windows 98': 1998, + 'Windows Me': 2000, + 'Windows NT4': 1996, + 'Windows Vista': 2007, + 'Windows XP 32-bit': 2001, + 'Windows XP 64-bit': 2005} + + @classmethod + def KeyFunction(cls, windows_version): + """Key function for sorting. + + Args: + windows_version (str): Windows version. + + Returns: + tuple[int, str]: sort key and Windows version + """ + sort_key = cls._SORT_KEY_PER_VERSION.get(windows_version, 9999) + return sort_key, windows_version diff --git a/winregrc/volume_scanner.py b/winregrc/volume_scanner.py index 33a02d6..aab9aab 100644 --- a/winregrc/volume_scanner.py +++ b/winregrc/volume_scanner.py @@ -136,9 +136,14 @@ def _GetUsername(self, options): if not usernames: return None - if options.username: - if options.username in usernames: - return options.username + # Handle options without an username. + if hasattr(options, 'username'): + if options.username == ['none']: + return None + + if options.username: + if options.username in usernames: + return options.username elif len(usernames) == 1: return usernames[0]