TrendMicro主催の初のコンテスト。superflipは15位。Windowsバイナリとプログラミングが多くて私好みだった。
Analysis-offensive 100
サインアップとログインするだけのサイト。ログインするとuserというセッションキーが設定される。
掴み所が分からなかったけど、セッションキーが現在の秒数から決まっていて同時にログインすると同じセッションキーになってしまう。 毎分1秒にUserID 1がログインしているので、同じタイミングでログインすれば良いだけ。
その発想は無かった……という感じなのだけど、何となくリアリティを感じる。実際にあった問題なのだろうか。
Welcome TMCTF{ad70c0b2a1d07792a310b2349cab8890}
Analysis-offensive 200
ウイルスを10000000匹倒さないといけないAndroidアプリ。
dex2jarとjd-guiで解析したところ、あちこちで少しずつ文字列を組み立て、Base64で復号してフラグとして表示しているらしい。
9uZ2dnZ3JhdHNfMTBNQ2xpY2tz
かな? → \x00\x0fnggggrats_10MClicks
。惜しいので前半を推測して投稿したら正解した。
TMCTF{Congrats_10MClicks}
Analysis-offensive 300
占いとご意見投稿があるだけのサイト。全く分からない。
Analysis-offensive 400
ヒントが76343947ada960b6269090638f5391068daee88d
。CVE-2015-0291の修正のコミットID。Exploitがあったら試してみようと思ったけど、見つからなかったので終了。
Analysis-defensive 100
libcrypto.so.1.0.0が無くて動かない。libcrypto.so.1.0.1eをコピーしてみたけど、OPENSSL_1.0.0
が無いと言われてダメ。しょうがないので、プログラムを読んだ。鍵もIVも/tmp/...,,,...,,
で、プログラムの0x5000バイト名以降をAES-128-CBCで復号し出てきたファイルを実行。
出てきたファイルもlibcrypto.so.1.0.0が必要なのでプログラムを読んだ。自身の0x500-0x57fのMD5ハッシュを出力。何かファイルサイズ+0x500バイトとかのメモリを確保している気がするのだけど、なぜだろう?
TMCTF{ce5d8bb4d5efe86d25098bec300d6954}
Analysis-defensive 200
WindowsのRAT。PC名がTMCTF2015-PC
だと動く。http://ctfquest.trendmicro.co.jp:13106/126ac9f6149081eb0e97c2e939eaad52/top.html
に繋ぎに行く。単なる日記サイトのようだけど接続先が暗号化されて書かれている。localhost|8888
だけど、このサイト中にはctfquest.trendmicro.co.jp|19400
も書かれている。User-AgentをTMCTF2015-13106
にする必要があり、Cookieに暗号化した指示を書き込む。
色々試行錯誤して解けたので良く分かっていないけど、localhostにg1v3m3k3y=0
で繋ぎにくるので、ctfquestにg1v3m3k3y=1
で繋ぐとファイルが降ってきて、localhostからこのファイルを返すとkey.binが出力された。処理の分岐を弄って、0x4b
のところを0x54
に対応する処理に飛ばしたら、key.binを読み込んで、復号し始めた。
TMCTF{MV8zXzFfMF82XzRfNV80XyhfMg==}
Analysis-defensive 300
Poison Ivyの通信の解析。時間が無くて取り組んでいない。
Analysis-defensive 400
自己解凍書庫っぽいのに実行しても動かないから、解凍ソフトで解凍したら、Windowsのイベントログが大量に出てきた。
Analysis-others 100
壊れたPDFの復号。PDFのフォーマットを良く分かっていないが、0x78
から始まるzlib圧縮っぽい部分がいくつかあって、そのうちの一つにBase64エンコードされたJpeg画像が含まれているXMLがあり、フラグが書いてあった。
TMCTF{There is always light behind the clouds.}
Analysis-others 200
アスキーアートの動画が流れる。30時から33時くらいまで取り組んでいたのに解けなくて悔しい。ヒント曰く、アスキーアートからPEファイルを抽出できるらしい。良く見ると、0
と1
がやたらとチラチラしている。DrawTextをフックするツールとかありそうだと思って探したけど見つからなかった。デバッガのスクリプトとかで何とかなるかもと思ったけど使ったことがないので無理。しょうがないから、
00459800 33C0 XOR EAX, EAX 00459802 50 PUSH EAX 00459803 50 PUSH EAX 00459804 6A 03 PUSH 3 00459806 50 PUSH EAX 00459807 50 PUSH EAX 00459808 68 00000040 PUSH 40000000 0045980D 68 EC974500 PUSH AsciiArt.004597EC ; ASCII "TrendMicro CTF" 00459812 E8 0DC6FAFF CALL <JMP.&kernel32.CreateFileA> 00459817 50 PUSH EAX 00459818 6A 02 PUSH 2 0045981A 6A 00 PUSH 0 0045981C 6A 00 PUSH 0 0045981E 50 PUSH EAX 0045981F E8 A0C7FAFF CALL <JMP.&kernel32.SetFilePointer> 00459824 58 POP EAX 00459825 50 PUSH EAX 00459826 6A 00 PUSH 0 00459828 68 F0BF4500 PUSH AsciiArt.0045BFF0 0045982D 68 08290000 PUSH 2908 00459832 FF75 E4 PUSH DWORD PTR SS:[EBP-1C] 00459835 50 PUSH EAX 00459836 E8 D979FAFF CALL <JMP.&kernel32.WriteFile> 0045983B E8 CCC5FAFF CALL <JMP.&kernel32.CloseHandle> 00459840 8BC6 MOV EAX, ESI 00459842 68 F1914500 PUSH AsciiArt.004591F1 00459847 ^ E9 B098FAFF JMP AsciiArt.004030FC
こんな処理をプログラム中に埋め込んで、ここに飛ばして抜き取った。同じフレームが何回か出力されているので重複を弾いてみたけど、なぜか1枚足りない。アスキーアートの展開ルーチンを解析するべきだったか……。
Analysis-others 300
Windowsバイナリ。時間が……。
Analysis-others 400
Windowsバイナリ。難読化されているらしい。
Analysis-others 500
Windows DLLファイル。知らんがな(´・ω・`)
Cryptography 100
256bit RSA。公開鍵がおかしい、失われた1bitは?とか書かれていて、公開鍵が偶数だった。最後のビットを1にしてmsieveにかけたら大きな素数が2個出てきた。あとは以前にやったのと同じ手順で解けた。
TMCTF{$@!zbo4+qt9=5}
Cryptography 200
CBCモードのAESで、鍵の2文字、IV全部、暗号文の前半1ブロックの9割くらいが潰されている。平文は分かる。この状態でIVを答えよという問題。CBCなので後半のブロックの復号にはIVは要らない。鍵の2文字を変えながら後半を復号して、前半の潰されていない部分とxorを取って、平文と一致すれば良い。これで鍵が分かる。あとは、後半の復号結果と平文から前半の暗号文が分かるので、これと平文の暗号結果のxorがIV。鍵の探索が上手く行かないとおもったら、鍵は英文字だけじゃなかったのか。
from Crypto.Cipher import AES KEY = "5d6I9pfR7C1JQt" for a in [chr(x) for x in range(256)]:#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": for b in [chr(x) for x in range(256)]:#"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789": aes = AES.new(KEY+a+b, AES.MODE_ECB) P = aes.decrypt("307df037c689300bbf2812ff89bc0b49".decode("hex")) if ord(P[-2])^0x9e==ord("S") and ord(P[-1])^0xc3==ord("!"): print KEY+a+b KEY = "5d6I9pfR7C1JQt7$" aes = AES.new(KEY, AES.MODE_ECB) P1 = "rotected by AES!" P2 = aes.decrypt("307df037c689300bbf2812ff89bc0b49".decode("hex")) C = "" for i in range(16): C += chr(ord(P1[i])^ord(P2[i])) print C.encode("hex") P = aes.decrypt(C) IV = "" for i in range(16): IV += chr(ord("The message is p"[i])^ord(P[i])) print IV
TMCTF{rVFvN9KLeYr6}
Cryptography 300
サンプルとして良く使われている暗号鍵を答えていくっぽい。2問目で挫折。
Cryptography 400
Outlookの暗号メール? 分からん。
Cryptography 500
解けたけど、意味の分からない問題。アルファベットで構成される2個の異なる文字列をBase64で符号化した場合に、同じ文字が同じ位置に表れることがある。中には出てこない文字もあるので、それを答えよと。
例えば、xABCDEFG
とyABCDEFG
のような文字列を考えれば、この問題は「アルファベットをBase64で符号化した場合に表れる文字列を答えよ」という問題と同じ。Base64は3文字を4文字に対応付けるので、アルファベット3文字くらいなら全列挙できるので何も難しくない。普通、同じ長さの文字列a
とb
が異なるといった場合には、a[i]
とb[i]
が異なるようなi
が少なくとも1個は存在するという意味だけど、全てのi
についてa[i]≠b[i]
が異なるという意味だろうか……とも思ったが、サンプルがそうではない。
from hashlib import * A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" B = "+/=0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" S = set() for a in A: for x in a.encode("base64"): S.add(x) for b in A: for x in (a+b).encode("base64"): S.add(x) for c in A: for x in (a+b+c).encode("base64"): S.add(x) ans = "".join(b for b in B if b not in S) print "TMCTF{%s}" % sha1(ans).hexdigest()
で、これを投稿したとはずだけどダメで、色々試していたけど、あとで投稿し直したら通った。試している時点では問題の修正(文字数が3で割りきれない場合の考慮漏れ?)が入った後だったのだけど……。謎。
SHA1の計算でポカをした可能性もある。TMCTF{+/7f}
がフラグではダメだったのだろうか。
TMCTF{67569763552b4e9b8ac950be0d25f446f8470c60}
Programming 100
色の違う正方形をクリックするゲーム。途中まで人力でやっていたけど、諦めた。最後のほうは1pxの点が並んでいるので諦めて正解だった。
from urllib import * from re import * import Image from collections import * url = "http://ctfquest.trendmicro.co.jp:43210/click_on_the_different_color" while True: html = urlopen(url).read() print html m = search(r"img/([0-9a-f]{44,44})\.png", html) hash = m.group(1) png = urlopen("http://ctfquest.trendmicro.co.jp:43210/img/%s.png"%hash).read() open("%s.png"%hash, "wb").write(png) img = Image.open("%s.png"%hash) w = img.size[1] h = img.size[0] d = img.getdata() C = defaultdict(int) for i in range(w*h): if d[i]!=(255,255,255): C[d[i]] += 1 mc = 0 mn = w*h for c in C: if C[c]<mn: mn = C[c] mc = c for i in range(w*h): if d[i]==mc: x = i%w y = i/w break print "x,y:",x,y url = "http://ctfquest.trendmicro.co.jp:43210/%s?x=%s&y=%s"%(hash,x,y)
The flag is TMCTF{U must have R0807 3Y3s!}
Programming 200
与えられた数式に回答していく、計算は四則演算だけなのでevalに突っ込めば良いけど、数値にカンマが入ったり、ローマ数字になったり、英語表記(one hundred ninety nine thousand, fifty nineとか)になったりする。最初は真面目に解析するのが面倒で、プログラムが解析できなかったら人間に投げていたけど、タイムアウトになるので結局ちゃんとプログラムで実装した。
from socket import * from time import * s = socket(AF_INET, SOCK_STREAM) s.connect(("ctfquest.trendmicro.co.jp", 51740)) T = [""]*1001 T[0] = "zero" T[1] = "one" T[2] = "two" T[3] = "three" T[4] = "four" T[5] = "five" T[6] = "six" T[7] = "seven" T[8] = "eight" T[9] = "nine" T[10] = "ten" T[11] = "eleven" T[12] = "twelve" T[13] = "thirteen" T[14] = "fourteen" T[15] = "fifteen" T[16] = "sixteen" T[17] = "seventeen" T[18] = "eighteen" T[19] = "nineteen" T[20] = "twenty" T[30] = "thirty" T[40] = "forty" T[50] = "fifty" T[60] = "sixty" T[70] = "seventy" T[80] = "eighty" T[90] = "ninety" for i in range(20,100,10): for j in range(1,10): T[i+j] = T[i]+" "+T[j] for i in range(100,1001): T[i] = T[i/100] + " hundred" if i%100!=0: T[i] += " "+T[i%100] def fix(t): for i in range(len(T)): if T[i]==t: return str(i) if any((c in t) for c in "IVXLCDM"): t = t.replace("IV", "IIII") t = t.replace("IX", "VIIII") t = t.replace("XL", "XXXX") t = t.replace("XC", "LXXXX") t = t.replace("CD", "CCCC") t = t.replace("CM", "DCCCC") return str( t.count("I") + t.count("V")*5 + t.count("X")*10 + t.count("L")*50 + t.count("C")*100 + t.count("D")*500 + t.count("M")*1000) if len(t)==1: return t t = t.replace(",","") if all(c in "0123456789" for c in t): return t if " billion " in t: a = t.split(" billion ") return str(int(fix(a[0]))*1000000000+int(fix(a[1]))) if " million " in t: a = t.split(" million ") return str(int(fix(a[0]))*1000000+int(fix(a[1]))) if " thousand " in t: a = t.split(" thousand ") return str(int(fix(a[0]))*1000+int(fix(a[1]))) print t+"?" return raw_input() def solve(q): X = q.split() T = [] for i in range(len(X)): if 0<i and (X[i-1][-1].isalpha() or X[i-1][-1]==",") and X[i][0].isalpha(): T[-1] += " "+X[i] else: T += [X[i]] return eval("".join(fix(t) for t in T)) c = 0 while True: q = s.recv(1000) print c,q a = solve(q[:-2]) s.send(str(a)+"\n") c += 1
The flag is TMCTF{U D1D 17!}
Programming 300
迷路を解く問題。体力に制限があり、途中に通過しなければならないチェックポイントがいくつかと、通過すると体力を回復できるエネルギードリンクがある。
まじめに解くとなると面倒そうだけど、一番近いチェックポイントに向かい、エネルギードリンクは無視するだけで、体力が足りた。Conglaturations! Your final energy is 1104.
def BT(B, W, H, px, py, f): # if f and B[py][px]=="G" or not f and B[py][px] in "EC": if f and B[py][px]=="G" or not f and B[py][px] in "C": return "",(px,py) c = B[py][px] B[py][px] = "X" for d in range(4): tx = px + [0,0,1,-1][d] ty = py + [-1,1,0,0][d] if B[ty][tx]!="#" and B[ty][tx]!="X": a,g = BT(B, W, H, tx, ty, f) if g != (): B[py][px] = c return "UDRL"[d]+a, g B[py][px] = c return "",() def solve(B, W, H): c = 0 for y in range(H): for x in range(W): if B[y][x]=="S": px,py = x,y #if B[y][x]=="E" or B[y][x]=="C": if B[y][x]=="C": c += 1 ans = "" while c>0: a,g = BT(B, W, H, px, py, False) ans += a px,py = g B[py][px] = "." c -= 1 a,g = BT(B, W, H, px, py, True) ans += a return ans for i in range(1001): W,H,N = map(int, raw_input().split()) B = [""]*H for y in range(H): B[y] = list(raw_input()) print solve(B, W, H)
TMCTF{AMAZING_1001_MAZES_lool}
Programming 400
青線の2台のサーバーのIPアドレスが交換できる。AとPのみIPアドレスを交換して、残りはそのままにする最小手順を答えよ。という問題。
一度に動かせるサーバーは2台なので、個々のサーバーの目的の位置との距離の和が残りの手数の2倍より大きければ打ち切りという枝刈りと、元の場所には戻らないという枝刈りで、サーバー台数12台までは解けた。PythonからC++にしたら14台も解けた。あとはそこから法則を探して制約(↓のプログラムのBTの先頭)を加えたけど、これもうちょっと頑張れば一般解になったのではw
#include <vector> #include <algorithm> #include <iostream> #include <string> using namespace std; int N; vector<vector<int>> E; vector<vector<int>> D; vector<int> S; vector<int> G; vector<int> GP; string ans; bool BT(int p, int c, int d, int t) { if (c==0) return S==G; if (d==N/2+(N/2%2==0?1:0)-2 && p!=2 || d==N/2+(N/2%2==0?1:0) && p!=0 || d==N/2+(N/2%2==0?1:0)+2 && p!=1 || d==N/2+(N/2%2==0?1:0)+4 && p!=2 || c==2 && p!=1 || c==N/2+(N/2%2==0?1:0) && p!=N-1) return false; int f = 0; for (int i=0; i<N; i++) f += D[i][GP[S[i]]]; if (f>c*2) return false; for (int e: E[p]) if (e != t) { swap(S[e], S[p]); ans += char('A'+e); if (BT(e, c-1, d+1, p)) return true; swap(S[e], S[p]); ans.pop_back(); } return false; } int main() { N = 16; for (int i=0; i<N; i++) { vector<int> t; for (int j=i%(N/2)-1; j<=i%(N/2)+1; j++) if (0<=j && j<N/2) t.push_back(i<N/2 ? j+N/2 : j); E.push_back(t); } D = vector<vector<int>>(N, vector<int>(N, 9999)); for (int i=0; i<N; i++) { D[i][i] = 0; for (int e: E[i]) D[i][e] = 1; } for (int i=0; i<N; i++) for (int j=0; j<N; j++) for (int k=0; k<N; k++) D[j][k] = min(D[j][k], D[j][i]+D[i][k]); for (int i=0; i<N; i++) S.push_back(i); G = S; swap(G[0], G[N-1]); GP = vector<int>(N); for (int i=0; i<N; i++) GP[G[i]] = i; for (int i=1; ; i+=2) { cout<<i<<endl; if (BT(N-1, i, 0, -1)) { cout<<ans<<endl; break; } } return 0; }
TMCTF{HOFMDKCJAIBKCJBKDMELCJBKDMELDMFOGNELDMFOGNFOHPGNFMDKBIA}
Programming 500
ブラックジャックでヒットとスタンドそれぞれの勝率を答えよという問題。現在の手札をメモしておけば現実的な時間で解ける。ディラーのバースト確率が書いてあるサイトを見ながらデバッグした。Aが2枚以上あった場合に、1枚を1、もう1枚を11にするというのが抜けていて悩んだ。
C = [0]+[8]*9+[32] P = [1,2] D = [3] for p in P: C[p] -= 1 for d in D: C[d] -= 1 def total(v): n1 = v.count(1) a = sum(v) + n1*10 for i in range(n1): if a-i*10<=21: return a-i*10 return a-n1*10 def dealer(): global D if total(D)>21: return 1. if total(D)>=17: return 1. if total(P)>total(D) else 0. p = 0. n = 0 for i in range(1,11): if C[i]>0: D += [i] C[i] -= 1 p += dealer() * (C[i]+1) n += C[i]+1 D.pop() C[i] += 1 return p/n memo = {} def player(d): global P global memo if total(P)>21: return 0. t = tuple(sorted(P)) if t in memo: return memo[t] ph = dealer() ps = 0. n = 0 for i in range(1,11): if d<=3: print " "*d,i if C[i]>0: P += [i] C[i] -= 1 ps += player(d+1) * (C[i]+1) n += C[i]+1 P.pop() C[i] += 1 memo[t] = max(ph, ps/n) return memo[t] p = player(0) d = dealer() print p print d print "TMCTF{HIT:%.4f:STAND:%.4f}"%(p,d)
TMCTF{HIT:0.5094:STAND:0.3770}
Miscellaneous 100
クロスワード。「the company hosting TMCTF」に関する問題がいっぱいあるw
Miscellaneous 200
学習型のジャンケンプログラムに30回連続で勝てという問題。プロコンっぽいのに解けなかった。ランダム要素も入っているのでどうしろと……。
Miscellaneous 300
CAPTCHA 500問。人力で250問目まで解いたけど、そこで間違えて心が折れた。人力は止めよう、プログラムを書こう。
普通のCAPTCHAと違ってミスが許されないのは辛いけど、位置も文字サイズも完全に固定なので、難しくはない。最初はときどきミスしていたけど、ノイズ部分を周囲の色で埋めたり、位置を微調整したり、サンプリングする画素数を増やしたりしたら通った。
from urllib import * from urllib2 import * from cookielib import * from re import * from PIL import Image from collections import * o = build_opener(HTTPCookieProcessor(CookieJar())) token = search(r'"([0-9a-f]{128,128})"', o.open("http://ctfquest.trendmicro.co.jp:8080/acf2e28903f698eda9bdefc043b6edc3/signin").read()).group(1) o.open("http://ctfquest.trendmicro.co.jp:8080/acf2e28903f698eda9bdefc043b6edc3/signin", urlencode({"username":"kusano_k", "password":"UBaAAYz5ZXZg", "fuel_csrf_token":token})).read() T = {} A = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789" for a in A: f = a if a.islower(): f = "_"+f T[a] = Image.open("temp\\%s.png" % f) for y in range(T[a].size[1]): for x in range(1, T[a].size[0]-1): if T[a].getpixel((x,y))==(255,255,255) and T[a].getpixel((x-1,y))==(0,0,0) and T[a].getpixel((x+1,y))==(0,0,0): T[a].putpixel((x,y), (0,0,0)) print "load" for i in range(500): token = search(r'"([0-9a-f]{128,128})"', o.open("http://ctfquest.trendmicro.co.jp:8080/acf2e28903f698eda9bdefc043b6edc3/challenge").read()).group(1) while True: try: d = o.open("http://ctfquest.trendmicro.co.jp:8080/acf2e28903f698eda9bdefc043b6edc3/image").read() except: continue break open("image\\%04d.png"%i, "wb").write(d) img = Image.open("image\\%04d.png"%i).convert("RGB") C = defaultdict(int) for y in range(img.size[1]): for x in range(img.size[0]): C[img.getpixel((x,y))] += 1 R = sorted([C[c] for c in C])[::-1] for y in range(img.size[1]): for x in range(img.size[0]): if C[img.getpixel((x,y))]==R[1]: img.putpixel((x,y), (0,0,0)) else: img.putpixel((x,y), (255,255,255)) for y in range(img.size[1]): for x in range(1, img.size[0]-1): if img.getpixel((x,y))==(255,255,255) and img.getpixel((x-1,y))==(0,0,0) and img.getpixel((x+1,y))==(0,0,0): img.putpixel((x,y), (0,0,0)) img.save("image\\%04d.png"%i) # img.crop((12, 0, 12+65, 300)).save("image\\%04d.png"%i) ans = "" for i in range(4): cx = [12, 12+66, 12+133, 12+200][i] mc = 0 for a in A: c = 0 for y in range(90,195,2): for x in range(0,65,2): if img.getpixel((cx+x,y))==T[a].getpixel((x,y)): c += 1 if c>mc: mc = c ma = a ans += ma print ans print o.open("http://ctfquest.trendmicro.co.jp:8080/acf2e28903f698eda9bdefc043b6edc3/challenge", urlencode({"captcha":ans, "fuel_csrf_token":token})).read()
TMCTF{217dae3fd34cee799658d4552e37827f}
Miscellaneous 400
解いてない。
Miscellaneous 500
解いてない。