Source code for winshlrc.extractor
"""Windows shell extractor."""
import logging
import pywrc
from dfimagetools import windows_registry
from dfvfs.helpers import volume_scanner as dfvfs_volume_scanner
from dfvfs.resolver import resolver as dfvfs_resolver
from dfwinreg import registry as dfwinreg_registry
from winshlrc import resource_file
[docs]
class ShellFolder:
"""Windows shell folder.
Attributes:
alternate_names (list[str]): alternate names.
class_name (str): class name (CLSID).
identifier (str): identifier (GUID).
name (str): name.
localized_string (str): localized string of the name.
"""
[docs]
def __init__(self, identifier=None, localized_string=None):
"""Initializes a Windows Shell folder.
Args:
identifier (Optional[str]): identifier (GUID).
localized_string (Optional[str]): localized string of the name.
"""
super().__init__()
self.alternate_names = []
self.class_name = None
self.identifier = identifier
self.localized_string = localized_string
self.name = None
[docs]
class WindowsShellExtractor(dfvfs_volume_scanner.WindowsVolumeScanner):
"""Windows shell extractor.
Attributes:
ascii_codepage (str): ASCII string codepage.
preferred_language_identifier (int): preferred language identifier (LCID).
"""
_CLASS_IDENTIFIERS_KEY_PATH = "HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID"
[docs]
def __init__(self, debug=False, mediator=None):
"""Initializes a Windows shell extractor.
Args:
debug (Optional[bool]): True if debug information should be printed.
mediator (dfvfs.VolumeScannerMediator): a volume scanner mediator or None.
"""
super().__init__(mediator=mediator)
self._debug = debug
self._format_scanner = None
self._registry = None
self._windows_version = None
self.ascii_codepage = "cp1252"
self.preferred_language_identifier = 0x0409
@property
def windows_version(self):
"""The Windows version (getter)."""
if self._windows_version is None:
self._windows_version = self._GetWindowsVersion()
return self._windows_version
@windows_version.setter
def windows_version(self, value):
"""The Windows version (setter)."""
self._windows_version = value
def _CollectShellFoldersFromKey(self, class_identifiers_key):
"""Retrieves shell folders from a Windows Registry key.
Args:
class_identifiers_key (dfwinreg.RegistryKey): class identifiers Windows
Registry key.
Yields:
ShellFolder: shell folder.
"""
for class_identifier_key in class_identifiers_key.GetSubkeys():
shell_folder_identifier = class_identifier_key.name.lower()
if shell_folder_identifier[0] == "{" and shell_folder_identifier[-1] == "}":
shell_folder_identifier = shell_folder_identifier[1:-1]
shell_folder_key = class_identifier_key.GetSubkeyByName("ShellFolder")
if shell_folder_key:
name = self._GetShellFolderName(class_identifier_key)
if name and name[0] == "@" and ",-" in name:
path, string_identifier = name[1:].rsplit(",-", maxsplit=1)
if ";" in string_identifier:
string_identifier, _ = string_identifier.rsplit(";", maxsplit=1)
elif "#" in string_identifier:
string_identifier, _ = string_identifier.rsplit("#", maxsplit=1)
elif "@" in string_identifier:
string_identifier, _ = string_identifier.rsplit("@", maxsplit=1)
windows_resource_file = self._GetStringResourceFile(path)
if windows_resource_file:
try:
string_identifier = int(string_identifier, 10)
name = self._GetString(
windows_resource_file, string_identifier
)
except ValueError:
pass
value = class_identifier_key.GetValueByName("LocalizedString")
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
localized_string = value.data.decode("utf-16-le").rstrip("\x00")
else:
localized_string = None
shell_folder = ShellFolder(
identifier=shell_folder_identifier,
localized_string=localized_string,
)
if name and name.startswith("CLSID_"):
shell_folder.class_name = name
else:
shell_folder.name = name
yield shell_folder
def _GetMUIWindowsResourceFile(self, windows_path, windows_resource_file):
"""Retrieves a MUI resource file.
Args:
windows_path (str): Windows path of the language neutral resource file.
windows_resource_file (WindowsResourceFile): language neutral resource
file.
Returns:
WindowsResourceFile: MUI resource file or None if not available.
"""
mui_language = windows_resource_file.GetMUILanguage()
if not mui_language:
return None
path, _, name = windows_path.rpartition("\\")
mui_windows_path = "\\".join([path, mui_language, f"{name:s}.mui"])
mui_windows_resource_file = self._OpenWindowsResourceFile(mui_windows_path)
if not mui_windows_resource_file:
mui_windows_path = "\\".join([path, f"{name:s}.mui"])
mui_windows_resource_file = self._OpenWindowsResourceFile(mui_windows_path)
if mui_windows_resource_file:
logging.info(
(
f"Resource file: {windows_path:s} references MUI resource file: "
f"{mui_windows_path:s}"
)
)
return mui_windows_resource_file
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 _GetString(self, windows_resource_file, string_identifier):
"""Retrieves a string from a Windows resource file.
Args:
windows_resource_file (WindowsResourceFile): Windows resource file.
string_identifier (int): string identifier.
Returns:
str: string or None if not available.
"""
wrc_resource = windows_resource_file.GetStringTableResource()
if not wrc_resource:
return None
for wrc_resource_item in wrc_resource.items:
base_string_identifier = wrc_resource_item.identifier * 16
if string_identifier <= base_string_identifier + 16:
wrc_resource_sub_item = wrc_resource_item.sub_items[0]
resource_data = wrc_resource_sub_item.read()
string_table_resource = pywrc.string_table_resource()
string_table_resource.copy_from_byte_stream(
resource_data, wrc_resource_item.identifier
)
for index in range(string_table_resource.number_of_strings):
stored_string_identifier = (
string_table_resource.get_string_identifier(index)
)
if string_identifier == stored_string_identifier:
return string_table_resource.get_string(index)
return None
def _GetStringResourceFile(self, windows_path):
"""Retrieves a string resource.
Args:
windows_path (str): Windows path of the Windows resource file.
Returns:
WindowsResourceFile: string resource file or None if not available.
"""
windows_resource_file = None
path_spec = self._path_resolver.ResolvePath(windows_path)
if path_spec:
windows_resource_file = self._OpenWindowsResourceFileByPathSpec(path_spec)
if not windows_resource_file:
logging.warning(f"Missing resource file: {windows_path:s}")
return None
if not windows_resource_file.HasStringTableResource():
# Windows Vista and later use a MUI resource to redirect to
# a language specific resource file.
mui_windows_resource_file = self._GetMUIWindowsResourceFile(
windows_path, windows_resource_file
)
if mui_windows_resource_file:
windows_resource_file.Close()
windows_resource_file = mui_windows_resource_file
if not windows_resource_file.HasStringTableResource():
logging.warning(
(
f"String table resource missing from resource file: "
f"{windows_path:s}"
)
)
windows_resource_file.Close()
return None
return windows_resource_file
def _GetSystemRoot(self):
"""Determines the value of %SystemRoot%.
Returns:
str: value of SystemRoot or None if the value cannot be determined.
"""
current_version_key = self._registry.GetKeyByPath(
"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion"
)
system_root = None
if current_version_key:
system_root_value = current_version_key.GetValueByName("SystemRoot")
if system_root_value:
system_root = system_root_value.GetDataAsObject()
if not system_root:
system_root = self._windows_directory
return system_root
def _GetWindowsVersion(self):
"""Determines the Windows version from kernel executable file.
Returns:
str: Windows version or None otherwise.
"""
system_root = self._GetSystemRoot()
# Windows NT variants.
kernel_executable_path = "\\".join([system_root, "System32", "ntoskrnl.exe"])
windows_resource_file = self._OpenWindowsResourceFile(kernel_executable_path)
if not windows_resource_file:
# Windows 9x variants.
kernel_executable_path = "\\".join(
[system_root, "System32", "\\kernel32.dll"]
)
windows_resource_file = self._OpenWindowsResourceFile(
kernel_executable_path
)
if not windows_resource_file:
# Windows Me variant.
kernel_executable_path = "\\".join(
[system_root, "System", "\\kernel32.dll"]
)
windows_resource_file = self._OpenWindowsResourceFile(
kernel_executable_path
)
if not windows_resource_file:
return None
return windows_resource_file.file_version
def _OpenWindowsResourceFile(self, windows_path):
"""Opens the Windows resource file specified by the Windows path.
Args:
windows_path (str): Windows path of the Windows resource file.
Returns:
WindowsResourceFile: Windows resource file or None.
"""
path_spec = self._path_resolver.ResolvePath(windows_path)
if path_spec is None:
return None
return self._OpenWindowsResourceFileByPathSpec(path_spec)
def _OpenWindowsResourceFileByPathSpec(self, path_spec):
"""Opens the Windows resource file specified by the path specification.
Args:
path_spec (dfvfs.PathSpec): path specification.
Returns:
WindowsResourceFile: Windows resource file or None.
"""
windows_path = self._path_resolver.GetWindowsPath(path_spec)
if windows_path is None:
logging.warning("Unable to retrieve Windows path.")
try:
file_object = dfvfs_resolver.Resolver.OpenFileObject(path_spec)
except OSError as exception:
logging.warning(
f"Unable to open: {path_spec.comparable:s} with error: {exception!s}"
)
file_object = None
if file_object is None:
return None
windows_resource_file = resource_file.WindowsResourceFile(
windows_path,
ascii_codepage=self.ascii_codepage,
preferred_language_identifier=self.preferred_language_identifier,
)
windows_resource_file.OpenFileObject(file_object)
return windows_resource_file
[docs]
def CollectShellFolders(self):
"""Retrieves shell folders.
Yields:
ShellFolder: shell folder.
"""
class_identifiers_key = self._registry.GetKeyByPath(
self._CLASS_IDENTIFIERS_KEY_PATH
)
if class_identifiers_key:
yield from self._CollectShellFoldersFromKey(class_identifiers_key)
# TODO: Add support for per-user shell folders
[docs]
def ScanForWindowsVolume(self, source_path, options=None):
"""Scans for a Windows volume.
Args:
source_path (str): source path.
options (Optional[VolumeScannerOptions]): volume scanner options. If None
the default volume scanner options are used, which are defined in the
VolumeScannerOptions class.
Returns:
bool: True if a Windows volume was found.
Raises:
ScannerError: if the source path does not exists, or if the source path
is not a file or directory, or if the format of or within
the source file is not supported.
"""
result = super().ScanForWindowsVolume(source_path, options=options)
if not result:
return False
registry_file_reader = (
windows_registry.StorageMediaImageWindowsRegistryFileReader(
self._file_system, self._path_resolver
)
)
self._registry = dfwinreg_registry.WinRegistry(
registry_file_reader=registry_file_reader
)
return True