Compreender o journal USN do NTFS ($UsnJrnl:$J)

Introdução prática ao journal Update Sequence Number do NTFS: o que é, como está estruturado em disco e por que é tão valioso na forense de Windows.

Por 6 min de leitura

O journal Update Sequence Number do NTFS é o primeiro artefacto a que recorro em qualquer host Windows onde precise de saber o que tocou no disco e quando. Está activado por predefinição em todos os volumes NTFS que o Windows expede desde o Vista, quase ninguém o desactiva, e quase ninguém o lê antes de algo correr mal. É essa assimetria que o torna útil.

Cada criação, eliminação, mudança de nome, truncamento, alternância de atributo e fecho de ficheiro no volume acrescenta um registo a $UsnJrnl:$J. Os registos são pequenos (algumas dezenas de bytes cada) e o ficheiro é um buffer circular de tamanho fixo, pelo que uma máquina activa retém grosso modo os últimos milhões de operações. Num desktop com a alocação predefinida de 32 MB são alguns dias. Num servidor de ficheiros dimensionado para 1 GB são semanas.

Onde o journal realmente vive

O journal não é um ficheiro normal. É um fluxo de dados alternativo chamado $J, anexado ao ficheiro de metadados \$Extend\$UsnJrnl na raiz do volume. Dois detalhes que apanham quem nunca o fez:

  • Um arrastar-e-largar no Explorador, ou um xcopy, ou um robocopy, vai copiar silenciosamente o fluxo predefinido vazio e ficará com um ficheiro de zero bytes. Use uma ferramenta que perceba ADS: FTK Imager, X-Ways, icat do The Sleuth Kit, RawCopy.exe, PowerForensics. O artigo complementar sobre extrair $J de uma imagem de disco percorre cada uma.
  • Existe um fluxo irmão chamado $Max no mesmo ficheiro de metadados. Contém apenas metadados de tamanho/configuração. Se extrair $Max por engano, não obtém nada útil. Escolha sempre $J.

O próprio ficheiro é esparso. O NTFS reserva o tamanho máximo configurado em disco mas mantém a região inicial não utilizada a zeros. Quando se extrai $J de uma imagem, os primeiros muitos megabytes costumam ser apenas zeros antes do primeiro registo real. Qualquer parser razoável (usnrs, o USN-Journal-Parser do PoorBillionaire, MFTECmd, o parser WebAssembly desta página) salta automaticamente os zeros iniciais.

Estrutura do registo (USN_RECORD_V2)

Cada registo é uma struct USN_RECORD_V2. A Microsoft documenta o layout em winioctl.h; segue o resumo campo a campo:

CampoBytesSignificado
RecordLength4Tamanho total do registo, incluindo nome do ficheiro
Major / Minor version2 + 2Sempre 2.0 no que vai encontrar na prática
FileReferenceNumber8Entrada MFT + sequência do ficheiro
ParentFileReferenceNumber8Entrada MFT + sequência do directório pai
USN8Posição deste registo no journal
Timestamp8FILETIME do Windows (ticks de 100 ns desde 1601-01-01)
Reason4Bitmask que descreve o que mudou
SourceInfo4Pistas (ex.: DATA_MANAGEMENT para alterações iniciadas pelo SO)
SecurityId4Índice em $Secure:$SII
FileAttributes4Atributos NTFS padrão
FilenameLength / Offset2 + 2Posição do nome UTF-16 no registo
FilenamenUTF-16 LE, sem terminador nulo

A bitmask Reason é o campo que justifica o seu peso. É aditiva, não exclusiva: um único registo pode trazer FileCreate | DataExtend | Close juntos. Um ciclo de vida típico de ficheiro é FileCreate | DataExtendDataExtend | CloseBasicInfoChange | CloseFileDelete | Close. Reconstituir essa sequência a posteriori é como se conta o que realmente aconteceu ao ficheiro. O artigo sobre códigos de razão é a referência de campo para cada bit.

