saltcandy123

CSAW CTF 2013 Qualification Round に参加しました

全体的な話

CSAW CTF はオンラインで年一回だけ開催される入門レベルの CTF コンテストです。 CTF 初心者である私は、時間制限があるコンテストも世界規模のコンテストも初めてだったので、わくわくどきどきしながらエントリーすることにしました。今回の問題構成は、次の通りでした。数字は配点です。

  • Trivia - 50, 50, 50, 50, 50
  • Recon - 100, 100, 100, 100, 100, 100, 100, 100
  • Web - 100, 200, 300, 400, 400
  • Reversing - 100, 100, 150, 200, 300, 400, 500, 500
  • Exploitation - 100, 200, 300, 400, 400, 500
  • Miscellaneous - 50, 50, 100, 200, 300
  • Crypto - 100, 300, 500

入門レベルなら、と思って参加したのですが、かなり難しく感じました。しかしながら、72時間もあったので、少しずつ問題を進めていき、結果的に、2150点を獲得することができました。リバースエンジニアリングとかその辺がまったくできないので、精進して来年はもっと得点したい所存です!!

解けた問題の Writeup とか感想とか

全然記録をとっておらず、うろ覚えで書いてあるところもあるので、多少間違っている可能性があります。

Trivia

Trivia は知識問題で、やるだけ、ググるだけの問題です。英語力が悲惨だったため、3問目が少々苦戦しましたが、無事全問解けました。

Recon

Recon はソーシャルエンジニアリングのような問題で、設問は大会関係者の名前の検索結果などから始まります。

Alexander Taylor (100 pt)

問題: https://www.google.com/search?&q=Alexander+Taylor

コンテスト終了数時間前にヒントが出てきて、解くことができました。

ヒントは PNG 画像を探すことを示唆する内容でした。そこで、コンテストの審査員ページを見てみると、 Alexander Taylor の PNG 画像が掲載されていたので、保存し、とりあえずテキストエディタで開いてみると、チャンクを調べることを示唆する内容が書かれていました。そこで、 TweekPNG を用いてチャンクを解析したところ、未定義のチャンク xORkkTXt があることが判明しました。この2つをテキストエディタで探してみると、 xORk の後には CSAW というデータが、 kTXt とその次のチャンクとの間にはある程度の長さのデータが含まれていました。 xORkkTXt がそれぞれ、 XOR とテキストデータを示唆していると考えると、 kTXt 直後のデータを CSAW で XOR すれば良さそうです。

#!/usr/bin/env python3
csaw = b'CSAW'
with open('ataylor.png', 'rb') as f:
    filedata = f.read()
    ktxt_pos = filedata.find(b'kTXt')
    data = filedata[ktxt_pos+4:ktxt_pos+100]
for i, c in enumerate(data):
    print(chr(c^csaw[i%4]), end='')

あとはこんなスクリプトを書いて実行すると、フラグが現れました。ぱちぱち。これで100ポイント。

key{SPECIFICATIONS SUBJECT TO CHANGE WITHOUT NOTICE}

シーサーが示唆。

historypeats (100 pt)

問題: https://www.google.com/search?&q=historypeats

昨年の Writeup を参考に historypeats を NameChk で調べてみると、reddit のアカウントを発見しました。そこに、フラグが書かれていたのでドヤ顔でサブミットしたら、違うと言われました…!つらすぎて何度もサブミットしたのですが、例外なくリジェクトされました。どうやら、参加者のいたずらのようです。

これでやる気を失って、無気力に探していたら、案外簡単に見つかってしまいました。

  1. historypeats をググって Twitter へ。
  2. Twitter のプロフィールのリンクから、ブログへ。
  3. ブログのコンタクトページから GitHub へ。
  4. GitHub での最新のコミットの diff へ。
  5. おやっ?これはフラグでは…!
key{whatDidtheF0xSay?}

Brandon Edwards (100 pt)

問題: https://www.google.com/search?&q=Brandon+Edwards historypeats ができた後、すぐにやったら簡単にできました。

  1. そのまま検索するとフットボールの人が出てくるので、 "football" を NOT にかけて検索。
  2. 目的の Bradon Edwards の Twitter アカウントが見つかるので、スクリーンネームで検索。
  3. GitHub アカウントが見つかるので、所属している Organization のアカウントへ。
  4. そのアカウントのプロフィールのリンクへ進む。
  5. 進んだページの HTML にコメントとしてフラグが…!
<!-- b.edwards csaw key{a959962111ea3fed179eb044d5b80407} -->

Theodore Reed (100 pt)

問題: http://prosauce.org/

  1. 設問である http://prosauce.org/ にアクセス。
  2. Projects ページへ。
  3. Video リンクへ。 YouTube のページが出てきます。
  4. コメントを表示します。
  5. フラグ!
If you're playing CSAWCTF2013 and looking for a flag try: shmoonconrocksglhfwithcsaw

Web

得点源のはずでした。

Guess Harder (100 pt)

問題は、ログインページでした。リンクなどは何もないので、頑張ってログインすれば、フラグが出てきそうな感じでした。

