Abra qualquer registo USN, qualquer entrada MFT, qualquer hive do registo, qualquer ficheiro LNK, qualquer trace de Prefetch, qualquer jump list, e os timestamps que vai encontrar têm todos o mesmo formato: um inteiro de 64 bits que sem conversão se lê como gibberish. Esse formato é FILETIME. Aprenda-o uma vez e leia todos os artefactos Windows para o resto da carreira.
A definição
Um FILETIME é um inteiro sem sinal de 64 bits a contar intervalos de 100 nanossegundos desde 1601-01-01 00:00:00 UTC. É essa toda a especificação.
A escolha de 1601 não é arbitrária — é o início do ciclo gregoriano de 400 anos que contém o presente, o que permite à aritmética de calendário saltar os casos limite de anos bissextos com que o tempo Unix tem de lidar. A Microsoft documenta o tipo na referência da estrutura FILETIME.
Um exemplo trabalhado: FILETIME 133593600000000000 é 2024-08-09 12:00:00 UTC. Verifique o seu conversor contra esse valor antes de confiar nele com dados de casos.
Converter para tempo Unix
Dois passos aritméticos:
- Divida por 10.000.000 para passar de ticks de 100 ns para segundos.
- Subtraia 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, que é exactamente 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, onde a biblioteca padrão trata da aritmética de calendário por si:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
# 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, onde é preciso BigInt para preservar a precisão de 64 bits:
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);
Em SQL (Postgres), útil quando tem uma coluna de valores FILETIME brutos despejados a partir de uma exportação Plaso:
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. Os timestamps de disco são quantizados ao que o NTFS se deu ao trabalho de registar, e as actualizações de $STANDARD_INFORMATION são guiadas por chamadas de sistema e não pelo relógio do sistema. Mas nos casos em que importa — correlacionar um registo USN com uma captura de pacotes, alinhar um evento 11 do Sysmon com um DataExtend — quer-se manter o valor em ticks de 100 ns até ao ú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")
O datetime do Python cobre tudo menos o último dígito. A maioria dos analistas trunca a milissegundos sem perder precisão significativa.
Variantes com que se vai cruzar
O Windows traz várias codificações de timestamp e elas escorregam entre artefactos de formas que apanham analistas recentes:
| Formato | Onde aparece | Codificação |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, registo, EVTX | 64 bits LE, ticks de 100 ns desde 1601-01-01 UTC |
SYSTEMTIME | Saída renderizada de EVTX, algumas APIs COM | 8× campos de 16 bits (ano, mês, dia, ...) |
TIME_T (32 bits) | Chaves de registo antigas, cabeçalhos de pagefile | Segundos Unix desde 1970, 32 bits |
DOSTIME | FAT (ocasionalmente escorrega para metadados NTFS copiados a partir de FAT) | Data de 16 bits empacotada + hora de 16 bits, hora local |
Em disco, FILETIME é little-endian — o LSB vem primeiro. O xxd mostra os bytes da esquerda para a direita; inverta a ordem dos bytes antes de interpretar se está a ler à mão. As ferramentas tratam disto automaticamente.
Armadilhas que custaram tempo real
Endianness em dumps em bruto. Os visualizadores hexadecimais mostram em ordem de memória; a aritmética da conversão assume que tem o valor inteiro. Os bytes precisam de ser invertidos primeiro.
A sentinela do zero. FILETIME = 0 é tecnicamente 1601-01-01 00:00:00 UTC. Na prática o Windows usa-o para significar «nunca definido». Trate o zero como null na camada de apresentação ou publicará relatórios que afirmam que um ficheiro foi acedido pela última vez em 1601. A mesma armadilha aplica-se a certos timestamps $FILE_NAME que o NTFS deixa por definir.
Com ou sem sinal. Alguns internals do Windows (e alguns parsers construídos sobre eles) tratam FILETIME como int64 com sinal. Valores depois de cerca de 30828 dC dão a volta para negativos, o que obviamente não importa na prática — mas um valor deliberadamente adulterado pode aterrar nesse intervalo e partir um conversor com bugs. Mantenha sem sinal.
Local vs UTC. FILETIME está sempre em UTC. Se uma ferramenta mostra hora local, converteu na saída. Para DFIR quer-se quase sempre UTC na camada de armazenamento — converta para fusos locais apenas ao renderizar para um stakeholder. Caso contrário, a correlação entre hosts e fusos horários desfaz-se.
$STANDARD_INFORMATION vs $FILE_NAME. Ambos vivem em cada entrada $MFT, ambos guardam tempos M/A/C/B em FILETIME. Os valores SI são escrevíveis a partir do espaço de utilizador; as cópias FN actualizam menos vezes e são muito mais difíceis de alterar. Comparar os dois é a detecção clássica de timestomping, e o artigo sobre timestomping explica exactamente como o journal USN complementa essa comparação.
Tabela de verificação
Se escrever o seu próprio conversor — e a certa altura toda a gente o faz, porque a linguagem em que está preso não trata da conversão nativamente — estes valores são contra os quais costumo verificar:
| FILETIME | Data UTC |
|---|---|
0 | 1601-01-01 00:00:00 (ou null, por convenção) |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
Se o seu conversor produz estes quatro, produz a coisa certa para tudo o resto.
Leituras adicionais
- Microsoft Learn — estrutura FILETIME e a função relacionada FileTimeToSystemTime.
- libfwnt do Joachim Metz — a implementação C de referência para conversão FILETIME que quase todos os parsers libyal usam.
- SANS DFIR — Windows time rules cheat sheet cobre como os timestamps
$STANDARD_INFORMATIONe$FILE_NAMEse actualizam sob diferentes operações, complemento prático ao próprio formato FILETIME.