USN の Reason コード — ビットの裏を読む

USN_RECORD の reason ビットマスクをフィールドごとにたどり、各組み合わせがディスク上でのファイルのライフサイクルについて何を語るかを解説します。

著者: 約 2 分で読了

USN ジャーナルを読む時間が少しでもあれば、reason ビットマスクは EVTX EventID と同じように体に染みついてきます。これは NTFS が各 USN_RECORD_V2 にセットして、ファイルについて何が変わったかを要約する 32 ビットのフィールドです。ビットは加算的であり、相互排他的ではありません。これが最初に身につけるべきことです。一つのレコードは FileCreate | DataExtend | Close を運ぶことができ、その組み合わせはどのビット単独とも違う意味を持ちます。

以下は、私がモニターの横の付箋に貼っているフィールドリファレンスです。

実際に出会う順に並べたビット

フラグHex実用上の意味
DataOverwrite0x00000001メインデータストリームの一部がその場で上書きされた
DataExtend0x00000002メインデータストリームが拡張された
DataTruncation0x00000004メインデータストリームが切り詰められた
NamedDataOverwrite / Extend / Truncation0x10 / 0x20 / 0x40上記三つと同じ、ただし代替データストリーム上で
FileCreate0x00000100新しいファイルまたはディレクトリが作成された
FileDelete0x00000200ファイルが名前空間からリンク解除された
EaChange0x00000400拡張属性が変更された
SecurityChange0x00000800ACL/オーナーが変更された
RenameOldName0x00001000リネームの「前」の半分
RenameNewName0x00002000リネームの「後」の半分
IndexableChange0x00004000インデックスフラグが切り替えられた
BasicInfoChange0x00008000タイムスタンプ、属性、圧縮フラグが書き換えられた
HardLinkChange0x00010000ハードリンクが追加または削除された
CompressionChange0x00020000NTFS の圧縮が切り替えられた
EncryptionChange0x00040000EFS の状態が変更された
ObjectIdChange0x00080000オブジェクト ID が設定またはクリアされた
ReparsePointChange0x00100000リパースポイントが追加/変更/削除された
StreamChange0x00200000代替データストリームが追加、リネーム、削除された
Close0x80000000変更を生んだハンドルが解放された

Close ビットは独立した段落に値します。これを読み違えるとイベント件数が桁違いに狂います。NTFS は同じハンドル下の連続する操作を併合し、フラッシュしながら中間レコードを発行し、ハンドルがなくなった時点で Close がセットされた最終サマリレコードを発行します。中間レコードは実在しますが二重カウントされます。パース済みのジャーナルをトリアージする際は、まず Close 付きのレコードをフィルタして、ハンドルあたり一行に操作を重複排除します。サブハンドル粒度が必要なときだけ非 Close レコードを引きます。

暗記する価値があるほど頻発するパターン

reason の組み合わせの中には、調査を重ねるうちに文字単位ではなく単語として読めるようになるくらい頻発するものがいくつかあります。

アプリが書いた新しいファイル。 FileCreate | DataExtend の後、ファイルが成長する間に DataExtend | Close レコードが続き、最後に BasicInfoChange | Close で締めくくられます。最後のレコードは、クローズ時にファイルが mtime をスタンプされる瞬間です。

ディレクトリをまたぐリネーム。 同じ FileReferenceNumber を共有する 2 つのレコード: RenameOldName | Close の後に RenameNewName | Close。2 つの間で親参照が異なります — そのデルタが移動を復元する方法です。リネームビットのどちらか一方だけでフィルタすると、半分のストーリーを失います。

アトミックな save-by-rename。 Office、VS Code、Notepad++、バックアップ付きの vim、ほぼすべての現代のエディタ。一時ファイル (~$report.docxreport.docx.tmp.swp など) に対する FileCreate、一つ以上の DataExtend レコード、元のファイルへの FileDelete | Close、そして元の名前を指す一時ファイルへの RenameNewName | Close が見えます。全シーケンスは単一の壁時計秒内に発生します。