Dois campos que o leitor recente costuma subestimar:

  • FileReferenceNumber é a sua chave de junção. Agrupe os registos por ela e tem toda a história do ficheiro através de mudanças de nome, alterações de atributos e eliminação. Note que os 16 bits superiores são um número de sequência: um ficheiro eliminado e um novo ficheiro alocados à mesma entrada MFT partilham a entrada mas diferem na sequência.
  • ParentFileReferenceNumber é a única informação de caminho do registo. O nome do ficheiro é apenas a folha. Precisa de um $MFT parseado para percorrer a cadeia parental até um caminho completo. Adquira ambos, sempre.

Por que ganha o seu lugar em DFIR

Duas razões que aparecem em quase todos os trabalhos.

Primeira, o journal sobrevive à eliminação. Mesmo depois de um ficheiro desaparecer do namespace e da sua entrada MFT ser reutilizada para outra coisa, o registo da sua criação, crescimento, mudança de nome e eliminação continua em $J até o buffer circular passar por cima. O mesmo se aplica a ficheiros esvaziados da reciclagem: os ficheiros $I da reciclagem são limpos, mas o journal lembra-se. Numa caça do tipo «este ficheiro alguma vez existiu», o journal aguenta mais que a entrada MFT e que os metadados da reciclagem na maior parte das vezes.

Segunda, é barato e auto-descritivo. Um $J de 100 MB normalmente contém cinco a dez milhões de registos cobrindo dias a semanas. Cada registo carrega o seu próprio timestamp, razão, referência do ficheiro e do pai. Pode passá-lo por um parser em segundos e pivotar sobre qualquer campo. Não existe outro artefacto Windows a este preço.

O que não lhe vai dizer é quem. Não há utilizador, processo nem linha de comandos em registo algum. Para atribuir um actor, correlacione por timestamp com o evento 4663 de Security.evtx (acesso a objecto, exige SACLs que quase de certeza não configurou), o evento 11 do Sysmon (criação de ficheiro), ou as árvores de processos do evento 1 do Sysmon. O journal responde ao quê e ao quando. Outros registos respondem ao quem.

Onde se encaixa na pilha de artefactos

O modelo mental que uso, ordenado de «estado actual» a «história histórica»:

  • $MFT — o índice de estado actual. Cada ficheiro e directório no volume, incluindo algumas entradas recentemente eliminadas. Quatro timestamps por atributo, sem histórico.
  • $UsnJrnl:$J — histórico por operação, dias a semanas. O que está a ler aqui.
  • $LogFile — o log de transacções de recuperação após crash do NTFS. Minutos a horas de imagens brutas antes/depois. O artigo USN journal vs MFT vs LogFile detalha quando recorrer a cada um.

Junte o journal a Prefetch, AmCache, Shimcache, ficheiros LNK e jump lists e consegue habitualmente reconstituir, ao minuto, o que correu, o que foi aberto e o que tocou no disco.

O que vem a seguir

Se nunca abriu um journal, a forma mais rápida de criar intuição é tirar um de uma máquina de teste e carregá-lo no parser no topo desta página juntamente com o $MFT correspondente. Filtre por FileCreate e leia o ficheiro pela ordem em que o Windows o escreveu. Os artigos seguintes desta série aprofundam as flags de razão, o procedimento de extracção, e os padrões que o journal expõe para ransomware, exfiltração, timestomping e recuperação de ficheiros eliminados.

Leituras adicionais

  • Microsoft Learn — Change Journals e a referência USN_RECORD_V2.
  • O usnrs da Airbus CERT — o parser USN_RECORD_V2 open-source mais limpo que conheço, e o que dá vida ao módulo WebAssembly deste site.
  • Brian Carrier, File System Forensic Analysis — o tratamento ao nível de livro dos internals do NTFS que ainda não foi substituído.