"""Classes for Windows Registry access."""
from dfwinreg import definitions
from dfwinreg import key_paths
from dfwinreg import regf
from dfwinreg import virtual
[docs]
class WinRegistryFileMapping:
"""Windows Registry file mapping.
Attributes:
key_path_prefix (str): Windows Registry key path prefix.
unique_key_paths (list[str]): key paths unique to the Windows Registry file.
windows_path (str): Windows path to the Windows Registry file, such as:
C:\\Windows\\System32\\config\\SYSTEM
"""
[docs]
def __init__(self, key_path_prefix, windows_path, unique_key_paths):
"""Initializes the Windows Registry file mapping.
Args:
key_path_prefix (str): Windows Registry key path prefix.
windows_path (str): Windows path to the Windows Registry file, such as:
C:\\Windows\\System32\\config\\SYSTEM
unique_key_paths (list[str]): key paths unique to the Windows Registry
file.
"""
super().__init__()
self.key_path_prefix = key_path_prefix
self.unique_key_paths = unique_key_paths
self.windows_path = windows_path
[docs]
class WinRegistry:
"""Windows Registry."""
_REGISTRY_FILE_MAPPINGS = [
# TODO: Windows 3.1
# Windows 9x/Me
WinRegistryFileMapping(
"HKEY_LOCAL_MACHINE",
"%SystemRoot%\\SYSTEM.DAT",
["\\Config", "\\Enum", "\\Hardware", "\\Network", "\\Software", "\\System"],
),
WinRegistryFileMapping(
"HKEY_USERS",
"%SystemRoot%\\USER.DAT",
[
"\\.DEFAULT\\AppEvents",
"\\.DEFAULT\\Control Panel",
"\\.DEFAULT\\Keyboard Layout",
"\\.DEFAULT\\Network",
"\\.DEFAULT\\Software",
],
),
# Windows NT
WinRegistryFileMapping(
"HKEY_CURRENT_USER",
"%UserProfile%\\NTUSER.DAT",
[
"\\AppEvents",
"\\Console",
"\\Control Panel",
"\\Environment",
"\\Keyboard Layout",
"\\Software",
],
),
# Windows Vista and later UsrClass.dat mapping
WinRegistryFileMapping(
"HKEY_CURRENT_USER\\Software\\Classes",
"%UserProfile%\\AppData\\Local\\Microsoft\\Windows\\UsrClass.dat",
["\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion"],
),
# Windows 2000, XP and 2003 UsrClass.dat mapping
WinRegistryFileMapping(
"HKEY_CURRENT_USER\\Software\\Classes",
(
"%UserProfile%\\Local Settings\\Application Data\\Microsoft\\"
"Windows\\UsrClass.dat"
),
["\\Software\\Microsoft\\Windows\\CurrentVersion"],
),
WinRegistryFileMapping(
"HKEY_LOCAL_MACHINE\\SAM",
"%SystemRoot%\\System32\\config\\SAM",
["\\SAM\\Domains\\Account\\Users"],
),
WinRegistryFileMapping(
"HKEY_LOCAL_MACHINE\\Security",
"%SystemRoot%\\System32\\config\\SECURITY",
["\\Policy\\PolAdtEv"],
),
WinRegistryFileMapping(
"HKEY_LOCAL_MACHINE\\Software",
"%SystemRoot%\\System32\\config\\SOFTWARE",
["\\Microsoft\\Windows\\CurrentVersion\\App Paths"],
),
WinRegistryFileMapping(
"HKEY_LOCAL_MACHINE\\System",
"%SystemRoot%\\System32\\config\\SYSTEM",
["\\MountedDevices", "\\Select", "\\Setup"],
),
]
_ROOT_KEY_ALIASES = {
"HKCC": "HKEY_CURRENT_CONFIG",
"HKCR": "HKEY_CLASSES_ROOT",
"HKCU": "HKEY_CURRENT_USER",
"HKLM": "HKEY_LOCAL_MACHINE",
"HKU": "HKEY_USERS",
}
_ROOT_SUB_KEYS = frozenset(
[
"HKEY_CLASSES_ROOT",
"HKEY_CURRENT_CONFIG",
"HKEY_CURRENT_USER",
"HKEY_DYN_DATA",
"HKEY_LOCAL_MACHINE",
"HKEY_PERFORMANCE_DATA",
"HKEY_USERS",
]
)
_LOCAL_MACHINE_SUB_KEYS = frozenset(["SAM", "Security", "Software", "System"])
_SELECT_KEY_PATH = "HKEY_LOCAL_MACHINE\\System\\Select"
_USER_PROFILE_LIST_KEY_PATH = (
"HKEY_LOCAL_MACHINE\\Software\\Microsoft\\Windows NT\\CurrentVersion\\"
"ProfileList"
)
_USER_SOFTWARE_CLASSES_KEY_PATH = "HKEY_CURRENT_USER\\SOFTWARE\\CLASSES"
# TODO: add support for HKEY_CLASSES_ROOT
# TODO: add support for HKEY_CURRENT_CONFIG
# TODO: add support for HKEY_CURRENT_USER
# TODO: add support for HKEY_DYN_DATA
_VIRTUAL_KEYS = [("HKEY_USERS", "_GetUsersVirtualKey")]
[docs]
def __init__(self, ascii_codepage="cp1252", registry_file_reader=None):
"""Initializes the Windows Registry.
Args:
ascii_codepage (Optional[str]): ASCII string codepage.
registry_file_reader (Optional[WinRegistryFileReader]): Windows Registry
file reader.
"""
super().__init__()
self._ascii_codepage = ascii_codepage
self._registry_file_reader = registry_file_reader
self._registry_files = {}
self._root_key = None
self._user_registry_files = {}
[docs]
def __del__(self):
"""Cleans up the Windows Registry object."""
for key_path_prefix_upper, registry_file in self._registry_files.items():
self._registry_files[key_path_prefix_upper] = None
if registry_file:
registry_file.Close()
for profile_path_upper, registry_file in self._user_registry_files.items():
self._user_registry_files[profile_path_upper] = None
if registry_file:
registry_file.Close()
def _GetCachedFileByPath(self, key_path_upper):
"""Retrieves a cached Windows Registry file for a key path.
Args:
key_path_upper (str): Windows Registry key path, in upper case with
a resolved root key alias.
Returns:
tuple: consist:
str: key path prefix
WinRegistryFile: corresponding Windows Registry file or None if not
available.
"""
longest_key_path_prefix_upper = ""
longest_key_path_prefix_length = len(longest_key_path_prefix_upper)
for key_path_prefix_upper in self._registry_files:
if key_path_upper.startswith(key_path_prefix_upper):
key_path_prefix_length = len(key_path_prefix_upper)
if key_path_prefix_length > longest_key_path_prefix_length:
longest_key_path_prefix_upper = key_path_prefix_upper
longest_key_path_prefix_length = key_path_prefix_length
if not longest_key_path_prefix_upper:
return None, None
registry_file = self._registry_files.get(longest_key_path_prefix_upper, None)
return longest_key_path_prefix_upper, registry_file
def _GetCachedUserFileByPath(self, profile_path_upper):
"""Retrieves a cached user Windows Registry file for a profile path.
Args:
profile_path_upper (str): user profile path, in upper case.
Returns:
WinRegistryFile: corresponding Windows Registry file or None if not
available.
"""
return self._user_registry_files.get(profile_path_upper)
def _GetCandidateFileMappingsByPath(self, key_path_upper):
"""Retrieves candidate Windows Registry file mappings for a specific path.
Args:
key_path_upper (str): Windows Registry key path, in upper case with
a resolved root key alias.
Returns:
list[WinRegistryFileMapping]: candidate Windows Registry file mappings.
"""
if key_path_upper[-1] == "\\":
key_path_upper = key_path_upper[:-1]
candidate_mappings = []
for mapping in self._REGISTRY_FILE_MAPPINGS:
if key_path_upper.startswith(mapping.key_path_prefix.upper()):
candidate_mappings.append(mapping)
return candidate_mappings
def _GetFileByPath(self, key_path_upper):
"""Retrieves a Windows Registry file for a specific path.
Args:
key_path_upper (str): Windows Registry key path, in upper case with
a resolved root key alias.
Returns:
tuple[st, WinRegistryFile]: upper case key path prefix and corresponding
Windows Registry file or None if not available.
"""
key_path_prefix, cached_registry_file = self._GetCachedFileByPath(
key_path_upper
)
# NTUSER.DAT and UsrClass.dat have overlapping keys hence for key paths that
# start "HKEY_CURRENT_USER\\Software\\Classes" do not assume the Windows
# Registry file for "HKEY_CURRENT_USER" contains the classes key as well.
if cached_registry_file and (
key_path_prefix != "HKEY_CURRENT_USER"
or not key_path_upper.startswith("HKEY_CURRENT_USER\\SOFTWARE\\CLASSES")
):
return key_path_prefix, cached_registry_file
key_path_prefix = ""
candidate_mappings = self._GetCandidateFileMappingsByPath(key_path_upper)
matching_mappings = []
for mapping in candidate_mappings:
mapping_key_path_prefix = mapping.key_path_prefix.upper()
if mapping_key_path_prefix in self._registry_files:
# Skip the Windows Registry file if it is already cached.
continue
try:
registry_file = self._OpenFile(mapping.windows_path)
except OSError:
registry_file = None
if len(key_path_prefix) < len(mapping_key_path_prefix):
key_path_prefix = mapping_key_path_prefix
if not registry_file:
continue
match = True
if mapping.unique_key_paths:
# If all unique key paths are found consider the file to match.
for unique_key_path in mapping.unique_key_paths:
registry_key = registry_file.GetKeyByPath(unique_key_path)
if not registry_key:
match = False
if match:
matching_mappings.append((mapping, registry_file))
if not matching_mappings:
return key_path_prefix, cached_registry_file
key_path_prefix = None
registry_file = None
while matching_mappings:
mapping, matching_registry_file = matching_mappings.pop(0)
if not key_path_prefix or len(mapping.key_path_prefix) > len(
key_path_prefix
):
if registry_file:
registry_file.Close()
key_path_prefix = mapping.key_path_prefix
registry_file = matching_registry_file
key_path_prefix_upper = key_path_prefix.upper()
self.MapFile(key_path_prefix_upper, registry_file)
return key_path_prefix_upper, registry_file
def _GetKeyByPathFromFile(self, key_path):
"""Retrieves the key for a specific path from file.
Args:
key_path (str): Windows Registry key path.
Returns:
WinRegistryKey: Windows Registry key or None if not available.
Raises:
RuntimeError: if the key path prefix does not match the key path.
"""
key_path_upper = key_path.upper()
key_path_prefix_upper, registry_file = self._GetFileByPath(key_path_upper)
if not registry_file:
return None
if not key_path_upper.startswith(key_path_prefix_upper):
raise RuntimeError("Key path prefix mismatch.")
key_path_suffix = key_path[len(key_path_prefix_upper) :]
relative_key_path = key_path_suffix or definitions.KEY_PATH_SEPARATOR
return registry_file.GetKeyByPath(relative_key_path)
def _GetRootVirtualKey(self):
"""Retrieves the root key.
Returns:
VirtualWinRegistryKey: Windows Registry root key.
Raises:
RuntimeError: if there are multiple matching mappings and
the correct mapping cannot be resolved.
"""
if not self._root_key:
self._root_key = virtual.VirtualWinRegistryKey("")
local_machine_key = None
for sub_key_name in self._ROOT_SUB_KEYS:
sub_registry_key = self.GetKeyByPath(sub_key_name)
if not sub_registry_key:
sub_registry_key = virtual.VirtualWinRegistryKey(
sub_key_name, registry=self, relative_key_path=sub_key_name
)
if sub_key_name == "HKEY_LOCAL_MACHINE":
local_machine_key = sub_registry_key
self._root_key.AddSubkey(sub_key_name, sub_registry_key)
if local_machine_key:
for sub_key_name in self._LOCAL_MACHINE_SUB_KEYS:
sub_key_path = definitions.KEY_PATH_SEPARATOR.join(
["HKEY_LOCAL_MACHINE", sub_key_name]
)
sub_registry_key = self.GetKeyByPath(sub_key_path)
if not sub_registry_key:
sub_registry_key = virtual.VirtualWinRegistryKey(
sub_key_name, registry=self, relative_key_path=sub_key_name
)
local_machine_key.AddSubkey(sub_key_name, sub_registry_key)
return self._root_key
def _GetUserFileByPath(self, path):
"""Retrieves an user Windows Registry file for a specific path.
Args:
path (str): path of the user Windows Registry file.
Returns:
WinRegistryFile: corresponding Windows Registry file or None if not
available.
"""
path_upper = path.upper()
registry_file = self._GetCachedUserFileByPath(path_upper)
if not registry_file:
try:
registry_file = self._OpenFile(path)
except OSError:
registry_file = None
return registry_file
def _GetUsersVirtualKey(self, key_path_suffix):
"""Virtual key callback to determine the users sub keys.
Args:
key_path_suffix (str): users Windows Registry key path suffix with
leading path separator.
Returns:
WinRegistryKey: the users Windows Registry key or None if not available.
Raises:
RuntimeError: if the key path suffix is not supported.
"""
if key_path_suffix[0] != definitions.KEY_PATH_SEPARATOR:
raise RuntimeError(f"Unsupported key path suffix: {key_path_suffix:s}")
user_key_name, _, key_path_suffix = key_path_suffix[1:].partition(
definitions.KEY_PATH_SEPARATOR
)
user_key_name = user_key_name.upper()
# HKEY_USERS\.DEFAULT is an alias for HKEY_USERS\S-1-5-18 which is
# the Local System account.
if user_key_name == ".DEFAULT":
search_key_name = "S-1-5-18"
else:
search_key_name = user_key_name
if search_key_name.endswith("_CLASSES"):
search_key_name = search_key_name[:-8]
user_profile_list_key = self.GetKeyByPath(self._USER_PROFILE_LIST_KEY_PATH)
if not user_profile_list_key:
return None
for user_profile_key in user_profile_list_key.GetSubkeys():
if search_key_name == user_profile_key.name:
profile_path_value = user_profile_key.GetValueByName("ProfileImagePath")
if not profile_path_value:
break
profile_path = profile_path_value.GetDataAsObject()
if not profile_path:
break
# HKEY_USERS\%SID%_CLASSES maps to UsrClass.dat
if user_key_name.endswith("_CLASSES"):
profile_path = definitions.KEY_PATH_SEPARATOR.join(
[
profile_path,
"AppData",
"Local",
"Microsoft",
"Windows",
"UsrClass.dat",
]
)
else:
profile_path = definitions.KEY_PATH_SEPARATOR.join(
[profile_path, "NTUSER.DAT"]
)
registry_file = self._GetUserFileByPath(profile_path)
if not registry_file:
break
key_path_prefix = definitions.KEY_PATH_SEPARATOR.join(
["HKEY_USERS", user_key_name]
)
key_path = definitions.KEY_PATH_SEPARATOR.join(
[key_path_prefix, key_path_suffix]
)
registry_file.SetKeyPathPrefix(key_path_prefix)
return registry_file.GetKeyByPath(key_path)
return None
def _GetVirtualKeyByPath(self, key_path):
"""Retrieves the virtual key for a specific path.
Args:
key_path (str): Windows Registry key path.
Returns:
VirtualWinRegistryKey: virtual Windows Registry key or None if not
available.
"""
key_path_upper = key_path.upper()
for virtual_key_path, virtual_key_callback in self._VIRTUAL_KEYS:
virtual_key_path_upper = virtual_key_path.upper()
if key_path_upper.startswith(virtual_key_path_upper):
key_path_suffix = key_path[len(virtual_key_path) :]
callback_function = getattr(self, virtual_key_callback)
virtual_key = callback_function(key_path_suffix)
if virtual_key:
return virtual_key
return None
def _OpenFile(self, path):
"""Opens a Windows Registry file.
Args:
path (str): path of the Windows Registry file.
Returns:
WinRegistryFile: Windows Registry file or None if not available.
"""
if not self._registry_file_reader:
return None
return self._registry_file_reader.Open(
path, ascii_codepage=self._ascii_codepage
)
[docs]
def GetKeyByPath(self, key_path):
"""Retrieves the key for a specific path.
Args:
key_path (str): Windows Registry key path.
Returns:
WinRegistryKey: Windows Registry key or None if not available.
Raises:
RuntimeError: if the root key is not supported or the key path prefix
does not match the key path.
"""
root_key_path, _, key_path = key_path.partition(definitions.KEY_PATH_SEPARATOR)
# Resolve a root key alias.
root_key_path = root_key_path.upper()
root_key_path = self._ROOT_KEY_ALIASES.get(root_key_path, root_key_path)
if root_key_path not in self._ROOT_SUB_KEYS:
raise RuntimeError(f"Unsupported root subkey: {root_key_path:s}")
key_path = definitions.KEY_PATH_SEPARATOR.join([root_key_path, key_path])
registry_key = self._GetKeyByPathFromFile(key_path)
if not registry_key:
registry_key = self._GetVirtualKeyByPath(key_path)
return registry_key
[docs]
def GetRegistryFileMapping(self, registry_file):
"""Determines the Registry file mapping based on the content of the file.
Args:
registry_file (WinRegistyFile): Windows Registry file.
Returns:
str: key path prefix or an empty string.
Raises:
RuntimeError: if there are multiple matching mappings and
the correct mapping cannot be resolved.
"""
if not registry_file:
return ""
candidate_mappings = []
for mapping in self._REGISTRY_FILE_MAPPINGS:
if not mapping.unique_key_paths:
continue
# If all unique key paths are found consider the file to match.
match = True
for key_path in mapping.unique_key_paths:
registry_key = registry_file.GetKeyByPath(key_path)
if not registry_key:
match = False
if match:
candidate_mappings.append(mapping)
if not candidate_mappings:
return ""
if len(candidate_mappings) == 1:
return candidate_mappings[0].key_path_prefix
key_path_prefixes = frozenset(
[mapping.key_path_prefix for mapping in candidate_mappings]
)
expected_key_path_prefixes = frozenset(
["HKEY_CURRENT_USER", "HKEY_CURRENT_USER\\Software\\Classes"]
)
if key_path_prefixes == expected_key_path_prefixes:
return "HKEY_CURRENT_USER"
raise RuntimeError("Unable to resolve Windows Registry file mapping.")
[docs]
def GetRootKey(self):
"""Retrieves the Windows Registry root key.
Returns:
VirtualWinRegistryKey: Windows Registry root key.
"""
return self._GetRootVirtualKey()
[docs]
def MapFile(self, key_path_prefix, registry_file):
"""Maps the Windows Registry file to a specific key path prefix.
Args:
key_path_prefix (str): key path prefix.
registry_file (WinRegistryFile): Windows Registry file.
"""
key_path_prefix_upper = key_path_prefix.upper()
self._registry_files[key_path_prefix_upper] = registry_file
registry_file.SetKeyPathPrefix(key_path_prefix)
# If HKEY_CURRENT_USER set HKEY_CURRENT_USER\\Software\\Classes as a virtual
# key in the file.
if key_path_prefix_upper == "HKEY_CURRENT_USER" and isinstance(
registry_file, regf.REGFWinRegistryFile
):
key_path_prefix_upper, usrclass_registry_file = self._GetFileByPath(
self._USER_SOFTWARE_CLASSES_KEY_PATH
)
if (
key_path_prefix_upper == self._USER_SOFTWARE_CLASSES_KEY_PATH
and usrclass_registry_file is not None
):
registry_key = usrclass_registry_file.GetKeyByPath(
"\\Software\\Classes"
)
if registry_key and isinstance(registry_key, regf.REGFWinRegistryKey):
# pylint: disable=protected-access
pyregf_key = registry_key._pyregf_key
registry_file.AddVirtualKey("\\Software\\Classes", pyregf_key)
# If HKEY_LOCAL_MACHINE\\System set HKEY_LOCAL_MACHINE\\System\\
# CurrentControlSet as a virtual key in the file.
elif key_path_prefix_upper == "HKEY_LOCAL_MACHINE\\SYSTEM" and isinstance(
registry_file, regf.REGFWinRegistryFile
):
registry_file.AddCurrentControlSetKey()
[docs]
def MapUserFile(self, profile_path, registry_file):
"""Maps the user Windows Registry file to a specific profile path.
Args:
profile_path (str): profile path.
registry_file (WinRegistryFile): user Windows Registry file.
"""
self._user_registry_files[profile_path.upper()] = registry_file
[docs]
def OpenAndMapFile(self, path):
"""Opens Windows Registry file and maps it to its key path prefix.
Args:
path (str): path of the Windows Registry file.
"""
registry_file = self._OpenFile(path)
key_path_prefix = self.GetRegistryFileMapping(registry_file)
self.MapFile(key_path_prefix, registry_file)
[docs]
def SplitKeyPath(self, key_path):
"""Splits the key path into path segments.
Args:
key_path (str): key path.
Returns:
list[str]: key path segments without the root path segment, which is an
empty string.
"""
return key_paths.SplitKeyPath(key_path)