Chapter 09
ファイルシステム — FAT・LittleFS・SPIFFS を読む
この章のゴール.
フラッシュ上のファイルシステムを種類ごとに見分け、 FAT・LittleFS・SPIFFS それぞれから、専用ツールでファイルを取り出せるようになること。 「フラッシュ向けファイルシステム」がなぜ普通の FAT と違う作りなのかを理解すること。
この章で使う既出の用語(定義は各リンク先). エントロピー(01 章 3 節)、binwalk(02 章 3 節)、オフセット(03 章 1 節)、サイズ(04 章 7 節)、テーブル(07 章 6 節)、文字列(07 章 6 節)、バージョン(08 章 3 節)
1. フラッシュとファイルシステムの相性
外付けフラッシュや内蔵フラッシュの一部が、ファイルシステムとして使われることがある(ログ、設定、Web の静的ファイル、音声データ)。 だが、PC の FAT や ext4 をそのまま載せると問題が起きる。フラッシュには 2 つの制約があるからだ。
- 消去はブロック単位(4 KB / 64 KB)でしかできず、書き込みは 1→0 の方向だけ。上書きは「ブロックごと消して書き直す」しかない
- 書き換え回数に寿命がある(1 万〜10 万回)。同じ場所を何度も書くと壊れる
そこで、書き込みを全域に分散する(ウェアレベリング)、電源断で壊れにくい(給電中断に強い)ように設計された、フラッシュ専用のファイルシステムが使われる。 LittleFS、SPIFFS、JFFS2、UBIFS、YAFFS などである。
2. 種類を見分ける
まずダンプの先頭とマジックで当たりをつける。
| ファイルシステム | 目印 | 用途 |
|---|---|---|
| FAT12/16/32 | オフセット 0 に EB/E9 のジャンプ、0x1FE に 55 AA、"FAT16 "/"FAT32 " 文字列 | SD カード、USB、大きめの外付け |
| LittleFS | ブロック内に "littlefs" 文字列、superblock の magic | マイコンの内蔵/外付け(Zephyr、Mbed、Arduino) |
| SPIFFS | 明確なマジックが乏しい。4 バイトのオブジェクト索引が規則的に並ぶ | ESP8266/ESP32 の古い構成 |
| FatFs(ChaN) | 中身は FAT | マイコンで最も普及した FAT 実装 |
| JFFS2 | 85 19(ノードマジック 0x1985) | 古い Linux 組み込み(15 章) |
| UBIFS / UBI | UBI#(0x23494255)、31 18 10 06 | NAND の Linux 機器(15 章) |
| squashfs | hsqs/sqsh | 読み取り専用ルート(15 章) |
binwalk fs.bin # 既知の FS を検出
strings fs.bin | grep -i littlefs # LittleFS の痕跡
xxd -l 16 fs.bin # FAT なら EB/E9 で始まる3. FAT を読む
FAT は PC と同じなので、道具が豊富である。
file fs.bin # "DOS/MBR boot sector" など
mdir -i fs.bin :: # mtools: ルートの一覧(マウント不要)
mcopy -i fs.bin ::/LOG.TXT . # ファイルを取り出す
7z l fs.bin # 7-Zip でも一覧・展開できる
# あるいはループバックマウント(Linux)
sudo mount -o loop,ro fs.bin /mntパーティションがある場合(先頭が MBR、Master Boot Record——ディスクの先頭にある区画表)は、まず fdisk -l fs.bin でパーティションの開始オフセットを調べ、その位置から FAT が始まる。mtools は -i fs.bin@@<offset> でオフセットを指定できる。
FAT の構造(ブートセクタ → FAT テーブル → ルートディレクトリ → データ領域)を手で読む必要はめったにないが、壊れた FAT からファイルを救うときは、ディレクトリエントリ(32 バイト固定、ファイル名 + 開始クラスタ + サイズ)を直接拾う。日時はビット詰めの 2+2 バイトである(03 章)。
4. LittleFS を読む
LittleFS(ARM が主導する、電源断に強い小型ファイルシステム)は、マイコンで今最も使われる。 専用の Python ツールで、ダンプからファイルを取り出せる。
pip install littlefs-pythonfrom littlefs import LittleFS
# ブロックサイズと個数はその機器の設定に合わせる(重要)
fs = LittleFS(block_size=4096, block_count=256, mount=False)
with open("lfs.bin","rb") as f:
fs.context.buffer = bytearray(f.read())
fs.mount()
for root, dirs, files in fs.walk("/"):
for name in files:
path = root.rstrip("/") + "/" + name
data = fs.open(path, "rb").read()
print(path, len(data))block_size と block_count が合わないとマウントに失敗する。値は機器のソース(lfs_config)か、フラッシュ容量から推定する(block_count × block_size = 領域サイズ)。 littlefs-fuse を使えば mount して普通のファイルとして扱うこともできる。
LittleFS の内部は、各ブロックがメタデータペア(2 ブロックを交互に使い、片方が壊れてももう片方が残る)と CTZ スキップリスト(Count Trailing Zeros——ブロックを飛び飛びにたどってファイルデータをつなぐ仕組み)でできている。手で追うのは骨だが、「同じ内容が 2 か所にある」「ブロックの先頭に revision カウンタがある」ことを知っておくと、目視でも構造が見える。
5. SPIFFS を読む
SPIFFS(SPI Flash File System)は ESP8266 / 古い ESP32 で使われた。マジックが弱く見分けにくいが、ESP-IDF のツールで生成・展開できる。
# ESP-IDF 付属(バージョンにより名前が違う)
python spiffsgen.py --help
mkspiffs -u out_dir spiffs.bin # 展開(page/block サイズを機器に合わせる)SPIFFS は flat(ディレクトリ階層を持たず、パスをファイル名に埋める)で、ページ単位のオブジェクト索引で管理する。新しい設計では LittleFS に置き換えられている。
6. パラメータが分からないとき
フラッシュ向け FS は、ブロックサイズ・ページサイズ・領域の開始オフセットと大きさを知らないと読めない。手がかり:
- 機器のソースコード / SDK の設定(
partitions.csv、lfs_config、fstab、storage_partition) - フラッシュチップのデータシート(消去ブロック 4 KB / 64 KB、ページ 256 バイト)
- ダンプ内で規則的に繰り返す境界を探す。エントロピーや
binwalkのオフセットが 0x1000(4 KB)や 0x10000(64 KB)の倍数に揃っていれば、それがブロック境界 - LittleFS なら superblock 内にジオメトリが書かれていることがある
# 4 KB ごとに先頭 16 バイトを見て、繰り返し構造を探す
python3 -c "
d=open('fs.bin','rb').read()
for off in range(0,len(d),0x1000):
print(hex(off), d[off:off+16].hex())" | head -407. 取り出した後
ファイルを取り出せたら、それ自体がまた解析対象になる。