USN Reason Codes — Reading Between the Bits

A field-by-field walkthrough of the USN_RECORD reason bitmask and what each combination tells you about a file's lifecycle on disk.

By 6 min read

If you spend any time reading USN journals, the reason bitmask becomes muscle memory the way EVTX EventIDs do. It is the 32-bit field NTFS sets on every USN_RECORD_V2 to summarise what just changed about a file. The bits are additive, not mutually exclusive, which is the first thing to internalise: a single record can carry FileCreate | DataExtend | Close and that combination means something different from any of those bits alone.

What follows is the field reference I keep on a sticky note next to my monitor.

The bits, in the order you will actually meet them

FlagHexWhat it means in practice
DataOverwrite0x00000001A region of the main data stream was overwritten in place
DataExtend0x00000002The main data stream grew
DataTruncation0x00000004The main data stream shrank
NamedDataOverwrite / Extend / Truncation0x10 / 0x20 / 0x40Same three, but on an alternate data stream
FileCreate0x00000100A new file or directory was created
FileDelete0x00000200The file was unlinked from the namespace
EaChange0x00000400Extended attributes changed
SecurityChange0x00000800ACLs/owner changed
RenameOldName0x00001000The "before" half of a rename
RenameNewName0x00002000The "after" half of a rename
IndexableChange0x00004000Indexed flag toggled
BasicInfoChange0x00008000Timestamps, attributes or compression flags rewritten
HardLinkChange0x00010000A hard link was added or removed
CompressionChange0x00020000NTFS compression toggled
EncryptionChange0x00040000EFS state changed
ObjectIdChange0x00080000Object ID set or cleared
ReparsePointChange0x00100000Reparse point added/changed/removed
StreamChange0x00200000An alternate data stream was added, renamed or deleted
Close0x80000000The handle that produced the change has been released

The Close bit deserves a separate paragraph because misreading it will throw off your event count by an order of magnitude. NTFS coalesces successive operations under the same handle and emits intermediate records as it flushes, then a final summary record with Close set once the handle is gone. The intermediate records are real, but they double-count. When you triage a parsed journal, filter for Close-bearing records first to dedupe operations to one row per handle. Pull the non-Close records only when you need sub-handle granularity.

Patterns that recur often enough to memorise

A handful of reason combinations show up so frequently across investigations that you stop reading them character by character and start reading them as words.

New file written by an app. FileCreate | DataExtend then more DataExtend | Close records as the file grows, capped by BasicInfoChange | Close. The last record is the file getting its mtime stamped on close.

Rename across directories. Two records sharing the same FileReferenceNumber: RenameOldName | Close then RenameNewName | Close. The parent reference differs between the two — that delta is how you reconstruct the move. If you only filter for one of the rename bits you lose half the story.

Atomic save-by-rename. Office, VS Code, Notepad++, vim with backup, almost every modern editor. You see a FileCreate on a temp file (~$report.docx, report.docx.tmp, .swp, etc.), one or more DataExtend records, a FileDelete | Close on the original, then RenameNewName | Close on the temp pointing at the original's name. The full sequence happens within a single wall-clock second.

Ransomware encrypt-then-rename. Sustained DataOverwrite records covering megabytes per file, followed by RenameOldName / RenameNewName pairs with a uniform suffix or fixed-length random extension. The DataOverwrite rate and the same-extension cluster on the renames are the two halves of the diagnostic. The ransomware detection post is the playbook.

Antivirus quarantine. A FileDelete | Close immediately following a recent FileCreate on the same MFT entry, often with a SecurityChange in between as the AV agent moves the file into a restricted directory. The journal preserves proof that AV touched the file even after the binary itself is gone.

Cloud-sync upload. RenameNewName | Close whose new parent is \Users\<u>\OneDrive\, \Dropbox\ or \Google Drive\ and whose old parent is the user profile. The agent itself produces DataExtend | Close records on files inside the sync folder for downloads — uploads tend to look like renames into the folder.

Timestomping. A bare BasicInfoChange | Close with no surrounding DataOverwrite, DataExtend, FileCreate or rename on the same FileReferenceNumber. A legitimate touch is paired with the write that triggered the timestamp update. The timestomping post is where this is worked out at length.

Stream injection / ADS abuse. StreamChange on its own usually indicates a Zone.Identifier from a browser download (paired with the original file's FileCreate). Repeated StreamChange records on the same FileReferenceNumber away from Downloads\, particularly on signed system binaries, are worth opening.

What the bits will never tell you

The reason field is about the file, not the actor. There is no user, no PID, no command line, no integrity level. Attaching an actor to an operation always means going somewhere else: Security.evtx 4663 (object access, only if SACLs were configured, which they usually were not), Sysmon event 11 (file create) and event 1 (process create with command line), or process execution evidence and Shimcache entries that put a binary on disk at the right moment.

The bits also do not tell you about content. The journal knows a region was overwritten, not what bytes used to be there. For that you need $LogFile before-images or a Volume Shadow Copy.

A reading recipe that holds up

When I open a parsed journal cold:

  1. Filter for FileCreate first. What is genuinely new in the window? Look for paths in user-writable directories (\AppData\Local\Temp\, \Users\Public\, \ProgramData\ outside known vendor subdirs, \PerfLogs\).
  2. Switch to RenameNewName | Close and group by new extension or new parent. Save-by-rename, cloud-sync moves and ransomware suffixes all surface here.
  3. Bucket DataExtend per minute. Bulk writes — backups, encryption, exfil-archive growth — show as sustained spikes against an otherwise quiet baseline.
  4. Read Close-bearing records first. Treat intermediates as supporting evidence, not independent events.
  5. Once you have a candidate file, group every record on the same FileReferenceNumber and read the full lifecycle in chronological order. That sequence is almost always the part that goes into the report.

The parser on this page exposes reason filtering directly, so steps 1-3 are a few clicks. For step 5, sort by FileReferenceNumber then by USN.

Further reading

  • Microsoft Learn — USN_RECORD_V2 reference covers every bit definition straight from the source.
  • Eric Zimmerman's MFTECmd — its $J parser surfaces the reason bitmask in a flat CSV that holds up to spreadsheet pivots.
  • Airbus CERT's usnrs — the open-source Rust implementation; reading src/reason.rs is the shortest way to internalise the bit layout.