29 Dec 2019, 21:54

SecureROMのmalloc/free追跡

Share

概要

axi0mX氏のcheckm8公開により、広く一般にSecureROM(iPhone/iPadのブートローダー)の動的解析が可能になった。SecureROMのヒープレイアウトへの理解を深める為にヒープ関連処理を解析し、その一環としてSecureROM開始時からDFUモード起動完了までの間に発生するmalloc/freeの呼び出しをデバッガーで追跡したので、研究用として記事に残す。

C97でもこの記事のコピ本を頒布予定(火曜日 南地区 リ-04b)で、間に合えば細かい話を加筆する。

関連記事

checkm8の解説ではa1exdandy氏による次の記事が非常によくまとまっている。

Technical analysis of the checkm8 exploit

上の記事でもヒープの確保シーケンスについて静的解析に基づいた解説があるが、この記事の動的解析でも上の記事と近い結果が得られた。

検証環境

  • iPhone 7 (A1779)
  • Bonobo JTAG/SWD Debug Cable
  • ipwndfu
    • Matthew Pierson氏によるfork版
    • commit bb3c1d618f96ce96956089823f396b777b4c46acに一部変更を加えたもの

調査手順

checkm8(ipwndfu)を使用してiPhone 7(ターゲット)からSecureROMを読み出しGhidraで解析した。解析によりいわゆるmalloc, free, memalignと思われる3関数のアドレスを特定した。

ipwndfuを使用してターゲットのSWDを有効化(demote)し、Bonobo JTAG/SWD Debug Cableを使用してgdbでターゲットにアタッチした。

SecureROM開始直後から実行を追跡するにはターゲットをリセットする必要があるが、通常の手順でターゲットをリセットすると、リセットによりdemotionの状態が初期化されてSWDが無効になり追跡できなくなる。このため、今回はdemote後にターゲットをreset vectorへジャンプさせてもう一度最初から起動処理を行わせて追跡した。Reset vectorへのジャンプ前に予めmalloc, free, memalignへハードウェアブレークポイントを張っておいて、それぞれへの呼び出しを監視・記録した。

追跡結果

備考

この章では、iPhone 7の実機から読みだした情報を引用する。この章で引用するメモリダンプのベースはDFUモード起動完了直後のもので、ベースは0x180000000である。

一覧

次の書式でalloc, free, memalignの呼び出しの遷移を記す。

呼び出し元アドレス : 関数名(引数) => 返り値

000000010000f858 : free(0x00000001801b4080)
000000010000edd0 : alloc(48)                    => 0x00000001801b4080
000000010000ede0 : alloc(256)                   => 0x00000001801b4100
0000000100011548 : alloc(4000)                  => 0x00000001801b4240
00000001000115d0 : free(0x00000001801b4240)
000000010000f124 : alloc(512)                   => 0x00000001801b4240
000000010000d10c : alloc(234)                   => 0x00000001801b4480
000000010000d10c : alloc(22)                    => 0x00000001801b45c0
000000010000d10c : alloc(62)                    => 0x00000001801b4640
000000010000d10c : alloc(198)                   => 0x00000001801b46c0
000000010000d10c : alloc(62)                    => 0x00000001801b4800
000000010000a9e0 : alloc(960)                   => 0x00000001801b4880
000000010000a9f0 : alloc(16384)                 => 0x00000001801b4c80
000000010000df08 : memalign(2048, 0x00000040)   => 0x00000001801b8d00
000000010000d2a4 : alloc(25)                    => 0x00000001801b9540
000000010000d2b4 : alloc(25)                    => 0x00000001801b95c0

メモ

000000010000f858 : free(0x00000001801b4080)

このfreeは不要領域の解放のためではなく、ヒープの初期化時に使用可能な領域をリストに登録するためのもの。

