Ouvre n'importe quel enregistrement USN, n'importe quelle entrée MFT, n'importe quelle hive de registre, n'importe quel fichier LNK, n'importe quelle trace Prefetch, n'importe quelle jump list, et les timestamps que tu trouves ont tous la même forme : un entier 64 bits qui se lit comme du charabia sans conversion. Ce format est FILETIME. Apprends-le une fois, lis tous les artefacts Windows pour le reste de ta carrière.
La définition
Un FILETIME est un entier non signé 64 bits qui compte les intervalles de 100 nanosecondes depuis 1601-01-01 00:00:00 UTC. Voilà toute la spécification.
Le choix de 1601 n'est pas arbitraire : c'est le début du cycle grégorien de 400 ans qui contient le présent, ce qui permet à l'arithmétique de calendrier de sauter les cas limites d'années bissextiles que le temps Unix doit gérer. Microsoft documente le type dans la référence de structure FILETIME.
Un exemple travaillé : FILETIME 133593600000000000 vaut 2024-08-09 12:00:00 UTC. Vérifie ton convertisseur contre cette valeur avant de lui faire confiance sur des données d'enquête.
Conversion en temps Unix
Deux étapes arithmétiques :
- Diviser par 10 000 000 pour passer des ticks de 100 ns aux secondes.
- Soustraire 11 644 473 600 — le nombre de secondes entre le 1601-01-01 et le 1970-01-01.
En pseudo-code :
unix_seconds = filetime / 10_000_000 - 11_644_473_600
En Rust, ce que fait exactement usnrs::Entry::unix_timestamp :
pub fn unix_timestamp(&self) -> i64 {
(self.timestamp as i64) / 10_000_000 - 11_644_473_600
}
En Python, où la bibliothèque standard gère l'arithmétique de calendrier pour toi :
unix_seconds = filetime / 10_000_000 - 11_644_473_600
# avec précision sub-seconde :
from datetime import datetime, timezone, timedelta
EPOCH = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = EPOCH + timedelta(microseconds=filetime / 10)
En JavaScript, où il te faut BigInt pour préserver la précision 64 bits :
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);
En SQL (Postgres), utile quand tu as une colonne de valeurs FILETIME brutes sorties d'un export Plaso :
SELECT TIMESTAMP '1601-01-01 00:00:00' + (filetime / 10000000) * INTERVAL '1 second';
Précision sub-seconde
La résolution 100 ns complète importe rarement pour la forensique. Les timestamps disque sont quantifiés à ce que NTFS a bien voulu enregistrer, et les mises à jour de $STANDARD_INFORMATION sont pilotées par les syscalls plutôt que par l'horloge système. Mais les cas où ça compte — corréler un enregistrement USN avec une capture paquets, aligner un événement Sysmon 11 avec un DataExtend — tu veux garder la valeur en ticks de 100 ns jusqu'à la toute dernière étape :
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")
Le datetime de Python couvre tout sauf le dernier chiffre. La plupart des analystes tronquent à la milliseconde sans perdre de précision significative.
Variantes que tu croiseras
Windows livre plusieurs encodages de timestamps et ils fuient entre artefacts d'une façon qui attrape les analystes débutants :
| Format | Où il apparaît | Encodage |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, registre, EVTX | 64 bits LE, ticks de 100 ns depuis 1601-01-01 UTC |
SYSTEMTIME | Sortie rendue EVTX, certaines API COM | 8× champs 16 bits (année, mois, jour, ...) |
TIME_T (32 bits) | Vieilles clés de registre, en-têtes de pagefile | Secondes Unix depuis 1970, 32 bits |
DOSTIME | FAT (fuit occasionnellement dans des métadonnées NTFS copiées depuis FAT) | Date 16 bits + heure 16 bits empaquetées, heure locale |
Sur disque, FILETIME est little-endian — le LSB vient en premier. xxd affiche les octets de gauche à droite ; inverse l'ordre des octets avant interprétation si tu lis à la main. Les outils gèrent ça automatiquement.
Pièges qui ont coûté du temps réel
Endianness dans les dumps bruts. Les visualiseurs hex affichent dans l'ordre mémoire ; l'arithmétique de conversion suppose que tu as la valeur entière. Il faut d'abord inverser les octets.
La sentinelle zéro. FILETIME = 0 est techniquement 1601-01-01 00:00:00 UTC. En pratique Windows l'utilise pour signifier « jamais défini ». Traite zéro comme null à la couche de présentation ou tu publieras des rapports prétendant qu'un fichier a été accédé en dernier en 1601. Le même piège s'applique à certains timestamps $FILE_NAME que NTFS laisse non définis.
Signed vs unsigned. Certains internes Windows (et certains parsers bâtis dessus) traitent FILETIME comme int64 signé. Les valeurs au-delà d'environ 30828 ap. J.-C. débordent en négatives, ce qui n'a évidemment pas d'importance en pratique — mais une valeur délibérément altérée peut tomber dans cette plage et casser un convertisseur bogué. Reste en unsigned.
Local vs UTC. FILETIME est toujours UTC. Si un outil affiche l'heure locale, il a converti à la sortie. Pour le DFIR tu veux presque toujours UTC à la couche de stockage — convertis vers les fuseaux locaux uniquement au moment du rendu pour un destinataire. La corrélation inter-hôtes à travers les fuseaux s'effondre sinon.
$STANDARD_INFORMATION vs $FILE_NAME. Les deux vivent dans chaque entrée $MFT, les deux stockent des temps M/A/C/B FILETIME. Les valeurs SI sont inscriptibles depuis le user-space ; les copies FN se mettent à jour moins souvent et sont beaucoup plus dures à changer. Comparer les deux est la détection classique du timestomping, et le billet sur le timestomping montre exactement comment le USN journal complète cette comparaison.
Table de vérification
Si tu écris ton propre convertisseur — et à un moment tout le monde le fait, parce que le langage avec lequel on est coincé ne gère pas la conversion nativement — voici les valeurs contre lesquelles je vérifie :
| FILETIME | Date UTC |
|---|---|
0 | 1601-01-01 00:00:00 (ou null, par convention) |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
Si ton convertisseur produit ces quatre, il produit la bonne chose pour tout le reste.
Lectures complémentaires
- Microsoft Learn — structure FILETIME et la fonction connexe FileTimeToSystemTime.
- libfwnt de Joachim Metz — l'implémentation C de référence pour la conversion FILETIME qu'utilise presque chaque parser libyal.
- SANS DFIR — la fiche des règles de temps Windows couvre comment les timestamps
$STANDARD_INFORMATIONet$FILE_NAMEse mettent à jour sous différentes opérations, ce qui est le complément pratique au format FILETIME lui-même.