Ö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:
- Durch 10 000 000 teilen, um von 100-ns-Ticks zu Sekunden zu kommen.
- 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:
| Format | Wo es auftaucht | Kodierung |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, Registry, EVTX | 64 Bit LE, 100-ns-Ticks seit 1601-01-01 UTC |
SYSTEMTIME | EVTX, einige COM-APIs | 8× 16-Bit-Felder (Jahr, Monat, Tag…) |
TIME_T (32 Bit) | Ältere Registry-Schlüssel | Unix-Sekunden seit 1970, 32 Bit |
DOSTIME | FAT (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.
xxdzeigt 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 = 0ist 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
FILETIMEals signedint64, 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_NAMEwerden 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:
| FILETIME | UTC-Datum |
|---|---|
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 |
Wenn dein Konverter diese Werte liefert, liefert er die richtigen.