USN Reason Codes — leggere tra i bit

Un tour campo per campo della bitmask Reason di USN_RECORD e cosa ogni combinazione dice sul ciclo di vita di un file su disco.

Di 6 min di lettura

Se passi tempo a leggere USN journal, la bitmask Reason diventa memoria muscolare come gli EventID EVTX. È il campo a 32 bit che NTFS imposta su ogni USN_RECORD_V2 per riassumere cosa è appena cambiato su un file. I bit sono additivi, non mutualmente esclusivi, che è la prima cosa da interiorizzare: un singolo record può portare FileCreate | DataExtend | Close e quella combinazione significa qualcosa di diverso da uno qualsiasi di quei bit da solo.

Quello che segue è la reference di campo che tengo su un post-it accanto al monitor.

I bit, nell'ordine in cui li incontrerai davvero

FlagHexCosa significa in pratica
DataOverwrite0x00000001Una regione del flusso dati principale è stata sovrascritta in loco
DataExtend0x00000002Il flusso dati principale è cresciuto
DataTruncation0x00000004Il flusso dati principale si è ridotto
NamedDataOverwrite / Extend / Truncation0x10 / 0x20 / 0x40Stessi tre, ma su un flusso dati alternativo
FileCreate0x00000100È stato creato un nuovo file o directory
FileDelete0x00000200Il file è stato scollegato dal namespace
EaChange0x00000400Attributi estesi cambiati
SecurityChange0x00000800ACL/owner cambiati
RenameOldName0x00001000La metà "prima" di una rinomina
RenameNewName0x00002000La metà "dopo" di una rinomina
IndexableChange0x00004000Flag indicizzabile commutato
BasicInfoChange0x00008000Timestamp, attributi o flag di compressione riscritti
HardLinkChange0x00010000Un hard link aggiunto o rimosso
CompressionChange0x00020000Compressione NTFS commutata
EncryptionChange0x00040000Stato EFS cambiato
ObjectIdChange0x00080000Object ID impostato o cancellato
ReparsePointChange0x00100000Reparse point aggiunto/cambiato/rimosso
StreamChange0x00200000Un flusso dati alternativo aggiunto, rinominato o cancellato
Close0x80000000L'handle che ha prodotto il cambio è stato rilasciato

Il bit Close merita un paragrafo a parte perché leggerlo male sballa il tuo conteggio eventi di un ordine di grandezza. NTFS unisce operazioni successive sotto lo stesso handle ed emette record intermedi mentre fa flush, poi un record di riassunto finale con Close impostato una volta che l'handle è andato. I record intermedi sono reali, ma contano doppio. Quando triagi un journal parsato, filtra prima per i record con Close per deduplicare le operazioni a una riga per handle. Tira fuori i record senza Close solo quando serve granularità sub-handle.

Pattern che si ripetono abbastanza spesso da memorizzare

Una manciata di combinazioni Reason appare così frequentemente nelle indagini che smetti di leggerle carattere per carattere e inizi a leggerle come parole.

Nuovo file scritto da un'app. FileCreate | DataExtend poi altri record DataExtend | Close mentre il file cresce, chiuso da BasicInfoChange | Close. L'ultimo record è il file che si fa stampare l'mtime alla chiusura.

Rinomina tra directory. Due record che condividono lo stesso FileReferenceNumber: RenameOldName | Close poi RenameNewName | Close. Il riferimento padre differisce tra i due; quella differenza è come ricostruisci lo spostamento. Se filtri solo per uno dei bit di rename perdi metà della storia.

Save-by-rename atomico. Office, VS Code, Notepad++, vim con backup, quasi ogni editor moderno. Vedi un FileCreate su un file temporaneo (~$report.docx, report.docx.tmp, .swp, ecc.), uno o più record DataExtend, un FileDelete | Close sull'originale, poi RenameNewName | Close sul temporaneo che punta al nome dell'originale. La sequenza completa avviene in un singolo secondo da orologio da parete.

