Formato de arquivo EVTX explicado: chunks, templates e internals de BinXML
Como um arquivo .evtx é organizado em nível de byte — file header, chunks de 64 KB, a tabela de templates e o stream de registros BinXML que a referencia.
O formato Windows Event Log — .evtx — foi introduzido com o Windows Vista para substituir o .evt orientado a linha. É um container binário, append-only, em chunks, projetado para ser escrito por um único processo (o serviço EventLog) e rotacionado ou selado quando cheio. Entender como ele é organizado torna os casos de recuperação forense — arquivos parciais, dirty chunks, carving — muito mais fáceis.
File header
Todo .evtx começa com um header de 4 KB (magic ElfFile\0\0, versão, contagem de chunks, índices oldest/current de chunk e um CRC32). O header é reescrito in-place toda vez que o arquivo é rotacionado ou um chunk é selado, o que torna seus flags Dirty e Full indicadores úteis: um arquivo com Dirty setado estava aberto quando o host crashou ou a imagem de disco foi adquirida ao vivo.
Após o header vem uma sequência de chunks de tamanho fixo.
Chunks (64 KB)
Cada chunk tem exatamente 64 KB e seu próprio header de 512 bytes (magic ElfChnk\0, log record IDs do primeiro e último registro no chunk, offsets de arquivo, dois CRC32 — um para o header, um para os dados de registro). Chunks são independentes: você pode recuperar um chunk de espaço não alocado e parseá-lo sem o resto do arquivo. Isso é o que torna EVTX recuperável de fragmentos de disco.
Dentro de um chunk:
- String table — strings internadas dentro deste chunk, referenciadas por offset.
- Template table — templates XML usados por registros neste chunk, também indexados por offset.
- Records — um stream de registros BinXML, cada um referenciando um template mais valores de substituição por registro.
BinXML e templates
Registros EVTX não são armazenados como texto XML. São armazenados como BinXML, uma representação binária tokenizada de um documento XML. Para economizar espaço, o esqueleto estrutural (nomes de elemento, nomes de atributo, a forma da árvore) é fatorado em um template armazenado uma vez na template table do chunk. Cada registro então diz "use template ID 5, com valores [alice, S-1-5-21-..., 3, 0xc000006a]".
Para reconstruir o XML de um registro, um parser:
- Lê o token stream do registro.
- Procura o template pelo ID na template table do chunk.
- Substitui os valores por registro nas posições de placeholder do template.
- Emite o XML resultante.
É por isso que parsers (incluindo o que powera esta página, omerbenamram/evtx) precisam rastrear contexto chunk-local — IDs de template não são globais entre o arquivo.
Chunks sealed vs dirty
Quando o serviço EventLog termina de escrever um chunk e move para o próximo, ele computa e escreve o CRC32 do chunk e marca o header do chunk como Full. Um arquivo limpo tem todo chunk nesse estado exceto o último.
Um chunk Dirty — last-modified time depois do header do arquivo ter sido atualizado pela última vez — é o tail ao vivo. Frequentemente é parseável, mas ferramentas às vezes se recusam a lê-lo porque o stream de registros pode terminar no meio de um token. Para forense isso importa: um atacante que adquiriu um host no meio da escrita verá um chunk final dirty, e o comportamento do seu parser nesse chunk precisa ser conhecido (ele pula, dá erro ou recupera o que pode?).
Implicações práticas para parsing
- Um
.evtxtruncado — comum quando você coleta de um host ativo — frequentemente ainda é mostly recuperável, porque todo chunk completo é independente. - Chunks carved de não alocado podem ser embrulhados com um file header sintético e parseados.
- Um parse falho de um chunk não significa falha do arquivo — parsers robustos passam para o próximo chunk.
- O CRC32 do chunk é o que sinaliza tampering: um registro modificado que não recomputa o CRC é detectável.