AppCompatCache (Shimcache): the RegRipper appcompatcache plugin
9 min read
AppCompatCache, the artifact everyone calls Shimcache, is one of the most cited and most misread structures in the Windows Registry. The RegRipper shimcache/appcompatcache plugin will happily dump you a list of paths and timestamps, and an inexperienced analyst will read that list as "these programs ran, at these times." Both halves of that sentence are wrong. Shimcache records that a file was present on the system in a way the application-compatibility subsystem noticed, and the timestamp it carries is the file's last-modified time, not an execution time. Knowing that distinction is the difference between a defensible timeline and a report that falls apart under cross-examination.
What it is and where it lives
The Application Compatibility Cache exists so Windows can decide whether a given executable needs a shim (a compatibility fix) before it runs. To make that decision fast, the cache holds metadata about recently seen executables. As a side effect, that metadata is gold for forensics: it survives reboots, covers binaries deleted long ago, and reaches files that never produced a Prefetch entry.
The data lives in the SYSTEM hive at:
SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache
The relevant value is named AppCompatCache (a single REG_BINARY blob). The cache is held in kernel memory while the system runs and is flushed to the registry at shutdown, so a live-acquired hive may lag the in-memory state. Grab the SYSTEM hive cleanly — see which registry hive to grab — and remember you are reading the last flushed snapshot, not the current moment.
The ControlSet gotcha
CurrentControlSet does not physically exist in an offline SYSTEM hive. It is a volatile symlink the kernel builds at boot. When you load a SYSTEM hive from a disk image, there is no CurrentControlSet key — only ControlSet001, usually ControlSet002, and a Select key telling you which one was current.
The correct resolution is to read SYSTEM\Select\Current, take that DWORD (say 1), and address ControlSet001. Our parser does exactly that: it reads Select\Current, formats the number as ControlSetNNN (zero-padded to three digits), and falls back to ControlSet001, then to CurrentControlSet, if Select is absent. RegRipper resolves the same way.
Why does this matter for Shimcache specifically? Because ControlSet001 and ControlSet002 can carry different AppCompatCache blobs from different boots. If you blindly parse the first ControlSet you find, you may be reading a stale cache and dating your timeline to the wrong day. Always confirm which set you parsed.
The format changes with every Windows version
There is no single Shimcache format. Microsoft rewrote it repeatedly, and a parser has to branch on the layout. The two our plugin handles, and the two you meet in almost every modern case, are the Windows 7 binary store and the Windows 8/10 entry format.
Windows 7 (and Server 2008 R2). The blob opens with a header magic of 0xBADC0FEE. The entry count is the DWORD at offset 4, and the entry array starts at offset 0x80. Entry size depends on architecture: 48 bytes on x64, 32 on x86. The plugin distinguishes them by reading the DWORD at 0x84 — on x64 it is padding (zero), on x86 a non-zero path offset. Each entry carries a path length, an offset to the UTF-16LE path string stored elsewhere in the blob, and an 8-byte FILETIME for the file's last-modified time.
Windows 8, 8.1, 10, 11. The header magic changed and entries became self-describing with a four-byte signature. On Windows 8 you typically see 00ts and 10ts; on Windows 10/11 the signature settled on 10ts. Each entry, after the signature, carries an unknown DWORD, a cacheEntrySize DWORD, a 2-byte path length, the UTF-16LE path, then the 8-byte FILETIME. The cacheEntrySize field lets you step entry-to-entry without trusting a global count.
The header offset for the first entry on Windows 10/11 has itself drifted across builds, which is why robust parsers — ours included — scan for the 10ts/00ts tag rather than hard-coding a start offset. If a tool produces zero entries on a hive you know is populated, a build-specific header offset is the usual culprit.
A parsed result, in the columns our plugin emits, looks like:
Cached path Last modified (UTC)
C:\Windows\System32\svchost.exe 2021-08-14 09:11:42
C:\Users\jdoe\AppData\Local\Temp\7z\setup.exe 2026-06-09 22:04:18
\??\C:\Windows\Temp\\psexesvc.exe 2026-06-09 22:07:51
Two columns. A path and a timestamp. That is all AppCompatCache gives you per entry — no run count, no last-run time, no user. Anyone who tells you Shimcache has an execution timestamp is confusing it with another artifact.
The timestamp is not what you think
This is the single most consequential misreading in the field. The FILETIME in each Shimcache entry is the target file's $STANDARD_INFORMATION last-modified time as taken from the file system, not the time the program ran and not the time the cache entry was created. It is, effectively, the same value you would read from the MFT's $SI modified field.
Consequences:
- If an attacker timestomps a binary, the Shimcache timestamp moves with the stomp. A
$SImodified time backdated to a Windows install date shows up verbatim in Shimcache. - The timestamp tells you nothing about when the file was seen by the compatibility subsystem. A binary modified in 2019 and first executed in 2026 shows a 2019 timestamp.
- You cannot order entries by their timestamps and call that an execution timeline. The timestamps are file metadata, not event times.
What does carry temporal meaning is position in the cache, discussed next.
Ordering: position matters more than the timestamp
AppCompatCache is roughly most-recently-used. New entries are inserted at the top and the cache ages out from the bottom (the Windows 7 store caps around 1,024 entries; Windows 10/11 holds fewer, often around 1,024 down to a few hundred depending on build). The blob is therefore ordered: the entry at the top was added to the cache more recently than the entry below it.
This is why preserving order matters and why a tool that sorts the output alphabetically or by timestamp destroys evidence. Our plugin appends rows in the order it walks the blob, so position-zero in the output corresponds to the top of the cache. RegRipper's plugin does the same and numbers its lines.
Position gives you relative sequencing — "this binary entered the cache after that one" — even though the timestamps do not. Combined with a known event (a reboot, a confirmed execution from another artifact), the ordering can bracket when an unknown binary was touched. Treat it as relative, never absolute, and never as proof of execution.
Why "may have executed" and not "executed"
The cache populates when the application-compatibility subsystem evaluates a file. On Windows 7 that evaluation was tied closely to execution. On Windows 8 and later it is looser: a file can land in Shimcache from being enumerated, browsed in Explorer, or otherwise touched by the shim infrastructure without ever running. The version-dependent behavior means you cannot make a universal "Shimcache = execution" claim. The defensible phrasing is: the file existed at this path on this system and was seen by the compatibility cache. If you need execution proof, you go elsewhere and corroborate.
Pairing with Amcache and Prefetch
Shimcache is strongest as a pivot, not a conclusion.
- Amcache records SHA-1 hashes and richer metadata, and its
InventoryApplicationFileentries are a far better "this ran / was installed" signal. A path in both Shimcache and Amcache means presence confirmed from two independent stores, plus a hash to run against threat intel. - Prefetch is your execution proof on workstation SKUs. A
.pffile with run counts and last-run timestamps converts a Shimcache "present" into an execution with a time. Its absence does not negate Shimcache — Prefetch is off on most servers — but its presence elevates the finding.
The triangle is: Shimcache says present, Amcache says present with a hash, Prefetch says executed at these times. A deleted dropper that appears in Shimcache but has no on-disk file is still a strong lead — Shimcache reached it precisely because it outlives deletion.
Tools
- RegRipper's
shimcache/appcompatcacheplugin. The reference parser. It resolves the ControlSet, handles the per-version formats, and prints one numbered line per entry preserving cache order. Read theshimcache.plsource to see exactly which formats it branches on. - Eric Zimmerman's AppCompatCacheParser. Purpose-built, CSV output, excellent version coverage and the cleanest handling of the Windows 10/11 header drift. The de facto standard for bulk Shimcache work.
- The dedicated Shimcache parser for a focused, browser-based look at a single blob.
- You can analyze a SYSTEM hive in your browser with the parser on this site; it resolves the correct ControlSet, walks both the
0xBADC0FEEstore and the10ts/00tsentries, and preserves cache order. See the RegRipper plugins reference for the full set.
Whatever tool you use, confirm two things: that it told you which ControlSet it parsed, and that it preserved cache order rather than sorting.
Edge cases worth knowing
ControlSet drift across boots. ControlSet001 and 002 can hold different blobs from different boots. Parse both, note Select\Current, and label which blob each finding came from.
Timestomped binaries propagate cleanly. Because the timestamp is $SI last-modified, a timestomp lands in Shimcache unchanged. Cross-check against the MFT's $FILE_NAME timestamps, which timestomping tools frequently leave alone.
The cache is capped and ages out. A binary that ran months ago may have aged out entirely. Absence from Shimcache is not absence from the system. Pull AppCompatCache from VSS snapshots to recover older cache states and reconstruct what aged out.
Paths use device notation. You will see \??\C:\... and SYSVOL\...-style prefixes depending on version. Normalize them before correlating with other artifacts, but do not strip information you might need to explain a UNC or volume-relative path.
Server versus workstation expectations. On servers, Prefetch is usually off, which makes Shimcache disproportionately important — and disproportionately likely to be over-read as execution. Hold the line on "present, may have executed."
Further reading
- Mandiant's original Shimcache research ("Leveraging the Application Compatibility Cache"), the paper that put this artifact on the DFIR map and first documented the per-version formats.
- The RegRipper
shimcache.plplugin source. - Eric Zimmerman's AppCompatCacheParser documentation for the current state of Windows 10/11 format handling.
Shimcache will not prove execution on its own, and it will lie about timing if you read the timestamp as an event time. What it does, better than almost anything, is remember that a file was here — even after the file is gone. Read it as presence, sequence by position, corroborate execution with Prefetch and Amcache, and it earns its place in the timeline.