Trend Micro CTF Asia Pacific & Japan 2015 Write-up

TrendMicro主催の初のコンテスト。superflipは15位。Windowsバイナリとプログラミングが多くて私好みだった。

Analysis-offensive 100

サインアップとログインするだけのサイト。ログインするとuserというセッションキーが設定される。

掴み所が分からなかったけど、セッションキーが現在の秒数から決まっていて同時にログインすると同じセッションキーになってしまう。 毎分1秒にUserID 1がログインしているので、同じタイミングでログインすれば良いだけ。

その発想は無かった……という感じなのだけど、何となくリアリティを感じる。実際にあった問題なのだろうか。

Welcome TMCTF{ad70c0b2a1d07792a310b2349cab8890}

Analysis-offensive 200

ウイルスを10000000匹倒さないといけないAndroidアプリ。

f:id:kusano_k:20150927155335p:plain:w128

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に暗号化した指示を書き込む。

色々試行錯誤して解けたので良く分かっていないけど、localhostg1v3m3k3y=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ファイルを抽出できるらしい。良く見ると、01がやたらとチラチラしている。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で符号化した場合に、同じ文字が同じ位置に表れることがある。中には出てこない文字もあるので、それを答えよと。

例えば、xABCDEFGyABCDEFGのような文字列を考えれば、この問題は「アルファベットをBase64で符号化した場合に表れる文字列を答えよ」という問題と同じ。Base64は3文字を4文字に対応付けるので、アルファベット3文字くらいなら全列挙できるので何も難しくない。普通、同じ長さの文字列abが異なるといった場合には、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

f:id:kusano_k:20150927163705p:plain:w320

青線の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

解いてない。