FAT32の解読をした話

## 背景

FAT32の概要については読んだことがあったが、実際に手作業でファイルシステムの解読を行う機会があったため内容をまとめる。


## FAT32のイメージ作成

まず下地を作る。コマンドは以下。

`dd if=/dev/zero of=./hoge bs=32M count=4`

ddコマンドはデータをブロック単位で読み出して、その変換およびコピーを行うコマンド。HDDのパーティションなども読み出すデータとして扱うことができる。

今回は `if=/dev/zero`というNULL文字を出力し続けるデバイスからデータを読み出す。bs*countの容量分(128MB)のNULL文字が`./hoge`ファイルに書き出される。

次に、作成した下地をFAT32にフォーマットする。コマンドは以下。

`sudo mkfs.vfat -F32 ./hoge`

`-F32`はFAT32を示す。`-F`だとFAT16でフォーマットされる。

`xxd ./hoge |head`でフォーマット領域の先頭部分の内容を見ることができる。
実行結果は以下のようになる。

```
00000000: eb58 906d 6b66 732e 6661 7400 0201 2000  .X.mkfs.fat... .
00000010: 0200 0000 00f8 0000 2000 4000 0000 0000  ........ .@.....
00000020: 0000 0400 e107 0000 0000 0000 0200 0000  ................
00000030: 0100 0600 0000 0000 0000 0000 0000 0000  ................
00000040: 8000 2990 5376 034e 4f20 4e41 4d45 2020  ..).Sv.NO NAME  
00000050: 2020 4641 5433 3220 2020 0e1f be77 7cac    FAT32   ...w|.
00000060: 22c0 740b 56b4 0ebb 0700 cd10 5eeb f032  ".t.V.......^..2
00000070: e4cd 16cd 19eb fe54 6869 7320 6973 206e  .......This is n
00000080: 6f74 2061 2062 6f6f 7461 626c 6520 6469  ot a bootable di
00000090: 736b 2e20 2050 6c65 6173 6520 696e 7365  sk.  Please inse
```

## ディレクトリエントリを読む

### 〜準備〜

ディレクトリエントリには、ディレクトリに含まれるファイルの情報が格納される。
格納される情報としてファイル名、ファイルの属性、作成日時、最終アクセス日時、ファイルが削除されているかどうかなどがある。以下のコマンドで準備を行う。

```
shibayama@shibayama-05:~/temp$ mkdir mount_dir
shibayama@shibayama-05:~/temp$ sudo mount ./hoge mount_dir -o uid=1011
shibayama@shibayama-05:~/temp$ cd mount_dir/
shibayama@shibayama-05:~/temp/mount_dir$ sudo touch samp1 samp2 longlongfilenamesample
shibayama@shibayama-05:~/temp/mount_dir$ sudo rm samp2
shibayama@shibayama-05:~/temp/mount_dir$ cd ..
shibayama@shibayama-05:~/temp$ sudo umount mount_dir 
```

マウントを行うことでフォーマットした領域に対して読み書きの作業が適用可能となる。
マウントポイントを`mount_dir`で指定する(コマンド1~2行目)。

ファイルがないとディレクトリエントリが見られないので、フォーマットした領域にファイルを作成する。`mount_dir`上に適当にファイルを作成する。ファイルシステムの構造をいろいろ見るため、長めのファイル名のものも用意する。またファイルを1つ削除しておく(コマンド3~5行目)。

その後マウントしたイメージをアンマウントする(コマンド6~7行目)。

### 〜実際に読んでみる〜

xxdコマンドでファイルを16進数でダンプし、その内容を閲覧できる。今回はファイル内の001fc400目から読んでみる。

```
shibayama@shibayama-05:~/temp$ xxd hoge | grep "^001fc4"
001fc400: 4173 0061 006d 0070 0031 000f 0063 0000  As.a.m.p.1...c..
001fc410: ffff ffff ffff ffff ffff 0000 ffff ffff  ................
001fc420: 5341 4d50 3120 2020 2020 2020 00bc 264b  SAMP1       ..&K
001fc430: 5f53 5f53 0000 264b 5f53 0000 0000 0000  _S_S..&K_S......
001fc440: e573 0061 006d 0070 0032 000f 0067 0000  .s.a.m.p.2...g..
001fc450: ffff ffff ffff ffff ffff 0000 ffff ffff  ................
001fc460: e541 4d50 3220 2020 2020 2020 00bc 264b  .AMP2       ..&K
001fc470: 5f53 5f53 0000 264b 5f53 0000 0000 0000  _S_S..&K_S......
001fc480: 4261 006d 0065 0073 0061 000f 0030 6d00  Ba.m.e.s.a...0m.
001fc490: 7000 6c00 6500 0000 ffff 0000 ffff ffff  p.l.e...........
001fc4a0: 016c 006f 006e 0067 006c 000f 0030 6f00  .l.o.n.g.l...0o.
001fc4b0: 6e00 6700 6600 6900 6c00 0000 6500 6e00  n.g.f.i.l...e.n.
001fc4c0: 4c4f 4e47 4c4f 7e31 2020 2020 00bc 264b  LONGLO~1    ..&K
001fc4d0: 5f53 5f53 0000 264b 5f53 0000 0000 0000  _S_S..&K_S......
001fc4e0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
001fc4f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................
```

実行結果の右側がダンプを復元した値になっている(先ほど作成したファイルの名前なども見える)。

