Source code for winregrc.appcompatcache

"""Application Compatibility Cache collector."""

import logging

from dtfabric.runtime import data_maps as dtfabric_data_maps

from winregrc import data_format
from winregrc import errors
from winregrc import interface


[docs] class AppCompatCacheHeader: """Application Compatibility Cache header. Attributes: number_of_cached_entries (int): number of cached entries. header_size (int): header size. """
[docs] def __init__(self): """Initializes an Application Compatibility Cache header.""" super().__init__() self.number_of_cached_entries = 0 self.header_size = 0
[docs] class AppCompatCacheCachedEntry: """Application Compatibility Cache cached entry. Attributes: cached_entry_size (int): size of the cached entry. data (bytes): data of the cached entry. file_size (int): size of file corresponding to the cached entry. insertion_flags (int): insertion flags of the cached entry. last_modification_time (int): last modification timestamp of the file corresponding to the cached entry. last_update_time (int): last update timestamp the cached entry. shim_flags (int): shim flags of the cached entry. path (str): path of the cached entry. """
[docs] def __init__(self): """Initializes an Application Compatibility Cache cached entry.""" super().__init__() self.cached_entry_size = 0 self.data = None self.file_size = None self.insertion_flags = None self.last_modification_time = None self.last_update_time = None self.shim_flags = None self.path = None
[docs] class AppCompatCacheDataParser(data_format.BinaryDataFormat): """Application Compatibility Cache data parser.""" _DEFINITION_FILE = "appcompatcache.yaml" _FORMAT_TYPE_2000 = 1 _FORMAT_TYPE_XP = 2 _FORMAT_TYPE_2003 = 3 _FORMAT_TYPE_VISTA = 4 _FORMAT_TYPE_7 = 5 _FORMAT_TYPE_8 = 6 _FORMAT_TYPE_10 = 7 _HEADER_SIGNATURES = { # AppCompatCache format signature used in Windows XP. 0xDEADBEEF: _FORMAT_TYPE_XP, # AppCompatCache format signature used in Windows 2003, Vista and 2008. 0xBADC0FFE: _FORMAT_TYPE_2003, # AppCompatCache format signature used in Windows 7 and 2008 R2. 0xBADC0FEE: _FORMAT_TYPE_7, # AppCompatCache format used in Windows 8.0 and 8.1. 0x00000080: _FORMAT_TYPE_8, # AppCompatCache format used in Windows 10 0x00000030: _FORMAT_TYPE_10, 0x00000034: _FORMAT_TYPE_10, } _HEADER_DATA_TYPE_MAP_NAMES = { _FORMAT_TYPE_XP: "appcompatcache_header_xp_32bit", _FORMAT_TYPE_2003: "appcompatcache_header_2003", _FORMAT_TYPE_VISTA: "appcompatcache_header_vista", _FORMAT_TYPE_7: "appcompatcache_header_7", _FORMAT_TYPE_8: "appcompatcache_header_8", _FORMAT_TYPE_10: "appcompatcache_header_10", } _SUPPORTED_FORMAT_TYPES = frozenset(_HEADER_DATA_TYPE_MAP_NAMES.keys()) # AppCompatCache format used in Windows 8.0. _CACHED_ENTRY_SIGNATURE_8_0 = b"00ts" # AppCompatCache format used in Windows 8.1. _CACHED_ENTRY_SIGNATURE_8_1 = b"10ts"
[docs] def __init__(self, debug=False, output_writer=None): """Initializes an Application Compatibility Cache data parser. Args: debug (Optional[bool]): True if debug information should be written. output_writer (Optional[OutputWriter]): output writer. """ super().__init__(debug=debug, output_writer=output_writer) self._cached_entry_data_type_map = None
def _DebugPrintCachedEntryXP(self, cached_entry): """Prints Windows XP AppCompatCache cached entry value debug information. Args: cached_entry (appcompatcache_cached_entry_xp_32bit): Windows XP AppCompatCache cached entry. """ # TODO: have dtFabric handle string conversion. string_size = 0 for string_index in range(0, 528, 2): if ( cached_entry.path[string_index] == 0 and cached_entry.path[string_index + 1] == 0 ): break string_size += 2 path = bytearray(cached_entry.path[0:string_size]).decode("utf-16-le") self._DebugPrintValue("Path", path) self._DebugPrintDecimalValue("File size", cached_entry.file_size) self._DebugPrintFiletimeValue("Last update time", cached_entry.last_update_time) def _DebugPrintCachedEntry2003(self, cached_entry): """Prints Windows 2003 AppCompatCache cached entry value debug information. Args: cached_entry_common (appcompatcache_cached_entry_2003_32bit| appcompatcache_cached_entry_2003_64bit| appcompatcache_cached_entry_vista_32bit| appcompatcache_cached_entry_vista_64bit| appcompatcache_cached_entry_7_32bit| appcompatcache_cached_entry_7_64bit): Windows 2003, Vista or 7 AppCompatCache cached entry. """ self._DebugPrintDecimalValue("Path size", cached_entry.path_size) self._DebugPrintDecimalValue( "Maximum path size", cached_entry.maximum_path_size ) self._DebugPrintValue("Path offset", f"0x{cached_entry.path_offset:08x}") if hasattr(cached_entry, "file_size"): self._DebugPrintDecimalValue("File size", cached_entry.file_size) if hasattr(cached_entry, "insertion_flags"): self._DebugPrintValue( "Insertion flags", f"0x{cached_entry.insertion_flags:08x}" ) if hasattr(cached_entry, "shim_flags"): self._DebugPrintValue("Shim flags", f"0x{cached_entry.shim_flags:08x}") if hasattr(cached_entry, "data_offset"): self._DebugPrintValue("Data offset", f"0x{cached_entry.data_offset:08x}") if hasattr(cached_entry, "data_size"): self._DebugPrintDecimalValue("Data size", cached_entry.data_size) # pylint: disable=missing-type-doc def _DebugPrintCachedEntry8(self, cached_entry_header, cached_entry_body): """Prints Windows 8 AppCompatCache cached entry value debug information. Args: cached_entry_header (appcompatcache_cached_entry_header_8): Windows 8 or 10 AppCompatCache cached entry header. cached_entry_body (appcompatcache_cached_entry_header_8_0| appcompatcache_cached_entry_header_8_1| appcompatcache_cached_entry_header_10): Windows 8.0, 8.1 or 10 AppCompatCache cached entry body. """ self._DebugPrintValue( "Signature", cached_entry_header.signature.decode("ascii") ) self._DebugPrintValue("Unknown1", f"0x{cached_entry_header.unknown1:08x}") self._DebugPrintDecimalValue( "Cached entry data size", cached_entry_header.cached_entry_data_size ) self._DebugPrintDecimalValue("Path size", cached_entry_body.path_size) self._DebugPrintValue("Path", cached_entry_body.path.rstrip("\x00")) if hasattr(cached_entry_body, "insertion_flags"): self._DebugPrintValue( "Insertion flags", f"0x{cached_entry_body.insertion_flags:08x}" ) if hasattr(cached_entry_body, "shim_flags"): self._DebugPrintValue("Shim flags", f"0x{cached_entry_body.shim_flags:08x}") if hasattr(cached_entry_body, "unknown1"): self._DebugPrintValue("Unknown1", f"0x{cached_entry_body.unknown1:04x}") self._DebugPrintFiletimeValue( "Last modification time", cached_entry_body.last_modification_time ) self._DebugPrintDecimalValue("Data size", cached_entry_body.data_size) # pylint: disable=missing-type-doc def _DebugPrintHeader(self, format_type, header): """Prints AppCompatCache header value debug information. Args: format_type (int): AppCompatCache format type. header (appcompatcache_header_xp_32bit|appcompatcache_header_vista| appcompatcache_header_7|appcompatcache_header_8| appcompatcache_header_10): AppCompatCache header. """ if format_type == self._FORMAT_TYPE_10: self._DebugPrintDecimalValue("Header size", header.signature) else: self._DebugPrintValue("Signature", f"0x{header.signature:08x}") if format_type in ( self._FORMAT_TYPE_XP, self._FORMAT_TYPE_2003, self._FORMAT_TYPE_VISTA, self._FORMAT_TYPE_7, self._FORMAT_TYPE_10, ): self._DebugPrintDecimalValue( "Number of cached entries", header.number_of_cached_entries ) if format_type == self._FORMAT_TYPE_XP: if self._debug: self._DebugPrintValue( "Number of LRU entries", f"0x{header.number_of_lru_entries:08x}" ) self._DebugPrintValue("Unknown1", f"0x{header.unknown1:08x}") elif format_type == self._FORMAT_TYPE_8: self._DebugPrintValue("Unknown1", f"0x{header.unknown1:08x}") if format_type != self._FORMAT_TYPE_XP: self._DebugPrintText("\n") def _GetCachedEntryDataTypeMap(self, format_type, value_data, cached_entry_offset): """Determines the cached entry data type map. Args: format_type (int): format type. value_data (bytes): value data. cached_entry_offset (int): offset of the first cached entry data relative to the start of the value data. Returns: dtfabric.DataTypeMap: data type map which contains a data type definition, such as a structure, that can be mapped onto binary data or None if the data type map is not defined. Raises: ParseError: if the cached entry data type map cannot be determined. """ if format_type not in self._SUPPORTED_FORMAT_TYPES: raise errors.ParseError(f"Unsupported format type: {format_type:d}") data_type_map_name = "" if format_type == self._FORMAT_TYPE_XP: data_type_map_name = "appcompatcache_cached_entry_xp_32bit" elif format_type in (self._FORMAT_TYPE_8, self._FORMAT_TYPE_10): data_type_map_name = "appcompatcache_cached_entry_header_8" else: cached_entry = self._ParseCommon2003CachedEntry( value_data, cached_entry_offset ) # Assume the entry is 64-bit if the 32-bit path offset is 0 and # the 64-bit path offset is set. if ( cached_entry.path_offset_32bit == 0 and cached_entry.path_offset_64bit != 0 ): number_of_bits = "64" else: number_of_bits = "32" if format_type == self._FORMAT_TYPE_2003: data_type_map_name = ( f"appcompatcache_cached_entry_2003_{number_of_bits:s}bit" ) elif format_type == self._FORMAT_TYPE_VISTA: data_type_map_name = ( f"appcompatcache_cached_entry_vista_{number_of_bits:s}bit" ) elif format_type == self._FORMAT_TYPE_7: data_type_map_name = ( f"appcompatcache_cached_entry_7_{number_of_bits:s}bit" ) return self._GetDataTypeMap(data_type_map_name) def _ParseCommon2003CachedEntry(self, value_data, cached_entry_offset): """Parses the cached entry structure common for Windows 2003, Vista and 7. Args: value_data (bytes): value data. cached_entry_offset (int): offset of the first cached entry data relative to the start of the value data. Returns: appcompatcache_cached_entry_2003_common: cached entry structure common for Windows 2003, Windows Vista and Windows 7. Raises: ParseError: if the value data could not be parsed. """ cached_entry_data = value_data[cached_entry_offset:] data_type_map = self._GetDataTypeMap("appcompatcache_cached_entry_2003_common") try: cached_entry = self._ReadStructureFromByteStream( cached_entry_data, cached_entry_offset, data_type_map, "cached entry" ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse cached entry value with error: {exception!s}" ) if cached_entry.path_size > cached_entry.maximum_path_size: raise errors.ParseError("Path size value out of bounds.") path_end_of_string_size = ( cached_entry.maximum_path_size - cached_entry.path_size ) if cached_entry.path_size == 0 or path_end_of_string_size != 2: raise errors.ParseError("Unsupported path size values.") return cached_entry
[docs] def CheckSignature(self, value_data): """Parses and validates the signature. Args: value_data (bytes): value data. Returns: int: format type or None if format could not be determined. Raises: ParseError: if the value data could not be parsed. """ data_type_map = self._GetDataTypeMap("uint32le") try: signature = self._ReadStructureFromByteStream( value_data, 0, data_type_map, "signature" ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse signature value with error: {exception!s}" ) format_type = self._HEADER_SIGNATURES.get(signature) if format_type == self._FORMAT_TYPE_2003: # TODO: determine which format version is used (2003 or Vista). return self._FORMAT_TYPE_2003 if format_type == self._FORMAT_TYPE_8: cached_entry_signature = value_data[signature : signature + 4] if cached_entry_signature in ( self._CACHED_ENTRY_SIGNATURE_8_0, self._CACHED_ENTRY_SIGNATURE_8_1, ): return self._FORMAT_TYPE_8 elif format_type == self._FORMAT_TYPE_10: # Windows 10 uses the same cache entry signature as Windows 8.1 cached_entry_signature = value_data[signature : signature + 4] if cached_entry_signature == self._CACHED_ENTRY_SIGNATURE_8_1: return self._FORMAT_TYPE_10 return format_type
[docs] def ParseCachedEntry( self, format_type, value_data, cached_entry_index, cached_entry_offset ): """Parses a cached entry. Args: format_type (int): format type. value_data (bytes): value data. cached_entry_index (int): cached entry index. cached_entry_offset (int): offset of the first cached entry data relative to the start of the value data. Returns: AppCompatCacheCachedEntry: cached entry. Raises: ParseError: if the value data could not be parsed. """ if not self._cached_entry_data_type_map: self._cached_entry_data_type_map = self._GetCachedEntryDataTypeMap( format_type, value_data, cached_entry_offset ) if not self._cached_entry_data_type_map: raise errors.ParseError("Unable to determine cached entry data type.") cached_entry_size = self._cached_entry_data_type_map.GetSizeHint() cached_entry_end_offset = cached_entry_offset + cached_entry_size cached_entry_data = value_data[cached_entry_offset:cached_entry_end_offset] if self._debug: if format_type not in (self._FORMAT_TYPE_8, self._FORMAT_TYPE_10): description = f"Cached entry: {cached_entry_index:d} data" self._DebugPrintData(description, cached_entry_data) try: cached_entry = self._ReadStructureFromByteStream( cached_entry_data, cached_entry_offset, self._cached_entry_data_type_map, "cached entry", ) except (ValueError, errors.ParseError) as exception: if self._debug: if format_type in (self._FORMAT_TYPE_8, self._FORMAT_TYPE_10): description = f"Cached entry: {cached_entry_index:d} header data" self._DebugPrintData(description, cached_entry_data) raise errors.ParseError( f"Unable to parse cached entry value with error: {exception!s}" ) if format_type in (self._FORMAT_TYPE_8, self._FORMAT_TYPE_10): if cached_entry.signature not in ( self._CACHED_ENTRY_SIGNATURE_8_0, self._CACHED_ENTRY_SIGNATURE_8_1, ): if self._debug: description = f"Cached entry: {cached_entry_index:d} header data" self._DebugPrintData(description, cached_entry_data) raise errors.ParseError("Unsupported cache entry signature") cached_entry_object = AppCompatCacheCachedEntry() data_offset = 0 data_size = 0 if format_type == self._FORMAT_TYPE_XP: if self._debug: self._DebugPrintCachedEntryXP(cached_entry) # TODO: have dtFabric handle string conversion. string_size = 0 for string_index in range(0, 528, 2): if ( cached_entry.path[string_index] == 0 and cached_entry.path[string_index + 1] == 0 ): break string_size += 2 last_modification_time = cached_entry.last_modification_time path = bytearray(cached_entry.path[0:string_size]).decode("utf-16-le") cached_entry_object.last_update_time = cached_entry.last_update_time elif format_type in ( self._FORMAT_TYPE_2003, self._FORMAT_TYPE_VISTA, self._FORMAT_TYPE_7, ): if self._debug: self._DebugPrintCachedEntry2003(cached_entry) last_modification_time = cached_entry.last_modification_time if format_type in (self._FORMAT_TYPE_VISTA, self._FORMAT_TYPE_7): cached_entry_object.insertion_flags = cached_entry.insertion_flags cached_entry_object.shim_flags = cached_entry.shim_flags path_size = cached_entry.path_size maximum_path_size = cached_entry.maximum_path_size path_offset = cached_entry.path_offset if path_offset > 0 and path_size > 0: path_size += path_offset maximum_path_size += path_offset if self._debug: self._DebugPrintData( "Path data", value_data[path_offset:maximum_path_size] ) path = value_data[path_offset:path_size].decode("utf-16-le") if self._debug: self._DebugPrintValue("Path", path) if format_type == self._FORMAT_TYPE_7: data_offset = cached_entry.data_offset data_size = cached_entry.data_size elif format_type in (self._FORMAT_TYPE_8, self._FORMAT_TYPE_10): cached_entry_data_size = cached_entry.cached_entry_data_size cached_entry_size = 12 + cached_entry_data_size cached_entry_end_offset = cached_entry_offset + cached_entry_size cached_entry_data = value_data[cached_entry_offset:cached_entry_end_offset] if self._debug: description = f"Cached entry: {cached_entry_index:d} data" self._DebugPrintData(description, cached_entry_data) if format_type == self._FORMAT_TYPE_10: data_type_map_name = "appcompatcache_cached_entry_body_10" elif cached_entry.signature == self._CACHED_ENTRY_SIGNATURE_8_0: data_type_map_name = "appcompatcache_cached_entry_body_8_0" elif cached_entry.signature == self._CACHED_ENTRY_SIGNATURE_8_1: data_type_map_name = "appcompatcache_cached_entry_body_8_1" else: data_type_map_name = None data_type_map = self._GetDataTypeMap(data_type_map_name) context = dtfabric_data_maps.DataTypeMapContext() try: cached_entry_body = self._ReadStructureFromByteStream( cached_entry_data[12:], cached_entry_offset + 12, data_type_map, "cached entry body", context=context, ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse cached entry body with error: {exception!s}" ) if self._debug: self._DebugPrintCachedEntry8(cached_entry, cached_entry_body) last_modification_time = cached_entry_body.last_modification_time path = cached_entry_body.path if format_type == self._FORMAT_TYPE_8: cached_entry_object.insertion_flags = cached_entry_body.insertion_flags cached_entry_object.shim_flags = cached_entry_body.shim_flags data_offset = cached_entry_offset + context.byte_size data_size = cached_entry_body.data_size if self._debug: self._DebugPrintText("\n") cached_entry_object.cached_entry_size = cached_entry_size cached_entry_object.file_size = getattr(cached_entry, "file_size", None) cached_entry_object.last_modification_time = last_modification_time cached_entry_object.path = path if data_size > 0: cached_entry_object.data = value_data[data_offset : data_offset + data_size] if self._debug: self._DebugPrintData("Data", cached_entry_object.data) return cached_entry_object
[docs] def ParseHeader(self, format_type, value_data): """Parses the header. Args: format_type (int): format type. value_data (bytes): value data. Returns: AppCompatCacheHeader: header. Raises: ParseError: if the value data could not be parsed. """ data_type_map_name = self._HEADER_DATA_TYPE_MAP_NAMES.get(format_type) if not data_type_map_name: raise errors.ParseError(f"Unsupported format type: {format_type:d}") data_type_map = self._GetDataTypeMap(data_type_map_name) context = dtfabric_data_maps.DataTypeMapContext() try: header = self._ReadStructureFromByteStream( value_data, 0, data_type_map, "header", context=context ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse header value with error: {exception!s}" ) header_data_size = context.byte_size if format_type == self._FORMAT_TYPE_10: header_data_size = header.signature cache_header = AppCompatCacheHeader() cache_header.header_size = header_data_size cache_header.number_of_cached_entries = getattr( header, "number_of_cached_entries", None ) if self._debug: self._DebugPrintHeader(format_type, header) if format_type == self._FORMAT_TYPE_XP: if self._debug: self._DebugPrintText("LRU entries:") data_offset = 16 number_of_lru_entries = header.number_of_lru_entries if 0 <= number_of_lru_entries <= 96: data_type_map = self._GetDataTypeMap("uint32le") for lru_entry_index in range(number_of_lru_entries): try: lru_entry = self._ReadStructureFromByteStream( value_data[data_offset : data_offset + 4], data_offset, data_type_map, "LRU entry", ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse LRU entry value with error: {exception!s}" ) data_offset += 4 if self._debug: offset_value = 400 + (lru_entry * 552) description = f"LRU entry: {lru_entry_index:d}" value_string = f"{lru_entry:d} (offset: 0x{offset_value:08x})" self._DebugPrintValue(description, value_string) if self._debug: self._DebugPrintText("\n") if self._debug: self._DebugPrintData("Unknown data", value_data[data_offset:400]) self._cached_entry_data_type_map = None return cache_header
[docs] class AppCompatCacheCollector(interface.WindowsRegistryKeyCollector): """Application Compatibility Cache collector. Attributes: cached_entries (list[AppCompatCacheCachedEntry]): cached entries. """
[docs] def __init__(self, debug=False, output_writer=None): """Initializes a Application Compatibility 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 = AppCompatCacheDataParser( debug=self._debug, output_writer=output_writer ) self._output_writer = output_writer self.cached_entries = []
def _CollectAppCompatCacheFromKey(self, app_compat_cache_key): """Collects Application Compatibility Cache from a Windows Registry key. Args: app_compat_cache_key (dfwinreg.WinRegistryKey): Application Compatibility Cache Windows Registry key. Returns: bool: True if the Application Compatibility Cache key was found, False if not. """ value = app_compat_cache_key.GetValueByName("AppCompatCache") if not value: logging.warning( f"Missing AppCompatCache value in key: {app_compat_cache_key.path:s}" ) return True value_data = value.data value_data_size = len(value.data) # TODO: add non debug output if self._debug: self._output_writer.WriteDebugData("Value data:\n", value_data) format_type = self._parser.CheckSignature(value_data) if not format_type: logging.warning("Unsupported signature.") return True cache_header = self._parser.ParseHeader(format_type, value_data) # On Windows Vista and 2008 when the cache is empty it will # only consist of the header. if value_data_size <= cache_header.header_size: return True cached_entry_offset = cache_header.header_size cached_entry_index = 0 while cached_entry_offset < value_data_size: cached_entry = self._parser.ParseCachedEntry( format_type, value_data, cached_entry_index, cached_entry_offset ) self.cached_entries.append(cached_entry) cached_entry_offset += cached_entry.cached_entry_size cached_entry_index += 1 if ( cache_header.number_of_cached_entries != 0 and cached_entry_index >= cache_header.number_of_cached_entries ): break return True
[docs] def Collect(self, registry, all_control_sets=False): """Collects the Application Compatibility Cache. Args: registry (dfwinreg.WinRegistry): Windows Registry. all_control_sets (Optional[bool]): True if the services should be collected from all control sets instead of only the current control set. Returns: bool: True if the Application Compatibility Cache key was found, False if not. """ result = False if all_control_sets: system_key = registry.GetKeyByPath("HKEY_LOCAL_MACHINE\\System\\") if not system_key: return result for control_set_key in system_key.GetSubkeys(): if control_set_key.name.startswith("ControlSet"): # Windows XP app_compat_cache_key = control_set_key.GetSubkeyByPath( "Control\\Session Manager\\AppCompatibility" ) if app_compat_cache_key: if self._CollectAppCompatCacheFromKey(app_compat_cache_key): result = True # Windows 2003 and later app_compat_cache_key = control_set_key.GetSubkeyByPath( "Control\\Session Manager\\AppCompatCache" ) if app_compat_cache_key: if self._CollectAppCompatCacheFromKey(app_compat_cache_key): result = True else: # Windows XP key_path = ( "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\" "Session Manager\\AppCompatibility" ) try: app_compat_cache_key = registry.GetKeyByPath(key_path) except RuntimeError: app_compat_cache_key = None if app_compat_cache_key: if self._CollectAppCompatCacheFromKey(app_compat_cache_key): result = True # Windows 2003 and later key_path = ( "HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\" "Session Manager\\AppCompatCache" ) try: app_compat_cache_key = registry.GetKeyByPath(key_path) except RuntimeError: app_compat_cache_key = None if app_compat_cache_key: if self._CollectAppCompatCacheFromKey(app_compat_cache_key): result = True return result