ReFS Forensics Reference

$STANDARD_INFORMATION ($SI)

$STANDARD_INFORMATION ($SI) is the most forensically important attribute — every file and directory has exactly one, carrying all timestamps, the file attributes, the SecurityId, and the per-file USN link. Structurally it is not an embedded sub-record: it occupies the fixed region of the object’s type-0x10 own-row value (read $SI only from a type-0x10 row). Its layout differs between v3.4 and v3.7+ in a non-backward-compatible way — the same byte offsets mean different things by version, so a parser must know the version first.

Value layout

Common fields (all versions, 0x00–0x57)

OffsetSizeFieldDescription
0x008Creation timeFILETIME (100 ns since 1601-01-01)
0x088Modification timelast content modification
0x108MFT change timelast metadata modification. For a directory this is not the value Windows reports — Windows reports the copy held in the directory’s own index entry (type-0x30 key_flags 0x02, value+0x20). The two disagree on 994 of 86,955 directories (1.14 %) across 106 of 141 images, and against PowerShell ground truth on lab314_main the index-entry copy matched Windows and the own row did not — with 0 cases anywhere in the corpus of the reverse. For files the two agree. forefst already reports the index-entry value
0x188Last access timenot updated on read by default
0x204File attributesWin32 FILE_ATTRIBUTE_*. Bit 28 (the ReFS directory flag) is carried here only on volumes written by Win10-era ReFS — formats 3.4, 3.7 and 3.9 — and is inherited by a volume upgraded from one. On 3.10 and on natively-formatted 3.14 a directory’s own row never carries it; there the flag lives only in the index entry at value+0x40, which carries it on every directory of every format. Bit 28 is the only attribute bit that ever differs between the two copies
0x244Internal flags6-bit field on v3.14+; 0 on v3.4–v3.10 (see Internal flags)
0x288Security IDresolves directly in the security table (OID 0x530)
0x308USN slot — unpopulatedAlways 0 in the type-0x10 $SI (0 non-zero across 32,629 own-rows). The live per-file→journal link is LastUsn at 0x40
0x388DataSize slot — unpopulatedAlways 0 in the type-0x10 $SI (0 non-zero across 32,629 own-rows). The PDB label (LastMftChangeTime) notwithstanding, it carries neither a timestamp nor a size
0x408LastUsnthe file’s last USN = byte offset of its most recent record in $UsnJrnl:$J (8-byte aligned; upper 32 bits always 0). See LastUsn / UsnJournalId
0x488UsnJournalIda FILETIME identifying the journal instance — one distinct value per volume
0x504PackedEaSize / ReparseTagLow16version-dependent: v3.10+ = PackedEaSize (EA size); v3.4–v3.9 = low 16 bits of the ReparseTag
0x544ReparseTagIO_REPARSE_TAG_* (0 if none)

v3.4 extension (Win10) — total 116 bytes (0x74)

OffsetSizeFieldDescription
0x588NextFileId ordinal (ExternalFileId_1)the directory child-creation ordinal (see NextFileId ordinal); non-zero on every v3.4 entry
0x608ExternalFileId_2cross-volume file-identity word; normally 0 same-volume, carries the source-volume id only for cross-volume-copied content. Co-occurs with ExternalFileId_3
0x688ExternalFileId_3the source parent-directory OID; 0 if same-volume
0x704HardLinkCount1 in an embedded record (329,120 of 329,120 rows over 141 images) but the real link count in a type-0x40 backing — over 107,525 backings it reads >1 on 101,252 (94.2 %) and equals the number of size-matched names on 99.84 %; on the fsutil-ground-truthed volume the three backings read 4/2/1 against fsutil’s 4/2/1. See Hard links

v3.7+ extension (Win11) — total 124 bytes (0x7C)

