Analisando o ShimCache: o formato binário do Windows 10 e 11

4 min de leitura

TL;DR. O primeiro u32 = tamanho do cabeçalho (tipicamente 0x300x36). Nesse offset, cada entrada começa com o magic de 4 bytes 10ts, seguido de um cabeçalho de 12 bytes e um corpo de tamanho variável: caminho UTF-16LE (com prefixo de tamanho, sem terminador nulo), mtime FILETIME e um blob de dados final. O flag de execução foi removido no Windows 10.

Toda versão do Windows desde o XP entregou um layout de ShimCache diferente. A variante Windows 10 / 11 é a que os investigadores mais veem hoje, e também a mais clemente de analisar — uma vez que você conhece as regras. Este post percorre o formato de ponta a ponta.

Layout binário de uma entrada ShimCache do Windows 10/11

O blob a analisar

Você começa com o valor binário em:

HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache\AppCompatCache

Se você extraiu esse valor da hive SYSTEM (veja onde o ShimCache fica armazenado), o que você tem é um único blob, tipicamente de dezenas a centenas de kilobytes. O blob contém um cabeçalho pequeno seguido por uma sequência de entradas, cada uma começando com um valor magic conhecido.

O cabeçalho

Os primeiros quatro bytes little-endian do blob dão o offset da primeira entrada. No Windows 10 / 11 isso é tipicamente 0x30, 0x34, 0x35 ou 0x36, dependendo do build. Algumas hives colocam 0x80 aqui — sinal de um layout Windows 8.x que usa a mesma assinatura de entrada 10ts / 00ts.

Um parser defensivo não codifica fixo o tamanho do cabeçalho; lê o primeiro u32 e valida que o byte naquele offset é o início de uma entrada reconhecida. Se nem 10ts nem 00ts aparecerem no offset declarado, varra para a frente em busca de 10ts e retome a partir daí — isso lida com hives ligeiramente fora da especificação que sistemas reais às vezes produzem.

Uma entrada Windows 10 / 11, byte a byte

Cada entrada moderna começa com a assinatura de quatro bytes 10ts (0x31 0x30 0x74 0x73). O layout completo, em ordem, é:

OffsetTamanhoCampo
0x004Magic — "10ts"
0x044Sequência / desconhecido (frequentemente ignorado pelos parsers)
0x084Tamanho dos dados da entrada — bytes após este campo
0x0C2Tamanho do caminho em bytes (UTF-16LE)
0x0ENBytes do caminho (UTF-16LE, sem terminador)
0x0E + N8Última modificação — FILETIME (100 ns desde 1601-01-01 UTC)
0x16 + N4Tamanho dos dados finais
0x1A + NMDados finais (geralmente vazios ou pequenos)

O tamanho total da entrada é portanto 0x0C + entry_data_size. A próxima entrada começa imediatamente depois.

Duas notas práticas:

  1. O caminho é UTF-16LE e não é terminado por nulo. Use o campo de tamanho do caminho; não leia até encontrar um null.
  2. O FILETIME é o mtime $STANDARD_INFORMATION do arquivo, não o momento em que o programa rodou. O post sobre prova de execução explica por que isso importa.

O que foi removido no Windows 10

Versões anteriores do Windows empacotavam um flag Executed dentro de cada entrada. No Windows 10 e 11, ele foi removido inteiramente. Não há forma, no cache, de saber se um binário realmente rodou — você precisa de Prefetch, AmCache ou logs de eventos para isso.

Na prática

Um parser robusto:

  1. Valida o magic de quatro bytes no offset anunciado pelo cabeçalho.
  2. Itera entradas lendo o campo de tamanho dos dados e avançando.
  3. Decodifica o caminho como UTF-16LE.
  4. Converte o FILETIME no formato de carimbo que seu tooling a jusante prefere (Unix ms é conveniente).
  5. Expõe o rótulo do formato do SO (Windows 10/11 vs 8.x vs 8.0) para filtragem a jusante.

Se você quer uma referência funcionando, o Shimcache Parser open source implementa exatamente o algoritmo acima em Rust e o entrega ao seu navegador como WebAssembly — sem servidor, sem upload.

Parsers de referência

Artigos relacionados