Source code for winspsrc.extractor
"""Windows serialized property extractor."""
import logging
# TODO: add CREG support
import pyfwps
import pyfwsi
import pylnk
import pyolecf
import pyregf
import pysigscan
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 winspsrc import errors
from winspsrc import jump_list
from winspsrc import resource_file
[docs]
class SerializedProperty:
"""Windows serialized property.
Attributes:
format_identifier (str): format class (or property set) identifier.
property_identifier (str): identifier of the property within the format
class (or property set).
origin (str): path of the file from which the property originates.
value_type (int): value type used by the property.
"""
[docs]
def __init__(self):
"""Initializes a Windows serialized property."""
super().__init__()
self.format_identifier = None
self.property_identifier = None
self.origin = None
self.value_type = None
@property
def lookup_key(self):
"""str: lookup key."""
return f"{{{self.format_identifier:s}}}/{self.property_identifier:s}"
[docs]
class SerializedPropertyExtractor(dfvfs_volume_scanner.WindowsVolumeScanner):
"""Windows serialized property extractor.
Attributes:
ascii_codepage (str): ASCII string codepage.
preferred_language_identifier (int): preferred language identifier (LCID).
"""
_LNK_GUID = b"\x01\x14\x02\x00\x00\x00\x00\x00\xc0\x00\x00\x00\x00\x00\x00\x46"
_SHELL_ITEM_MRU_KEY_PATHS = [
"\\Local Settings\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
"\\Local Settings\\Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU",
"\\Software\\Microsoft\\Windows\\Shell\\BagMRU",
"\\Software\\Microsoft\\Windows\\ShellNoRoam\\BagMRU",
(
"\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\"
"Shell\\BagMRU"
),
(
"\\Software\\Classes\\Local Settings\\Software\\Microsoft\\Windows\\"
"ShellNoRoam\\BagMRU"
),
(
"\\Software\\Classes\\Wow6432Node\\Local Settings\\Software\\"
"Microsoft\\Windows\\Shell\\BagMRU"
),
(
"\\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
]
_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
(
"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\"
"DesktopStreamMRU"
),
(
"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\"
"OpenSavePidlMRU"
),
"\\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
]
_STRING_AND_SHELL_ITEM_MRU_KEY_PATHS = [
"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs"
]
_STRING_AND_SHELL_ITEM_MRU_KEY_PATHS = [
key_path.upper() for key_path in _STRING_AND_SHELL_ITEM_MRU_KEY_PATHS
]
_STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
(
"\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\"
"LastVisitedPidlMRU"
)
]
_STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS = [
key_path.upper() for key_path in _STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS
]
[docs]
def __init__(self, debug=False, mediator=None):
"""Initializes a Windows serialized property 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 _CreateFormatScanner(self):
"""Creates a signature scanner for required format check."""
scanner_object = pysigscan.scanner()
scanner_object.set_scan_buffer_size(65536)
# Custom Destinations Jump List (.customDestinations-ms) file.
scanner_object.add_signature(
"custom_destination",
4,
b"\xab\xfb\xbf\xba",
pysigscan.signature_flags.RELATIVE_FROM_END,
)
# Windows Shortcut (LNK) file.
scanner_object.add_signature(
"lnk", 4, self._LNK_GUID, pysigscan.signature_flags.RELATIVE_FROM_START
)
# OLE Compound File (OLECF).
scanner_object.add_signature(
"olecf",
0,
b"\xd0\xcf\x11\xe0\xa1\xb1\x1a\xe1",
pysigscan.signature_flags.RELATIVE_FROM_START,
)
# beta version of OLE Compound File (OLECF).
scanner_object.add_signature(
"olecf_beta",
0,
b"\x0e\x11\xfc\x0d\xd0\xcf\x11\x0e",
pysigscan.signature_flags.RELATIVE_FROM_START,
)
# Windows NT Registry File (REGF).
scanner_object.add_signature(
"regf", 0, b"regf", pysigscan.signature_flags.RELATIVE_FROM_START
)
self._format_scanner = scanner_object
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:
return None
return windows_resource_file.file_version
def _ListFileEntry(self, file_entry, parent_path_segments):
"""Lists a file entry.
Args:
file_entry (dfvfs.FileEntry): file entry to list.
parent_path_segments (str): path segments of the full path of the parent
file entry.
Yields:
tuple[dfvfs.FileEntry, list[str]]: file entry and path segments.
"""
path_segments = list(parent_path_segments)
if not file_entry.IsRoot():
path_segments.append(file_entry.name)
if file_entry.IsFile():
yield file_entry, path_segments
for sub_file_entry in file_entry.sub_file_entries:
yield from self._ListFileEntry(sub_file_entry, path_segments)
def _ListFileEntries(self):
"""Lists file entries.
Yields:
tuple[dfvfs.FileEntry, list[str]]: file entry and path segments.
"""
file_entry = self._file_system.GetRootFileEntry()
yield from self._ListFileEntry(file_entry, [""])
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
def _CollectSerializedProperiesFromAutomaticDestinationsFile(
self, file_object, path_segments
):
"""Retrieves serialized properties from a .automaticDestinations-ms file.
Args:
file_object (dfvfs.FileIO): file-like object.
path_segments (str): path segments of the full path of the file entry.
Yields:
SerializedProperty: serialized property.
"""
jump_list_file = jump_list.AutomaticDestinationsFile()
olecf_file = pyolecf.file()
olecf_item = None
try:
olecf_file.open_file_object(file_object)
try:
if olecf_file.root_item: # pylint: disable=using-constant-test
# pylint: disable=no-member
olecf_item = olecf_file.root_item.get_sub_item_by_name("DestList")
finally:
olecf_file.close()
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(f"Unable to open: {path:s} with error: {exception!s}")
if olecf_item:
try:
jump_list_file.Open(file_object)
try:
for jump_list_entry in jump_list_file.GetJumpListEntries():
yield from self._CollectSerializedProperiesFromLNK(
jump_list_entry.lnk_file
)
finally:
jump_list_file.Close()
except errors.ParseError as exception:
path = "\\".join(path_segments)
logging.warning(
f"Unable to parse .automaticDestinations-ms file: "
f"{path:s} with error: {exception!s}"
)
def _CollectSerializedProperiesFromCustomDestinationsFile(
self, file_object, path_segments
):
"""Retrieves serialized properties from a .customDestinations-ms file.
Args:
file_object (dfvfs.FileIO): file-like object.
path_segments (str): path segments of the full path of the file entry.
Yields:
SerializedProperty: serialized property.
"""
jump_list_file = jump_list.CustomDestinationsFile()
try:
jump_list_file.Open(file_object)
try:
for jump_list_entry in jump_list_file.GetJumpListEntries():
yield from self._CollectSerializedProperiesFromLNK(
jump_list_entry.lnk_file
)
finally:
jump_list_file.Close()
except errors.ParseError as exception:
path = "\\".join(path_segments)
logging.warning(
f"Unable to parse .customDestinations-ms file: "
f"{path:s} with error: {exception!s}"
)
def _CollectSerializedProperiesFromLNK(self, lnk_file):
"""Retrieves serialized properties from a Windows Shortcut (LNK).
Args:
lnk_file (pylnk.file): Windows Shortcut (LNK) file.
Yields:
SerializedProperty: serialized property.
"""
if lnk_file.link_target_identifier_data:
fwsi_item_list = pyfwsi.item_list()
fwsi_item_list.copy_from_byte_stream(lnk_file.link_target_identifier_data)
yield from self._CollectSerializedProperiesFromShellItemList(fwsi_item_list)
for lnk_data_block in iter(lnk_file.data_blocks):
if lnk_data_block.signature == 0xA0000009 and lnk_data_block.data:
fwps_store = pyfwps.store()
fwps_store.copy_from_byte_stream(lnk_data_block.data)
yield from self._CollectSerializedProperiesFromPropertyStore(fwps_store)
def _CollectSerializedProperiesFromLNKFile(self, file_object, path_segments):
"""Retrieves serialized properties from a Windows Shortcut (LNK) file.
Args:
file_object (dfvfs.FileIO): file-like object.
path_segments (str): path segments of the full path of the file entry.
Yields:
SerializedProperty: serialized property.
"""
lnk_file = pylnk.file()
try:
lnk_file.open_file_object(file_object)
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(f"Unable to open: {path:s} with error: {exception!s}")
try:
yield from self._CollectSerializedProperiesFromLNK(lnk_file)
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(
f"Unable to collect serialized properties from: {path:s} with "
f"error: {exception!s}"
)
finally:
lnk_file.close()
def _CollectSerializedProperiesFromPropertyStore(self, fwps_store):
"""Retrieves serialized properties from a property store.
Args:
fwps_store (pyfwps.store): property store.
Yields:
SerializedProperty: serialized property.
"""
for fwps_set in iter(fwps_store.sets):
for fwps_record in iter(fwps_set.records):
if fwps_record.entry_name:
property_identifier = fwps_record.entry_name
else:
property_identifier = f"{fwps_record.entry_type:d}"
serialized_property = SerializedProperty()
serialized_property.format_identifier = fwps_set.identifier
serialized_property.property_identifier = property_identifier
serialized_property.value_type = fwps_record.value_type
yield serialized_property
def _CollectSerializedProperiesFromREGFFile(self, file_object, path_segments):
"""Retrieves serialized properties from a Windows NT Registry File (REGF).
Args:
file_object (dfvfs.FileIO): file-like object.
path_segments (str): path segments of the full path of the file entry.
Yields:
SerializedProperty: serialized property.
"""
regf_file = pyregf.file()
try:
regf_file.open_file_object(file_object)
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(f"Unable to open: {path:s} with error: {exception!s}")
try:
regf_root_key = regf_file.get_root_key()
if regf_root_key:
# Ignore the name of the root key.
yield from self._CollectSerializedProperiesFromREGFKey(
[""], regf_root_key
)
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(
f"Unable to collect serialized properties from: {path:s} with "
f"error: {exception!s}"
)
finally:
regf_file.close()
def _CollectSerializedProperiesFromREGFKey(self, key_path_segments, regf_key):
"""Retrieves serialized properties from a Windows NT Registry key.
Args:
key_path_segments (list[str]): key path segments.
regf_key (pyregf.key): Windows NT Registry key.
Yields:
SerializedProperty: serialized property.
"""
value_names = [regf_value.name for regf_value in regf_key.values]
if "MRUList" in value_names or "MRUListEx" in value_names:
yield from self._CollectSerializedProperiesFromREGFKeyWithMRU(
key_path_segments, regf_key
)
for regf_sub_key in regf_key.sub_keys:
key_path_segments.append(regf_sub_key.name)
try:
yield from self._CollectSerializedProperiesFromREGFKey(
key_path_segments, regf_sub_key
)
finally:
key_path_segments.pop(-1)
def _CollectSerializedProperiesFromREGFKeyWithMRU(
self, key_path_segments, regf_key
):
"""Retrieves serialized properties from a Registry key with a MRU.
Args:
key_path_segments (list[str]): key path segments.
regf_key (pyregf.key): Windows NT Registry key.
Yields:
SerializedProperty: serialized property.
"""
key_path = "\\".join(key_path_segments).upper()
if self._InKeyPaths(key_path, self._SHELL_ITEM_MRU_KEY_PATHS):
known_key_type = "shell-item"
elif self._InKeyPaths(key_path, self._SHELL_ITEM_LIST_MRU_KEY_PATHS):
known_key_type = "shell-item-list"
elif self._InKeyPaths(key_path, self._STRING_AND_SHELL_ITEM_MRU_KEY_PATHS):
known_key_type = "string-and-shell-item"
elif self._InKeyPaths(key_path, self._STRING_AND_SHELL_ITEM_LIST_MRU_KEY_PATHS):
known_key_type = "string-and-shell-item-list"
else:
known_key_type = None
if known_key_type:
for regf_value in regf_key.values:
if not regf_value.data or regf_value.name in (
"MRUList",
"MRUListEx",
"NodeSlot",
"NodeSlots",
"ViewStream",
):
continue
data_offset = 0
data_size = len(regf_value.data)
if known_key_type.startswith("string-and-shell-item"):
for data_offset in range(0, data_size, 2):
if regf_value.data[data_offset : data_offset + 2] == b"\0\0":
data_offset += 2
break
if data_offset >= data_size:
continue
data = regf_value.data[data_offset:]
if known_key_type.endswith("shell-item"):
fwsi_item = pyfwsi.item()
fwsi_item.copy_from_byte_stream(data)
yield from self._CollectSerializedProperiesFromShellItem(fwsi_item)
elif known_key_type.endswith("shell-item-list"):
fwsi_item_list = pyfwsi.item_list()
fwsi_item_list.copy_from_byte_stream(data)
yield from self._CollectSerializedProperiesFromShellItemList(
fwsi_item_list
)
def _CollectSerializedProperiesFromShellItem(self, fwsi_item):
"""Retrieves serialized properties from a shell item.
Args:
fwsi_item (pyfwsi.item): shell item.
Yields:
SerializedProperty: serialized property.
"""
if isinstance(fwsi_item, pyfwsi.users_property_view):
if fwsi_item.property_store_data:
fwps_store = pyfwps.store()
fwps_store.copy_from_byte_stream(fwsi_item.property_store_data)
yield from self._CollectSerializedProperiesFromPropertyStore(fwps_store)
def _CollectSerializedProperiesFromShellItemList(self, fwsi_item_list):
"""Retrieves serialized properties from a shell item list.
Args:
fwsi_item_list (pyfwsi.item_list): shell item list.
Yields:
SerializedProperty: serialized property.
"""
for fwsi_item in fwsi_item_list.items:
yield from self._CollectSerializedProperiesFromShellItem(fwsi_item)
def _InKeyPaths(self, key_path_upper, key_paths):
"""Checks if a specific key path is defined in a list of key paths.
Args:
key_path_upper (list[str]): key path in upper case.
key_paths (list[str]): list of Windows Registry key paths in upper case.
Returns:
bool: True if the key path is defined in the list of key paths.
"""
for matching_key_path in key_paths:
if key_path_upper.startswith(matching_key_path):
return True
return False
[docs]
def CollectSerializedProperies(self):
"""Retrieves serialized properties.
Yields:
SerializedProperty: serialized property.
"""
if not self._format_scanner:
self._CreateFormatScanner()
for file_entry, path_segments in self._ListFileEntries():
file_object = file_entry.GetFileObject()
scan_results = []
if file_object:
scan_state = pysigscan.scan_state()
try:
self._format_scanner.scan_file_object(scan_state, file_object)
scan_results = [
scan_result.identifier
for scan_result in iter(scan_state.scan_results)
]
except OSError as exception:
path = "\\".join(path_segments)
logging.warning(
f"Unable to open: {path:s} with error: {exception!s}"
)
generator = None
if "custom_destination" in scan_results:
generator = self._CollectSerializedProperiesFromCustomDestinationsFile(
file_object, path_segments
)
elif "lnk" in scan_results:
generator = self._CollectSerializedProperiesFromLNKFile(
file_object, path_segments
)
elif "olecf" in scan_results:
generator = (
self._CollectSerializedProperiesFromAutomaticDestinationsFile(
file_object, path_segments
)
)
elif "regf" in scan_results:
generator = self._CollectSerializedProperiesFromREGFFile(
file_object, path_segments
)
# TODO: add support for shell item formats
if generator:
for serialized_property in generator:
serialized_property.origin = "\\".join(path_segments)
yield serialized_property
[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