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コードの断片が見えるのが有情。

Pythonでalpha値を元に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")
これだった。悔やまれる。