PS3 Hacked!!
例のPS3ハックを理解するために、PPC64およびPS3のメモリ管理と仮想化の仕組みを理解しておこう。目標は、あの解説を読める程度になるレベルで。
(調べながら書いてるので流れが少し変かも)
手元にはPowerPC Operating Environment Architecture Book III Ver2.02(以下Book III)を用意しておいてほしい。
いくつかPPC64用語を解説しておこう。
LPAR
PPC64では、x86でいう仮想化みたいなもの…OSの上位にいるプログラムを書く仕組みは、Logical Partitioning(LPAR)と名前が付けられており、アーキテクチャの一部として定義されている。
あと、x86でいう仮想化は、FullVirtualization支援の意味が強いが、PowerPCのLPARはParaVirtualization支援っぽい感じだと思う。
Hypervisor, Supervisor, privilege
実行権には3段階あって、
- Hypervisor : 神
- Supervisor : 中間管理職
- privilege : 何の権限も持たないドレイ
よく使われる用語と対応させると、
- Hypervisor : VMM
- Supervisor : ゲストOS
- privilege : アプリケーション
みたいな感じ。
Effective Address, Virtual Address, Real Address
アドレスがいくつかあって、
- Effective Address(EA) : プログラムに出てくるアドレス 64bit
- Virtual Address(VA) : アドレス変換の途中で出現するアドレス 80bit
- Real Address(RA) : 実際にバスに流れるアクセス 62bit
というような。
- -
例のアレを理解するのに必要なのは、HTABにアクセス可能な状態というのがどういう状態なのかを理解することだ。以下、そこらへんを説明する。
(以下の説明では、HTABはページテーブルみたいなもの、という表現になってる。厳密には違うのだけど、説明上それほど問題ではないので、ページテーブル関連のテーブルは全て「ページテーブル」と表記している。あと僕はHTABあまり理解してない)
まずはアドレス変換。アドレス変換は二段階になっていて、
- Effective Address -> Virtual Address(セグメンテーション)
- Virtual Address -> Real Address(ページテーブルルックアップ)
となる。詳細はBook IIIの4.3あたり。
セグメンテーションでは、64bitのEAの上位36bitをキーにして、セグメントを探してきて、そのセグメントに入っているアドレスと、EAの下位28ビットをくっつけて、VAとする。
ページテーブルルックアップは…えーと、ちょっとややこしいのであまり理解してないが、VA上位いくらかビットをハッシュでごにょごにょしたら、ページテーブルを指すアドレスになって、そっからRAを計算するなどする。
また、PPC64は、MMUを無効にすることができて、その場合は、プログラム中に出てくるアドレスがRAとなる。
ここで、Supervisorは、アドレス変換有効時/無効時に、それぞれ以下の点でHypervisorの制御下に置かれる。
アドレス変換無効時
1. RAにオフセットが付くようになる。
RMOR(Real Mode Offset Register)というHypervisorのみが読み書きできるレジスタがある。
SupervisorがRAにアクセスすると、このRMORが加算される。
たとえば、RMORが0x2000とかに設定されてると、Supervisorは0x110にアクセスしたつもりが、0x2110にアクセスすることになる。
2. アドレスの最大値に制限がかかる。
RMLR(Real Mode Limit Register)というHypervisorのみが読み書きできるレジスタがある。
Supervisorはここに設定された値を超えるRAにアクセスできなくなる。
アドレス変換有効時
ページテーブルの値が書きかえられない。
具体的には、RMOR、RMLRの制限を使うとSupervisorがアクセスできない領域を作り出せるのを利用して、ページテーブルをSupervisorがアクセスできない領域に置く。これによって、ページテーブルは、Hypervisorだけが変更できる聖域となり、Supervisorは、アドレス変換をするときには、毎回Hypervisorにお伺いを立てないといけなくなる。
いわゆる仮想マシンを実現する場合、Hypervisorは、Supervisorからのページテーブル書き換え要求をチェックすることによって、不正なアドレスがSupervisorから見えたりすることのないようにできる。
なお、セグメンテーションはSupervisorの自由にできる。
- -
以上が、PPC64のLPARと保護の仕組みである。これを突破するには、
- HypervisorになんとかしてRMOR,RMLRを書きかえさせる
- なんとかして不正なページにアクセスできるページテーブルを作る
のどちらかが必要である。例のハックでは、後者を使っており、やっていることは、
- なんとかして聖域であるページテーブルをSupervisorに見えるところにマップさせる
- あとはページテーブル書き換えられるので、Supervisorから任意のRAにアクセスできる。
という流れ。
さて、ここで、どうやって、ページテーブルをSupervisorから見えるところにマップさせるか、という点だが…
PS3のハイパバイザコールは、いくつかあるのだが、ここで重要なのは以下
- lv1_memory_allocate
- Hypervisorにメモリを見せてくれと頼む。成功すると、RAが返ってきて、このRAはマップ可能になっている。
- lv1_write_htab_entry
- ページテーブルを設定して、VA->RAの変換を行うようにする。Hypervisorは、ここで、RAのチェックをしており、lv1_memory_allocateで割りあてられたRA以外を見ようとするとエラーになる(多分)
- lv1_release_memory
- lv1_memory_allocateで割り当てられたメモリを解放する。また、ページテーブルをチェックして、このメモリへの変換が有効になっている場合、そのテーブルエントリを無効にする。
さて、ここで重要な挙動は、release_memoryは、ページテーブルの操作も行っているという点。このページテーブルの操作を行う瞬間に、メモリアクセスを奪ってやれば(特定の部分に40nsのパルスを入れるとできるらしい)、「release_memoryで解放された領域を指すページテーブルエントリ」が残ることになる。ここまでが例のコードのexploit_first_stageにあたる部分。
次に、Hypervisorに新しいvirtual address space(以下vas:仮想マシンのインスタンスみたいなもん)を作るように頼む(lv1_construct_virtual_address_space)。
Hypervisorは新しいvasを作るときに、どこかからそのvasのためのページテーブルを割り当ててくるのだが、この時、ヒープアロケータの挙動の都合上、最近解放したメモリから割り当てられる可能性が高い!ここで、最近解放したメモリというのは、exploit_first_stageで解放したメモリであり、つまり、Supervisorが読み書き可能になっている領域である。
よーするに…うまくいけば、「第二のvasのページテーブルにアクセスできるページテーブルエントリを持った状態」というのを作り上げることができるわけである。おめでとう、ページテーブルは聖域ではなくなった!
こっから先はやりたい放題なので、実装依存なのだが、例のexploitのコードでは、(Linuxのvasをvas1, 第二のvasをvas2として)
- vas2のページテーブルがvas1のページテーブルにアクセスできるように設定する
- vas2に切り換える(OMG, CRAZY, IN OTHER SPACEと書いてある箇所)
- vas2から、vas1のページテーブルがvas1自身のページテーブルを書きかえられるように設定する
- vas1に戻る
となっている。これによって、Linux上で、ページテーブルを自由に操作できるようにしている。
いやーすごい。よくこんなの思い付くよ…あと、仮想化によるセキュリティというのの脆弱さというものについて考えさせられるね…