Chapter 08
ブートローダとイメージ形式 — MCUboot のヘッダとトレーラ
この章のゴール.
MCUboot イメージのヘッダ・本体・TLV トレーラの 3 部構成を読み解け、 スロットの状態(どちらが有効か、更新の途中か)をフラッシュから判定できるようになること。 署名・ハッシュがどこにあり、何を守っているかを説明できること。
この章で使う既出の用語(定義は各リンク先). binwalk(02 章 3 節)、オフセット(03 章 1 節)、サイズ(04 章 7 節)、ブートローダ(04 章 5 節)、ベクタテーブル(07 章 1 節)、ロードアドレス(07 章 10 節)
1. なぜ「ヘッダ付きイメージ」なのか
07 章の生イメージは「ただのコード」だった。だが実際の製品では、アプリの前後にメタデータが付く。理由は 3 つ。
- 更新の管理: バージョン、サイズ、複数スロットのどちらが新しいか
- 完全性と真正性: ハッシュと署名で「壊れていないか」「正規のものか」を起動前に確かめる(PSA 編・IoT セキュリティ編)
- 配置の自由: ブートローダがアプリを別スロットへコピー・入れ替えできる
組み込みで最も広く使われるのが MCUboot(Arm 系・ESP・Nordic・Zephyr・TF-M で採用される、オープンソースのセキュアブートローダ)である。本章はこれを軸に読む。
2. フラッシュ全体のレイアウト
MCUboot を使う機器のフラッシュは、おおむね次のように分かれる。
| 領域 | 内容 |
|---|---|
| Bootloader | MCUboot 本体(0x0 付近)。それ自身はベクタテーブルで始まる普通の Cortex-M イメージ(07 章) |
| Primary slot(slot0) | いま動くアプリ。ヘッダ + アプリ + トレーラ |
| Secondary slot(slot1) | 更新候補、または入れ替え用の退避先。同じ構造 |
| Scratch(構成による) | 入れ替え時の一時退避領域 |
| Storage | 設定・ログ(10 章〜12 章) |
各スロットの先頭にヘッダ、末尾にトレーラがあり、その間がアプリ本体である。
3. イメージヘッダ — 先頭 32 バイト
各スロットの先頭には image_header(bootutil/image.h で定義)がある。マジック 0x96f3b83d で始まる。
| オフセット | 型 | フィールド | 意味 |
|---|---|---|---|
| 0x00 | u32 | ih_magic | 0x96f3b83d(リトルなので 3d b8 f3 96) |
| 0x04 | u32 | ih_load_addr | ロードアドレス(RAM 実行時のみ、通常 0) |
| 0x08 | u16 | ih_hdr_size | ヘッダの大きさ(通常 0x20 = 32)。アプリ本体はこの後ろから始まる |
| 0x0A | u16 | ih_protect_tlv_size | 保護 TLV のサイズ |
| 0x0C | u32 | ih_img_size | アプリ本体のバイト数(トレーラを含まない) |
| 0x10 | u32 | ih_flags | フラグ(暗号化など) |
| 0x14 | u8×4 | ih_ver | バージョン(major.minor.revision + build。例 01 02 00 00 …) |
| 0x1C | u32 | — | 予約 |
これを読めば、このスロットにどのバージョンのアプリが、何バイト入っているかが分かる。
import struct
d = open("slot0.bin","rb").read()
magic, load, hdr, ptlv, img, flags, vmaj, vmin, vrev, vbuild = struct.unpack_from("<IIHHII BBHI", d, 0)
assert magic == 0x96f3b83d
print(f"version {vmaj}.{vmin}.{vrev}+{vbuild}, header {hdr} B, image {img} B")
# アプリ本体は d[hdr : hdr+img]、トレーラは d[hdr+img :]アプリの本当の先頭はヘッダの後ろ.
ヘッダの直後(オフセット
ih_hdr_size= 32)から、07 章のベクタテーブルが始まる。 つまりアプリのリセットハンドラのアドレスは、スロット先頭 + 32 を基準に計算される。 生イメージを 07 章の手順で読むときは、この 32 バイト分をずらすことを忘れない。 リンカは「スロット先頭 + ヘッダ分」を起点にアプリを配置している。
4. TLV トレーラ — ハッシュと署名
アプリ本体の後ろに、TLV(Type-Length-Value)形式のトレーラが付く。 TLV は「種別(2 バイト)+ 長さ(2 バイト)+ 値」を並べた汎用の入れ物で、可変個の情報を順に置ける。
トレーラは 0x6907(保護なし)または 0x6908(保護あり)というマジックの image_tlv_info で始まり、続いて個々の TLV が並ぶ。
| TLV type | 意味 |
|---|---|
0x10 | IMAGE_TLV_SHA256 — アプリ本体(+ 保護 TLV)の SHA-256 ハッシュ |
0x20 | IMAGE_TLV_KEYHASH — 署名に使った公開鍵のハッシュ(どの鍵かの識別) |
0x22 | IMAGE_TLV_ECDSA_SIG(旧 0x21 RSA、0x22 ECDSA など) — 署名 |
0x30 台 | 暗号化情報、依存関係、セキュリティカウンタ |
何を守っているか: SHA-256 はヘッダとアプリ本体の完全性(壊れていない)、署名はその真正性(正規の鍵で作られた)を保証する。 ブートローダは起動前にハッシュを計算し直し、署名を公開鍵で検証してから、そのスロットへジャンプする(PSA 編 14 章のアテステーション、IoT セキュリティ編のセキュアブート)。
imgtool(MCUboot 付属)で構造を読める。
imgtool dumpinfo slot0.bin # ヘッダ・TLV を一覧表示
imgtool verify -k pub.pem slot0.bin # 署名を検証5. スロットのトレーラ(magic / swap status)
TLV とは別に、各スロットのフラッシュ区画の末尾に、MCUboot が入れ替え状態を管理する領域がある("image trailer")。ここを読むと、いま更新のどの段階にいるかが分かる。
| 末尾の領域 | 意味 |
|---|---|
| boot magic(末尾 16 バイト) | 0x77c295f360d2ef7f...(固定値)。書かれていれば「このスロットは有効」 |
| swap type / copy done / image ok | 入れ替えの種類(test / permanent)、コピー完了フラグ、確認フラグ |
| swap status(可変) | 入れ替えの進捗(電源断からの復旧に使う) |
読み方の要点:
- image ok がセットされていないなら、「テスト起動中で、まだ確認されていない」。次の再起動で前のスロットに戻る(ロールバック)
- swap status に中途半端な値があれば、更新の途中で電源が切れた状態。ブートローダが再開する
- boot magic が消去済み(0xFF)なら、そのスロットは空
これは現場で「更新が中途半端に止まった機器」を診断するときの決め手になる。
6. 他のイメージ形式
| 形式 | マジック / 目印 | ツール |
|---|---|---|
| UF2 | 0x0A324655(UF2\n)、512 バイトブロック | uf2conv.py -c -o out.bin in.uf2 |
| ESP-IDF アプリ | 0xE9(esp_image_header)。8 KB オフセットにパーティションテーブル(0xAA50) | esptool.py image_info、espsecure.py |
| Nordic DFU(.zip) | init packet(protobuf)+ .bin + マニフェスト | nrfutil pkg display |
| TI-TXT | @ADDR 行 + 16 進、q で終了 | srec_cat |
| Zephyr / Nordic の MCUboot | 上と同じ MCUboot | imgtool、west |
| Android boot.img | ANDROID! | unpackbootimg、magiskboot(15 章) |
7. 更新パッケージ(OTA)を開く
配布される OTA ファイルは、上のイメージ形式をさらに包んだもの(署名付きマニフェスト + 差分 + 本体)のことが多い。
- まず
binwalk/file/stringsで外側の形式を見る - ZIP / tar なら展開して中の
.bin/.hexを取り出す - 独自形式なら、ヘッダの固定バイト列を手がかりに構造を推定(03 章)し、中に MCUboot イメージ(
0x96f3b83d)が現れるか全域検索する
8. 手を動かす
MCUboot イメージを解剖する
この章のポイント
- 実機のスロットは ヘッダ(32 B)+ アプリ本体 + TLV トレーラ の 3 部構成
- MCUboot ヘッダのマジックは
0x96f3b83d。ih_hdr_sizeとih_img_sizeで本体の範囲、ih_verでバージョン - アプリのベクタテーブルはスロット先頭 + ヘッダ分から始まる(07 章と組み合わせる)
- TLV トレーラに SHA-256(完全性)と署名(真正性)。
imgtool dumpinfo / verifyで読める - スロット末尾の boot magic / image ok / swap status で更新の段階(テスト中・中断・有効)を判定できる