TL;DR. Primeiro u32 = tamanho do cabeçalho, tipicamente 0x30 a 0x36. Nesse offset cada entrada começa com a magic de quatro bytes 10ts, seguida por um cabeçalho de 12 bytes e um corpo de comprimento variável: caminho UTF-16LE (com prefixo de tamanho, sem terminador nulo), mtime em FILETIME e um blob de dados final. A flag de execução foi removida no Windows 10.
Cada lançamento do Windows desde o XP entregou uma disposição diferente da AppCompatCache. A variante Windows 10/11 é a que os investigadores veem na maior parte dos dias, e é a mais tolerante uma vez que se saiba onde estão as armadilhas. Este é o passo a passo que eu gostaria de ter tido quando tentei fazer um parser à mão pela primeira vez.
O blob que vai analisar
Comece pelo valor binário em:
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCompatCache\AppCompatCache
Extraia-o da hive SYSTEM (veja onde a ShimCache está armazenada). O que obtém é um único blob, tipicamente com dezenas a centenas de kilobytes. Pequeno cabeçalho, depois uma sequência de entradas, cada uma a começar com uma magic conhecida.
O cabeçalho
Os primeiros quatro bytes little-endian do blob são o offset da primeira entrada. No Windows 10/11 isto é normalmente 0x30, 0x34, 0x35 ou 0x36, consoante o build. Algumas hives trazem aqui 0x80, o que sinaliza uma disposição Windows 8.x que ainda usa a assinatura de entrada 10ts / 00ts.
Não fixe o tamanho do cabeçalho em código. Leia o primeiro u32 e valide que o byte nesse offset é o início de uma entrada reconhecida. Se nem 10ts nem 00ts aparecerem no offset declarado, varra para a frente à procura de 10ts e retome a partir daí. Hives reais entregam dados ligeiramente fora da especificação, e um parser que desiste à primeira incompatibilidade de byte é um parser que perde casos.
Uma entrada Windows 10/11, byte a byte
Cada entrada moderna começa com a assinatura de quatro bytes 10ts (0x31 0x30 0x74 0x73). Disposição completa:
| Offset | Tamanho | Campo |
|---|---|---|
| 0x00 | 4 | Magic, "10ts" |
| 0x04 | 4 | Sequência / desconhecido, frequentemente ignorado |
| 0x08 | 4 | Tamanho dos dados da entrada, bytes a seguir a este campo |
| 0x0C | 2 | Tamanho do caminho em bytes (UTF-16LE) |
| 0x0E | N | Bytes do caminho, UTF-16LE, sem terminador |
| 0x0E + N | 8 | Hora da última modificação, FILETIME (100 ns desde 1601-01-01 UTC) |
| 0x16 + N | 4 | Tamanho dos dados finais |
| 0x1A + N | M | Dados finais, normalmente vazios ou pequenos |
O tamanho total da entrada é 0x0C + entry_data_size. A próxima entrada começa imediatamente a seguir.
Duas notas práticas:
- O caminho é UTF-16LE e não tem terminador nulo. Use o campo de tamanho do caminho. Não leia até atingir um zero; vai engolir o cabeçalho da entrada seguinte.
- O
FILETIMEé o mtime$STANDARD_INFORMATIONdo ficheiro. Não a hora em que o programa correu. Não a hora em que a entrada foi inserida. O mtime do ficheiro, copiado diretamente do NTFS no momento do exame. O artigo de prova de execução explica porque isso importa.
O que saiu no Windows 10
Versões anteriores do Windows acondicionavam uma flag Executed dentro de cada entrada. Analistas de Win7 e 8.x aprenderam a lê-la. O Windows 10 removeu-a. O Windows 11 não a repôs. Não há forma em cache de saber se um binário correu de facto. Precisa de Prefetch, AmCache, ou EVTX / Sysmon.
Para as disposições de campos antigas (XP até 8.1), veja formatos antigos da ShimCache.
Parsing na prática
Um parser que sobrevive a revisão:
- Valida a magic de quatro bytes no offset anunciado pelo cabeçalho.
- Itera entradas lendo o campo de tamanho dos dados da entrada e avançando.
- Descodifica o caminho como UTF-16LE usando o prefixo de tamanho.
- Converte o
FILETIMEpara o formato de timestamp que as ferramentas a jusante preferem. Milissegundos Unix é conveniente. ISO 8601 UTC é o que os relatórios efetivamente querem. - Expõe o rótulo de formato de OS detetado (Windows 10/11 vs 8.x vs 8.0) para filtragem a jusante.
- Reproduz
SYSTEM.LOG1eSYSTEM.LOG2antes de ler o valor. Se o seu parser não fizer isto, está a ler uma hive desatualizada.
O Shimcache Parser open source implementa os passos 1 a 5 em Rust e entrega-os ao seu browser como WebAssembly. Lê um único ficheiro de hive e não reproduz os logs de transação, por isso passe primeiro uma hive dirty pelo rla.exe ou pelo Registry Explorer. Sem servidor, sem upload. Útil quando a hive SYSTEM veio de um ambiente regulado.
Leitura adicional
- AppCompatCacheParser de Eric Zimmerman, implementação C# padrão-ouro que cobre todas as variantes do Windows.
- Shim Cache Parser da Mandiant, proof-of-concept Python original, útil como referência cruzada de formato.
- Velociraptor
Windows.Registry.AppCompatCache, artefacto VQL para recolha em sistema vivo. - Windows 10/11 AppCompatCache deep dive no Ø Security, o estudo público mais completo da disposição moderna.