Encrypt-then-rename di ransomware. Record DataOverwrite sostenuti che coprono megabyte per file, seguiti da coppie RenameOldName / RenameNewName con un suffisso uniforme o un'estensione casuale a lunghezza fissa. Il tasso di DataOverwrite e il cluster di stessa estensione sulle rinomine sono le due metà della diagnosi. Il post di rilevamento ransomware è il playbook.

Quarantena antivirus. Un FileDelete | Close immediatamente dopo un recente FileCreate sulla stessa entry MFT, spesso con un SecurityChange in mezzo mentre l'agente AV sposta il file in una directory ristretta. Il journal conserva la prova che l'AV ha toccato il file anche dopo che il binario stesso è sparito.

Upload cloud-sync. RenameNewName | Close il cui nuovo padre è \Users\<u>\OneDrive\, \Dropbox\ o \Google Drive\ e il cui vecchio padre è il profilo utente. L'agente stesso produce record DataExtend | Close su file dentro la cartella di sync per i download — gli upload tendono ad apparire come rinomine dentro la cartella.

Timestomping. Un BasicInfoChange | Close nudo senza DataOverwrite, DataExtend, FileCreate o rename circostanti sullo stesso FileReferenceNumber. Un tocco legittimo è accompagnato dalla scrittura che ha innescato l'aggiornamento del timestamp. Il post sul timestomping è dove la cosa è elaborata per esteso.

Stream injection / abuso di ADS. StreamChange da solo di solito indica un Zone.Identifier da un download del browser (abbinato al FileCreate del file originale). Record StreamChange ripetuti sullo stesso FileReferenceNumber fuori da Downloads\, in particolare su binari di sistema firmati, meritano di essere aperti.

Cosa i bit non ti diranno mai

Il campo Reason riguarda il file, non l'attore. Niente utente, niente PID, niente riga di comando, niente livello di integrità. Attaccare un attore a un'operazione significa sempre andare altrove: Security.evtx 4663 (object access, solo se le SACL sono state configurate, cosa che di solito non è), Sysmon evento 11 (file create) ed evento 1 (process create con riga di comando), o prove di esecuzione processi e voci Shimcache che mettono un binario su disco al momento giusto.

I bit non ti dicono nemmeno del contenuto. Il journal sa che una regione è stata sovrascritta, non quali byte c'erano prima. Per quello servono le immagini "prima" di $LogFile o una Volume Shadow Copy.

Una ricetta di lettura che regge

Quando apro un journal parsato a freddo:

  1. Filtra prima per FileCreate. Cos'è genuinamente nuovo nella finestra? Cerca percorsi in directory scrivibili dall'utente (\AppData\Local\Temp\, \Users\Public\, \ProgramData\ fuori dai sotto-directory di vendor noti, \PerfLogs\).
  2. Passa a RenameNewName | Close e raggruppa per nuova estensione o nuovo padre. Save-by-rename, movimenti di cloud-sync e suffissi di ransomware emergono tutti qui.
  3. Raggruppa DataExtend per minuto. Scritture massive — backup, cifratura, crescita di archivi di esfiltrazione — appaiono come picchi sostenuti contro una base altrimenti tranquilla.
  4. Leggi prima i record con Close. Tratta gli intermedi come prove di supporto, non eventi indipendenti.
  5. Una volta identificato un file candidato, raggruppa ogni record sullo stesso FileReferenceNumber e leggi l'intero ciclo di vita in ordine cronologico. Quella sequenza è quasi sempre la parte che finisce nel report.

Il parser di questa pagina espone il filtro per Reason direttamente, quindi i passi 1-3 sono pochi clic. Per il passo 5, ordina per FileReferenceNumber poi per USN.

Letture aggiuntive

  • Microsoft Learn — la reference USN_RECORD_V2 copre ogni definizione di bit direttamente dalla fonte.
  • MFTECmd di Eric Zimmerman — il suo parser $J espone la bitmask Reason in un CSV piatto che regge a pivot su foglio di calcolo.
  • usnrs di Airbus CERT — l'implementazione Rust open source; leggere src/reason.rs è la via più breve per interiorizzare il layout dei bit.