Chapter 04
ダンプを手に入れる — 実機からフラッシュと RAM を読む
この章のゴール.
SWD / JTAG・SPI 直読み・ブートローダ・エミュレータという 4 つの吸い出し経路を理解し、 それぞれで「何が読めて、何が読めないか」を説明できるようになること。 読み出し保護に当たったときの見分け方を知ること。
この章で使う既出の用語(定義は各リンク先). メモリマップ(01 章 3 節)、OpenOCD(02 章 7 節)、pyOCD(02 章 7 節)
1. どこから読むか
解析するには、まず中身をファイルにする必要がある。経路は主に 4 つある。
| 経路 | 対象 | 読めるもの | 難しさ |
|---|---|---|---|
| デバッグポート(SWD / JTAG) | 内蔵フラッシュ・RAM・レジスタ | 保護が無効なら全部 | 低(配線があれば) |
| フラッシュチップの直読み | 外付け SPI / NAND フラッシュ | チップの全内容 | 中(クリップか取り外し) |
| ブートローダ / ISP | 内蔵フラッシュ | ブートローダが許す範囲 | 中 |
| エミュレータ / ソフト | イメージファイル | ファイルそのもの | 低(実機不要) |
2. デバッグポート — SWD / JTAG
SWD(Serial Wire Debug、Arm の 2 線式デバッグ)や JTAG は、開発時にプログラムの書き込みとデバッグに使う端子である。 基板に端子(4〜10 ピン、または露出したパッド)が残っていれば、そこから内蔵フラッシュと RAM を丸ごと読める。
必要なもの: デバッグプローブ(ST-Link、J-Link、CMSIS-DAP、Raspberry Pi Debug Probe など)と、それを操作するソフト(OpenOCD、pyOCD、ベンダツール)。
OpenOCD でダンプする
# 接続(プローブとチップに合わせて設定ファイルを選ぶ)
openocd -f interface/stlink.cfg -f target/stm32u5x.cfg
# 別の端末から telnet localhost 4444 で接続し:
# reset halt
# flash read_bank 0 flash0.bin ← バンク 0 を全部
# dump_image ram.bin 0x20000000 0x40000 ← RAM 256 KBpyOCD(Python 製、設定が楽)
pyocd list # 見えているプローブとターゲット
pyocd cmd -t stm32u585xx
# > savemem 0x08000000 0x200000 flash.bin ← アドレスと長さを指定J-Link Commander
J-Link> connect
J-Link> savebin flash.bin 0x08000000 0x200000アドレスに注意: Cortex-M では内蔵フラッシュは 0x08000000 付近、RAM は 0x20000000 付近にマップされている(そのチップのメモリマップで確認)。 ダンプしたファイルのオフセット 0 が、実機のそのアドレスに対応することを記録しておく(06 章のアドレス変換で必要)。
3. 読み出し保護に当たったとき
多くのチップには、フラッシュの読み出しを禁止する保護がある(STM32 の RDP レベル 1/2、nRF の APPROTECT、ESP の Flash Encryption と Secure Boot)。 保護が有効だと、次のような症状が出る。
- 接続はできるが、フラッシュを読むと全部 0x00 か 0xFF が返る、または読み出しエラー
- デバッガの接続自体が拒否される(
Error: init mode failed) - 一部だけ読めて、保護区間だけ 0 になる
これは設計どおりの安全機構である。自分の製品でも、量産設定では保護が入っている。 保護を外すには、多くのチップでフラッシュ全消去を伴うレベル下げ(RDP 1→0 で全消去)が必要で、中身は失われる。 つまり「保護された製品の中身は、正規の手段では読めない」——それがセキュアブートとフラッシュ暗号化の目的である(PSA 編・IoT セキュリティ編を参照)。
「読めた」ことの意味.
保護なしで読めたなら、それは攻撃者にも読めるということである。 自分の製品を解析していて鍵や証明書が平文で出てきたら、それは脆弱性の発見である(13 章)。 「解析できた」で終わらず、「保護すべきものが保護されていたか」を確認する。
4. フラッシュチップの直読み — SPI NOR / NAND
設定やログ、ときにはファームウェア本体が、基板上の外付けフラッシュチップ(8 ピンの SOIC、SPI NOR フラッシュが典型。W25Q… などの型番)に入っていることがある。
- クリップで挟む: SOIC8 クリップ + USB-SPI アダプタ(CH341A、FT2232 系)+
flashrom。基板に載ったまま読めることが多いが、電源が競合すると失敗する - 取り外す: ホットエアで外し、専用のソケットで読む。確実だが手間
flashrom -p ch341a_spi -r spiflash.bin # 全内容を読む
flashrom -p ch341a_spi --flash-name # チップの型番・容量を確認NAND フラッシュ(大容量、生の ECC やバッドブロック管理が絡む)は難易度が上がる。専用リーダか、チップを外しての読み出しになる。Linux 機器の 15 章で扱う。
5. ブートローダ / ISP 経由
多くのマイコンは、工場書き込み用のブートローダ(ROM に焼かれた小さなプログラム)を持ち、UART や USB で書き込み・読み出しができる。
| チップ | 手段 | ツール |
|---|---|---|
| STM32 | システムブートローダ(UART / USB DFU) | stm32flash、dfu-util、STM32CubeProgrammer |
| ESP32 | ROM ブートローダ(UART) | esptool.py read_flash 0 0x400000 flash.bin |
| Nordic nRF | UART / USB DFU(ブートローダが載っていれば) | nrfutil、adafruit-nrfutil |
| NXP i.MX | serial download(USB) | uuu、sdphost |
| Raspberry Pi Pico | BOOTSEL + UF2、または picotool | picotool save -a flash.bin |
ここでも読み出し保護が効くと、読めない・0 が返る。ESP32 の esptool.py read_flash は保護がなければ手軽で確実である。
esptool.py --port /dev/ttyUSB0 flash_id # チップと容量
esptool.py --port /dev/ttyUSB0 read_flash 0 ALL flash.bin # 全部6. 実機なしで — イメージファイルとエミュレータ
解析対象がすでにファイルの場合(ビルド成果物の .elf / .hex / .bin、OTA 配布物、ベンダ提供のファーム)は、吸い出しは不要である。 また、エミュレータで動かしてメモリを観察する手もある(17 章)。
- QEMU: 一部の Cortex-M ボードを再現。
qemu-system-arm -M mps2-an385 -kernel fw.elf - Renode: 多数のボードと周辺機器を再現。実機がなくてもフラッシュ・RAM を読める。回帰的な解析に向く
7. ダンプの健全性を確かめる
吸い出した直後に、それが正しく取れているかを確認する。
- 2 回読んで比較する。
cmp dump1.bin dump2.bin。差があれば、読み出しが不安定(配線・電源・クロック) - サイズがフラッシュ容量と一致するか
- 先頭にベクタテーブルらしきもの(07 章)があるか。内蔵フラッシュの先頭なら、最初の u32 が RAM 範囲のアドレス(0x2000xxxx)になっているはず
- 全部 0x00 / 0xFF なら読み出し保護か、アドレス指定ミス
xxd -e -g4 -l 32 flash.bin # 先頭がベクタテーブルか
cmp <(xxd flash.bin) <(xxd flash2.bin) && echo "2 回とも同一"8. 手を動かす
経路を選ぶ
この章のポイント
- 吸い出しは SWD/JTAG・SPI 直読み・ブートローダ・エミュレータ の 4 経路
- Cortex-M ではフラッシュ 0x08000000 付近、RAM 0x20000000 付近。ダンプのオフセット 0 が実機のどのアドレスかを記録する
- 読み出し保護(RDP・APPROTECT・Flash Encryption)に当たると 0 が返る。それは設計どおりで、平文で読めたら逆に脆弱性
- 吸い出したら 2 回読んで比較し、サイズとベクタテーブルで健全性を確認する