Binary Analysis 04 · ダンプを手に入れる — 実機からフラッシュと RAM を読む

Chapter 04

ダンプを手に入れる — 実機からフラッシュと RAM を読む

この章のゴール.

SWD / JTAG・SPI 直読み・ブートローダ・エミュレータという 4 つの吸い出し経路を理解し、 それぞれで「何が読めて、何が読めないか」を説明できるようになること。 読み出し保護に当たったときの見分け方を知ること。

この章で使う既出の用語(定義は各リンク先). メモリマップ(01 章 3 節)、OpenOCD(02 章 7 節)、pyOCD(02 章 7 節)

1. どこから読むか

解析するには、まず中身をファイルにする必要がある。経路は主に 4 つある。

経路対象読めるもの難しさ
デバッグポート(SWD / JTAG)内蔵フラッシュ・RAM・レジスタ保護が無効なら全部低(配線があれば)
フラッシュチップの直読み外付け SPI / NAND フラッシュチップの全内容中(クリップか取り外し)
ブートローダ / ISP内蔵フラッシュブートローダが許す範囲中
エミュレータ / ソフトイメージファイルファイルそのもの低(実機不要)
吸い出しの 4 経路デバッグポートSWD / JTAG。内蔵フラッシュ・RAM。保護が無効なら全部。OpenOCD / pyOCD / J-Linkフラッシュ直読み外付け SPI / NAND。クリップか取り外し。flashromブートローダ / ISPUART / USB。ブートローダが許す範囲。esptool / stm32flash / dfu-utilエミュレータ / ソフト既にファイルがある場合。QEMU / Renode。実機不要Cortex-M: フラッシュ 0x08000000 付近、RAM 0x20000000 付近ダンプのオフセット 0 が、実機のどのアドレスに対応するかを必ず記録する(6・7 章のアドレス変換で必要)吸い出したら 2 回読んで比較(cmp)・サイズ確認・先頭がベクタテーブルか全部 0x00 / 0xFF なら読み出し保護かアドレス指定ミス
SWD・SPI 直読み・ブートローダ・エミュレータ。オフセット 0 が実機のどのアドレスかを記録する

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 KB

pyOCD(Python 製、設定が楽)

pyocd list                                   # 見えているプローブとターゲット
pyocd cmd -t stm32u585xx
#   > savemem 0x08000000 0x200000 flash.bin  ← アドレスと長さを指定
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)。 保護が有効だと、次のような症状が出る。

これは設計どおりの安全機構である。自分の製品でも、量産設定では保護が入っている。 保護を外すには、多くのチップでフラッシュ全消去を伴うレベル下げ(RDP 1→0 で全消去)が必要で、中身は失われる。 つまり「保護された製品の中身は、正規の手段では読めない」——それがセキュアブートとフラッシュ暗号化の目的である(PSA 編・IoT セキュリティ編を参照)。

「読めた」ことの意味.

保護なしで読めたなら、それは攻撃者にも読めるということである。 自分の製品を解析していて鍵や証明書が平文で出てきたら、それは脆弱性の発見である(13 章)。 「解析できた」で終わらず、「保護すべきものが保護されていたか」を確認する。

読み出し保護 — 読めないのは設計どおり保護ありSTM32 RDP / nRF APPROTECT / ESP Flash Encryption。接続できてもフラッシュは 0x00 / 0xFF が返る、または接続拒否。レベル下げには全消去が要り中身は失われる保護なしSWD や SPI 直読みで全部読める。=攻撃者にも読める「読めた」ことの意味保護なしで鍵や証明書が平文で出てきたら、それは脆弱性の発見(13 章)。「解析できた」で終わらず「保護すべきものが保護されていたか」を確認する。対策は PSA 編・IoT セキュリティ編
読み出し保護は安全機構。平文で読めたら逆に脆弱性

4. フラッシュチップの直読み — SPI NOR / NAND

設定やログ、ときにはファームウェア本体が、基板上の外付けフラッシュチップ(8 ピンの SOIC、SPI NOR フラッシュが典型。W25Q… などの型番)に入っていることがある。

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
ESP32ROM ブートローダ(UART)esptool.py read_flash 0 0x400000 flash.bin
Nordic nRFUART / USB DFU(ブートローダが載っていれば)nrfutil、adafruit-nrfutil
NXP i.MXserial download(USB)uuu、sdphost
Raspberry Pi PicoBOOTSEL + UF2、または picotoolpicotool 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 章)。

7. ダンプの健全性を確かめる

吸い出した直後に、それが正しく取れているかを確認する。

xxd -e -g4 -l 32 flash.bin      # 先頭がベクタテーブルか
cmp <(xxd flash.bin) <(xxd flash2.bin) && echo "2 回とも同一"

8. 手を動かす

経路を選ぶ


この章のポイント