← Voltar ao blog

Windows FILETIME explicado — convertendo timestamps NTFS em algo legível

FILETIME é o formato de timestamp do Windows que você vai encontrar em $MFT, $UsnJrnl, registro, $Recycle.Bin e quase todo artefato Windows. Referência curta e completa: o que é, como converter, as armadilhas.

4 min de leitura

Abra qualquer registro de journal USN, qualquer entrada MFT, qualquer hive de registro, e os timestamps que encontra têm todos o mesmo formato: um inteiro de 64 bits que não significa nada sem conversão. Esse formato é FILETIME, e depois que você pega o jeito, todo outro formato de tempo do Windows passa a fazer sentido.

O que é FILETIME

Um FILETIME é um inteiro de 64 bits sem sinal. Seu valor é o número de intervalos de 100 nanosegundos desde 1º de janeiro de 1601 às 00:00:00 UTC. Só isso.

A escolha de 1601 não é arbitrária — é o início do ciclo gregoriano de 400 anos que contém hoje, o que mantém a aritmética de calendário consistente sem casos especiais de ano bissexto. A Microsoft documenta o tipo na referência da estrutura FILETIME.

Um pequeno exemplo: o FILETIME 133593600000000000 corresponde a 2024-08-09 12:00:00 UTC.

Conversão para tempo Unix

A conversão tem dois passos aritméticos:

  1. Dividir por 10 000 000 para ir de ticks de 100 ns a segundos.
  2. Subtrair 11 644 473 600 — o número de segundos entre 1601-01-01 e 1970-01-01.

Em pseudocódigo:

unix_seconds = filetime / 10_000_000 - 11_644_473_600

Em Rust (é exatamente o que usnrs::Entry::unix_timestamp faz):

pub fn unix_timestamp(&self) -> i64 {
    (self.timestamp as i64) / 10_000_000 - 11_644_473_600
}

Em Python:

unix_seconds = filetime / 10_000_000 - 11_644_473_600
# ou com precisão sub-segundo:
from datetime import datetime, timezone, timedelta
EPOCH = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = EPOCH + timedelta(microseconds=filetime / 10)

Em JavaScript:

// filetime é um BigInt para manter precisão de 64 bits
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);

Em SQL (Postgres):

SELECT TIMESTAMP '1601-01-01 00:00:00' + (filetime / 10000000) * INTERVAL '1 second';

Precisão sub-segundo

A resolução completa de 100 ns raramente importa em forense — timestamps em disco estão quantizados ao que o SO se deu ao trabalho de gravar, e $STANDARD_INFORMATION atualiza por syscall, não por relógio. Mas para os casos em que importa (correlação com capturas de pacotes, por exemplo), manter o valor em ticks de 100 ns até o último passo:

import datetime as dt
EPOCH = dt.datetime(1601, 1, 1, tzinfo=dt.timezone.utc)
ticks_100ns = 133593612345678901
microseconds = ticks_100ns // 10
nanoseconds_remainder = (ticks_100ns % 10) * 100
timestamp = EPOCH + dt.timedelta(microseconds=microseconds)
print(timestamp, f"+{nanoseconds_remainder}ns")

A resolução de microssegundos do datetime do Python cobre tudo, exceto o último dígito de ticks_100ns. A maioria dos analistas trunca em milissegundos sem perder precisão significativa.

Variantes que você vai encontrar

Vários artefatos Windows usam codificações ligeiramente diferentes da mesma ideia:

FormatoOnde apareceCodificação
FILETIME$MFT, $UsnJrnl, registro, EVTX64 bits LE, ticks de 100 ns desde 1601-01-01 UTC
SYSTEMTIMEEVTX, algumas APIs COM8× campos de 16 bits (ano, mês, dia…)
TIME_T (32 bits)Chaves antigas do registroSegundos Unix desde 1970, 32 bits
DOSTIMEFAT (às vezes vaza para metadados NTFS)Data 16 bits + hora 16 bits empacotados, hora local

Ao parsear uma estrutura binária, a ordem de bytes é little-endian — o LSB do FILETIME vem primeiro.

Armadilhas comuns

  • Endianness em dumps brutos. xxd mostra bytes da esquerda para a direita; bytes de FILETIME precisam ser invertidos antes da interpretação. Ferramentas e parsers fazem isso automaticamente; análise manual em geral significa inverter a ordem primeiro.
  • Valor zero. FILETIME = 0 é um valor real (1601-01-01) mas também uma sentinela para "nunca foi definido". Tratar como null na exibição.
  • Esquisitices do complemento de dois. Algumas implementações (inclusive internas do Windows) tratam FILETIME como int64 com sinal, fazendo valores além de ~30828 d.C. envolverem em negativo. Na dúvida, usar sem sinal.
  • Local vs UTC. FILETIME é sempre UTC. Se uma ferramenta te mostra horário local, ela converteu na saída. Para DFIR, você quase sempre quer UTC para correlacionar entre hosts.
  • Timestamps $STANDARD_INFORMATION vs $FILE_NAME. Ambos vivem em $MFT, ambos são FILETIME, ambos registram tempos M/A/C/B. As cópias de $FILE_NAME atualizam com menos frequência, e é por isso que a detecção de timestomping compara os dois. As notas do Brian Carrier e o paper Mandiant sobre timestomp (hoje atrás das notas do X-Ways) são as referências padrão.

Uma pequena tabela de conversão para validar

Se você escrever seu próprio conversor, esses valores são úteis para checar:

FILETIMEData UTC
01601-01-01 00:00:00
1164447360000000001970-01-01 00:00:00
1329235200000000002022-02-23 16:00:00
1335936000000000002024-08-09 12:00:00

Se seu conversor produz esses, produz o resultado certo.