Si tu passes du temps à lire des USN journals, le bitmask Reason devient une mémoire musculaire comme le sont les EventID EVTX. C'est le champ de 32 bits que NTFS pose sur chaque USN_RECORD_V2 pour résumer ce qui vient de changer sur un fichier. Les bits sont additifs, pas mutuellement exclusifs, c'est la première chose à intégrer : un seul enregistrement peut porter FileCreate | DataExtend | Close et cette combinaison veut dire autre chose que chacun de ces bits seul.
Ce qui suit, c'est la référence de champ que je garde sur un post-it à côté de mon écran.
Les bits, dans l'ordre où tu les croiseras vraiment
| Flag | Hex | Ce que ça veut dire en pratique |
|---|---|---|
DataOverwrite | 0x00000001 | Une région du flux de données principal a été écrasée sur place |
DataExtend | 0x00000002 | Le flux de données principal a grandi |
DataTruncation | 0x00000004 | Le flux de données principal a rétréci |
NamedDataOverwrite / Extend / Truncation | 0x10 / 0x20 / 0x40 | Pareil, mais sur un flux de données alternatif |
FileCreate | 0x00000100 | Un nouveau fichier ou répertoire a été créé |
FileDelete | 0x00000200 | Le fichier a été délié du namespace |
EaChange | 0x00000400 | Attributs étendus modifiés |
SecurityChange | 0x00000800 | ACL/propriétaire modifiés |
RenameOldName | 0x00001000 | La moitié « avant » d'un renommage |
RenameNewName | 0x00002000 | La moitié « après » d'un renommage |
IndexableChange | 0x00004000 | Flag indexable basculé |
BasicInfoChange | 0x00008000 | Timestamps, attributs ou flags de compression réécrits |
HardLinkChange | 0x00010000 | Un hard link a été ajouté ou retiré |
CompressionChange | 0x00020000 | Compression NTFS basculée |
EncryptionChange | 0x00040000 | État EFS modifié |
ObjectIdChange | 0x00080000 | Object ID posé ou effacé |
ReparsePointChange | 0x00100000 | Reparse point ajouté/modifié/retiré |
StreamChange | 0x00200000 | Un flux de données alternatif a été ajouté, renommé ou supprimé |
Close | 0x80000000 | Le handle qui a produit le changement a été libéré |
Le bit Close mérite un paragraphe à part parce que mal le lire fausse ton compteur d'événements d'un ordre de grandeur. NTFS coalèse les opérations successives sous le même handle et émet des enregistrements intermédiaires au fil des flush, puis un enregistrement de résumé final avec Close une fois le handle parti. Les enregistrements intermédiaires sont réels, mais ils comptent double. Quand tu triagises un journal parsé, filtre d'abord sur les enregistrements porteurs de Close pour dédoublonner les opérations à une ligne par handle. Ne sors les enregistrements sans Close que si tu as besoin de granularité sub-handle.
Motifs qui reviennent assez souvent pour les mémoriser
Une poignée de combinaisons Reason apparaît si fréquemment dans les enquêtes qu'on cesse de les lire caractère par caractère et qu'on les lit comme des mots.
Nouveau fichier écrit par une appli. FileCreate | DataExtend puis d'autres enregistrements DataExtend | Close à mesure que le fichier grandit, terminé par BasicInfoChange | Close. Le dernier enregistrement est le fichier qui se fait timestamper sa mtime à la fermeture.
Renommage entre répertoires. Deux enregistrements partageant le même FileReferenceNumber : RenameOldName | Close puis RenameNewName | Close. La référence parent diffère entre les deux ; cette différence est la façon de reconstituer le déplacement. Si tu ne filtres que sur un des bits de rename, tu perds la moitié de l'histoire.
Save-by-rename atomique. Office, VS Code, Notepad++, vim avec backup, à peu près tout éditeur moderne. Tu vois un FileCreate sur un fichier temporaire (~$report.docx, report.docx.tmp, .swp, etc.), un ou plusieurs enregistrements DataExtend, un FileDelete | Close sur l'original, puis RenameNewName | Close sur le temporaire pointant vers le nom de l'original. La séquence complète se passe en une seule seconde d'horloge.
Chiffrement-puis-renommage de ransomware. Enregistrements DataOverwrite soutenus couvrant des mégaoctets par fichier, suivis de paires RenameOldName / RenameNewName avec un suffixe uniforme ou une extension aléatoire de longueur fixe. Le taux de DataOverwrite et le cluster d'extension identique sur les renommages sont les deux moitiés du diagnostic. Le billet de détection de ransomware est le playbook.
Quarantaine antivirus. Un FileDelete | Close immédiatement après un FileCreate récent sur la même entrée MFT, souvent avec un SecurityChange entre les deux pendant que l'agent AV déplace le fichier dans un répertoire restreint. Le journal conserve la preuve que l'AV a touché le fichier même après que le binaire lui-même ait disparu.
Upload cloud-sync. RenameNewName | Close dont le nouveau parent est \Users\<u>\OneDrive\, \Dropbox\ ou \Google Drive\ et dont l'ancien parent est le profil utilisateur. L'agent lui-même produit des enregistrements DataExtend | Close sur les fichiers à l'intérieur du dossier de sync pour les téléchargements — les uploads ressemblent plutôt à des renommages dans le dossier.
Timestomping. Un BasicInfoChange | Close nu, sans DataOverwrite, DataExtend, FileCreate ou rename autour sur le même FileReferenceNumber. Une touche légitime est accompagnée de l'écriture qui a déclenché la mise à jour du timestamp. Le billet sur le timestomping traite ça en détail.
Injection de flux / abus d'ADS. StreamChange seul indique d'habitude un Zone.Identifier d'un téléchargement de navigateur (apparié avec le FileCreate du fichier d'origine). Des enregistrements StreamChange répétés sur le même FileReferenceNumber hors de Downloads\, en particulier sur des binaires système signés, méritent qu'on les ouvre.
Ce que les bits ne te diront jamais
Le champ Reason parle du fichier, pas de l'acteur. Pas d'utilisateur, pas de PID, pas de ligne de commande, pas de niveau d'intégrité. Attacher un acteur à une opération veut toujours dire aller voir ailleurs : Security.evtx 4663 (object access, uniquement si les SACL étaient configurées, ce qu'elles n'étaient en général pas), Sysmon événement 11 (file create) et événement 1 (process create avec ligne de commande), ou preuves d'exécution de processus et entrées Shimcache qui placent un binaire sur disque au bon moment.
Les bits ne te disent rien non plus sur le contenu. Le journal sait qu'une région a été écrasée, pas quels octets étaient là avant. Pour ça il te faut les images avant de $LogFile ou un Volume Shadow Copy.
Une recette de lecture qui tient
Quand j'ouvre un journal parsé à froid :
- Filtre d'abord sur
FileCreate. Qu'est-ce qui est vraiment neuf dans la fenêtre ? Cherche les chemins dans les répertoires inscriptibles par l'utilisateur (\AppData\Local\Temp\,\Users\Public\,\ProgramData\hors sous-répertoires éditeur connus,\PerfLogs\). - Bascule sur
RenameNewName | Closeet groupe par nouvelle extension ou nouveau parent. Save-by-rename, déplacements de cloud-sync et suffixes de ransomware remontent tous ici. - Bucket
DataExtendà la minute. Les écritures en masse — sauvegardes, chiffrement, croissance d'archive d'exfiltration — apparaissent comme des pics soutenus sur une base par ailleurs calme. - Lis d'abord les enregistrements porteurs de
Close. Traite les intermédiaires comme des preuves d'appoint, pas des événements indépendants. - Une fois un fichier candidat identifié, groupe chaque enregistrement sur le même
FileReferenceNumberet lis tout le cycle de vie dans l'ordre chronologique. Cette séquence est presque toujours la partie qui va dans le rapport.
Le parser sur cette page expose le filtrage par Reason directement, donc les étapes 1-3 sont quelques clics. Pour l'étape 5, trie par FileReferenceNumber puis par USN.
Lectures complémentaires
- Microsoft Learn — la référence USN_RECORD_V2 couvre chaque définition de bit directement à la source.
- MFTECmd d'Eric Zimmerman — son parser
$Jexpose le bitmask Reason dans un CSV plat qui tient bien aux pivots tableur. - usnrs d'Airbus CERT — l'implémentation Rust open source ; lire
src/reason.rsest le chemin le plus court pour intégrer le layout des bits.