ヒープのベースは0x1801b4000で、空ブロック1つ(長さ0x40)と、追加しようとしているブロック自身のメタデータ(長さ0x40)が先行して配置されるため、ベース + 0x80が追加する領域の(データ部分の)オフセットになる。空ブロックは後方にも配置される。

000000010000edd0 : alloc(48) => 0x00000001801b4080

役割不明のバッファー。

000000010000ede0 : alloc(256) => 0x00000001801b4100

役割不明のバッファー。

0000000100011548 : alloc(4000) => 0x00000001801b4240

役割不明のバッファー。

00000001000115d0 : free(0x00000001801b4240)

直前で確保した長さ4000のバッファーを解放。

000000010000f124 : alloc(512) => 0x00000001801b4240

役割不明のバッファー。

メモリの内容を確認すると、先頭に”host bridge”とのASCII文字列が書き込まれている。

001b4240  68 6f 73 74 20 62 72 69  64 67 65 00 00 00 00 00  |host bridge.....|
001b4250  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
001b4310  0c 16 00 00 01 00 00 00  6c 16 00 00 01 00 00 00  |........l.......|
001b4320  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 c0  |................|
001b4330  00 00 00 02 00 00 00 00  00 00 00 00 00 00 00 00  |................|
001b4340  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
001b4440

000000010000d10c : alloc(234) => 0x00000001801b4480

USBのString Descriptor (Descriptor Index = 1)

ApNonceとSEPNonceをHostに通知するためのもの。ASCII文字による16進表記かつ1文字あたり2バイトで表現するのでApNonceとSEPNonceの情報量に比してサイズが大きい。

これ以降の追跡結果はa1exdandy氏の記事の解析結果と概ね一致する。

000000010000d10c : alloc(22) => 0x00000001801b45c0

USBのString Descriptor (Manufacturer)

000000010000d10c : alloc(62) => 0x00000001801b4640

USBのString Descriptor (Product)

000000010000d10c : alloc(198) => 0x00000001801b46c0

USBのString Descriptor (Serial Number)

DFUモードではここにハードウェアとソフトウェアのバージョンや設定情報を詰め込むので長い。

000000010000d10c : alloc(62) => 0x00000001801b4800

USBのString Descriptor (Configuration)

000000010000a9e0 : alloc(960) => 0x00000001801b4880

USBコントローラーを制御するタスクのタスク構造体。

000000010000a9f0 : alloc(16384) => 0x00000001801b4c80

USBコントローラーを制御するタスクのタスクスタック。

ここの確保サイズはa1exdandy氏の記事と異なる。a1exdandy氏の記事では確保サイズが0x1000となっているのに対して、今回実機で確認できた確保サイズは0x4000(16384)だった。呼び出し箇所の周辺を調べるとmax(0x1000, 0x4000)の比較結果を確保サイズとしていた。

000000010000df08 : memalign(2048, 0x00000040) => 0x00000001801b8d00

USBの入出力用バッファー。

000000010000d2a4 : alloc(25) => 0x00000001801b9540

USBのConfiguration Descriptor

a1exdandy氏の記事によればこちらがHigh-Speed用のdescriptorとのこと。書き込まれるデータは後述のFull-Speed用descriptorと同一。

ipwndfu/src/checkm8_arm64.S内のgUSBDescriptorsは、ここで確保されたバッファーへのポインターを指す。

00088a30  40 95 1b 80 01 00 00 00  c0 95 1b 80 01 00 00 00  |@...............|

000000010000d2b4 : alloc(25) => 0x00000001801b95c0

USBのConfiguration Descriptor

a1exdandy氏の記事によればこちらがFull-Speed用のdescriptorとのこと。

備考

今回はreset vectorにジャンプすることでwarm reset(的な操作)を施してSecureROMを開始直後から追跡したが、warm reset時の状態は通常起動時と異なるため、通常起動時と挙動が一致するかどうかは不明。ただし、warm reset後でもcheckm8が成功することは確認できた。

編集履歴

2019/12/31

USB関連の記述に誤りがあったため訂正した。