Windows.Registry.AppCompatCache
Windows.Registry.AppCompatCache
Parses the AppCompatCache (Shimcache) registry value to enumerate recently executed application paths.
AppCompatCache, also known as Shimcache, is a component of the Application Compatibility Database, which was created by Microsoft and used by the Windows operating system to identify application compatibility issues. This helps developers troubleshoot legacy functions and contains data related to Windows features.
NOTES:
- Windows 10+ systems Execution flag of 1 indicates execution.
- The appcompatcache artifact does not currently support execution flag in Windows 7 and 8 / 8.1 Systems.
name: Windows.Registry.AppCompatCache
author: Matt Green - @mgreen27
description: |
Parses the AppCompatCache (Shimcache) registry value to enumerate
recently executed application paths.
AppCompatCache, also known as Shimcache, is a component of the
Application Compatibility Database, which was created by Microsoft
and used by the Windows operating system to identify application
compatibility issues. This helps developers troubleshoot legacy
functions and contains data related to Windows features.
**NOTES:**
- Windows 10+ systems Execution flag of 1 indicates execution.
- The appcompatcache artifact does not currently support execution
flag in Windows 7 and 8 / 8.1 Systems.
reference:
- https://www.mandiant.com/resources/caching-out-the-val
parameters:
- name: AppCompatCacheKey
default: HKEY_LOCAL_MACHINE/System/ControlSet*/Control/Session Manager/AppCompatCache/AppCompatCache
precondition: SELECT OS From info() where OS = 'windows'
export: |
LET AppCompatCacheParser <= '''[
["HeaderWin10", "x=>x.HeaderSize", [
["HeaderSize", 0, "unsigned int"],
["Entries", "x=>x.HeaderSize", Array, {
type: "Entry",
sentinel: "x=>x.Size = 0",
count: 10000,
}]
]],
["HeaderWin8", 128, [
["Entries", 128, Array, {
type: "EntryWin8",
sentinel: "x=>x.EntrySize = 0",
count: 10000,
}]
]],
["EntryWin8", "x=>x.EntrySize + 12", [
["Signature", 0, "String", {
length: 4,
}],
["EntrySize", 8, "unsigned int"],
["PathSize", 12, "uint16"],
["Path", 14, "String", {
length: "x=>x.PathSize",
encoding: "utf16",
}],
["LastMod", "x=>x.PathSize + 14 + 10", "WinFileTime"],
["Execution", 0, "Value",{"value":"N/A"}],
]],
["Entry", "x=>x.Size + 12", [
["Signature", 0, "String", {
length: 4,
}],
["Size", 8, "unsigned int"],
["PathSize", 12, "uint16"],
["Path", 14, "String", {
length: "x=>x.PathSize",
encoding: "utf16",
}],
["LastMod", "x=>x.PathSize + 14", "WinFileTime"],
["DataSize", "x=>x.PathSize + 14 + 8", "uint32"],
["Data", "x=>x.PathSize + 14 + 8 + 4" , "String", {
length: "x=>x.DataSize",
}],
# The last byte of the Data block is 1 for execution
["Execution", "x=>x.PathSize + 14 + 8 + 4 + x.DataSize - 4", "uint32"]
]],
# This is the Win7 parser but we don't use it right now.
["HeaderWin7x64", 128, [
["Signature", 0, "uint32"],
["Entries", 128, "Array", {
count: 10000,
sentinel: "x=>x.PathSize = 0",
type: EntryWin7x64,
}]
]],
["EntryWin7x64", 48, [
["PathSize", 0, "uint16"],
["PathOffset", 8, "uint32"],
["Path", "x=>x.PathOffset - x.StartOf", "String", {
encoding: "utf16",
length: "x=>x.PathSize",
}],
["LastMod", 16, "WinFileTime"],
["Execution", 0, "Value",{"value":"N/A"}],
]]
]'''
LET AppCompatCacheWin10(Blob) = parse_binary(
accessor="data",
filename=Blob,
profile=AppCompatCacheParser,
struct="HeaderWin10")
LET AppCompatCacheWin8(Blob) = parse_binary(
accessor="data",
filename=Blob,
profile=AppCompatCacheParser,
struct="HeaderWin8")
LET AppCompatCache(Blob) = SELECT *
FROM foreach(
row=if(
condition=AppCompatCacheWin10(Blob=Blob).HeaderSize IN (52, 48),
then=AppCompatCacheWin10(Blob=Blob).Entries,
else=AppCompatCacheWin8(Blob=Blob).Entries))
sources:
- query: |
-- first find all ControlSet Keys in scope
LET AppCompatKeys <= SELECT OSPath FROM glob(globs=AppCompatCacheKey, accessor='registry')
-- when greater than one key we need to extract results and order later
LET results <= SELECT
ModificationTime,
regex_replace(source=Name,re='''^SYSVOL''',replace="%SystemRoot%") as Path,
expand(path=regex_replace(source=Name,re='''^SYSVOL''',replace="%SystemRoot%")) as Expanded,
ExecutionFlag,
ControlSet,
Key
FROM foreach(
row={
SELECT OSPath FROM glob(accessor='registry',
globs=AppCompatCacheKey)
}, query={
SELECT OSPath AS Key, Path AS Name,
LastMod AS ModificationTime,
Execution as ExecutionFlag,
OSPath[2] as ControlSet
FROM AppCompatCache(Blob=read_file(
accessor='registry', filename=OSPath))
})
WHERE ModificationTime // removes junk build in references
-- find position of entry for each ControlSet. Lower numbers more recent
LET ControlSetPosition(cs) = SELECT *, count() - 1 as Position
FROM results WHERE ControlSet = cs
LET position = SELECT ControlSetPosition(cs=ControlSet) as Results
FROM foreach(
row={
SELECT ControlSet, count(items=ControlSet) as Entries
FROM results GROUP BY ControlSet
})
LET mutli_controlset = SELECT *
FROM foreach(
row=position.Results,
query={
SELECT * FROM foreach(row=_value)
})
-- include final test for Path/Expanded to cover dead disk usecases
SELECT
Position,
ModificationTime,
if(condition = split(string=Path,sep='''%[^%]+%''')[1] = Expanded,
then = Path,
else = Expanded ) as Path,
ExecutionFlag
FROM if(condition= len(list=AppCompatKeys.OSPath)=1,
then={
SELECT *, count() - 1 as Position FROM results
},
else= mutli_controlset )
GROUP BY Position, Path, ModificationTime