以下に示すFAT32のディレクトリエントリ(SFN)の構造に従って、内容を読んでみる。

| 項目名           | オフセット | サイズ(B) | 概要|
|:-----------     |---------:|:-----:|:--------------:|
| DIR_Name        | 0x0      |11    |8+拡張子3文字のファイル名。先頭がE5の場合**ファイルは削除済み。**              |
| DIR_Attr        | 0xB      | 1     |ファイルの属性。0x20がファイル、0x10がディレクトリ、0x0FがLFNを示す。                |
| DIR_NTRes       | 0xC      | 1     |ファイル名の大文字、小文字を記録する。
| DIR_CrtTimeTenth| 0xD      | 1     |ファイルの作成時刻を0~200の値で0.01秒の精度で記録する。
| DIR_CrtTime     | 0xE      | 2     |ファイルの作成時刻
| DIR_CrtDate        | 0x10      | 2    |ファイルの作成日              |
| DIR_LstAccDate        | 0x12      | 2     |ファイルの最終アクセス日               |
| DIR_FstClusHi       | 0x14      | 2     |ファイルの先頭クラスタ番号の上位16ビット
| DIR_WrtTime| 0x16      | 2     |ファイルの最終書き込み時刻
| DIR_WrtDate     | 0x18      | 2     |ファイルの最終書き込み日付
| DIR_FstClusLo| 0x1A      | 2     |ファイルの先頭クラスタ番号の下位16ビット
| DIR_FileSize     | 0x1C      | 4     |ファイルサイズ


0xBがLFNであるというのは、ファイル名が拡張子を含めて11文字以上であることを示す。その場合、構造体の内容が変わる。

| 項目名           | オフセット | サイズ(B) | 概要|
|:-----------     |---------:|:-----:|:--------------:|
| LDIR_Ord        | 0x0      |1    |LFNの構造体の順番を示す(6ビット目が1 = 0x40なら始点)。0xE5の場合は削除されている。             |
| LDIR_Attr        | 0x1      | 10     |ファイル名の1~5文字目が**UTF-16LE**で記録される。              |
| LDIR_NTRes       | 0xB      | 1     |0x0Fが格納され、LFNであることが示される。
| LDIR_CrtTimeTenth| 0xC      | 1     |LFNの種類(現在は0のみ)
| LDIR_CrtTime     | 0xD      | 1     |続きとなるSFNエントリのチェックサム
| LDIR_CrtDate        | 0xE      | 11    | ファイル名の6~11目        |
| LDIR_LstAccDate        | 0x1A      | 1     |0が書き込まれる。               |

ファイル名が11文字に収まらない場合、0xBをLFNとしてファイル名の続きを格納する。

#### samp1

```
001fc420: 5341 4d50 3120 2020 2020 2020 00bc 264b  SAMP1       ..&K
001fc430: 5f53 5f53 0000 264b 5f53 0000 0000 0000  _S_S..&K_S......
```

001fc420の始めから見てみると、先頭5バイトがファイル名になっていることが分かる。また先頭から0x0Bの位置が0x20であり、samp1という名前のファイルであることが分かる。

ファイルの日時に関する情報は、16進数を2進数に直した上で以下のように示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521871/39cebe4d-3c23-33cc-4c0f-81815618e564.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1521871/21e65ad5-2e56-d4aa-77d1-ea0312a6c92d.png)

0xEからの2バイトの値はリトルエンディアン(LE)で4B26で表される。2進数で表すと0100101100100110となり、01001は9、0100101は25、00110は6を示すため作成時間は9時25分12秒であることが分かる。

また0x10からの2バイトの値は535fで表される。2進数では0101001101011111となり、0101001は41、1010は10、11111は31を示すため作成日時は2021年10月31日となる。

#### samp2

```
001fc460: e541 4d50 3220 2020 2020 2020 00bc 264b  .AMP2       ..&K
001fc470: 5f53 5f53 0000 264b 5f53 0000 0000 0000  _S_S..&K_S......
```

先頭バイトがE5になっているため、前述の手順の通りファイルが削除されていることが分かる。削除されている場合、ファイルの1文字目は分からなくなる。それ以外の項目についてはsamp1と同じであるため省略。

#### longlongfilenamesample
```
001fc480: 4261 006d 0065 0073 0061 000f 0030 6d00  Ba.m.e.s.a...0m.
001fc490: 7000 6c00 6500 0000 ffff 0000 ffff ffff  p.l.e...........
001fc4a0: 016c 006f 006e 0067 006c 000f 0030 6f00  .l.o.n.g.l...0o.
001fc4b0: 6e00 6700 6600 6900 6c00 0000 6500 6e00  n.g.f.i.l...e.n.
```

ディレクトリエントリの始めの1バイトが「01」、その後はLEに従って「006c」「006f」「006e」...といったように読み進め、0xBの値が「0F」となっていてLFNであることが分かる。LFNエントリはチェックサムの値に関連づけられたSFNエントリの直前に降順で配置される。直前のエントリの1バイト目が「42」(0x40 + 0x41)となっており、1→2であることから「01」のエントリの続きであること、4のフラグが立っていることからLFNエントリであることが分かる。

ファイル名がLFNの場合は先頭のSNF + LFNにによってディレクトリエントリが構成される。
1番目のエントリにlonglongfilen、次のエントリにamesampleがファイル名として格納されている。


## 参考

[FATファイルシステムのしくみと操作法](http://elm-chan.org/docs/fat.html#fat_struct)