パスワードしか入力するものがないので、簡単な SQL インジェクションの問題かなと思って、定石をいくつか試したのですが、うまくいきませんでした。そこで、 Burp を使ってどんなリクエストとレスポンスが返ってきているのか見ることにしました。すると、素晴らしい Cookie が…!

admin=false

falsetrue に変更してアクセスしたらフラグが出てきました!

Nevernote (200 pt)

めんどうなので、問題の説明はあとで書きます。

「 Admin は送られてきたメッセージのリンクは全部見るよ」というヒントが出たのですが、まさか本当にリンクにアクセスしてくれるわけはないよな、と思って少しひねくれたことを試していましたが、どれもうまくいきませんでした。そんなすごいことをしてくれることはない、ということを証明するために、サーバを立てて、そのサーバへのリンクを送りつけたら、本当にアクセスしてきました!!!びっくり!!!結局のところ、次のようにすればよかったわけです。

  1. サーバを立てて、アクセス情報を全て記録するようにする。
  2. サーバへのリンクを Admin に送りつける。
  3. Admin のアクセス記録のリファラから、メッセージ表示ページへの URI を得る。
  4. メッセージ表示ページの下に Admin のノート一覧があるはずなので、ノートを見てみる。
  5. ノートにフラグが…!

これにてフラグをゲットしました。なお、 Heroku を使うと良い感じでした。

WidgetCorp (400 pt)

めんどうなので、問題の説明はあとで書きます。

いろいろと入力できるところがあったのですが、 SQL インジェクションできそうなところはなさそうだったので、 Cookie をごにょごにょして admin 権限でログインできないか、なんてことを考えました。そこで Cookie を見てみると、PHPSESSID widget_tracker widget_validate の3種類がありました。 PHPSESSID はおなじみのものですが、残りの2つは見たことがないものです。とりあえず、 widget_validate の値をでたらめに変更してみたところ、「クラックを検知しました」と言わました。つらい。 widget_tracker の値をよく見てみると、 base64 でエンコードされているようなので、デコードしてみると、次のようなテキストデータでした。

a:2:{i:0;i:2034;i:1;i:2038;}

よく見てみると、作成したウィジェットの ID がデータとして埋め込まれているようでした。これを使えば何かできそうな予感がしたので、 widget_validate の仕組みの解決に挑みました。この値は128桁の16進数であったことから、 SHA512 の可能性が高そうだと思いました。とりあえず、先ほどデコードした widget_tracker の値を SHA512 にかけてみたら見事一致しました。これで「クラックを検知しました」と言われることなく、実験できそうです。

ここまで済んだので widget_tracker の値をいじって動作を実験してみることにしました。すこし実験してみてわかったのですが、このデータは :1:{i:0;i:2034;} ではウィジェット一覧に ID:2034 のデータが表示されるのに、 a:1:{i:0;i:2034} では表示されないというくらい、柔軟性のないものでした。

なので、このデータ構造の仕様を知る必要がありました。どう検索していいか分からなかったので、思い切ってデータそのものをググってみたところ、同じようなデータがヒットしました。そのデータをみたところ、 i は整数型の識別であることや、a は配列で a:N:{i:0;X:Y;i:1; ...; i:N-1;X:Y;} という形式であることが分かりました。また、文字列型は ss:N:"string" (N は文字数) という形式であることも分かりました。

ここまで分かったので、 a:1:{i:0;i:2034;} の代わりに a:1:{i:0;s:4:"2034";} と値を変えて同じように動作するか実験してみたところ、予想通り、 ID: 2034 のデータが表示されました。そうなれば、 SQL インジェクションを意識せざるを得ません。そこで、 a:1:{i:0;s:10:"2034 ; -- ";} というデータを送ったところ、ご丁寧なこと、エラーメッセージが返ってきました。パースエラーだそうです。そのメッセージにしたがって、括弧をつけて a:1:{i:0;s:10:"2034); -- ";} というデータを送ったら、エラーが表示されなくなりました。ここまでくれば、こちらのものです! 素晴らしい SQL インジェクションの解説 のように、 union all を使ってごにょごにょすれば、もうあっという間!実際、 flag テーブルの flag カラムにフラグがありました。

テーブル名とカラム名は少しひねるべきだと思います!!

Reversing

リバースエンジニアリングの分野ですね!!

DotNet (100 pt)

タイトル通り、 .NET な EXE が問題でした。とりあえず動かしてみると、パスコードの入力が求められ、でたらめな値を入力してみると、「残念!さやかちゃんでした」と表示されました。

ILSpy というツールを使って、 C# なコードに変換してみると、こんなコードが出てきました。

