"""Windows Programs Cache information collector."""
import logging
import uuid
from dtfabric.runtime import data_maps as dtfabric_data_maps
import pyfwsi
from winregrc import data_format
from winregrc import errors
from winregrc import interface
[docs]
class ProgramsCacheDataParser(data_format.BinaryDataFormat):
"""Programs Cache data parser."""
_DEFINITION_FILE = "programscache.yaml"
def _DebugPrintEntryFooter(self, entry_footer):
"""Prints entry footer value debug information.
Args:
entry_footer (programscache_entry_footer): entry footer.
"""
self._DebugPrintValue("Sentinel", f"0x{entry_footer.sentinel:02x}")
def _DebugPrintEntryHeader(self, entry_header):
"""Prints entry header value debug information.
Args:
entry_header (programscache_entry_header): entry header.
"""
self._DebugPrintDecimalValue("Entry data size", entry_header.data_size)
def _DebugPrintHeader(self, header):
"""Prints header value debug information.
Args:
header (programscache_header): header.
"""
self._DebugPrintDecimalValue("Format version", header.format_version)
def _DebugPrintShellItem(self, shell_item):
"""Prints shell item value debug information.
Args:
shell_item (pyfwsi.shell_item): shell item.
"""
self._DebugPrintValue("Shell item class type", f"0x{shell_item.class_type:02x}")
value_string = getattr(shell_item, "name", "")
self._DebugPrintValue("Shell item name", value_string)
def _ParseEntryFooter(self, value_data, value_data_offset):
"""Parses an entry footer from the value data.
Args:
value_data (bytes): value data.
value_data_offset (int): offset of the entry footer relative to the start
of the value data.
Returns:
tuple: containing:
programscache_entry_footer: entry footer.
int: entry footer data size.
Raises:
ParseError: if the entry footer could not be parsed.
"""
data_type_map = self._GetDataTypeMap("programscache_entry_footer")
data_size = data_type_map.GetSizeHint()
if self._debug:
self._DebugPrintData(
"Entry footer data",
value_data[value_data_offset : value_data_offset + data_size],
)
try:
entry_footer = self._ReadStructureFromByteStream(
value_data[value_data_offset:],
value_data_offset,
data_type_map,
"entry footer",
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse entry footer value with error: {exception!s}"
)
if self._debug:
self._DebugPrintEntryFooter(entry_footer)
return entry_footer, data_size
def _ParseHeader(self, value_data):
"""Parses a header from the value data.
Args:
value_data (bytes): value data.
Returns:
tuple: containing:
programscache_header: header.
int: header data size.
Raises:
ParseError: if the header could not be parsed.
"""
data_type_map = self._GetDataTypeMap("programscache_header")
data_size = data_type_map.GetSizeHint()
if self._debug:
self._DebugPrintData("Header data", value_data[:data_size])
try:
header = self._ReadStructureFromByteStream(
value_data, 0, data_type_map, "header"
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse header value with error: {exception!s}"
)
if self._debug:
self._DebugPrintHeader(header)
if header.format_version not in (1, 9, 12, 19):
raise errors.ParseError("Unsupported format.")
return header, data_size
[docs]
def Parse(self, value_data):
"""Parses the value data.
Args:
value_data (bytes): value data.
Raises:
ParseError: if the value data could not be parsed.
"""
if self._debug:
self._DebugPrintData("Value data", value_data)
header, value_data_offset = self._ParseHeader(value_data)
if header.format_version == 1:
value_data_offset += 4
elif header.format_version == 9:
data_type_map = self._GetDataTypeMap("programscache_header9")
context = dtfabric_data_maps.DataTypeMapContext()
try:
header9 = self._ReadStructureFromByteStream(
value_data[value_data_offset:],
value_data_offset,
data_type_map,
"header9",
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse header9 value with error: {exception!s}"
)
value_data_offset += context.byte_size
if self._debug:
self._DebugPrintValue("Unknown1", f"0x{header9.unknown1:08x}")
elif header.format_version in (12, 19):
uuid_object = uuid.UUID(bytes_le=value_data[4:20])
value_data_offset += 16
if self._debug:
self._DebugPrintValue("Known folder identifier", f"{uuid_object!s}")
sentinel = 0
if header.format_version != 9:
entry_footer, data_size = self._ParseEntryFooter(
value_data, value_data_offset
)
value_data_offset += data_size
sentinel = entry_footer.sentinel
if self._debug:
self._DebugPrintText("\n")
value_data_size = len(value_data)
while sentinel in (0, 1):
if value_data_offset >= value_data_size:
break
data_type_map = self._GetDataTypeMap("programscache_entry_header")
context = dtfabric_data_maps.DataTypeMapContext()
try:
entry_header = self._ReadStructureFromByteStream(
value_data[value_data_offset:],
value_data_offset,
data_type_map,
"entry header",
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse entry header value with error: {exception!s}"
)
if self._debug:
self._DebugPrintValue("Entry data offset", f"0x{value_data_offset:08x}")
self._DebugPrintEntryHeader(entry_header)
value_data_offset += context.byte_size
entry_data_size = entry_header.data_size
shell_item_list = pyfwsi.item_list()
shell_item_list.copy_from_byte_stream(value_data[value_data_offset:])
for shell_item in iter(shell_item_list.items):
if self._debug:
self._DebugPrintShellItem(shell_item)
value_data_offset += entry_data_size
entry_footer, data_size = self._ParseEntryFooter(
value_data, value_data_offset
)
value_data_offset += data_size
if self._debug:
self._DebugPrintText("\n")
if entry_footer.sentinel == 2 and value_data_offset < value_data_size:
# TODO: determine the logic to this value.
while ord(value_data[value_data_offset]) != 0x00:
value_data_offset += 1
value_data_offset += 7
entry_footer, data_size = self._ParseEntryFooter(
value_data, value_data_offset
)
value_data_offset += data_size
if self._debug:
self._DebugPrintText("\n")
if value_data_offset < value_data_size:
self._DebugPrintValue("Trailing data offset", f"0x{value_data_offset:08x}")
self._DebugPrintData("Trailing data:", value_data[value_data_offset:])
[docs]
class ProgramsCacheCollector(interface.WindowsRegistryKeyCollector):
"""Windows program cache collector."""
_STARTPAGE_KEY_PATH = (
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\StartPage"
)
_STARTPAGE2_KEY_PATH = (
"HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\"
"Explorer\\StartPage2"
)
[docs]
def __init__(self, debug=False, output_writer=None):
"""Initializes a Windows program cache collector.
Args:
debug (Optional[bool]): True if debug information should be printed.
output_writer (Optional[OutputWriter]): output writer.
"""
super().__init__(debug=debug)
self._parser = ProgramsCacheDataParser(debug=debug, output_writer=output_writer)
def _CollectProgramsCacheFromValue(self, registry, key_path, value_name):
"""Collects Programs Cache from a Windows Registry value.
Args:
registry (dfwinreg.WinRegistry): Windows Registry.
key_path (str): path of the Programs Cache key.
value_name (str): name of the Programs Cache value.
Returns:
bool: True if the Programs Cache information key was found, False if not.
"""
startpage_key = registry.GetKeyByPath(key_path)
if not startpage_key:
return False
value = startpage_key.GetValueByName(value_name)
if not value:
logging.warning(f"Missing {value_name:s} value in key: {key_path:s}")
return True
self._parser.Parse(value.data)
return True
[docs]
def Collect(self, registry): # pylint: disable=arguments-differ
"""Collects the Programs Cache information.
Args:
registry (dfwinreg.WinRegistry): Windows Registry.
Returns:
bool: True if the Programs Cache information key was found, False if not.
"""
result = False
if self._CollectProgramsCacheFromValue(
registry, self._STARTPAGE_KEY_PATH, "ProgramsCache"
):
result = True
if self._CollectProgramsCacheFromValue(
registry, self._STARTPAGE2_KEY_PATH, "ProgramsCache"
):
result = True
if self._CollectProgramsCacheFromValue(
registry, self._STARTPAGE2_KEY_PATH, "ProgramsCacheSMP"
):
result = True
if self._CollectProgramsCacheFromValue(
registry, self._STARTPAGE2_KEY_PATH, "ProgramsCacheTBP"
):
result = True
return result