OffsetSizeFieldDescription
0x588NextFileId ordinalthe directory child-creation ordinal; persisted to the own-row only when version < 0x30b (v3.11), so 0 on native v3.14 own-rows while resident files stay populated. See NextFileId ordinal
0x608ExternalFileId_2cross-volume file-identity word; 0 on native v3.7+, non-zero only as upgrade carryover. A same-volume move does not set it (the driver checks the origin volume, not the directory) — a moved file’s home is carried by its non-resident directory entry, not here
0x688ExternalFileId_3source parent-directory OID; 0 on native v3.7+, non-zero only as upgrade carryover; a same-volume move leaves it 0
0x704HardLinkCountread from FCB+0xb4. 1 in an embedded record (329,120 of 329,120 rows over 141 images) but the real link count in a type-0x40 backing — over 107,525 backings it reads >1 on 101,252 (94.2 %) and equals the number of size-matched names on 99.84 %; on the fsutil-ground-truthed volume the three backings read 4/2/1 against fsutil’s 4/2/1. See Hard links. A backing that reads 0 (38 of 107,525) is re-sourced from the resident FCB — do not treat 0 as corruption
0x748NextStreamSetIdper-file next-stream-set-ID allocator, base 0xF000; non-zero only on a file owning an extra stream set

The mapper enforces the size: a Win10 driver requires value > 0x73 (≥116 B), a Win11 driver requires value > 0x7b (≥124 B) — which is why a Win11 driver rejects a Win10-sized $SI.

Internal flags

A 6-bit field at $SI+0x24, 0 on v3.4–v3.10, active on v3.14+. Bits 0–3 are computed by RefsComputeStandardInformationInternalFromFcb (from FCB+0x08); bits 4–5 are added by the caller via (FCB[0xf4] << 4) & 0x30.

BitMaskNameFCB sourceNotes
00x01DELETE / DISPOSITIONFCB+0x08 bit 27delete-disposition / EFS transient state — not integrity (the integrity marker is file_attrs 0x8000, never reflected here)
10x02DEDUP_OR_COWFCB+0x08 bit 22dedup / CoW related
20x04FLAG_0xBFCB+0x08 bit 11rarely observed; also set by the link-count-zero path
30x08FLAG_0x1FFCB+0x08 bit 31most common on v3.14 regular files
40x10FCB_F4_BIT0FCB[0xf4] bit 0
50x20SYMLINK_TRUSTFCB[0xf4] bit 1symlink/junction redirection-trust level (IoComputeRedirectionTrustLevel), written only in RefsSetReparsePointInternal for tags 0xa000000c / 0xa0000003

LastUsn / UsnJournalId

$SI+0x40 and $SI+0x48 are the USN change-journal fields — written atomically (both zero or both non-zero), and non-zero only on a volume with an active USN journal (v3.14+):

  • $SI+0x40 = LastUsn — the byte offset of the file’s most recent record in $UsnJrnl:$J, taken from the journal write cursor (VCB+0x3a0) and cached in FCB+0xe0. Always 8-byte aligned.
  • $SI+0x48 = UsnJournalId — a FILETIME identifying the journal instance (VCB+0x388), one distinct value per volume. It does not change when the journal is re-created: measured on lab314_main, fsutil usn createjournal restarted USNs at 0 under the same id, so this field cannot be used to detect that a stale LastUsn now points into a different numbering epoch.
  • The pair is gated by FCB bit 23 (“the cached LastUsn is valid for the current journal epoch”: set only when the journal is active and the on-disk UsnJournalId matches the current journal — a stale-epoch check, like NTFS journal-ID validation). Both are written by RefsWriteFcbUsnRecordToJournal.

NextFileId ordinal

$SI+0x58 (also ExternalFileId_1 on v3.4) is the directory child-creation ordinal: a directory’s own-row holds NextFileId = max(child ordinal), and each resident child’s $SI+0x58 is its assigned ordinal. RefsMoveFile (the unified create/rename/link path) increments the parent directory’s NextFileId and stamps the child. It is persisted to the own-row only when version < 0x30b (v3.11) — so on native v3.14 the own-row reads 0 while the object-record payload carries it. A fresh directory starts at 1 (0/1 reserved); child ordinals are near-contiguous from 2. The counter is increment-only — a deleted child’s ordinal is never re-used (it stays a permanent gap), so a file’s reference (directory OID, ordinal) is stable over time — never reassigned to a later file — which USN correlation relies on. It is not a per-write counter.

The two “0x90"s — $SI is not $I30_INDEX

