2012年5月13日日曜日

SECCON CTF 2012/2/18-19 Binary 300 Devide Section

問題:「キーを見つけろ」
まず最初に、説明が長い割に適当なので根気強く見てください(笑)。

問題のzipファイルをダウンロードし、中身を開くと
5つのバイナリファイルが入ってる。
#ファイル名は何を意味するんだろうか。謎だ。
これをバイナリエディタで見ると以下のようになる。
図1

左の2つのファイル(MILEENA.binとPITS.bin)の先頭を見るとMZとPEのマジックがあるので、明らかに Windows PE(Protable Executable)、つまり実行ファイルに関係することがわかる。また右下のREPTILE.binにはKERNEL32.dllの文字列が現れているのでインポートするWindows APIを表していることが伺える。
ここで、問題の名前、Divide Sectionから予想するにPEファイルがSectionごとに分割されていて、おそらくそれを結合すればキーが得られるという仕組みだと疑う。
またPEは
  • MZヘッダ
  • PEヘッダ
  • セクションテーブル
  • セクション1
  • セクション2
  • ...
のフォーマットとなっていることからもこのSecitonを意味している可能性が高い。
早速、この順番に結合してみたが(やはり)実行はできない。
#ここからバイナリエディタとの格闘が始まるのであった。

この分割されたデータを復元するにはSectionのデータをファイル内の正しいアドレスに書き込む必要がある。PEファイルを実行するときはそういったアドレスを参照しながら自分の中にどういうデータがあり、どこに格納されているかを知るのである。
#ちなみにぼくはアナライジング・マルウェアという本を参考にしました。著者の方々、ありがとうございます。

上の図はPEヘッダ(PITS.bin)で実行のヒントとなる部分をマークしたものである。
???の部分はおそらくIAT(Import Address Table)だと思われるが、確信がない。
こう見てみると、大事なところがDE AD BE EFでかき消されていることがわかる。

まず、PEヘッダ内のSection情報をもとに各セクションを復元する。
図中に示されていないNumberOfSectionという場所が0x03であることから残りの3つのファイルがそれに当たると察し、さらに残りのファイルのバイナリから予想して、それらは text(コード領域)、rdata(インポートする関数を並べる領域?)、data(データ領域)だとする。
ここでSection name領域の後のメモリにロードされたときのサイズとアドレス(緑線)と、ディスク上でのサイズとアドレス(青線)が大事となる。
例えば、先頭のtextセクション(file offset: 0xF8)の
  • VirtualAddress : 0x1000, VirtualSize : 0x93A
  • PointerToRawData : 0x400, SizeOfRawData : 0xA00
となります。
※Intelマシンはリトルエンディアンであることに注意してくださいね。
※(00 04 00 00 -> 0x400)

つまり、textセクションとなるデータ(図1の右真ん中TOASTY!.bin)はファイルオフセットが0x400となる場所に来ます。なので0x00でパッディングしなければなりません。
例えばMZヘッダとPEヘッダの間はGapが存在するため0x00を16個パッディングします。
同じようにパッディングを全セクションにすることで最終的には、
PEファイル =
                   MZヘッダ + (0x00 * 16) +
                   PEヘッダ + 0x00 * (16 * 26 + 18) +
                   text + 0x00 * (16 * 12 + 6) +
                   rdata + 0x00 * (16 * 6) +
                   data
となります。
これでヘッダとセクションは正しく配置されました。
 次にImportのアドレスを直します。
Importのアドレスはrdataの間から考えると図1の右下REPTILE.binから指し示す場所を見つけなければなりません。
本当はきちんとrdataのオフセットの差(VirtualAddress - PointerToRawData = 0x1200)を利用しDLLの出現位置から逆算してアドレスを決めるところですが、他の問題(Binary 100、200)のPEファイルから似たパターンを見つけることでimportアドレスを発見しました。
図の2番目のマーカがそのアドレスを示しています。
0x22B4(1番目のマーカ) - 0x1200(オフセット差) = 0x10B4(2番目のマーカ)になりますし、合ってる気がします。
とimportアドレスも分かったのであとはEntryPointです。
これも面倒くさかったのでBinary 100, 200のプログラムからEntryPointのパターン(C6 75 08 33)を探してVirtualAddressに変換して埋めました。

#プログラムが違っても、最初の初期化のルーチンは一緒なんだろうと思います。
つまりオフセット0x5DD + 0x1200 = 0x13DDがEntryPointのはずです。
こうして変換していくと、PEヘッダが下図の左から右に変わります。

試しに、修正したファイルを実行してみるとエラーなく実行できる。
#もうここまでで十分なのだが、問題はまだ続く。
Password:
と聞かれてしまったからである。つなり正しいパスワードが求めていたキーである。
となればデバッガの出番。
#DLLを調べたときにisDebuggerPresentの文字列が出て、まさかアンチデバッガじゃないだろうなと萎えてたけど、そうじゃないみたいで良かった。

逆アセンブルすると、test eax, eaxの結果でCorrectかTry again.に分岐するのでその前の
call loc_401090が怪しいとにらみ、break-pointを埋め込む。
loc_401090でも謎の処理があり、loop loc_4010B9を抜けると怪しいcmp命令がずらずら並ぶ。またesiをベースとしたアドレスが0x4増加するため、文字列を4Byteずつで比較しているのがわかる。それぞれのcmp命令の2番目のオペランドを結合すると答えの文字列が表れる。
以上です。
説明も長く、かけた時間も長く、回答の文字列も長かった。

0 件のコメント:

コメントを投稿