# -*- coding: utf-8 -*-
"""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 AppCompatCacheCachedEntry(object):
"""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(AppCompatCacheCachedEntry, self).__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(AppCompatCacheDataParser, self).__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, None)
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]
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(AppCompatCacheCollector, self).__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