"""Windows Registry plugin to parse the Application Compatibility Cache key."""
import os
from dfdatetime import filetime as dfdatetime_filetime
from dtfabric.runtime import data_maps as dtfabric_data_maps
from plaso.containers import events
from plaso.lib import dtfabric_helper
from plaso.lib import errors
from plaso.parsers import winreg_parser
from plaso.parsers.winreg_plugins import interface
[docs]
class AppCompatCacheEventData(events.EventData):
"""Application Compatibility Cache event data.
Attributes:
entry_index (int): cache entry index number for the record.
file_entry_modification_time (dfdatetime.DateTimeValues): last modification
date and time of the corresponding file entry.
key_path (str): Windows Registry key path.
last_update_time (dfdatetime.DateTimeValues): last update date and time of
the Application Compatibility Cache entry.
offset (int): offset of the Application Compatibility Cache entry relative
to the start of the Windows Registry value data, from which the event
data was extracted.
registry_last_written_time (dfdatetime.DateTimeValues): key last written
date and time.
path (str): full path to the executable.
insertion_flags (int): Execution flag.
control_set (int): Control set number of AppCompatCache registry key.
"""
DATA_TYPE = "windows:registry:appcompatcache"
[docs]
def __init__(self):
"""Initializes event data."""
super().__init__(data_type=self.DATA_TYPE)
self.entry_index = None
self.file_entry_modification_time = None
self.key_path = None
self.last_update_time = None
self.registry_last_written_time = None
self.offset = None
self.path = None
self.insertion_flags = None
self.control_set = None
[docs]
class AppCompatCacheCachedEntry:
"""Application Compatibility Cache cached entry."""
[docs]
def __init__(self):
"""Initializes the cached entry object."""
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 AppCompatCacheWindowsRegistryPlugin(
interface.WindowsRegistryPlugin, dtfabric_helper.DtFabricHelper
):
"""Application Compatibility Cache data Windows Registry plugin."""
NAME = "appcompatcache"
DATA_FORMAT = "Application Compatibility Cache Registry data"
FILTERS = frozenset(
[
interface.WindowsRegistryKeyPathFilter(
"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\"
"Session Manager\\AppCompatibility"
),
interface.WindowsRegistryKeyPathFilter(
"HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\"
"Session Manager\\AppCompatCache"
),
]
)
_DEFINITION_FILE = os.path.join(os.path.dirname(__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 signature used in Windows 8.0 and 8.1.
0x00000080: _FORMAT_TYPE_8,
# AppCompatCache format signatures 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):
"""Initializes a Application Compatibility Cache Registry plugin."""
super().__init__()
self._cached_entry_data_type_map = None
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.
"""
signature_map = self._GetDataTypeMap("uint32le")
try:
signature = self._ReadStructureFromByteStream(value_data, 0, signature_map)
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
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("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.
"""
data_type_map = self._GetDataTypeMap("appcompatcache_cached_entry_2003_common")
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:], cached_entry_offset, data_type_map
)
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
def _ParseCachedEntryXP(self, value_data, cached_entry_offset):
"""Parses a Windows XP cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
context = dtfabric_data_maps.DataTypeMapContext()
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
# 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
try:
path = bytearray(cached_entry.path[0:string_size]).decode("utf-16-le")
except UnicodeDecodeError:
raise errors.ParseError("Unable to decode cached entry path to string")
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = context.byte_size
cached_entry_object.file_size = cached_entry.file_size
cached_entry_object.last_modification_time = cached_entry.last_modification_time
cached_entry_object.last_update_time = cached_entry.last_update_time
cached_entry_object.path = path
return cached_entry_object
def _ParseCachedEntry2003(self, value_data, cached_entry_offset):
"""Parses a Windows 2003 cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
context = dtfabric_data_maps.DataTypeMapContext()
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
path_size = cached_entry.path_size
maximum_path_size = cached_entry.maximum_path_size
path_offset = cached_entry.path_offset
path = None
if path_offset > 0 and path_size > 0:
path_size += path_offset
maximum_path_size += path_offset
try:
path = value_data[path_offset:path_size].decode("utf-16-le")
except UnicodeDecodeError:
raise errors.ParseError("Unable to decode cached entry path to string")
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = context.byte_size
cached_entry_object.file_size = getattr(cached_entry, "file_size", None)
cached_entry_object.last_modification_time = cached_entry.last_modification_time
cached_entry_object.path = path
return cached_entry_object
def _ParseCachedEntryVista(self, value_data, cached_entry_offset):
"""Parses a Windows Vista cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
context = dtfabric_data_maps.DataTypeMapContext()
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
path_size = cached_entry.path_size
maximum_path_size = cached_entry.maximum_path_size
path_offset = cached_entry.path_offset
path = None
if path_offset > 0 and path_size > 0:
path_size += path_offset
maximum_path_size += path_offset
try:
path = value_data[path_offset:path_size].decode("utf-16-le")
except UnicodeDecodeError:
raise errors.ParseError("Unable to decode cached entry path to string")
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = context.byte_size
cached_entry_object.insertion_flags = cached_entry.insertion_flags
cached_entry_object.last_modification_time = cached_entry.last_modification_time
cached_entry_object.path = path
cached_entry_object.shim_flags = cached_entry.shim_flags
return cached_entry_object
def _ParseCachedEntry7(self, value_data, cached_entry_offset):
"""Parses a Windows 7 cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
context = dtfabric_data_maps.DataTypeMapContext()
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
path_size = cached_entry.path_size
maximum_path_size = cached_entry.maximum_path_size
path_offset = cached_entry.path_offset
path = None
if path_offset > 0 and path_size > 0:
path_size += path_offset
maximum_path_size += path_offset
try:
path = value_data[path_offset:path_size].decode("utf-16-le")
except UnicodeDecodeError:
raise errors.ParseError("Unable to decode cached entry path to string")
data_offset = cached_entry.data_offset
data_size = cached_entry.data_size
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = context.byte_size
cached_entry_object.insertion_flags = cached_entry.insertion_flags
cached_entry_object.last_modification_time = cached_entry.last_modification_time
cached_entry_object.path = path
cached_entry_object.shim_flags = cached_entry.shim_flags
if data_size > 0:
cached_entry_object.data = value_data[data_offset : data_offset + data_size]
return cached_entry_object
def _ParseCachedEntry8(self, value_data, cached_entry_offset):
"""Parses a Windows 8.0 or 8.1 cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
if cached_entry.signature not in (
self._CACHED_ENTRY_SIGNATURE_8_0,
self._CACHED_ENTRY_SIGNATURE_8_1,
):
raise errors.ParseError("Unsupported cache entry signature")
cached_entry_data = value_data[cached_entry_offset:]
if 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,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry body with error: {exception!s}"
)
data_offset = context.byte_size
data_size = cached_entry_body.data_size
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = 12 + cached_entry.cached_entry_data_size
cached_entry_object.insertion_flags = cached_entry_body.insertion_flags
cached_entry_object.last_modification_time = (
cached_entry_body.last_modification_time
)
cached_entry_object.path = cached_entry_body.path
cached_entry_object.shim_flags = cached_entry_body.shim_flags
if data_size > 0:
cached_entry_object.data = cached_entry_data[
data_offset : data_offset + data_size
]
return cached_entry_object
def _ParseCachedEntry10(self, value_data, cached_entry_offset):
"""Parses a Windows 10 cached entry.
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:
AppCompatCacheCachedEntry: cached entry.
Raises:
ParseError: if the value data could not be parsed.
"""
try:
cached_entry = self._ReadStructureFromByteStream(
value_data[cached_entry_offset:],
cached_entry_offset,
self._cached_entry_data_type_map,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry value with error: {exception!s}"
)
if cached_entry.signature not in (
self._CACHED_ENTRY_SIGNATURE_8_0,
self._CACHED_ENTRY_SIGNATURE_8_1,
):
raise errors.ParseError("Unsupported cache entry signature")
cached_entry_data = value_data[cached_entry_offset:]
data_type_map = self._GetDataTypeMap("appcompatcache_cached_entry_body_10")
context = dtfabric_data_maps.DataTypeMapContext()
try:
cached_entry_body = self._ReadStructureFromByteStream(
cached_entry_data[12:],
cached_entry_offset + 12,
data_type_map,
context=context,
)
except (ValueError, errors.ParseError) as exception:
raise errors.ParseError(
f"Unable to parse cached entry body with error: {exception!s}"
)
data_offset = cached_entry_offset + context.byte_size
data_size = cached_entry_body.data_size
cached_entry_object = AppCompatCacheCachedEntry()
cached_entry_object.cached_entry_size = 12 + cached_entry.cached_entry_data_size
cached_entry_object.last_modification_time = (
cached_entry_body.last_modification_time
)
cached_entry_object.path = cached_entry_body.path
if data_size > 0:
cached_entry_object.data = cached_entry_data[
data_offset : data_offset + data_size
]
return cached_entry_object
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, 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", 0
)
return cache_header
def _ExtractControlSet(self, path):
"""Extract control set number from key path.
Args:
path (str): Path
Returns:
Optional[int]: Control set number.
"""
parts = path.split("\\")
for part in parts:
if part.upper().startswith("CONTROLSET"):
try:
return int(part[10:], 10)
except ValueError:
pass
return None
winreg_parser.WinRegistryParser.RegisterPlugin(AppCompatCacheWindowsRegistryPlugin)