A common trap: $SI is the type-0x10 own-row’s fixed region, and its schema slot is historically labeled 0x190 / embedded code 0x90. But the embedded type-0x90 sub-record is $I30_INDEX (the directory-index template), a different thing. Read $SI only from a type-0x10 row — a top-level type-0x90 row is $I30_INDEX, not $SI. See $I30_INDEX.

Driver functions

  • RefsMapStandardInfo — maps $SI from a B+-tree row (and enforces the min-size gate)
  • RefsGetStandardInfo / RefsSetStandardInfo — read / write (RefsSetStandardInfo via MsUpdateDataWithRoot)
  • RefsComputeStandardInformationFromFcb — builds $SI from the in-memory FCB state

Forensic value

FieldForensic value
Timestamps (0x00–0x18)the MACB timeline
LastUsn + UsnJournalId (0x40 / 0x48)the file’s last $UsnJrnl:$J offset and the journal instance (v3.14+)
ReparseTag (0x54)symlinks, mount points, WSL / WOF special files
PackedEaSize (0x50, v3.10+)presence of extended attributes
Internal flags (0x24, v3.14+)feature state (dedup / CoW); bit 5 = reparse own-row
NextFileId ordinal (0x58)directory child-creation order (versions < v3.11) — corroborating for timeline reasoning

Timestomping detection. ReFS carries one $SI timestamp set per name. For a single-named file there is no NTFS-style $SI-vs-$FN twin to compare — but a hard-linked file has one independent timestamp copy per name, and those copies can hold different times — where NTFS’s hard links share a single $SI and cannot diverge at all. Divergence between a file’s names is evidence worth following. Agreement is not: calling SetFileTime through one name was measured to leave both name rows byte-identical across their whole value, so every name is updated at once. Which name holds the true birth is not established. For any file, timestomping is also detectable via the metadata-change time ($SI+0x10) (left at the real write moment by the high-level APIs that timestomp tools use), the USN journal (records the edit and the true create time — the stronger anchor), and the volume creation time (a hard lower bound). See Timestomping Detection.

Last access time. ReFS does not update last-access on read by default (RefsDisableLastAccessUpdate defaults to 1). Per-handle suppression bits at CCB+4 (bit 6 access / bit 5 modify / bit 4 change) are the in-driver timestomp-suppression bits, set when a caller supplies an explicit time via SetFileTime.

Raw example

A $STANDARD_INFORMATION value on a v3.14 image (a file created and untouched, so all four stamps match), value-relative offsets:

+0x00:  92 10 67 69 18 e6 dc 01    <- Created   (FILETIME)  2026-05-17 16:15
+0x08:  92 10 67 69 18 e6 dc 01    <- Modified  (FILETIME)
+0x10:  92 10 67 69 18 e6 dc 01    <- Changed   (FILETIME)
+0x18:  92 10 67 69 18 e6 dc 01    <- Accessed  (FILETIME)
+0x20:  00 00 00 00 00 00 00 00
+0x28:  62 9b 9f 0a 01 00 00 00    <- SecurityId 0x10a9f9b62 (resolves in the security table, OID 0x530)

The four 8-byte FILETIMEs sit at 0x00 / 0x08 / 0x10 / 0x18; SecurityId is the u32 at 0x28. (ReFS carries one timestamp set per name — there is no NTFS-style $FILE_NAME duplicate, but a hard-linked file has one such set per name, and those can diverge under a name-scoped timestomp.)

Cross-references

Evidence

The $SI layout, the version split, the internal-flag bits, and the USN / NextFileId resolutions are confirmed in the decompiled driver (RefsComputeStandardInformationFromFcb, RefsComputeStandardInformationInternalFromFcb, RefsWriteFcbUsnRecordToJournal, RefsMoveFile) and raw-disk decoded across the corpus. The per-name MACB timestomp cross-check is proven on disk on a hard-link pair. The remaining field-level statements on this page are registered as — each with its own evidence tier and witness in the claim register. Prior work tabulated these fields twice — once as a File Table and once as a Directory Descriptor — and both sets describe this one structure, since a file’s $SI and a directory’s $SI share the layout: duplicate. Both are kept in the register because each records a distinct source table.