ランサムウェアの暗号化→リネーム。 メガバイト単位で各ファイルをカバーする持続的な DataOverwrite レコード、続いて均一なサフィックスや固定長のランダム拡張子を持つ RenameOldName / RenameNewName のペア。DataOverwrite のレートとリネーム上の同一拡張子クラスタが、診断の二つの半分です。プレイブックは ランサムウェア検出記事 にあります。

アンチウイルスの検疫。 同じ MFT エントリに対する最近の FileCreate の直後に続く FileDelete | Close。AV エージェントがファイルを制限付きディレクトリに移動するため、その間に SecurityChange を伴うことがよくあります。ジャーナルは、バイナリ自体が消えた後も AV がファイルに触れた証拠を保持します。

クラウド同期のアップロード。 新しい親が \Users\<u>\OneDrive\\Dropbox\\Google Drive\ で、古い親がユーザプロファイルである RenameNewName | Close。同期エージェント自身は、ダウンロード について同期フォルダ内ファイルに対する DataExtend | Close レコードを生成します。アップロードはフォルダへのリネームに見える傾向があります。

Timestomping。 同じ FileReferenceNumber 上で周囲に DataOverwriteDataExtendFileCreate、リネームを伴わない裸の BasicInfoChange | Close。正当な touch はタイムスタンプ更新を引き起こした書き込みとペアになります。詳細に展開しているのが timestomping 記事 です。

ストリーム注入 / ADS 悪用。 単独の StreamChange は通常、ブラウザダウンロードからの Zone.Identifier を示します (元ファイルの FileCreate とペア)。Downloads\ から離れた、特に署名済みシステムバイナリ上の同じ FileReferenceNumber における繰り返しの StreamChange レコードは、調査する価値があります。

ビットが決して教えてくれないこと

reason フィールドはファイルに関するもので、アクターに関するものではありません。ユーザ、PID、コマンドライン、整合性レベルはありません。操作にアクターを紐付けるには、常にどこか別の場所に行く必要があります: Security.evtx 4663 (オブジェクトアクセス。SACL が設定されていた場合のみで、通常は設定されていません)、Sysmon イベント 11 (ファイル作成) とイベント 1 (コマンドライン付きのプロセス作成)、または適切な瞬間にバイナリをディスク上に置く プロセス実行証拠Shimcache のエントリです。

ビットはコンテンツについても何も教えません。ジャーナルは領域が上書きされたことを知っていますが、以前のバイトが何だったかは知りません。それには $LogFile の before-image またはボリュームシャドウコピーが必要です。

通用する読み方のレシピ

パース済みのジャーナルをコールドで開くとき:

  1. まず FileCreate でフィルタします。ウィンドウ内で本当に新規なものは何ですか? ユーザが書き込み可能なディレクトリ (\AppData\Local\Temp\\Users\Public\、既知のベンダーサブディレクトリ外の \ProgramData\\PerfLogs\) のパスを探します。
  2. RenameNewName | Close に切り替えて、新しい拡張子や新しい親でグループ化します。save-by-rename、クラウド同期の移動、ランサムウェアのサフィックスがここに現れます。
  3. 分単位で DataExtend をバケットします。一括書き込み — バックアップ、暗号化、流出アーカイブの成長 — は、それ以外は静かなベースラインに対する持続的なスパイクとして現れます。
  4. Close 付きレコードを最初に読みます。中間レコードは独立したイベントではなく、補助的な証拠として扱います。
  5. 候補ファイルを得たら、同じ FileReferenceNumber 上のすべてのレコードをグループ化して、ライフサイクル全体を時系列で読みます。その系列がほぼ常に、レポートに入る部分です。

本ページのパーサは reason フィルタリングを直接公開しているので、ステップ 1〜3 は数クリックです。ステップ 5 には FileReferenceNumber でソートし、その後 USN でソートします。

参考資料

  • Microsoft Learn — USN_RECORD_V2 リファレンス は、すべてのビット定義を出典から直接カバーしています。
  • Eric Zimmerman の MFTECmd — その $J パーサは、スプレッドシートのピボットに耐えるフラットな CSV に reason ビットマスクを浮上させます。
  • Airbus CERT の usnrs — オープンソースの Rust 実装。ビットレイアウトを身に染み込ませる最短の道は src/reason.rs を読むことです。