こんにちは。技術統括部のucqです。UBIイメージからUBIFSをマウントする方法を書いてみました。細かい話はどうでもいい人は UBIイメージからUBIFSをマウントする方法 だけ読めばいいと思います。
そして、近日開催の学生向けイベントのお知らせがあります。締め切りが7月31日と間近ですので、忘れずにご応募ください!
[PR]
2025年8月21日から8月22日にかけて、学生向けイベント『Binary Exploitation Workshop for Students』を開催します!
CDIで開催されているトレーニング『Binary Exploitation Fundamentals』をベースにカスタムしたコンテンツで、主にスタックベースのバッファオーバーフローに対する攻撃を取り扱います。 新しい技術を学びたい方はもちろん、CDIのエンジニアとお話したい方の参加もお待ちしております!
詳細はこちらからご確認ください。

[/PR]
はじめに
ファームウェア解析でUBIイメージを展開する際、多くの記事で紹介されているようにUBI Readerのような専用の展開ツールを使用することが紹介されています。しかし、通常のファイルシステムのようにマウントして直接展開・書き換えしたい場面も多いでしょう。UBIFSのマウントには一般的なファイルシステムと比べて複数の手順が必要で、情報も古いものが多く日本語の記事も少ないため苦労したので備忘録としてブログに残しておきます。また、なぜ直感的なmount -t ubifs firmware.ubifs /mnt/ubifs
コマンドが失敗するのかをおまけで載せました。
UBI/UBIFSとは
まずUBI/UBIFSで使う言葉を少しだけ説明します。
MTD(Memory Technology Device)
生のNAND/NORフラッシュメモリの一度消去しなければ上書きできず、消去はブロック単位でしか行えないといったようなハードウェア固有の制約を吸収し、読み込み・書き込み・消去といった操作しやすくするサブシステムです。
UBI(Unsorted Block Images)
UBI(Unsorted Block Images)は、LVMにおける論理セクターと物理セクターの対応付けに似た仕組みで、MTD(Memory Technology Device)の物理消去ブロック(PEB)を論理消去ブロック(LEB)にマッピングしたものです。ウェアレベリングや不良ブロック管理、論理ボリューム管理することでフラッシュメモリの耐久性と利用効率を高める中間管理層です。
UBIFS(Unsorted Block Image File System)
UBI層の上で動作する実際のファイルシステムです。フラッシュメモリの特性を活かした効率的な読み書きが可能で、組み込みシステムで広く使用されています。また、ジャーナリング機能により電源断時の耐性もあります。
最小I/O単位サイズ
フラッシュメモリの読み書きの最小単位です。NORフラッシュなら1バイト、NANDフラッシュはページ単位(512~4096バイト)です。
物理消去ブロック: PEB(Physical Erase Block)
フラッシュメモリの消去単位です。ハードウェア仕様で決まる物理的なブロックサイズを指します。
論理消去ブロック: LEB(Logical Erase Block)
UBIが管理する論理的な消去ブロックです。PEBからUBIメタデータ(通常128バイト)を引いたサイズです。
UBIイメージとUBIFSイメージの違い
ファームウェア解析では、UBIイメージとUBIFSイメージという2つの異なる形式に遭遇することがあります。ファームウェアに対してbinwalkで見つかるのはほとんどUBIイメージでしょう。またこれらの用語は混同されていることがあると思っている(主観)ので注意しましょう。
UBIイメージ(.ubi)
- UBI層全体(UBIFSを含む)イメージファイル
- 1つ以上のUBIFSボリュームを含む可能性がある
- 本記事で扱うのはこちらの形式
$ file firmware.ubi
firmware.ubi: UBI image, version 1
$ binwalk firmware.ubi
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 UBI erase count header, version: 1, EC: 0x0, VID header offset: 0x40, data offset: 0x80
UBIFSイメージ(.ubifs)
- UBIFSファイルシステムのみのイメージファイル
- UBI層の情報は含まない
$ file firmware.ubifs
firmware.ubifs: UBIfs image, sequence number 10, length 4096, CRC 0x4466a7aa
$ binwalk firmware.ubifs
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 UBIFS filesystem superblock node, CRC: 0x4466A7AA, flags: 0x0, min I/O unit size: 512, erase block size: 129024, erase block count: 13, max erase blocks: 24, format version: 4, compression type: lzo
129024 0x1F800 UBIFS filesystem master node, CRC: 0xCB7C23DC, highest inode: 65, commit number: 0
258048 0x3F000 UBIFS filesystem master node, CRC: 0xC74CD6C1, highest inode: 65, commit number: 0
テスト用UBIイメージの作成
まずはテスト用のUBIイメージファイルの作成をしていきます。ファームウェアから抽出したUBIイメージ等が既にある場合このステップは不要です。
# テスト用ディレクトリとファイルの作成
mkdir -p /tmp/test-dir
echo "test content" > /tmp/test-dir/testfile.txt
# UBIFSイメージの作成
mkfs.ubifs -r /tmp/test-dir -m 1 -e 130944 -c 25 -o /tmp/firmware.ubifs
# UBI設定ファイルの作成
cat > /tmp/ubinize.cfg << EOF
[ubifs]
mode=ubi
image=/tmp/firmware.ubifs
vol_id=0
vol_size=3MiB
vol_name=rootfs
vol_type=dynamic
EOF
# UBIイメージの作成
ubinize -o /tmp/firmware.ubi -p 131072 -m 1 /tmp/ubinize.cfg
MTD公式ドキュメントに基づいて、各パラメータの意味を解説します。
mkfs.ubifsコマンドのパラメータ
-m 1
: 最小I/O単位サイズ(バイト)-e 130944
: 論理消去ブロック(LEB)サイズ(バイト)-c 25
: 最大LEB数
ubinizeコマンドのパラメータ
-p 131072
: 物理消去ブロック(PEB)サイズ(バイト)-m 1
: 最小I/O単位サイズ(バイト)
パラメータの適切な値の決定方法
マウントするシステム上でmtdram
ドライバをロードしてMTD情報を確認することで、上記のコマンドのパラメータを決めることができます。
$ cat /proc/mtd
dev: size erasesize name
mtd0: 00400000 00020000 "mtdram test device"
$ mtdinfo -u /dev/mtd0
mtd0
Name: mtdram test device
Type: ram
Eraseblock size: 131072 bytes, 128.0 KiB # ubinize -p 131072
Amount of eraseblocks: 32 (4194304 bytes, 4.0 MiB)
Minimum input/output unit size: 1 byte # mkfs.ubifs -m 1
Sub-page size: 1 byte
Character device major/minor: 90:0
Bad blocks are allowed: false
Device is writable: true
Default UBI VID header offset: 64
Default UBI data offset: 128
Default UBI LEB size: 130944 bytes, 127.8 KiB # mkfs.ubifs -e 130944
Maximum UBI volumes count: 128
これらの情報から、正しいパラメータは以下です。
mkfs.ubifs -m 1 -e 130944
ubinize -p 131072 -m 1
最大LEB数(mkfs.ubifsコマンドの-c)はどのくらいのUBIFSボリュームのサイズが欲しいかによってユーザが決めます。「LEBサイズ * LEB数」で計算できるためこれを基準に決めると良いでしょう。
例えば3MBのボリュームを作りたい場合は次のような計算です。
必要なLEB数 = 3MiB / 130944バイト = 24.02... → 25個(切り上げ)
実際のボリュームサイズ = 130944 * 25 = 3273600バイト(約3.1MB)
UBIイメージからUBIFSをマウントする方法
/dev/mtd0
と/dev/ubi0
がないことを確認してください。
これらのデバイスが既に存在する場合、以下のコマンドを実行すると既存デバイスの内容が破壊される可能性があります。
そのまま実行する場合は数字をよく確認して実行してください。UBIイメージからUBIFSをマウント手順は以下の通りです。実行にはMTD utilsが必要です。
# 0. UBIイメージのパラメータを確認する
ubireader_utils_info -r firmware.ubi
# 1. 仮想MTDを作成する
sudo modprobe mtdram total_size=40960 erase_size=128
# 2. UBIイメージをMTDに書き込む
sudo ubiformat /dev/mtd0 -f /tmp/firmware.ubi
# 3. UBIドライバを読み込んでボリュームを認識させる
sudo modprobe ubi mtd=0
# 4. UBIFSファイルシステムをマウントする
sudo mkdir -p /mnt/ubifs
sudo mount -t ubifs ubi0_0 /mnt/ubifs
0. UBIイメージのパラメータを確認する
UBIイメージのファイルサイズはPEBの倍数である必要があります。適当に切り出した場合はpaddingするなどして調整してください。マウント作業を始める前に、ubi_readerのubireader_utils_info
コマンドを使用してUBIイメージのパラメータを確認します。これにより、後続の手順で必要な設定値を事前に把握できます。
$ ubireader_utils_info -r /tmp/firmware.ubi
Volume rootfs
alignment -a 1
default_compr -x lzo
fanout -f 8
image_seq -Q 102436144
key_hash -k r5
leb_size -e 130944
log_lebs -l 4
max_bud_bytes -j 523776
max_leb_cnt -c 23
min_io_size -m 8
name -N rootfs
orph_lebs -p 1
peb_size -p 131072
sub_page_size -s 64
version -x 1
vid_hdr_offset -O 64
vol_id -n 0
#ubinize.ini#
[rootfs]
vol_type=dynamic vol_flags=0
vol_id=0
vol_name=rootfs
vol_alignment=1
vol_size=3273600
上記の例ではpeb_size
が131072バイト(128KB)なので、次の手順でerase_size=128
(131072/1024)と指定します。また、ボリューム名がrootfs
なので、マウント時にubi0:rootfs
として指定できることも分かります。
1. 仮想MTDを作成する
modprobe mtdram
コマンドで仮想的なMTDを作成します。total_size
パラメータはKB単位で総サイズを指定し、UBIイメージのサイズにerase_size
以上の余裕を持たせる必要があります。erase_size
は前の手順で確認したpeb_size
をKB単位で指定します。nandsim
ドライバでもmtdram
ドライバと同様にデバイスを作成できますが、NANDのモデルによるIDパラメータの設定になるため面倒です。こちらを使う場合の設定方法はほかの記事に任せます(参考: NAND simulator の使用方法)。
2. UBIイメージをMTDに書き込む
ubiformat
コマンドでUBIイメージをMTDに書き込みます。例ではpeb_size
が131072バイト(128KB)なので、erase_size=128
(131072/1024)と指定します。
ddコマンドでも書き込みは可能ですが、ubiformatの方が以下の理由で推奨されます。
- mtdramドライバのパラメータ設定ミスがあればエラーで教えてくれる
- UBIメタデータを正しく処理してくれる
- ddコマンドではドライバのアンロードとリセットが必要になる場合がある(これは筆者の知識不足で原因はよくわかってません)
3. UBIドライバを読み込んでボリュームを認識させる
modprobe ubi mtd=0
でUBIドライバを読み込みます。mtd=0
パラメータを指定することで、自動的にubiattach -m 0
相当の処理が実行され、以下のデバイスファイルが作成されます。
/dev/ubi0
: UBIデバイス全体を表すデバイスファイル/dev/ubi0_0
: 最初のボリューム(ボリュームID 0)を表すデバイスファイル
/dev/ubi0_0
が作成されない場合はUBIとして正しく認識できていない可能性があります。その場合はdmesg
コマンドでエラーを確認すると良いでしょう。
4. UBIFSファイルシステムをマウントする
マウントポイントを作成し、mount
コマンドでUBIFSファイルシステムをマウントします。ボリュームの指定方法にはボリュームIDを使った指定(ubi0_0)やボリューム名を使った指定(ubi:rootfs)ができます。また、ファームウェア解析の場合など、書き込みが不要な場合は読み取り専用オプション(-o ro
)を付けることを推奨します。これにより、誤って元のファームウェアイメージを変更してしまうことを防げます。
クリーンアップ
作業後やエラーでおかしくなった時には環境を元に戻しましょう。ドライバも不要になればアンロードしておきましょう。
sudo umount /mnt/ubifs
sudo ubidetach -m 0
## 不要ならアンロードする
sudo modprobe -r ubifs ubi mtdram
おまけ: なぜUBIFSを直接マウントできないのか
UBIイメージからUBIFSイメージを取り出すことにより直感的にはmount -t ubifs firmware.ubifs /mnt/ubifs
でマウントできそうですが、このコマンドは失敗します。
これができれば面倒な手順が不要でマウントできると思ったのでうまくできる方法はないかをLinuxカーネルのソースコードを読んで調べてみました。
mountの解析
mount -t ubifs firmware.ubifs /mnt/ubifs
の実行時に何が起こっているかstraceで確認してみます。すべてのトレースを表示すると多いため、ブログでは最低限必要な他のプログラムの実行とマウントの確認のためにexecveとmountシステムコールに絞ってトレースを取得しています。
$ sudo strace -e trace=execve,mount -f mount -t ubifs firmware.ubifs /mnt/ubifs
execve("/usr/bin/mount", ["mount", "-t", "ubifs", "firmware.ubifs", "/mnt/ubifs"], 0x7ffc00e400d8 /* 13 vars */) = 0
strace: Process 10597 attached
[pid 10597] execve("/sbin/mount.ubifs", ["/sbin/mount.ubifs", "/dev/loop0", "/mnt/ubifs", "-o", "rw"], 0x7ffc2166f9b8 /* 9 vars */) = 0
strace: Process 10598 attached
[pid 10598] execve("/bin/mount", ["/bin/mount", "-i", "-t", "ubifs", "/dev/loop0", "/mnt/ubifs", "-o", "rw"], 0x5bc7b5f805c8 /* 10 vars */) = 0
[pid 10598] mount("/dev/loop0", "/mnt/ubifs", "ubifs", 0, NULL) = -1 EINVAL (Invalid argument)
mount: /mnt/ubifs: wrong fs type, bad option, bad superblock on /dev/loop0, missing codepage or helper program, or other error.
dmesg(1) may have more information after failed mount system call.
[pid 10598] +++ exited with 32 +++
[pid 10597] --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10598, si_uid=0, si_status=32, si_utime=0, si_stime=0} ---
[pid 10597] +++ exited with 32 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=10597, si_uid=0, si_status=32, si_utime=0, si_stime=0} ---
+++ exited with 32 +++
注目したいのはmountシステムコールでマウント対象がfirmware.ubifs
ではなく/dev/loop0
が渡されていることです。
mount(8) - Linux manual pageの「LOOP-DEVICE SUPPORT」セクションによると、ファイルシステムタイプが指定されていて、libblkidで認識される場合、通常のファイルからループデバイスを自動的に作成します。
blkid
コマンドで確認するとlibblkidによりUBIFSイメージを認識していることがわかります。
$ blkid /tmp/firmware.ubifs
/tmp/firmware.ubifs: UUID="2a245116-dc07-4606-9a11-8f3cd01eb75b" TYPE="ubifs"
カーネル内UBIFSドライバーの処理
ここからはLinuxカーネル内でのUBIFSの処理を追跡していきます。UBIFSファイルシステムはregister_filesystem
関数によってカーネルに登録されています。
Linux kernelのUBIFS実装 (Linux v6.15)を確認すると、ubifs_init
関数でregister_filesystem
を呼び出していることがわかります。
ubifs_fs_type
がregister_filesystem
によりファイルシステムとして登録される構造体です。ここで重要となるのはubifs_init_fs_context
で行われる初期化のうち、fc->ops = &ubifs_context_ops
の設定です。ここで設定した構造体の.get_tree = ubifs_get_tree
によりマウント可能なルートブロックの取得・作成します。
|
|
ubifs_get_tree
関数内でopen_ubi
関数を呼び出しています。
|
|
fc->source
の文字列のパターンによって処理が異なります。最初のubi_open_volume_path
以降は"ubi"から始まる文字列の処理しています。ubi0_0
やubi0:rootfs
などの文字が含まれていたときの処理です。
今回は/dev/loop0
になっています。
|
|
続いて/drivers/mtd/ubi/kapi.cのubi_open_volume_path
の処理をみましょう。
ubi_get_num_by_path
でパスからボリューム番号を取得する関数です。さらにこれ読んでみます。
|
|
ハイライトされている305行目の!S_ISCHR(stat.mode)
でキャラクタデバイスか確認し違うのであればこの関数は失敗します。UBIFSはキャラクタデバイス(例:/dev/ubi0_0
)のみをマウントできる設計になっているため、ブロックデバイスである/dev/loop0
では失敗するというわけですね。頑張っても単純な方法でマウントできなそうですね。
|
|
おわりに
UBIFSのマウントを雰囲気でやっててよくわかってないしやり方忘れるのでまとめるかーと軽い気持ちでやったら、知識が浅い概念の勉強や想定外の挙動引きまくる検証作業で沼にはまって泣きました。少しでも参考になれば幸いです。