NTFS ファイルシステムの概要
NTFS とは Windows の標準のファイルシステムです。New Technology File System の略で、読み方はそのまま "エヌティーエフエス" です。
MFT の構造
NTFS にフォーマットされた Volume の最初には、MFT (Master File Table) という特殊領域が配置されています。
この MFT には NTFS 全体管理に関する情報や、NTFS 内の全ファイル/全フォルダのエントリおよび属性情報を保有しています。Linux で言うところの i-node 領域です。
通常時はディスク空き容量の 12.5% が MFT 領域用として予約されています。
MFT には $ で始まるメタファイルの群と、各ファイル/フォルダのレコード (1 KBytes 固定長) で構成されています。以下のような構造になっています。
各ファイル/フォルダの構造
前述の通り、NTFS ファイルシステム上の各ファイル/各フォルダのエントリーは、MFT 内に 1 KByte 長のレコードとして格納されます。
1 ファイルに 1 レコードです。Linux の XFS 等と同様、ディレクトリ(フォルダ)もファイルの 1 種と見なされます。
各レコードにはそのファイルに付随する属性情報が格納されます。属性の前にも $ が付きますが、メタファイルと混同してはいけません。
Type | Attribute | Description |
---|---|---|
0x10 | $STANDARD _INFORMATION | タイムスタンプやFlag(ファイルの種類)、USN等 |
0x20 | $ATTRIBUTE _LIST | MFT に入りきらなかった属性とその位置情報 |
0x30 | $FILE_NAME | ファイル名(もしくはフォルダ名) |
0x40 | $OBJECT_ID | ファイル識別子(Officeアプリ等で利用) |
0x80 | $DATA | ファイルに書き込まれた内容 |
0x50 | $SECURITY _DESCRIPTOR | 所有者やアクセス権情報 |
0xE0 | $EA | 拡張属性 (Extended Attributes, 後述します) |
0x90 | $INDEX_ROOT | B+Tree の最上位ノードの位置を示す (フォルダで使われる属性) |
0xA0 | $INDEX _ALLOCATION | B+Tree の割り当て済 Index のリスト (フォルダで使われる属性) |
0xB0 | $BITMAP | B+Tree の Index の割り当て, 未割当を示す (フォルダで使われる属性) |
0xC0 | $REPARSE _POINT | Symbolic Link 等のリンク先を指定 |
Linux の XFS 等では "属性(メタ情報)" と "データ" は区別されますが、Windows の NTFS では "データ" ($DATA) も属性の 1 つに位置づけられます。
例えばメモ帳等のテキストエディタで "test" と書き込んで保存したファイル (test.txt) のデータ領域 ($DATA属性) には "test" という4文字が入り、4 bytes のサイズになります。プロパティのサイズを確認すると 4 bytes になっています。(Linux だと勝手に改行コードが入り、5 bytes になりますが)
一般的なファイルでは例えば以下の 4 つの属性が含まれます。
- $STANDARD_INFORMATION
- $FILE_NAME
- $OBJECT_ID
- $DATA(フォルダの場合は $INDEX_ROOT, $INDEX_ALLOCATION, $BITMAP)
属性全体のサイズが 1 KBytes を超えないうちは、全ての属性 ($DATA 含む) を MFT 領域内に格納します。その結果、前述の test.txt の図のように「ディスク上のサイズ」は 0 Byte になります。
このサイズが大きくなり MFT に格納しきれなくなると、大きく育った属性の Attribute Header にある "Non-resident" フラグを 1 にした上で、属性値には "Mapping Pair" (Runlist とも呼ぶ) というパラメータを使って『File System Data』領域 (ディスク上のサイズにカウントされる領域) に書き込んでいきます。(後述します)
HDD へのアクセスは「クラスターサイズ (アロケーションユニットやブロックサイズとも言う。最近の一般的な PC では 4KB)」単位なので、データに文字列をどんどん追記していくと「ディスク上のサイズ」は 4 KBytes になります。例えば test.txt のデータ領域を708 Bytes まで増やしてみると以下のようになりました。
このように MFT に入りきらないことを Non-resident と呼びます。(その対義語で、MFT内に入ることを Resident と呼びます。)
なお、MFT に入りきらない属性が $DATA かそれ以外の属性かで処理が変わります。
$DATA が MFT に入りきらない場合は前述の通り、MFT 内の $DATA 属性に "Mapping Pair" が格納されます。これは、$DATA の情報が『File System Data』領域のどのクラスタ範囲に格納するかを示しています。
具体的には { First Cluster (クラスタの開始位置)=X1, Cluster Count (クラスタ数)=Y1 } が格納され、クラスタの位置 X1 から X1+Y1 までの連続するクラスタに $DATA が格納されていることを示します。これは以下の図のように複数に分かれることもあります(フラグメンテーション)し、これがひどい場合はデフラグを実行しないとパフォーマンスが落ちます。
具体的にツールで見てみましょう。Active@ Disk Editor というフリーウェアをインストール、起動し、下図のように test.txt を選択した状態で「Inspect File Record」をクリックします。
まずは 4 Bytes の状態では以下のように表示されます。
$DATA の属性ヘッダの Non resident flag が 0 で、$DATA の属性値として "test" という文字列が ASCII で格納されています。
次に、704 Bytes まで増やした場合は以下のようになります。
$DATA の属性ヘッダの "Non resident flag" が 1 となり、$DATA の属性値として "Mapping Pair" が格納されています。
Size が 0x41 となっています。これはややこしいのですが、4 が First Cluster (クラスタ位置) のサイズ(4 Bytes)を意味し、1 が Cluster count のサイズ(1 byte)を意味します。セクター開始位置が 20,182,697 という大きな値なので、4 Bytes のサイズが必要になる、という意味です。Cluster count は 1 という値なので、1 byte だけ使っています。
一方、ケースとしては少ないですが、$DATA 以外の情報量が多すぎて 1 KByte をはみ出してしまう場合、MFT 内に同じく 1KB 長の補助レコード ( AUX Record, Child Record ) を作ります。この補助レコードにはみ出た属性を格納していきますが、その代わり元のレコード (Base Record) には $ATTRIBUTE_LIST 属性を加え、その中に補助レコードの位置情報やはみ出た属性情報を記録します。
代替データストリーム (Alternate Data Stream : ADS)
$DATA属性はマルチストリームに対応しており、隠しデータのように情報を持つことができます。.exe ファイル等にセキュリティ情報を付加するために使われたりもしますが、逆にウィルスの存在場所として使われるケースもあります。
例えば C:\test\test.txt に Strm1 という名前の代替データストリームを持たせ、その中に "ads1" というデータを持たせるためには、Power Shell で以下コマンドを打ちます。
PS C:\test> Set-Content test.txt -Value ads1 -Stream Strm1
これでセットできました。このデータを同じく Power Shell で見るには以下を打ちます。
PS C:\test> Get-Content test.txt -Stream Strm1
ads1
データとして ads1 が含まれていることが確認できました。
次に、この状態を今度はコマンドプロンプトの dir /r コマンドで見てみます。
上図の通り、代替データストリームは以下の形式で表現されます。
また、代替ではないメインデータストリーム(普通のファイル)は "unname (名前なし)" であり、
という形式になります。上図の例では test.txt がこれに当たり、$DATA は省略されています。
もう少し厳密に言うと、$DATA に限らず全ての属性においてマルチストリームに対応しており、一般には以下の形式になります。
同様に大抵は "unnname (名前なし)” ですが、メタファイルにはこれが使われることが多いです。
拡張属性 (Extended Attributes)
NTFS には拡張属性と呼ばれる領域があります。(拡張ファイル属性とも呼ばれているようです。)
これも代替データストリームと似ていて、隠しデータのように使われます。ただし、まだあまり実装は見受けられません。
Windows でセットする仕組みが見つかりませんでしたが、Linux から CIFS 経由で設定できることは確認できました。
手順1. CentOS 7 へ attr をインストール
[root@localhost ~]# yum -y install attr
手順2. CentOS 7 から Windows(172.16.2.3) へ CIFS マウント
[root@localhost ~]# mount -t cifs //172.16.2.3/home/test -o username=test,password=P@$$w0rd /mnt/test
手順3. CentOS 7 のマウント先にある test.txt へ拡張属性へ値を書き込み
[root@localhost test]# setfattr -n user.test -v test-value test.txt [root@localhost test]# getfattr -d test.txt # file: test.txt user.TEST="test-value"
Linux の拡張属性は 4 つの名前空間 {user, trusted, security, system} がありますが、そのうち user は Windows の拡張属性に通じているようです。以下サイトにある "EaQuery64.exe" というコマンドを使うと Windows 上で拡張属性の情報が見れます。
実行コマンドと結果は以下の通りです。
C:\home\test>EaQuery64.exe /Target:C:\home\test\test.txt /Mode:0 /Verbose:2 /Identifier:*
EaQuery v1.0.0.1
TargetFile: C:\home\test\test.txt
NextEntryOffset: 0
Flags: 0x00
EaNameLength: 4
EaName: TEST
EaValueLength: 10
EaValue:
0000 74 65 73 74 2d 76 61 6c 75 test-valu
C:\home\test>
なぜか最後の 1 文字 "e" が欠けています。EaValueLength は 10 になっているのでおそらく EaQuery64.exe の表示上のバグでしょう。
ここで、test.txt の アクセス権を変更してみます。具体的には、CIFS マウント時のユーザ "test" について、高度なアクセス権の「拡張属性の読み取り」と「拡張属性の書き込み」を外してみます。
すると Linux からは読み取りも書き込みもできなくなりました。
[root@localhost test]# getfattr -d test.txt getfattr: test.txt: 許可がありません [root@localhost test]# setfattr -n user.test2 -v test-value test.txt setfattr: test.txt: 許可がありません
これが Linux の拡張属性の user 空間が、Windows の拡張属性に繋がっていることの根拠の 1 つです。
なお、Linux から CIFS 経由で user 以外の空間 {trusted, security, system} の拡張属性に書き込みを行おうとすると、「サポートされていない操作です」というエラーが出て、実行に失敗します。
[root@localhost test]# setfattr -n trusted.test2 -v test2-value test.txt setfattr: test.txt: サポートされていない操作です [root@localhost test]# setfattr -n security.test2 -v test2-value test.txt setfattr: test.txt: サポートされていない操作です [root@localhost test]# setfattr -n system.test2 -v test2-value test.txt setfattr: test.txt: サポートされていない操作です
コメント