Hack.lu CTF 2013 Breznparadisebugmaschine
はじめに
katagaitai CTF 勉強会 #8 に参加しました。
このような外部の勉強会に参加させていただいたのは初めてでしたが、とても勉強になりました。(と同時に無力さを痛感しましたが…)
勉強会を主催してくださった方々に感謝申し上げます。
さて、勉強会当日は解説についていくのがやっとで実際に攻略までたどり着くことができませんでしたので勉強会資料を閲覧しつつ攻略を実施してみます。ほとんど勉強会資料を再喝したような内容となっていますがご覧いただけたらと思います。
使用環境
攻撃対象サーバ
WindowsServer2012
アドレスリークまで
今回のバイナリには以下の2つの脆弱性が存在します。
- Type Confusion & Buffer Over Read(Info Food動作内)
- Use After Free(Robotの周期的な動作内)
まずは一つ目のType Confusion & Buffer Over Readを用いてHeap内のデータをリークさせます。
上記はInfo Foodの逆アセンブラの一部です。アドレス0x040407Aにて標準入力から入力された値を"input_food_type"に代入し、その後、"input_food_type"が0以上2以下であるかを判定しています。
さらに、"input_food_type"を"food_type"に代入し、その後、"food_type"が0(Brezel)/1(Laugenstangerl)/2(Semmel)各々のケースに応じて表示するレングスを決定しています。上記の0x004040B4が真の場合(すなわち表示するFoodのタイプにBrezelを選択した場合)は表示するレングスに"512"が設定されます。他のケースも見ていただければわかりますが、1(Laugenstangerl)の場合は"1024"、2(Semmel)の場合は"512"が設定されます。
ここで、InsertしたFoodが2(Semmel)で、InfoするFoodのTypeを1(Laugenstangerl)に指定した場合、何が表示されるでしょうか。
Semmelのアスキーアートの後ろに環境変数らしき文字列が出力されており、Insertしたデータより広い範囲の領域を出力していることが確認できます。これを用いて攻略に有効なデータをリークすることを考えます。
上記は各Foodの構造体です。各Foodはメンバとしてvtableのポインタを持っています。また、Semmelだけアスキーアートの他にHeapのポインタをメンバとして持っています。vtableとHeapのアドレスをリークしてみたいと思います。
まずは、SemmelをInsertします。
その後、いずれかのFoodをInsertします。今回はLaugenstangerlをInsertします。
この状態でSemmelに対して"Laugenstangerl"を指定してInfoを実施します。その際、以下の通り、Heapのアドレスとvtableのアドレスが出力されます。(以下の図のオレンジの部分が出力される範囲のイメージです。)
上記を実施した時のダンプが以下です。(SemmelとLaugenstangerlの境界線は実際とは少し異なりますがご了承ください。)InsertしたSemmelは出力できていますが、Laugenstangerlが出力されると想定していたオレンジ色の箇所が想定とは異なるデータが出力されています。
API MonitorでInsert Food時に確保したHeapのアドレスを確認してみます。該当のHeapAllocかはHeapAllocの第3引数(確保するサイズ)から特定します。Semmelが556、Laungenstangerlは1064であるため、該当の箇所は以下の通りです。
上記のReturn Value(確保した領域の先頭アドレス)を見てみると各々のアドレスがかなり離れていることがわかります。これはSemmel指定時の確保に過去にFreeした領域が再利用されているからのようです。そこで、Free済み領域を使用しきるためSemmelをいくつか(今回は2つ)InsertしたのちLaugenstangerlをInsertしてみるとどうなるか見てましょう。
今度は期待通りInsertしたLaungenstangerlの情報も出力されていることが確認できます。出力された情報をさらに詳しく見てみましょう。
0x26bから始まる4byteのがSemmelのメンバであるHeapのアドレス、0x27bから始まる4byteがLaugenstangerlのメンバであるvtableのアドレスです。
次に各アドレスのオフセットを算出するためx32dbgでメモリマップを確認してみます。
Heapの先頭アドレスは0x00C80000(上の枠内)、.idataの先頭アドレスは0x11B0000(下の枠内)であることがわかります。よってHeapの先頭アドレスまでのオフセットは0x3970(= 0x00C83970 - 0x00C80000)、.idataの先頭アドレスまでのオフセットは0x10F70(= 0x011C0F70 - 0x011B0000)となります。
以上を用いてリークしたアドレスを出力してみます。
これでASLRによってランダム化されるHeapの先頭アドレスとバイナリの先頭アドレスを算出することができました。
EIP奪取まで
次にvtableのポインタを任意のアドレスに書き換え、Use After Freeを利用して制御を奪取することを考えます。
まず、vtableの書き換えについてですが、作成したFoodの領域に編集可能な領域を重ね合わせ、その領域に任意の値を書き込むことでFood内のvtableを書き換えます。イメージは以下です。
十分な大きさの編集可能な領域はEdit Food内の以下で確保するBufferを使用します。以下の処理でHeap上にレングスが0x800のBufferを確保しています。
(中略)
確保したBufferとUse After Free可能な領域を重ね合わせてみます。
上記のようにある2つのFoodをInsertします。ここではこの2つのFoodを"Food A"と"Victim Food"とします。次にVictim FoodをRobotを使用して解放します。この際、領域は解放しますがvectorから外す処理が存在しません。(Use After Free 可能。)その後、Food AをRemove Foodにを用いて解放します。Removeの際に実施するHeapFreeによって解放済みのFood Aの領域と解放済みのVictim Foodの領域が合わさり、一つの大きな空き領域となります。最後にEdit Foodを実施し、その空き領域上に編集可能な領域を確保します。そうすることで生成した大きな一つの空き領域と編集可能な領域が重なります。実際に実施してみます。
Insert Food(Food A)での領域確保
Insert Food(Victim Food )での領域確保
RobotによるVictim Food の解放
Remove Food(Food A )での解放
Edit Foodでの確保
イメージ図にすると以下の通りです。左がInsert Food終了時、右がEdit Food終了時のイメージです。領域を重ね合わせられていることが確認できました。
ただ、教科書ではFood Aと編集可能な領域の先頭アドレスが一致し領域がきれいに重なっていますが、私の環境では0x18分ずれてしまいます。この差を考慮して後述するコードでは各アドレスを計算する必要があります。
次に編集可能な領域に対して'A'を書き込み制御を奪取できるのか確認してみます。上記の状態のあと、Ovenを動作させることでUse After Freeによって以下の図のget_AAが起動されますが、vtableは'A'で書き換えれているため不正アドレス参照となる想定です。
実際に動作させた際の結果は以下の通りです。
EAXの値をEDXに転送し、その後にEDXの値をcallしています。ここでEAXは編集可能な領域に書き込んだ"41414141"( = "AAAA" )であるため、任意の書き換えたアドレスの実行に成功したと言えます。
ローカルサーバ上での任意のコマンド実行まで
EIPを奪取できたため、あとは任意のコードを実行するためのコードを組んでいきます。現状できることを整理しますと、
- Heapの先頭アドレスと.idataの先頭アドレスがリークできる
- Food内のvtableのポインタを任意のアドレスに書き換え実行できる
です。これらを用いて以下の攻撃を実施します。
- Heap上にvtableを用意してvtable内のポインタを偽装する
- 上記のポインタ経由でStack pivotを発動させる(ESP
レジスタがHeapを指す) - ROPを実施しVirtualProtectを実行することでHeap領域に実行属性を与える
- シェルコードを仕込み実行する
Heapへ書き込む内容を以下に図示します。(WIndowsの標準搭載のペイントで作成したため手書き感満載の矢印です。。)
必要となるのがStack pivotを実施するガジェットのアドレスと.idata内のVirtualProtectのアドレスです。ガジェットのアドレスはrp++で確認しました。以下の0x00401b24のガジェットを使用します。
次にVIrtualProtectの.idata上のアドレスはIDA Proのディスアセンブルの結果から確認します。
シェルコードについてはこの時点では任意のコードを実行できることを確認するため"¥x90¥x90¥x90¥xcc"を仕込んであります。また、コードに記載するアドレスはリークしたHeapアドレスを使用して計算していますが、前述の通りEdit Foodで確保した領域の開始アドレスがFood Aの開始アドレスより0x18前であることを考慮して計算する必要があります。
これで攻撃に必要なものは準備できました。では、仕込んだシェルコードが実行できるか試してみます。
仕込んだ"¥x90¥x90¥x90¥xcc"が実行されているのがわかります。
最後に仕込むシェルコードをリバースシェルにかえて、ローカルサーバ上での任意コマンドの実行まで確認します。なお、リバースシェルはMetasploitで生成(payload: windows/shell_reverse_tcp)しました。
攻撃実施端末
待ち受け端末
サーバ上で任意のコマンドを実行できました!
ここまでで使用したコードについては以下に掲載していますので参考にされる方はご覧ください。
まとめ
今回の問題で経験したことは以下です。
脆弱性
- Type Confusion
- Buffer Over Read
- Use After Free
緩和策
- NX
- ASLR
攻撃手法
- ROP
- Stack pivot
本番サーバがまだ稼働中ということですので、別途、本番サーバに対しても実施してみたいと思います。また、資料のおまけに触れられていないのでこちらも別途実施してみたいと思います。
まだ理解が及んでおらず内容がわかりにくいところもあったかと思いますが、最後までお付き合いありがとうございました。
参考資料
教科書
リバースシェルについて
セキュリティ・ダークナイト ライジング(3):ファンタスティックな4つのMetasploitペイロード (1/2) - @IT
2017/3/16 追記
本番サーバでの攻略も実施しました。私のローカル環境で実施した際との差分ですが教科書の解説の通りHeapの先頭アドレスからリークしたアドレスのオフセットが異なることと、ダミーのFoodの個数を1つにしたことでした。