Apri un qualsiasi record USN, una qualsiasi entry MFT, una qualsiasi hive del registro, un qualsiasi file LNK, una qualsiasi traccia Prefetch, una qualsiasi jump list, e i timestamp che trovi hanno tutti la stessa forma: un intero a 64 bit che si legge come un'assurdità senza conversione. Quel formato è FILETIME. Imparalo una volta, leggi ogni artefatto Windows per il resto della tua carriera.
La definizione
Un FILETIME è un intero senza segno a 64 bit che conta intervalli di 100 nanosecondi dal 1601-01-01 00:00:00 UTC. Questa è l'intera specifica.
La scelta del 1601 non è arbitraria: è l'inizio del ciclo gregoriano di 400 anni che contiene il presente, il che permette all'aritmetica del calendario di saltare i casi limite di anno bisestile che il tempo Unix deve gestire. Microsoft documenta il tipo nella reference della struttura FILETIME.
Un esempio lavorato: FILETIME 133593600000000000 è 2024-08-09 12:00:00 UTC. Verifica il tuo convertitore contro quel valore prima di fidartene su dati di caso.
Conversione in tempo Unix
Due passi aritmetici:
- Dividi per 10.000.000 per passare da tick di 100 ns a secondi.
- Sottrai 11.644.473.600 — il numero di secondi tra 1601-01-01 e 1970-01-01.
In pseudocodice:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
In Rust, che è esattamente quello che fa usnrs::Entry::unix_timestamp:
pub fn unix_timestamp(&self) -> i64 {
(self.timestamp as i64) / 10_000_000 - 11_644_473_600
}
In Python, dove la libreria standard gestisce l'aritmetica del calendario per te:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
# con precisione sub-secondo:
from datetime import datetime, timezone, timedelta
EPOCH = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = EPOCH + timedelta(microseconds=filetime / 10)
In JavaScript, dove ti serve BigInt per preservare la precisione a 64 bit:
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);
In SQL (Postgres), utile quando hai una colonna di valori FILETIME grezzi scaricati da un export Plaso:
SELECT TIMESTAMP '1601-01-01 00:00:00' + (filetime / 10000000) * INTERVAL '1 second';
Precisione sub-secondo
La risoluzione piena a 100 ns raramente conta per la forense. I timestamp su disco sono quantizzati a quello che NTFS si è degnato di registrare, e gli aggiornamenti di $STANDARD_INFORMATION sono guidati da syscall piuttosto che dall'orologio di sistema. Ma i casi in cui conta — correlare un record USN con una cattura pacchetti, allineare un Sysmon evento 11 contro un DataExtend — vuoi tenere il valore in tick da 100 ns fino all'ultimissimo 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")
Il datetime di Python copre tutto tranne l'ultima cifra. La maggior parte degli analisti tronca ai millisecondi senza perdere precisione significativa.
Varianti in cui ti imbatterai
Windows spedisce diverse codifiche di timestamp e filtrano tra gli artefatti in modi che colpiscono gli analisti nuovi:
| Formato | Dove appare | Codifica |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, registro, EVTX | 64 bit LE, tick da 100 ns dal 1601-01-01 UTC |
SYSTEMTIME | Output renderizzato EVTX, alcune API COM | 8 campi a 16 bit (anno, mese, giorno, ...) |
TIME_T (32 bit) | Chiavi di registro più vecchie, header del pagefile | Secondi Unix dal 1970, 32 bit |
DOSTIME | FAT (talvolta trapela in metadati NTFS copiati da FAT) | Data 16 bit + ora 16 bit impacchettate, ora locale |
Su disco, FILETIME è little-endian: il LSB viene per primo. xxd mostra i byte da sinistra a destra; inverti l'ordine dei byte prima dell'interpretazione se leggi a mano. Gli strumenti lo gestiscono automaticamente.
Insidie che hanno fatto perdere tempo vero
Endianness nei dump grezzi. I visualizzatori hex mostrano in ordine memoria; l'aritmetica di conversione assume di avere il valore intero. I byte vanno invertiti prima.
La sentinella zero. FILETIME = 0 è tecnicamente 1601-01-01 00:00:00 UTC. In pratica Windows lo usa per dire "mai impostato". Tratta lo zero come null al livello di presentazione altrimenti pubblicherai report che dicono che un file è stato accesso l'ultima volta nel 1601. La stessa trappola si applica a certi timestamp $FILE_NAME che NTFS lascia non impostati.
Signed vs unsigned. Alcuni interni di Windows (e alcuni parser costruiti sopra) trattano FILETIME come int64 con segno. I valori oltre circa il 30828 d.C. vanno in overflow in negativi, il che ovviamente non conta in pratica — ma un valore deliberatamente manomesso può cadere in quell'intervallo e rompere un convertitore con bug. Resta su unsigned.
Locale vs UTC. FILETIME è sempre UTC. Se uno strumento mostra l'ora locale, ha convertito all'uscita. Per il DFIR vuoi quasi sempre UTC al livello di storage — converti in fusi locali solo quando renderizzi per uno stakeholder. La correlazione cross-host attraverso i fusi orari cade altrimenti.
$STANDARD_INFORMATION vs $FILE_NAME. Entrambi vivono in ogni entry $MFT, entrambi memorizzano tempi M/A/C/B in FILETIME. I valori SI sono scrivibili dal user-space; le copie FN si aggiornano meno spesso e sono molto più difficili da cambiare. Confrontare i due è il rilevamento classico del timestomping, e il post sul timestomping mostra esattamente come il USN journal complementa quel confronto.
Tabella di verifica
Se scrivi il tuo convertitore — e a un certo punto tutti lo fanno, perché il linguaggio con cui sono bloccati non gestisce la conversione in modo nativo — questi sono i valori contro cui verifico:
| FILETIME | Data UTC |
|---|---|
0 | 1601-01-01 00:00:00 (o null, per convenzione) |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
Se il tuo convertitore produce quei quattro, produce la cosa giusta per tutto il resto.
Letture aggiuntive
- Microsoft Learn — struttura FILETIME e la funzione correlata FileTimeToSystemTime.
- libfwnt di Joachim Metz — l'implementazione C di riferimento per la conversione FILETIME che quasi ogni parser libyal usa.
- SANS DFIR — il cheat sheet delle regole temporali Windows copre come i timestamp
$STANDARD_INFORMATIONe$FILE_NAMEsi aggiornano sotto operazioni diverse, che è il complemento pratico al formato FILETIME stesso.