diff --git a/config/dpkg/control b/config/dpkg/control index 8009178..bc7afc2 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 (>= 20230131), libfwsi-python3 (>= 20240224), 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 (>= 20220129), python3-dfvfs (>= 20240115), python3-dfwinreg (>= 20211207), 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 (>= 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} 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 c6eb500..2ae1a8a 100644 --- a/dependencies.ini +++ b/dependencies.ini @@ -25,7 +25,7 @@ version_property: __version__ [dfimagetools] dpkg_name: python3-dfimagetools -minimum_version: 20220129 +minimum_version: 20240301 rpm_name: python3-dfimagetools version_property: __version__ @@ -37,7 +37,7 @@ version_property: __version__ [dfwinreg] dpkg_name: python3-dfwinreg -minimum_version: 20211207 +minimum_version: 20240229 rpm_name: python3-dfwinreg version_property: __version__ @@ -162,7 +162,7 @@ version_property: get_version() [pyfwps] dpkg_name: libfwps-python3 l2tbinaries_name: libfwps -minimum_version: 20240224 +minimum_version: 20240225 pypi_name: libfwps-python rpm_name: libfwps-python3 version_property: get_version() @@ -170,7 +170,7 @@ version_property: get_version() [pyfwsi] dpkg_name: libfwsi-python3 l2tbinaries_name: libfwsi -minimum_version: 20240224 +minimum_version: 20240225 pypi_name: libfwsi-python rpm_name: libfwsi-python3 version_property: get_version() diff --git a/docs/sources/windows-registry/Files.md b/docs/sources/windows-registry/Files.md index 3227039..f654181 100644 --- a/docs/sources/windows-registry/Files.md +++ b/docs/sources/windows-registry/Files.md @@ -49,7 +49,7 @@ SOFTWARE | %SystemRoot%\System32\config | Software specific part of the Registry Syscache.hve | System Volume Information | *TODO* | | 7, 2008 SYSTEM | %SystemRoot%\System32\config | System specific part of the Registry | `HKEY_LOCAL_MACHINE\System` | NT 4 and later userdiff | %SystemRoot%\System32\config | *TODO* | | NT 4 and later -UsrClass.dat | %UserProfile%\Local Settings\Application Data\Microsoft\Windows | File associations and COM Registry entries | | 2000, XP, 2003 +UsrClass.dat | %UserProfile%\Local Settings\Application Data\Microsoft\Windows | `HKEY_CURRENT_USER\Software\Classes` | | 2000, XP, 2003 UsrClass.dat | %UserProfile%\AppData\Local\Microsoft\Windows | File associations and COM Registry entries | `HKEY_CURRENT_USER\Software\Classes` | Vista and later *TODO Windows NT 3.1 user specific file under %SystemRoot%\System32\config* @@ -165,7 +165,17 @@ Vista, 7 | %SID%_Classes, where %SID%_Classes is a string of the SID of the user 8 | *TODO* 10 | %SID%_Classes, where %SID%_Classes is a string of the SID of the user -## Notes -*TODO what about earlier versions of Windows?* +### Virtual keys +Windows version | Key path | Description +--- | --- | --- +2000 and later | `HKEY_LOCAL_MACHINE\System\CurrentControlSet` | See [Current control set](../system-keys/Current-control-set.html) +2000 and later | `HKEY_CURRENT_USER\Software\Classes` | Maps to UsrClass.dat +2000 and later | `HKEY_CLASSES_ROOT` | Combination of system (`HKEY_LOCAL_MACHINE\Software\Classes`) and user-specific (`HKEY_CURRENT_USER\Software\Classes`) settings +Vista and later | `HKEY_CURRENT_USER\Software\Classes\VirtualStore\Machine\Software` | User-specific changes to `HKEY_LOCAL_MACHINE\Software` + +## External Links + +* [HKEY_CLASSES_ROOT Key](https://learn.microsoft.com/en-us/windows/win32/sysinfo/hkey-classes-root-key) +* [Registry Virtualization](https://learn.microsoft.com/en-us/windows/win32/sysinfo/registry-virtualization) diff --git a/docs/sources/windows-registry/MUI-form.md b/docs/sources/windows-registry/MUI-form.md index 013b0f1..245c547 100644 --- a/docs/sources/windows-registry/MUI-form.md +++ b/docs/sources/windows-registry/MUI-form.md @@ -58,6 +58,6 @@ Yet another variation seen in Windows 2000. ## External Links -* [MSDN: Multilingual User Interface](https://learn.microsoft.com/en-us/windows/win32/intl/multilingual-user-interface) -* [MSDN: Using Registry String Redirection](https://learn.microsoft.com/en-us/windows/win32/intl/using-registry-string-redirection) +* [Multilingual User Interface](https://learn.microsoft.com/en-us/windows/win32/intl/multilingual-user-interface) +* [Using Registry String Redirection](https://learn.microsoft.com/en-us/windows/win32/intl/using-registry-string-redirection) diff --git a/requirements.txt b/requirements.txt index 7ebfbcc..7ea23b6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ acstore >= 20230101 artifacts >= 20220219 cffi >= 1.9.1 dfdatetime >= 20221112 -dfimagetools >= 20220129 +dfimagetools >= 20240301 dfvfs >= 20240115 -dfwinreg >= 20211207 +dfwinreg >= 20240229 dtfabric >= 20230518 libbde-python >= 20220121 libcaes-python >= 20240114 @@ -20,8 +20,8 @@ libfsntfs-python >= 20211229 libfsxfs-python >= 20220829 libfvde-python >= 20220121 libfwnt-python >= 20210717 -libfwps-python >= 20230131 -libfwsi-python >= 20240224 +libfwps-python >= 20240225 +libfwsi-python >= 20240225 libhmac-python >= 20230205 libluksde-python >= 20220121 libmodi-python >= 20210405 diff --git a/scripts/mru.py b/scripts/mru.py index ef79ea5..d66c8ad 100755 --- a/scripts/mru.py +++ b/scripts/mru.py @@ -6,7 +6,7 @@ import logging import sys -from dfvfs.helpers import volume_scanner as dfvfs_volume_scanner +from dfvfs.lib import errors as dfvfs_errors import pyfwps import pyfwsi @@ -19,6 +19,15 @@ class StdoutWriter(output_writers.StdoutOutputWriter): """Stdout output writer.""" + _SHELL_PROPERTY_KEYS = { + '{b725f130-47ef-101a-a5f1-02608c9eebac}/4': 'PKEY_ItemTypeText', + '{b725f130-47ef-101a-a5f1-02608c9eebac}/10': 'PKEY_ItemNameDisplay', + '{b725f130-47ef-101a-a5f1-02608c9eebac}/12': 'PKEY_Size', + '{b725f130-47ef-101a-a5f1-02608c9eebac}/14': 'PKEY_DateModified', + '{dabd30ed-0043-4789-a7f8-d013a4736622}/100': ( + 'PKEY_ItemFolderPathDisplayNarrow'), + } + def _WriteShellItemControlPanelCategory(self, shell_item): """Writes a control panel category shell item to stdout. @@ -80,18 +89,23 @@ def _WriteShellItemUsersPropertyView(self, shell_item): for fwps_set in iter(fwps_store.sets): for fwps_record in iter(fwps_set.records): - if fwps_record.value_type == 0x000b: - value_string = str(fwps_record.get_data_as_boolean()) - elif fwps_record.value_type in (0x0013, 0x0014): + if fwps_record.value_type == 0x0001: + value_string = '' + elif fwps_record.value_type in (0x0003, 0x0013, 0x0014, 0x0015): value_string = str(fwps_record.get_data_as_integer()) - elif fwps_record.value_type == 0x001f: + elif fwps_record.value_type in (0x0008, 0x001e, 0x001f): value_string = fwps_record.get_data_as_string() + elif fwps_record.value_type == 0x000b: + value_string = str(fwps_record.get_data_as_boolean()) elif fwps_record.value_type == 0x0040: filetime = fwps_record.get_data_as_integer() value_string = self._FormatFiletimeValue(filetime) elif fwps_record.value_type == 0x0042: # TODO: add support value_string = '' + elif fwps_record.value_type & 0xf000 == 0x1000: + # TODO: add support + value_string = '' else: raise RuntimeError( f'Unsupported value type: 0x{fwps_record.value_type:04x}') @@ -101,9 +115,12 @@ def _WriteShellItemUsersPropertyView(self, shell_item): else: entry_string = f'{fwps_record.entry_type:d}' - # TODO: print PKEY_ name + property_key = f'{{{fwps_set.identifier:s}}}/{entry_string:s}' + shell_property_key = self._SHELL_PROPERTY_KEYS.get( + property_key, 'Unknown') self.WriteText( - f'\tProperty: {{{fwps_set.identifier:s}}}/{entry_string:s}\n') + f'\tProperty: {property_key:s} ({shell_property_key:s})\n') + self.WriteValue( f'\t\tValue (0x{fwps_record.value_type:04x})', value_string) @@ -130,7 +147,24 @@ def WriteShellItem(self, shell_item): Args: shell_item (pyfwsi.item): Shell item. """ - self.WriteValue('Shell item', f'0x{shell_item.class_type:02x}') + if isinstance(shell_item, pyfwsi.control_panel_category): + shell_item_type = 'Control Panel Category' + elif isinstance(shell_item, pyfwsi.control_panel_item): + shell_item_type = 'Control Panel Item' + elif isinstance(shell_item, pyfwsi.file_entry): + shell_item_type = 'File Entry' + elif isinstance(shell_item, pyfwsi.network_location): + shell_item_type = 'Network Location' + elif isinstance(shell_item, pyfwsi.root_folder): + shell_item_type = 'Root Folder' + elif isinstance(shell_item, pyfwsi.users_property_view): + shell_item_type = 'User Property View' + elif isinstance(shell_item, pyfwsi.volume): + shell_item_type = 'Volume' + else: + shell_item_type = f'Unknown (0x{shell_item.class_type:02x})' + + self.WriteValue('Shell item', shell_item_type) if shell_item.delegate_folder_identifier: self.WriteValue( @@ -196,17 +230,21 @@ def WriteShellItem(self, shell_item): self.WriteValue('\t\tFile reference', file_reference) + # TODO: add support for 0xbeef0000 # TODO: add support for 0xbeef0019 # TODO: add support for 0xbeef0025 - self.WriteText('\n') + elif extension_block.signature not in ( + 0xbeef0000, 0xbeef0013, 0xbeef0019, 0xbeef0025, 0xbeef0026, + 0xbeef0029): + self.WriteText('MARKER2\n') def Main(): - """The main program function. + """Entry point of console script to extract Most Recently Used information. Returns: - bool: True if successful or False if not. + int: exit code that is provided to sys.exit(). """ argument_parser = argparse.ArgumentParser(description=( 'Extracts Most Recently Used information from a NTUSER.DAT Registry ' @@ -216,12 +254,16 @@ def Main(): '-d', '--debug', dest='debug', action='store_true', default=False, help='enable debug output.') + argument_parser.add_argument( + '-u', '--username', dest='username', action='store', metavar='USERNAME', + default=None, help='username within a storage media image.') + 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 NTUSER.DAT Registry file.')) + 'or the path of a NTUSER.DAT or UsrClass.dat Registry file.')) options = argument_parser.parse_args() @@ -230,7 +272,7 @@ def Main(): print('') argument_parser.print_help() print('') - return False + return 1 logging.basicConfig( level=logging.INFO, format='[%(levelname)s] %(message)s') @@ -240,22 +282,36 @@ def Main(): if not output_writer.Open(): print('Unable to open output writer.') print('') - return False + return 1 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 = options.username volume_scanner_options.volumes = ['none'] - if not scanner.ScanForWindowsVolume( - options.source, options=volume_scanner_options): + try: + result = scanner.ScanForWindowsVolume( + options.source, options=volume_scanner_options) + + except dfvfs_errors.ScannerError as exception: + print(f'[ERROR] {exception!s}', file=sys.stderr) + print('') + return 1 + + except KeyboardInterrupt: + print('Aborted by user.', file=sys.stderr) + print('') + return 1 + + if not result: print((f'Unable to retrieve the volume with the Windows directory from: ' f'{options.source:s}.')) print('') - return False + return 1 collector_object = mru.MostRecentlyUsedCollector( debug=options.debug, output_writer=output_writer) @@ -264,36 +320,37 @@ def Main(): result = collector_object.Collect(scanner.registry) if not result: print('No Most Recently Used key found.') - else: - for mru_entry in collector_object.mru_entries: - output_writer.WriteValue('Key path', mru_entry.key_path) - output_writer.WriteValue('Value name', mru_entry.value_name) + return 0 - if mru_entry.string: - output_writer.WriteValue('String', mru_entry.string) + for mru_entry in collector_object.mru_entries: + output_writer.WriteValue('Key path', mru_entry.key_path) + output_writer.WriteValue('Value name', mru_entry.value_name) - if mru_entry.shell_item_data: - shell_item = pyfwsi.item() - shell_item.copy_from_byte_stream(mru_entry.shell_item_data) + if mru_entry.string: + output_writer.WriteValue('String', mru_entry.string) - output_writer.WriteShellItem(shell_item) + if mru_entry.shell_item_data: + shell_item = pyfwsi.item() + shell_item.copy_from_byte_stream(mru_entry.shell_item_data) + + output_writer.WriteShellItem(shell_item) - elif mru_entry.shell_item_list_data: - shell_item_list = pyfwsi.item_list() - shell_item_list.copy_from_byte_stream(mru_entry.shell_item_list_data) + elif mru_entry.shell_item_list_data: + shell_item_list = pyfwsi.item_list() + shell_item_list.copy_from_byte_stream(mru_entry.shell_item_list_data) - for shell_item in iter(shell_item_list.items): - output_writer.WriteShellItem(shell_item) + for index, shell_item in enumerate(shell_item_list.items): + if index > 1: + output_writer.WriteText('\n') + + output_writer.WriteShellItem(shell_item) - output_writer.WriteText('') + output_writer.WriteText('\n') output_writer.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 25b8334..fb8aecf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = winregrc -version = 20240224 +version = 20240301 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/delegatefolders.py scripts/environment_variables.py scripts/eventlog_providers.py scripts/knownfolders.py @@ -92,8 +93,8 @@ requires = libfsxfs-python3 >= 20220829 libfvde-python3 >= 20220121 libfwnt-python3 >= 20210717 - libfwps-python3 >= 20230131 - libfwsi-python3 >= 20240224 + libfwps-python3 >= 20240225 + libfwsi-python3 >= 20240225 libhmac-python3 >= 20230205 libluksde-python3 >= 20220121 libmodi-python3 >= 20210405 @@ -113,9 +114,9 @@ requires = python3-artifacts >= 20220219 python3-cffi >= 1.9.1 python3-dfdatetime >= 20221112 - python3-dfimagetools >= 20220129 + python3-dfimagetools >= 20240301 python3-dfvfs >= 20240115 - python3-dfwinreg >= 20211207 + python3-dfwinreg >= 20240229 python3-dtfabric >= 20230518 python3-idna >= 2.5 python3-pytsk3 >= 20210419 diff --git a/winregrc/mru.py b/winregrc/mru.py index 49582e1..82bbe25 100644 --- a/winregrc/mru.py +++ b/winregrc/mru.py @@ -62,7 +62,11 @@ class MostRecentlyUsedCollector(data_format.BinaryDataFormat): ('HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\' 'Microsoft\\Windows\\Shell\\BagMRU'), ('HKEY_CURRENT_USER\\Software\\Classes\\Local Settings\\Software\\' - 'Microsoft\\Windows\\ShellNoRoam\\BagMRU')] + 'Microsoft\\Windows\\ShellNoRoam\\BagMRU'), + ('HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\' + 'Software\\Microsoft\\Windows\\Shell\\BagMRU'), + ('HKEY_CURRENT_USER\\Software\\Classes\\Wow6432Node\\Local Settings\\' + 'Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU')] _SHELL_ITEM_MRU_KEY_PATHS = [ key_path.upper() for key_path in _SHELL_ITEM_MRU_KEY_PATHS] @@ -196,7 +200,7 @@ def _ProcessKeyWithMRUListValue(self, registry_key): if self._debug: self._output_writer.WriteText( - f'Key: {registry_key.path:s}\nValue: {registry_value.name:s}') + f'Key: {registry_key.path:s}\nValue: {registry_value.name:s}\n') if self._InKeyPaths(registry_key.path, self._SHELL_ITEM_MRU_KEY_PATHS): self._ProcessMRUEntryShellItem( @@ -275,7 +279,7 @@ def _ProcessKeyWithMRUListExValue(self, registry_key): if self._debug: self._output_writer.WriteText( - f'Key: {registry_key.path:s}\nValue: {registry_value.name:s}') + f'Key: {registry_key.path:s}\nValue: {registry_value.name:s}\n') if self._InKeyPaths(registry_key.path, self._SHELL_ITEM_MRU_KEY_PATHS): self._ProcessMRUEntryShellItem( @@ -457,7 +461,7 @@ def Collect(self, registry): # pylint: disable=arguments-differ result = True if not result: - # Fallback for if source is a single Usrclass.dat file. + # Fallback for if source is a single UsrClass.dat file. current_user_classes_key = registry.GetKeyByPath( 'HKEY_CURRENT_USER\\Software\\Classes') if current_user_classes_key: diff --git a/winregrc/volume_scanner.py b/winregrc/volume_scanner.py index 727ef74..cac9c13 100644 --- a/winregrc/volume_scanner.py +++ b/winregrc/volume_scanner.py @@ -13,6 +13,27 @@ from dfwinreg import registry as dfwinreg_registry +class VolumeScannerOptions(dfvfs_volume_scanner.VolumeScannerOptions): + """Volume scanner options. + + Attributes: + credentials (list[tuple[str, str]]): credentials, per type, to unlock + volumes. + partitions (list[str]): partition identifiers. + scan_mode (str): mode that defines how the VolumeScanner should scan + for volumes and snapshots. + snapshots (list[str]): snapshot identifiers. + username (str): username. + volumes (list[str]): volume identifiers, e.g. those of an APFS or LVM + volume system. + """ + + def __init__(self): + """Initializes volume scanner options.""" + super(VolumeScannerOptions, self).__init__() + self.username = None + + class SingleFileWindowsRegistryFileReader( dfwinreg_interface.WinRegistryFileReader): """Single file Windows Registry file reader.""" @@ -43,12 +64,19 @@ def Open(self, path, ascii_codepage='cp1252'): if file_object is None: return None - registry_file = windows_registry.WindowsRegistryFile( - ascii_codepage=ascii_codepage) - try: + signature = file_object.read(4) + + if signature == b'regf': + registry_file = windows_registry.REGFWindowsRegistryFile( + ascii_codepage=ascii_codepage) + else: + registry_file = windows_registry.CREGWindowsRegistryFile( + ascii_codepage=ascii_codepage) + + # Note that registry_file takes over management of file_object. registry_file.Open(file_object) - # Note that WindowsRegistryFile takes over management of file_object. + except IOError: file_object.close() return None @@ -75,47 +103,60 @@ def __init__(self, mediator=None): self.registry = None - def IsSingleFileRegistry(self): - """Determines if the Registry consists of a single file. - - Returns: - bool: True if the Registry consists of a single file. - """ - return self._single_file - - def OpenFile(self, windows_path): - """Opens the file specified by the Windows path. + def _GetUsername(self, options): + """Determines the username. Args: - windows_path (str): Windows path to the file. + options (VolumeScannerOptions): volume scanner options. Returns: - dfvfs.FileIO: file-like object or None if the file does not exist. + str: username or None if not available. Raises: - ScannerError: if the scan node is invalid or the scanner does not know - how to proceed. + ScannerError: if the scanner does not know how to proceed. + UserAbort: if the user requested to abort. """ - windows_path_upper = windows_path.upper() - if windows_path_upper.startswith('%USERPROFILE%'): - if not self._mediator: - raise dfvfs_errors.ScannerError( - 'Unable to proceed. %UserProfile% found in Windows path but no ' - 'mediator to determine which user to select.') + usernames = [] - users_path_spec = self._path_resolver.ResolvePath('\\Users') - # TODO: handle alternative users path locations - if users_path_spec is None: - raise dfvfs_errors.ScannerError( - 'Unable to proceed. %UserProfile% found in Windows path but no ' - 'users path found to determine which user to select.') + # TODO: handle alternative users path locations + users_path_spec = self._path_resolver.ResolvePath('\\Users') + if users_path_spec: + users_file_entry = dfvfs_resolver.Resolver.OpenFileEntry( + users_path_spec) + for sub_file_entry in users_file_entry.sub_file_entries: + if sub_file_entry.IsDirectory(): + usernames.append(sub_file_entry.name) - users_file_entry = dfvfs_resolver.Resolver.OpenFileEntry(users_path_spec) - self._mediator.PrintUsersSubDirectoriesOverview(users_file_entry) + if not usernames: + return None - # TODO: list users and determine corresponding windows_path + if options.username: + if options.username in usernames: + return options.username - return super(WindowsRegistryVolumeScanner, self).OpenFile(windows_path) + elif len(usernames) == 1: + return usernames[0] + + if not self._mediator: + raise dfvfs_errors.ScannerError( + 'Unable to proceed. Found user profile paths but no mediator to ' + 'determine which user to select.') + + try: + username = self._mediator.GetUsername(usernames) + + except KeyboardInterrupt: + raise dfvfs_errors.UserAbort('Volume scan aborted.') + + return username + + def IsSingleFileRegistry(self): + """Determines if the Registry consists of a single file. + + Returns: + bool: True if the Registry consists of a single file. + """ + return self._single_file def ScanForWindowsVolume(self, source_path, options=None): """Scans for a Windows volume. @@ -144,6 +185,11 @@ def ScanForWindowsVolume(self, source_path, options=None): registry_file_reader = SingleFileWindowsRegistryFileReader(source_path) elif result: + username = self._GetUsername(options) + if username: + self._path_resolver.SetEnvironmentVariable( + 'UserProfile', f'\\Users\\{username:s}') + registry_file_reader = ( windows_registry.StorageMediaImageWindowsRegistryFileReader( self._file_system, self._path_resolver)) @@ -159,25 +205,65 @@ class WindowsRegistryVolumeScannerMediator( dfvfs_command_line.CLIVolumeScannerMediator): """Windows Registry volume scanner mediator.""" - def PrintUsersSubDirectoriesOverview(self, users_file_entry): - """Prints an overview of the Users sub directories. + _USER_PROMPT_USERNAMES = ( + 'Please specify the username that should be processed. Note that you can ' + 'abort with Ctrl^C.') + + def GetUsername(self, usernames): + """Retrieves a username. + + This method can be used to prompt the user to provide a username. Args: - users_file_entry (dfvfs.FileEntry): file entry of the Users directory. + usernames (list[str]): usernames. + + Returns: + str: selected username or None. """ - if users_file_entry.IsLink(): - users_file_entry = users_file_entry.GetLinkedFileEntry() + self._PrintUsernames(usernames) - # TODO: handle missing sub directories + while True: + self._output_writer.Write('\n') - if users_file_entry: - column_names = ['Username'] - table_view = dfvfs_command_line.CLITabularTableView( - column_names=column_names) + lines = self._textwrapper.wrap(self._USER_PROMPT_USERNAMES) + self._output_writer.Write('\n'.join(lines)) + self._output_writer.Write('\n\nUsername: ') - for sub_file_entry in users_file_entry.sub_file_entries: - if sub_file_entry.IsDirectory(): - table_view.AddRow([sub_file_entry.name]) + try: + selected_username = self._input_reader.Read() + selected_username = selected_username.strip() + if selected_username in usernames: + break + except ValueError: + pass self._output_writer.Write('\n') - # table_view.Write(self._output_writer) + + lines = self._textwrapper.wrap( + 'Unsupported username, please try again or abort with Ctrl^C.') + self._output_writer.Write('\n'.join(lines)) + self._output_writer.Write('\n\n') + + return selected_username + + def _PrintUsernames(self, usernames): + """Prints an overview of usernames. + + Args: + usernames (list[str]): usernames. + + Raises: + ScannerError: if a username cannot be resolved. + """ + header = 'The following usernames were found:\n' + self._output_writer.Write(header) + + column_names = ['Username', 'Profile path'] + table_view = dfvfs_command_line.CLITabularTableView( + column_names=column_names) + + for username in sorted(usernames, key=lambda username: username.lower()): + table_view.AddRow([username, f'C:\\Users\\{username:s}']) + + self._output_writer.Write('\n') + table_view.Write(self._output_writer)