Parsing the ShimCache: the Windows 10 and 11 binary format

4 min read

TL;DR. First u32 = header size (typically 0x300x36). 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.

Windows 10/11 ShimCache binary entry layout

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:

OffsetSizeField
0x004Magic — "10ts"
0x044Sequence / unknown (often unused by parsers)
0x084Entry data size — bytes following this field
0x0C2Path size in bytes (UTF-16LE)
0x0ENPath bytes (UTF-16LE, no terminator)
0x0E + N8Last-modified time — FILETIME (100-ns since 1601-01-01 UTC)
0x16 + N4Trailing data size
0x1A + NMTrailing data (usually empty or small)

Total entry size is therefore 0x0C + entry_data_size. The next entry begins immediately after.

Two practical notes:

  1. The path is UTF-16LE and is not zero-terminated. Use the path-size field; don't read until you hit a null.
  2. The FILETIME is the file's $STANDARD_INFORMATION mtime, 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:

  1. Validates the four-byte magic at the offset advertised by the header.
  2. Iterates entries by reading the entry-data-size field and advancing.
  3. Decodes the path as UTF-16LE.
  4. Converts the FILETIME to whatever timestamp format your downstream tooling prefers (Unix ms is convenient).
  5. 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

Related articles