Binary Analysis 08 · ブートローダとイメージ形式 — MCUboot のヘッダとトレーラ

Chapter 08

ブートローダとイメージ形式 — MCUboot のヘッダとトレーラ

この章のゴール.

MCUboot イメージのヘッダ・本体・TLV トレーラの 3 部構成を読み解け、 スロットの状態(どちらが有効か、更新の途中か)をフラッシュから判定できるようになること。 署名・ハッシュがどこにあり、何を守っているかを説明できること。

この章で使う既出の用語(定義は各リンク先). binwalk(02 章 3 節)、オフセット(03 章 1 節)、サイズ(04 章 7 節)、ブートローダ(04 章 5 節)、ベクタテーブル(07 章 1 節)、ロードアドレス(07 章 10 節)

1. なぜ「ヘッダ付きイメージ」なのか

07 章の生イメージは「ただのコード」だった。だが実際の製品では、アプリの前後にメタデータが付く。理由は 3 つ。

組み込みで最も広く使われるのが MCUboot(Arm 系・ESP・Nordic・Zephyr・TF-M で採用される、オープンソースのセキュアブートローダ)である。本章はこれを軸に読む。

なぜアプリの前後にメタデータが付くか更新の管理バージョン、サイズ、複数スロットのどちらが新しいか完全性と真正性ハッシュで壊れていないか、署名で正規のものかを起動前に確かめる配置の自由ブートローダがアプリを別スロットへコピー・入れ替えできる組み込みで最も普及 = MCUboot(Arm 系・ESP・Nordic・Zephyr・TF-M)PSA 編・IoT セキュリティ編のセキュアブートの実体
更新管理・完全性/真正性・配置の自由のため。標準は MCUboot

2. フラッシュ全体のレイアウト

MCUboot を使う機器のフラッシュは、おおむね次のように分かれる。

領域内容
BootloaderMCUboot 本体(0x0 付近)。それ自身はベクタテーブルで始まる普通の Cortex-M イメージ(07 章)
Primary slot(slot0)いま動くアプリ。ヘッダ + アプリ + トレーラ
Secondary slot(slot1)更新候補、または入れ替え用の退避先。同じ構造
Scratch(構成による)入れ替え時の一時退避領域
Storage設定・ログ(10 章〜12 章)

各スロットの先頭にヘッダ、末尾にトレーラがあり、その間がアプリ本体である。

フラッシュ全体とスロットの 3 部構成Bootloader(MCUboot 本体)それ自身が Cortex-M イメージ0x0Primary slot(slot0)— いま動くアプリヘッダ + アプリ + トレーラSecondary slot(slot1)— 更新候補同じ構造Scratch(構成による)入れ替えの一時退避Storage — 設定・ログ10〜12 章各スロットの詳細ヘッダ 32 B0x96f3b83dアプリ本体ベクタテーブル(7章)はここからTLV トレーラhash・署名
スロットは ヘッダ + アプリ本体 + TLV トレーラ。アプリのベクタテーブルはヘッダの後ろから

3. イメージヘッダ — 先頭 32 バイト

各スロットの先頭には image_header(bootutil/image.h で定義)がある。マジック 0x96f3b83d で始まる。

オフセット型フィールド意味
0x00u32ih_magic0x96f3b83d(リトルなので 3d b8 f3 96)
0x04u32ih_load_addrロードアドレス(RAM 実行時のみ、通常 0)
0x08u16ih_hdr_sizeヘッダの大きさ(通常 0x20 = 32)。アプリ本体はこの後ろから始まる
0x0Au16ih_protect_tlv_size保護 TLV のサイズ
0x0Cu32ih_img_sizeアプリ本体のバイト数(トレーラを含まない)
0x10u32ih_flagsフラグ(暗号化など)
0x14u8×4ih_verバージョン(major.minor.revision + build。例 01 02 00 00 …)
0x1Cu32—予約

これを読めば、このスロットにどのバージョンのアプリが、何バイト入っているかが分かる。

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意味
0x10IMAGE_TLV_SHA256 — アプリ本体(+ 保護 TLV)の SHA-256 ハッシュ
0x20IMAGE_TLV_KEYHASH — 署名に使った公開鍵のハッシュ(どの鍵かの識別)
0x22IMAGE_TLV_ECDSA_SIG(旧 0x21 RSA、0x22 ECDSA など) — 署名
0x30 台暗号化情報、依存関係、セキュリティカウンタ

何を守っているか: SHA-256 はヘッダとアプリ本体の完全性(壊れていない)、署名はその真正性(正規の鍵で作られた)を保証する。 ブートローダは起動前にハッシュを計算し直し、署名を公開鍵で検証してから、そのスロットへジャンプする(PSA 編 14 章のアテステーション、IoT セキュリティ編のセキュアブート)。

イメージヘッダと TLV トレーラヘッダ(先頭 32 バイト)+0x00 ih_magic 0x96f3b83d+0x08 ih_hdr_size 32(本体はこの後ろ)+0x0C ih_img_size アプリのバイト数+0x14 ih_ver 01 02 00 00 = v1.2.0TLV トレーラ(本体の後ろ)magic 0x6907 / 0x69080x10 SHA256 本体の完全性0x20 KEYHASH どの鍵か0x22 署名 真正性SHA-256 が完全性(壊れていない)、署名が真正性(正規の鍵)ブートローダは起動前にハッシュを計算し直し、署名を公開鍵で検証してからジャンプする。imgtool dumpinfo / verify で読めるスロット末尾の boot magic / image ok / swap status で更新の段階(テスト中・中断・有効)が分かるimage ok が未セット → テスト起動中。次の再起動で前スロットに戻る(ロールバック)
ヘッダのマジックは 0x96f3b83d。TLV に SHA-256(完全性)と署名(真正性)。末尾で更新段階を判定

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(可変)入れ替えの進捗(電源断からの復旧に使う)

読み方の要点:

これは現場で「更新が中途半端に止まった機器」を診断するときの決め手になる。

6. 他のイメージ形式

形式マジック / 目印ツール
UF20x0A324655(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上と同じ MCUbootimgtool、west
Android boot.imgANDROID!unpackbootimg、magiskboot(15 章)

7. 更新パッケージ(OTA)を開く

配布される OTA ファイルは、上のイメージ形式をさらに包んだもの(署名付きマニフェスト + 差分 + 本体)のことが多い。

8. 手を動かす

MCUboot イメージを解剖する


この章のポイント