← Zurück zum Blog

Windows FILETIME erklärt — NTFS-Zeitstempel in lesbare Werte umrechnen

FILETIME ist das Windows-Zeitstempelformat, dem man in $MFT, $UsnJrnl, der Registry, $Recycle.Bin und praktisch jedem Windows-Artefakt begegnet. Kurze, vollständige Referenz: was es ist, wie man es umrechnet, die Stolperfallen.

3 Min. Lesezeit

Öffne irgendeinen USN-Journal-Eintrag, irgendeinen MFT-Eintrag, irgendeine Registry-Hive, und die Zeitstempel haben dieselbe Form: ein 64-Bit-Integer, der ohne Umrechnung nichts bedeutet. Dieses Format ist FILETIME, und sobald du ein Gefühl dafür hast, ergeben alle anderen Windows-Zeitformate Sinn.

Was FILETIME ist

Ein FILETIME ist ein vorzeichenloser 64-Bit-Integer. Sein Wert ist die Anzahl von 100-Nanosekunden-Intervallen seit dem 1. Januar 1601 um 00:00:00 UTC. Mehr nicht.

Die Wahl von 1601 ist nicht zufällig — es ist der Anfang des 400-jährigen gregorianischen Zyklus, der das Heute enthält, was Kalenderarithmetik ohne Schaltjahr-Sonderfälle konsistent hält. Microsoft dokumentiert den Typ in der FILETIME-Strukturreferenz.

Ein kleines Beispiel: FILETIME 133593600000000000 entspricht 2024-08-09 12:00:00 UTC.

Umrechnen in Unix-Zeit

Die Umrechnung erfolgt in zwei arithmetischen Schritten:

  1. Durch 10 000 000 teilen, um von 100-ns-Ticks zu Sekunden zu kommen.
  2. 11 644 473 600 subtrahieren — die Anzahl der Sekunden zwischen 1601-01-01 und 1970-01-01.

In Pseudocode:

unix_seconds = filetime / 10_000_000 - 11_644_473_600

In Rust (genau das tut usnrs::Entry::unix_timestamp):

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

In Python:

unix_seconds = filetime / 10_000_000 - 11_644_473_600
# oder mit Sub-Sekunden-Präzision:
from datetime import datetime, timezone, timedelta
EPOCH = datetime(1601, 1, 1, tzinfo=timezone.utc)
dt = EPOCH + timedelta(microseconds=filetime / 10)

In JavaScript:

// filetime ist ein BigInt, um 64-Bit-Präzision zu erhalten
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);

In SQL (Postgres):

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

Sub-Sekunden-Präzision

Die volle 100-ns-Auflösung ist für Forensik selten wichtig — Plattenzeitstempel werden quantisiert auf das, was das OS gerade festhielt, und $STANDARD_INFORMATION wird per Syscall aktualisiert, nicht per Uhr. Wenn sie aber doch wichtig ist (z. B. Korrelation mit Paketmitschnitten), halte den Wert bis zum letzten Schritt in 100-ns-Ticks:

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")

Pythons datetime mit Mikrosekunden-Auflösung deckt alles bis auf die letzte Stelle von ticks_100ns ab. Die meisten Analysten kürzen auf Millisekunden, ohne nennenswerte Präzision zu verlieren.

Varianten, denen man begegnet

Mehrere Windows-Artefakte verwenden leicht unterschiedliche Kodierungen derselben Idee:

FormatWo es auftauchtKodierung
FILETIME$MFT, $UsnJrnl, Registry, EVTX64 Bit LE, 100-ns-Ticks seit 1601-01-01 UTC
SYSTEMTIMEEVTX, einige COM-APIs8× 16-Bit-Felder (Jahr, Monat, Tag…)
TIME_T (32 Bit)Ältere Registry-SchlüsselUnix-Sekunden seit 1970, 32 Bit
DOSTIMEFAT (taucht manchmal in NTFS-Metadaten auf)Gepackt 16 Bit Datum + 16 Bit Zeit, in Ortszeit

Beim Parsen einer Binärstruktur ist die Bytereihenfolge little-endian — das LSB des FILETIME kommt zuerst.

Häufige Stolperfallen

  • Endianness in rohen Dumps. xxd zeigt Bytes von links nach rechts; FILETIME-Bytes müssen vor der Interpretation umgedreht werden. Tools und Parser erledigen das automatisch; manuelle Analyse heißt meist, vorher zu drehen.
  • Nullwert. FILETIME = 0 ist ein echter Wert (1601-01-01), aber auch ein Sentinel für „nie gesetzt". Bei Anzeige als null behandeln.
  • Zweierkomplement-Eigenheiten. Manche Implementierungen (auch einige Windows-interne) behandeln FILETIME als signed int64, wodurch Werte jenseits von ~30828 n. Chr. in negative Werte umschlagen. Im Zweifel unsigned nutzen.
  • Lokal vs UTC. FILETIME ist immer UTC. Wenn ein Tool dir Ortszeit zeigt, hat es beim Ausgeben konvertiert. Für DFIR willst du fast immer UTC für die Host-übergreifende Korrelation.
  • $STANDARD_INFORMATION- vs $FILE_NAME-Zeitstempel. Beide leben in $MFT, beide sind FILETIME, beide halten M/A/C/B-Zeiten fest. Die Kopien in $FILE_NAME werden seltener aktualisiert, weshalb Timestomp-Detektion beide vergleicht. Brian Carriers Notizen und das Mandiant-Timestomp-Paper (heute hinter X-Ways-Notizen) sind die Standardreferenzen.

Eine kleine Umrechnungstabelle zur Verifikation

Wenn du deinen eigenen Konverter schreibst, sind diese Werte praktisch zum Gegenchecken:

FILETIMEUTC-Datum
01601-01-01 00:00:00
1164447360000000001970-01-01 00:00:00
1329235200000000002022-02-23 16:00:00
1335936000000000002024-08-09 12:00:00

Wenn dein Konverter diese Werte liefert, liefert er die richtigen.