読者です 読者をやめる 読者になる 読者になる

DEFCON CTF2013 annyong writeup

DEFCON CTFの過去問を解いていきます。

binariesが欲しい方は前回の記事を参照してください。

 

2つめはExploitation400(だったっけ)のannyongです。

当日は競技終了1時間前から解き始めて、ROPの組み立ての取っ掛かりまでは進みましたが、まだまだ間に合わなかったのを覚えています。

 

実行してる内容自体は非常にシンプルで、

  1. fgetsで入力を受け付ける
  2. 入力した文字列内にnが入っていたら"I don't think so..."と出力して4へ
  3. 入っていなければprintf(input)を実行して4へ
  4. あらかじめ0で初期化されている-0x4(rbp)が0以外の値になっていればreturn, なっていなければ1へ

 という無限ループです。

 

要点は4つ。

  •  NX bitとPIEが有効。

f:id:potetisensei:20140216161734p:plain

  • 実行ファイル内で標準入出力を利用しているため、xintedやsocatなどを用いて実行されている。したがってremote shellを取る必要はない。
  • fgetsが0x900 byte受け付けるのに対し、bufferのsizeは0x80c byteであるのでbuffer overflowが起きる。この時、-0x4(rbp)の値が0でなければreturnされるのでこれをうまく調節する必要がある。
  • strchrによって入力内に'n'が入っていないかを確認する。入っていなければformat string attckが可能。よって、memoryのleakにはこれを利用する。

 

さて、この場合、どのような方針を立てるにせよ、任意のコード実行を行うためには、system関数、あるいはexec系列の関数のAddressが必要です。

format string attackによるleakでは、

  1. stack上に存在している値
  2. NULL文字が来るまでの任意のアドレスの値

の2種類を読むこと可能です。

したがって、1を用いてstack上で「値も位置も常に変化することがなく、最も読むのに適している」return addressをleakさせ、そこからbase addressを計算し、2を用いてgot.pltに存在している関数のlibc内での位置をつきとめることができます。

今回はsystemがGOTには存在していないため、libc予測が必要になります。

当然、使用されているOSやそのバージョンによってlibc内の関数の相対的な位置は変化するので、今回はsystem関数のsignature(になりそうなバイト列)を用いてsystem関数の位置を突き止めます。

 

sysytem関数のaddressを突き止めた後は、system関数をどう実行するかが問題になります。

annyongはx86-64であるため、基本的に関数の第一引数から第六引数(だっけ)はレジスタによって渡されます。

レジスタの値はbuffer overflowやformat string attackでは書き換えることが出来ないため、return-to-libc、あるいはROPが必要になります。

今回は実行したいコマンドをrdiレジスタに格納する必要があるため、なかなかめんどくさいです。

 

このために僕が考えた方針は2つ。

  1. GOT overwriteによって適当な関数の呼び出しでsystemが呼び出されるようにする。 この時、書き換えられる関数の呼び出しの際に、ローカル変数などからrdiに値を読み込んでいるならば、buffer overflowでローカル変数を自由に操作できるため、rdiにコマンドを渡しつつsystemを実行することが出来る。
  2. rdiにコマンドを渡して、そのままsystem関数にreturn出来るようなROPを見つける

 

1のGOT overwriteは、GOTの書き換えさえうまく出来るならば、書き換える関数はいくらでもあるため、簡単にpayloadを作ることが出来ます。しかし、x64のGOT overwriteは非常にめんどくさいというのと、実行するためには一度「"\x00" + payload」のようなstrchrによるチェックに引っかからないような文字列を送信した後にbuffer overflowでpayloadだけをprintfするようにstackを調節してreturnする必要があり、試してみたところ、4回のoverwriteを行う間に、rbpの値が非常に大きな値になってしまい、stack領域を超えてしまうので不可能に思えます。

2の場合、rdiにコマンドを渡すことが出来るようなROPを見つけるのは非常にめんどくさいですが、ROPは基本的に気力さえあれば何でもできる(と信じてる)ので、どうにかなりそうです。

探してみたところ、write関数を呼び出す際にrsiに格納するbufferのアドレスを操作することができる場所とmov rdi, rsiを実行する場所があったので、これらを繋げてやればrdiにコマンドを格納出来ます。

 

DEFCON Writeup annyong

 

標準入出力を使われるとexploit書くときにめんどくさいですね。

bata_24氏に教えていただいたpwntool/pwn/process.pyを利用しました。

 

system関数の検索の部分は、確実にsystem関数よりも上位にあると思われる__libc_start_mainから始めているので非常に無駄がありますが、おそらくlibcのバージョン間による相対的な位置の誤差は±500byte程度に収まる気がするので、もう少し探索幅を狭めてやればremoteに対する実行でも十分間に合うでしょう。

また、一度検索した場所は二度と検索する必要はないので、狭めなくとも30分ぐらいあれば探索は終わるかなと。

 

f:id:potetisensei:20140216170057p:plain

 

The key is: Kernel airbags have been fully deployed

所要時間 3時間21分。

Popenのバグを踏んだにしても、方針の立て方が悪かったせいで時間がかかりすぎたので反省したい。

今日はここまで。