string value = Console.ReadLine();
long num = Convert.ToInt64(value);
long num2 = 53129566096L;
long num3 = 65535655351L;
if ((num ^ num2) == num3) {
    ....

(num ^ num2) == num3(num2 ^ num3) == num と等価なはずなので、左辺を計算すると、 13371337255 となります。これを入力してみると、フラグが表示されました。

flag{I'll create a GUI interface using visual basic...see if I can track an IP address.}

CSAW Reversing 2013 1 (100 pt)

問題は EXE ファイルでした。これを動かしてみると、よく分からない文字列が表示されました。「リバースエンジニアリングとかよくわからないけれども、頑張って OllyDbg とか IDA とか使ってフラグを見つけるぞ!」と意気込んで、デバッガに読み込ませました。とりあえず、ブレークポイントとか何も付けないで動かしてみたら、なぜかフラグが表示されました。こわい。

flag{this1isprettyeasy:)}

Exploitation

正直言って Exploitation がどんな分野なのか分からないのですが、ソフトウェアをいじめて本来動作しないはずのことをさせる、という分野なのでしょうか?

Exploitation 1 (100 pt)

問題は、 netcat コマンドと exploit1 というバイナリと exploit1.c という C のソースの断片でした。この分野はよくわからないので、指示通りに netcat で通信してみると、何か入力を求められました。とりあえず、ものすごい量の a を送ってみると楽しそうだったので、そうしてみたところ、フラグが出てきました。こわい。

Misc

雑多な問題集です。たぶん。

Networking 1 (50 pt)

問題は、パケットキャプチャファイル networking.pcap です。 Wireshark で開いてみると、 Telnet で通信しているようなので、通信しているデータを順に見ていくとフラグが見つかりました。

flag{d316759c281bf925d600be698a4973d5}

Networking 2 (50 pt)

問題は、前の問題と同じパケットキャプチャファイルと、よく分からないファイル networking.pcap.process です。よく分からないので、とりあえずテキストエディタで調べてみたら、フラグが書いてありました。こわい。

flag{f9b43c9e9c05be5e08ea163007af5144}.exe

Black & White (100 pt)

問題は、 PNG 画像です。開いてみると何も面白くない白いだけの画像でした。どのくらい白いのか気になったので、お絵かきツールで調べてみたところ、 RGB が #ffffff の部分と #fefefe の部分があることが分かりました。そこで、 #ffffff 以外の部分を黒く染めてみたところ、フラグが浮かび上がってきました。

問題の画像の #ffffff を黒く染めた画像。 key{forensics_is_fun} と書かれている。

Life (300 pt)

問題は、 netcat コマンドでした。言われるがままに netcat してみると、"Round: 1 Generations: 36" のようなテキストの下に、 # で書かれた四角の中に * が散らばっているようなテキストがくっついたものが返ってきました。それから数秒すると、 "Timed out" と表示され、コネクションが閉じられます。タイトルや、返ってきたデータから察すると、ライフゲームをシミュレートすることが求められているようでした。

とりあえず、指定された世代だけライフゲームをシミュレートして、結果をサーバに送りつけると、 Round 2 として同じようなデータが返ってきました。どうやらこの方針は正しいようなので、続けて同様のやりとりをするスクリプトを書きました。

#!/usr/bin/env python3

import telnetlib
import time

def parse(data):
    data = data.split('\n')
    gen = int(data[1].split()[3])
    cells = list()
    for l in data[3:-2]:
        cells.append(list())
        for c in l.strip().strip('#'):
            if c == '*':
                cells[-1].append(True)
            else:
                cells[-1].append(False)
    return gen, cells

def simulate(gen, cells):
    height = len(cells)
    width = len(cells[0])
    for i in range(gen):
        new = list()
        for x in range(height):
            new.append(list())
            for y in range(width):
                count = 0
                for dx in (-1, 0, 1):
                    for dy in (-1, 0, 1):
                        if dx == dy == 0:
                            continue
                        x_n, y_n = x+dx, y+dy
                        if x_n < 0 or y_n < 0 or height<=x_n or width<=y_n:
                            continue
                        if cells[x_n][y_n]:
                            count += 1
                if cells[x][y]:
                    if 1 < count < 4:
                        new[x].append(True)
                    else:
                        new[x].append(False)
                else:
                    if count == 3:
                        new[x].append(True)
                    else:
                        new[x].append(False)
        cells = new
    return cells

def generate_form(cells):
    width = len(cells[0])
    res = '#'*(width+2)+'\n'
    for line in cells:
        res += '#'
        for c in line:
            if c:
                res += '*'
            else:
                res += ' '
        res += '#\n'
    res += '#'*(width+2)+'\n'
    return res

if __name__ == '__main__':
    tn = telnetlib.Telnet('128.238.66.216', 45678)
    while True:
        time.sleep(1)
        data = tn.read_very_eager().decode()
        print(data)                          # 返ってきたデータを表示
        gen, cells = parse(data)             # データをパース
        cells = simulate(gen, cells)
        form = generate_form(cells)
        print(form)                          # 送るデータを表示
        tn.write(form.encode())
    tn.close()

Round 100 くらいまでいったあと、フラグが表示され、データのパースする箇所でエラーが起きてスクリプトが終了しました。

Crypto

暗号系の問題のはずですが、何をおっしゃっているのか全然分からなくてこわい…。