TL;DR. First u32 = header size (typically 0x30–0x36). At that offset each entry begins with the four-byte magic 10ts, followed by a 12-byte header and a variable-length body: UTF-16LE path (length-prefixed, not null-terminated), FILETIME mtime, and a trailing data blob. The execution flag was removed in Windows 10.
Every Windows version since XP has shipped a different ShimCache layout. The Windows 10 / 11 variant is the most common one investigators see today, and it is also the most forgiving to parse — once you know the rules. This post walks through that format end-to-end.
The blob you're parsing
You start with the binary value at:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache\AppCompatCache
If you've extracted that value from the SYSTEM hive (see where the ShimCache is stored), what you have is a single blob, typically tens to hundreds of kilobytes. The blob holds a small header followed by a sequence of entries, each starting with a known magic value.
The header
The first four little-endian bytes of the blob give the offset of the first entry. On Windows 10 / 11 this is commonly 0x30, 0x34, 0x35, or 0x36, depending on the build. Some hives place 0x80 here instead — that signals a Windows 8.x layout that uses the same "10ts" / "00ts" entry signature.
A defensible parser does not hardcode the header size; it reads the first u32 and validates that the byte at that offset is the start of a recognized entry. If neither 10ts nor 00ts shows up at the declared offset, scan forward for 10ts and pick up from there — this handles slightly off-spec hives that real systems sometimes produce.
A Windows 10 / 11 entry, byte by byte
Each modern entry starts with the four-byte signature 10ts (0x31 0x30 0x74 0x73). The full layout, in order, is:
| Offset | Size | Field |
|---|---|---|
| 0x00 | 4 | Magic — "10ts" |
| 0x04 | 4 | Sequence / unknown (often unused by parsers) |
| 0x08 | 4 | Entry data size — bytes following this field |
| 0x0C | 2 | Path size in bytes (UTF-16LE) |
| 0x0E | N | Path bytes (UTF-16LE, no terminator) |
| 0x0E + N | 8 | Last-modified time — FILETIME (100-ns since 1601-01-01 UTC) |
| 0x16 + N | 4 | Trailing data size |
| 0x1A + N | M | Trailing data (usually empty or small) |
Total entry size is therefore 0x0C + entry_data_size. The next entry begins immediately after.
Two practical notes:
- The path is UTF-16LE and is not zero-terminated. Use the path-size field; don't read until you hit a null.
- The
FILETIMEis the file's$STANDARD_INFORMATIONmtime, not the time the program ran. The proof-of-execution post covers why this matters.
What got removed in Windows 10
Earlier Windows versions packed an Executed flag inside each entry. On Windows 10 and 11, that flag was removed entirely. There is no in-cache way to tell whether a binary actually ran — you need Prefetch, AmCache, or event logs for that.
Parsing in practice
A robust parser:
- Validates the four-byte magic at the offset advertised by the header.
- Iterates entries by reading the entry-data-size field and advancing.
- Decodes the path as UTF-16LE.
- Converts the
FILETIMEto whatever timestamp format your downstream tooling prefers (Unix ms is convenient). - Surfaces the OS-format label (Windows 10/11 vs 8.x vs 8.0) for downstream filtering.
If you want a working reference, the open source Shimcache Parser implements exactly the algorithm above in Rust and ships it to your browser as WebAssembly — no server, no upload.
Reference parsers
- Eric Zimmerman's AppCompatCacheParser — the gold-standard C# implementation that covers every Windows variant.
- Mandiant ShimCacheParser — the original Python proof-of-concept, useful as a format cross-reference.
- Velociraptor
Windows.Registry.AppCompatCache— VQL artifact for live collection. - Windows 10/11 AppCompatCache deep dive (Ø Security) — the most thorough public write-up of the modern layout.