# HITCON CTF 2018 - Super Hexagon (Part 7/7) ###### tags: `trustzone` `ctf` `pwn` `ARM` `Aarch64`, `kernel` `hypervisor` # はじめに この記事は,[CTF Advent Calendar 2020](https://adventar.org/calendars/5338) の9日目の記事です. 8日目は私の「[HITCON CTF 2018 - Super Hexagon (Part 6/7)](https://hackmd.io/@bata24/H1N-W6vf8)」でした. # リンク集 - Part1/7 (EL0) - https://hackmd.io/@bata24/HyMQI7PuB - Part2/7 (EL1) - https://hackmd.io/@bata24/HJMKyaDfI - Part3/7 (EL2) - https://hackmd.io/@bata24/S1bHxavMU - Part4/7 (S-EL0) - https://hackmd.io/@bata24/BJHBSc0g8 - Part5/7 (S-EL0別解) - https://hackmd.io/@bata24/By9QPlFMU - Part6/7 (S-EL1) - https://hackmd.io/@bata24/H1N-W6vf8 - Part7/7 (S-EL3) - https://hackmd.io/@bata24/HJhLZTvGI # S-EL3の攻略 S-EL3のコードはFlash上にあるため改変不可能である.すなわち,コード改変以外の方法で制御を奪う必要がある.つまりデータやスタックの改変が本命ということだ. ## S-EL3 BIOSの解析 `smc`のハンドラ,特にセキュアワールドからノーマルワールドへの遷移をもう少し解析してみよう. ![](https://i.imgur.com/9J3KZA3.png) 56行目で呼ばれる`get_context()`の中身は以下の通りで,グローバル変数の`g_ctx`からポインタを拾ってきている. ![](https://i.imgur.com/F2XcI3t.png) ![](https://i.imgur.com/VSWWDaF.png) この`get_context()`は57行目の`revert_sysregs()`でも参照されている. ![](https://i.imgur.com/94CceK8.png) ![](https://i.imgur.com/01xngiI.png) また58行目の`push_ctx_to_stack()`でも参照されている. ![](https://i.imgur.com/Pp9KrIQ.png) つまり`ctx`は,ノーマルワールドのレジスタ状態を保持していることが分かる. ちなみに`ctx`構造体はおそらく以下のような形だ. ![](https://i.imgur.com/VypGBMV.png) ![](https://i.imgur.com/GFjmM1b.png) ![](https://i.imgur.com/vxukT1G.png) ### 脆弱性 `g_ctx`が書換可能な物理メモリにあることが脆弱性である. ![](https://i.imgur.com/VSWWDaF.png) この領域は`bios.bin`上にはないので,S-EL1にマッピングすれば,中身を変更することが可能である. ## 任意コード実行へ `g_ctx`はポインタの配列(要素数は2)である.`g_ctx[1]`をコントロールできれば,`smc`のハンドラ内で`ctx`と名付けたポインタもコントロールできる.`ctx`の指す先にはノーマルワールドのレジスタ全てが入っているが,これはEL1(?)のものでありS-EL3のレジスタではないため,S-EL3の制御を直接奪うことはできない. しかしこの`ctx`ポインタを`smc`のハンドラ内で利用する箇所が存在する. 以下のコードは,59行目で`ctx->regs.x0 = arg1`を行っている. ![](https://i.imgur.com/GSIIvnq.png) これは,もし`ctx`がスタック上のリターンアドレスの位置を指していれば,リターンアドレスを`arg1`で上書きすることが可能となることを意味している.実際にどうなるか見てみよう. 以下は`smc`のハンドラからリターンする瞬間を示している.リターンアドレスを保持する`x30(=lr)`レジスタは,`ldp`命令により`0x864`という値になっている.スタックを少し遡ればこの値は見つかり,`0xe000f48`から復元されたと考えられる. `CTX`構造体のオフセット0は`regs.x0`に当たるので,従って`g_ctx[1]`の中身(ポインタ)を`0xe000f48`にしておくと,`ctx->regx.x0 = arg1`のコードによって`arg1`で更新されるため,S-EL3のリターンアドレスを奪えることになる. ```= (gdb) this ##### register ##############################################)############ x0 0xe002450 234890320 x1 0x0 0 x2 0xe002430 234890288 x3 0xe002430 234890288 x4 0x0 0 x5 0x0 0 x6 0xe002210 234889744 x7 0x1 1 x8 0xff000006 4278190086 x9 0x8000000 134217728 x10 0x41000020 1090519072 x11 0x0 0 x12 0xe404000 239091712 x13 0xff0000000e40c900 -72057593798801152 x14 0xe40170800000000 1026846038167650304 x15 0x0 0 x16 0x0 0 x17 0x0 0 x18 0x731 1841 x19 0x40105000 1074810880 x20 0x83000006 2197815302 x21 0x7ffeffffe000 140733193379840 x22 0x7ffeffffe000 140733193379840 x23 0x7ffeffffd000 140733193375744 x24 0x23fe000 37740544 x25 0x0 0 x26 0x0 0 x27 0x0 0 x28 0x0 0 x29 0xe000f80 234884992 x30 0x864 2148 ★ret-addr sp 0xe000f80 0xe000f80 pc 0x510 0x510 cpsr 0x200002cc 536871628 fpsr 0x0 0 fpcr 0x0 0 ... ##### disassemble ######################################################## 0x500: ldr x19, [sp, #16] 0x504: ldp x21, x22, [sp, #32] 0x508: ldp x23, x24, [sp, #48] 0x50c: ldp x29, x30, [sp], #64 ★x30に値を代入するldp命令 => 0x510: ret 0x514: stp x29, x30, [sp, #-16]! 0x518: mov x29, sp 0x51c: bl 0x52c 0x520: bl 0x55c 0x524: ldp x29, x30, [sp], #16 0x528: ret 0x52c: stp x29, x30, [sp, #-16]! 0x530: mov x29, sp 0x534: mov w2, #0xc200 // #49664 ##### stack ############################################################## ... ************************************************************************** (gdb) x/32xw $sp-0x100 0xe000e80: 0x0000000000000000 0x0000000000000000 0xe000e90: 0x0000000000000000 0x0000000000000000 0xe000ea0: 0x0000000000000000 0x0000000000000000 0xe000eb0: 0x0000000000000000 0x0000000000000000 0xe000ec0: 0x0000000000000000 0x0000000000000000 0xe000ed0: 0x0000000000000000 0x0000000000000000 0xe000ee0: 0x0000000000000000 0x0000000000000000 0xe000ef0: 0x0000000000000000 0x0000000000000000 0xe000f00: 0x000000000e000fe0 0x0000000008000000 0xe000f10: 0x00000000ffffffff 0x000000000e404200 0xe000f20: 0x000000000e000f40 0x00000000000003f8 0xe000f30: 0x000000000e000f40 0x0000000000000408 0xe000f40: 0x000000000e000f80 0x0000000000000864 ★ret-addr 0xe000f50: 0x0000000040105000 0x0000000083000006 0xe000f60: 0x00007ffeffffe000 0x00007ffeffffe000 0xe000f70: 0x00007ffeffffd000 0x00000000023fe000 (gdb) ``` 尚,S-EL3の`.data`領域を改変しなくとも,S-EL3のスタックをS-EL1にマップして直接破壊すれば良いようにも見えるが,これはうまく行かない.何故ならS-EL3のスタックは,`smc`割り込みが発生したタイミングで最初から積み直されるため,S-EL1からスタックを編集しても正しい値で上書きしなおされてしまうからだ. さてリターンアドレスを上書きできたら,あとはどこへ飛ばせばよいだろうか.S-EL3には`RWX`な場所がないので,自前で領域を用意しなければならない.幸い,S-EL1では簡単に物理メモリをマップすることが可能なので,以下の2つをマップして,自前のシェルコード用領域を用意してしまおう. 1. 適当な未使用領域 - S-EL1に`RW-`でマップ - S-EL1から見えるようになったら,S-EL3のシェルコードを書いておく 2. S-EL3のページテーブル - S-EL1に`RW-`でマップ - S-EL1から見えるようになったら,S-EL3向けのページテーブルエントリとして1.を`R-X`で追加する - S-EL3から1.の領域にアクセス&実行するため 最終的に,以下のようになった.非常にごちゃごちゃしているが,S-EL1のPoCと比較すればわかりやすいだろう. :::spoiler Click {%gist bata24/a6fc016f232f68945e8d108e992ac1bc %} ::: ---- # 最後に 解析難易度が非常に高いが,AArch64とTrustZoneの詳細を知ることのできる良問であるので,是非挑戦してみてほしい. 私はこの問題を解くことで,以下を知ることができた. - AArch64のEL毎のページテーブル - EL間,ノーマルワールド/セキュアワールド間の遷移と実装 - AArch64とAArch32のデバッグの使い分け - AArch64のシステムレジスタ - TrustZoneを用いたプログラムのexploitの典型 # おまけ 全てのELのフラグを取得する(一つにまとめた)exploitは下記の通り. :::spoiler Click {%gist bata24/3d48705014e64bd650caac950cfa1843 %} ::: ---- # おわりに 明日は私の[gefを改造した話](https://hackmd.io/@bata24/rJVtBJsrP)です.(公開日になるまで閲覧できません)