Knacken Sie einen beliebigen USN-Eintrag, einen beliebigen MFT-Eintrag, eine beliebige Registry-Hive, eine beliebige LNK-Datei, eine beliebige Prefetch-Spur, eine beliebige Jump List auf, und die Zeitstempel, die Sie finden, haben alle dieselbe Form: eine 64-Bit-Ganzzahl, die ohne Umwandlung wie Unsinn aussieht. Dieses Format ist FILETIME. Lernen Sie es einmal, lesen Sie jedes Windows-Artefakt für den Rest Ihrer Karriere.
Die Definition
Ein FILETIME ist eine 64-Bit-Ganzzahl ohne Vorzeichen, die 100-Nanosekunden-Intervalle seit 1601-01-01 00:00:00 UTC zählt. Das ist die gesamte Spezifikation.
Die Wahl von 1601 ist nicht willkürlich — es ist der Beginn des 400-Jahres-Gregorian-Zyklus, der die Gegenwart enthält, was es der Kalenderarithmetik erlaubt, die Schaltjahr-Sonderfälle zu überspringen, mit denen die Unix-Zeit umgehen muss. Microsoft dokumentiert den Typ in der FILETIME-Strukturreferenz.
Ein durchgearbeitetes Beispiel: FILETIME 133593600000000000 ist 2024-08-09 12:00:00 UTC. Überprüfen Sie Ihren Konverter gegen diesen Wert, bevor Sie ihm bei Falldaten vertrauen.
Umwandlung in Unix-Zeit
Zwei Rechenschritte:
- 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, was usnrs::Entry::unix_timestamp tut:
pub fn unix_timestamp(&self) -> i64 {
(self.timestamp as i64) / 10_000_000 - 11_644_473_600
}
In Python, wo die Standardbibliothek die Kalenderarithmetik für Sie übernimmt:
unix_seconds = filetime / 10_000_000 - 11_644_473_600
# 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, wo Sie BigInt brauchen, um die 64-Bit-Präzision zu erhalten:
const unixMs = Number((filetime - 116444736000000000n) / 10000n);
const date = new Date(unixMs);
In SQL (Postgres), nützlich, wenn Sie eine Spalte roher FILETIME-Werte aus einem Plaso-Export haben:
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 die Forensik selten wichtig. Disk-Zeitstempel sind auf das quantisiert, was NTFS aufzuzeichnen für richtig hielt, und $STANDARD_INFORMATION-Updates werden von Syscalls und nicht von der Systemuhr getrieben. Aber in den Fällen, in denen es darauf ankommt — Korrelation eines USN-Eintrags mit einer Paketerfassung, Abgleich eines Sysmon-Event-11 mit einem DataExtend — wollen Sie den Wert bis zum letzten Schritt in 100-ns-Ticks halten:
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 deckt alles außer der letzten Stelle ab. Die meisten Analysten kürzen auf Millisekunden ohne sinnvollen Präzisionsverlust.
Varianten, denen Sie begegnen werden
Windows liefert mehrere Zeitstempel-Codierungen aus, und sie sickern in einer Weise über Artefakte, die neue Analysten erwischt:
| Format | Wo es erscheint | Codierung |
|---|---|---|
FILETIME | $MFT, $UsnJrnl, Registry, EVTX | 64-bit LE, 100-ns-Ticks seit 1601-01-01 UTC |
SYSTEMTIME | EVTX-Renderausgabe, einige COM-APIs | 8× 16-Bit-Felder (Jahr, Monat, Tag, ...) |
TIME_T (32-bit) | Ältere Registry-Schlüssel, Pagefile-Header | Unix-Sekunden seit 1970, 32-bit |
DOSTIME | FAT (sickert gelegentlich in NTFS-Metadaten, die aus FAT kopiert wurden) | Gepacktes 16-Bit-Datum + 16-Bit-Zeit, Lokalzeit |
Auf der Festplatte ist FILETIME Little-Endian — das LSB kommt zuerst. xxd zeigt Bytes von links nach rechts; tauschen Sie die Byte-Reihenfolge vor der Interpretation um, wenn Sie von Hand lesen. Werkzeuge handhaben das automatisch.
Stolperfallen, die echte Zeit gekostet haben
Endianness in Rohdumps. Hex-Viewer zeigen in Speicherreihenfolge an; die Konvertierungsarithmetik geht davon aus, dass Sie den Ganzzahlwert haben. Bytes müssen erst umgekehrt werden.
Der Null-Sentinel. FILETIME = 0 ist technisch 1601-01-01 00:00:00 UTC. In der Praxis verwendet Windows ihn, um "nie gesetzt" zu bedeuten. Behandeln Sie null auf der Präsentationsebene als null, sonst veröffentlichen Sie Berichte, die behaupten, eine Datei sei zuletzt 1601 zugegriffen worden. Dieselbe Falle gilt für bestimmte $FILE_NAME-Zeitstempel, die NTFS nicht gesetzt lässt.
Signed vs. unsigned. Einige Windows-Interna (und einige darauf aufbauende Parser) behandeln FILETIME als signed int64. Werte nach etwa 30828 n. Chr. laufen in negative Werte über, was in der Praxis offensichtlich keine Rolle spielt — aber ein bewusst manipulierter Wert kann in diesem Bereich landen und einen fehlerhaften Konverter brechen. Bleiben Sie unsigned.
Lokal vs. UTC. FILETIME ist immer UTC. Wenn ein Werkzeug Lokalzeit anzeigt, hat es bei der Ausgabe konvertiert. Für DFIR wollen Sie fast immer UTC auf der Speicherebene — konvertieren Sie nur in lokale Zonen, wenn Sie für einen Stakeholder rendern. Andernfalls bricht die Cross-Host-Korrelation über Zeitzonen hinweg zusammen.
$STANDARD_INFORMATION vs. $FILE_NAME. Beide leben in jedem $MFT-Eintrag, beide speichern FILETIME-M/A/C/B-Zeiten. Die SI-Werte sind aus dem User-Space schreibbar; die FN-Kopien aktualisieren sich seltener und sind viel schwerer zu ändern. Der Vergleich der beiden ist die klassische Timestomping-Erkennung, und der Timestomping-Beitrag zeigt genau, wie das USN-Journal diesen Vergleich ergänzt.
Verifikationstabelle
Wenn Sie Ihren eigenen Konverter schreiben — und irgendwann tut das jeder, weil die Sprache, mit der er feststeckt, die Umwandlung nicht nativ beherrscht — sind dies die Werte, gegen die ich prüfe:
| FILETIME | UTC-Datum |
|---|---|
0 | 1601-01-01 00:00:00 (oder null, per Konvention) |
116444736000000000 | 1970-01-01 00:00:00 |
132923520000000000 | 2022-02-23 16:00:00 |
133593600000000000 | 2024-08-09 12:00:00 |
Wenn Ihr Konverter diese vier produziert, produziert er für alles andere das Richtige.
Weiterführende Literatur
- Microsoft Learn — FILETIME-Struktur und die verwandte Funktion FileTimeToSystemTime.
- Joachim Metzs libfwnt — die Referenz-C-Implementierung für die FILETIME-Konvertierung, die fast jeder libyal-Parser verwendet.
- SANS DFIR — das Windows Time Rules Cheat Sheet deckt ab, wie
$STANDARD_INFORMATION- und$FILE_NAME-Zeitstempel sich unter verschiedenen Operationen aktualisieren, was die praktische Ergänzung zum FILETIME-Format selbst ist.