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:
- Dividir por 10 000 000 para ir de ticks de 100 ns a segundos.
- 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:
| Formato | Onde aparece | Codificação |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, registro, EVTX | 64 bits LE, ticks de 100 ns desde 1601-01-01 UTC |
SYSTEMTIME | EVTX, algumas APIs COM | 8× campos de 16 bits (ano, mês, dia…) |
TIME_T (32 bits) | Chaves antigas do registro | Segundos Unix desde 1970, 32 bits |
DOSTIME | FAT (à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.
xxdmostra 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
FILETIMEcomoint64com 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_INFORMATIONvs$FILE_NAME. Ambos vivem em$MFT, ambos são FILETIME, ambos registram tempos M/A/C/B. As cópias de$FILE_NAMEatualizam 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:
| FILETIME | Data UTC |
|---|---|
0 | 1601-01-01 00:00:00 |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
Se seu conversor produz esses, produz o resultado certo.