SECCON CTF web予選に参加した
チーム竹田氏として参加した。解けたのはbin100, bin200。あまり力になれなくて悲しい。
- bin100(Enjoy the Game)
64bitアプリケーションだったので各種フリーのデバッガやプロセスエディタが使えなかった。バイナリエディタのみで解いた。
まず、data/kabe.mpoを弄って床だけにしてやる。一見ゴールに辿り着くパスが無いように見える。
フィールド情報を弄ってゴールにたどり着けるようにすればいいんじゃないかと思った。
サンプルコードを見るに以下のようなフィールド情報がグローバル変数として宣言されている。
char Map[ BLOCK_NUM_Z ][ BLOCK_NUM_X ] = { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,1,1,1,1,0,1,1,1,0,1,1,0,1,1,0, 0,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0, 0,1,1,1,1,1,1,0,1,0,0,1,1,1,1,0, 0,1,0,1,0,0,0,0,1,0,0,1,0,0,0,0, 0,0,0,0,0,0,0,0,1,0,0,1,1,1,0,0, 0,0,0,1,1,1,1,1,1,0,0,0,0,1,1,0, 0,0,0,1,1,1,0,1,0,0,0,1,0,0,1,0, 0,1,1,1,1,1,0,1,1,1,1,1,1,1,1,0, 0,0,0,1,0,0,0,1,0,0,0,1,0,0,1,0, 0,0,0,1,1,1,1,1,0,0,0,1,0,0,1,0, 0,0,0,0,0,1,0,0,0,0,1,1,1,1,1,0, 0,1,1,1,0,1,0,0,0,0,1,0,1,0,1,0, 0,1,0,1,1,1,0,0,0,1,1,0,1,0,0,0, 0,1,0,1,0,0,0,1,1,1,0,0,1,1,1,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, } ;
0x00が壁、0x01が道に相当する。
グローバル変数は.dataセクションに格納されているので、PEヘッダからある程度範囲がわかる&データもサンプルコードに則るだろうということで検索してやると発見できる。
全て0x01(道)で埋め立てpassの場所に移動すると3D_GameAAAと出る。しかしこれは答えではなかった。
3D_GameAAA_is_NOT_password_Enjoy_HackingAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
よく見るとサンプルコードにないデータとして0x02が入っている。試してみるとこれはテクスチャは壁に相当する(道が無いようにみえる)のに通ることは可能というものだった。つまり、一見ゴールにたどり着くのは不可能に見えるが、実際は0x02の場所を通れるので正規の手段でクリアできる。
あとは楽しく迷路をたどるとフラグを得た。
- bin200(名前忘れた)
指定されたportにncでアクセスると文字列の入力を促される。入力すると文字が反転して表示される。80文字以上入れるとセグフォする。
与えられていたバイナリをIDAに突っ込んで読んでいくと、反転処理はreverse80()で行っていることが分かる。また、flag()といういかにもな関数があって、
これに飛ばしてやればフラグが取れるらしいことも分かった。
reverse80()はreverse()から呼ばれているのだが、reverse()で文字数のチェックを行い、strlength = max(strlength, 80)的処理を行う。
ebp+var_54がループカウンタ、ebp+arg_4がstrlengthに相当する。
reverse(char *input, int strlength){ char tmpbuff[0x50]; int i; for(i=0; i<=strlength; i++){ tmpbuff[i] = input[i]; } for(i=0; i<=strlength; i++){ input[i] = tmpbuff[strlength-i]; } }
Cに起こすとこんな感じ。i
上記を参考に"x"*76 + "\x80\x8e\x04\x08" + "??"的な文字列を投げてやると良い。(\x80\x8e\x04\x08はflag()の関数アドレスのリトルエンディアン)
さて、ebpを上書きする"??"はmain()関数のesp/ebpから逆算出来るはずなのだが、何度やっても計算が合わなかった。仕方ないのでx100回総当りするとフラグが送られてきた。
- その他400(solve the steganography)
かなり近いところまで行っていたのだけども解ききれなかった。
まず、謎の透明(に見える)pngを問題から得る。この時SECCONページだとビミョーにQRコードの断片が見えるのが有情。
import Image img = Image.open("stegano.png") out = Image.new("RGB", (75, 75)) for i in range(75): for j in range(75): if img.getpixel((i, j))[3] == 0: out.putpixel((i, j),(255, 255, 255)) out.save("out3.png")
その他の色要素であるRGBについてもQRコードの情報が埋まっていると踏んだ。
変化を見やすくするためにピクセルの色を3乗してみた。
import Image img = Image.open("stegano.png") out = Image.new("RGB", (75, 75)) for i in range(75): for j in range(75): c = img.getpixel((i, j))[0] out.putpixel((i, j),(c**3/(255**2), c**3/(255**2), c**3/(255**2))) out.save("out0.png")
複数のQRコードの断片が重なっているようにみえる。ここで、何枚かの画像が重なっているにも関わらず、ある程度模様が認識できるのは、それぞれの状態が独立しているためだと考えた。つまり、端的にいうと色情報のnビット目が立っているかどうかで画像を分けた。
import Image img = Image.open("stegano.png") for l in range(4): for k in range(8): out = Image.new("RGB", (75, 75)) for i in range(75): for j in range(75): c = img.getpixel((i, j))[l] if c & (1<<k) == 0: out.putpixel((i, j),(255, 255, 255)) out.save("test%d/out%d-%d.png" %(l, l, k))
これをつなげて文字を消すとこうなる。
読み込むとbase64でエンコードされた文字列が出てきたので複合。それをuuencodeで再度複合…してやったのだがここで上手く行っていなかったらしく正しいデータが得られなかった。rot13にも気付いて、最終的に得られた文字列は
I SAY HEY-YEAH-YEA-EAH, HEY YEA YEA
I SAY HEY! WHA|D L )HUZr9CL8%9r!U Y?24("HEYYEYAAEYAAAEYAEYAA")
これだった。悔やまれる。