この大会は2023/11/4 16:00(JST)~2023/11/5 16:00(JST)に開催されました。
今回もチームで参戦。結果は552点で560チーム中51位でした。
自分で解けた問題をWriteupとして書いておきます。
Sanity Check (Warmup)
Discordに入り、#readmeチャネルのメッセージを見ると、フラグが書いてあった。
TSGCTF{G3n3r@t1v3_@I_4lways_c0mes_up_w1th_b3tt3r_punchl1n3s_th@n_m3!}
Complicated Function (Crypto)
pの最小値は2**1023、最大値は2**1024。pの範囲の中央値を取りながら、qを算出し、p*qの値からNに近づくよう範囲を狭めていく。p, qの値がわかったら、通常通り復号すればよい。
#!/usr/bin/env python3 from math import isqrt, sin, ceil def q_from_p(p): return isqrt(p**2 + p * (2**512 - 6) + ceil(isqrt(p) * sin(p))) + 2**1023 with open('output.txt', 'r') as f: params = f.read().splitlines() N = int(params[0].split(' ')[-1]) c = int(params[1].split(' ')[-1]) e = 0x10001 p_min = 2**1023 p_max = 2**1024 while True: p = (p_min + p_max) // 2 q = q_from_p(p) diff = N - p * q if diff == 0: break elif diff > 0: p_min = p else: p_max = p assert p * q == N phi = (p - 1) * (q - 1) d = pow(e, -1, phi) m = pow(c, d, N) flag = m.to_bytes((m.bit_length() - 1) // 8 + 1, 'big').decode() print(flag)
TSGCTF{From which angle did you solve this, binary search or convergence of f(p)-p?}
Unique Flag (Crypto)
暗号処理の概要は以下の通り。
・p, q: 1024ビット素数 ・N = p * q ・e = 0x10001 ・flagの長さは33バイト ・flag_header: flagの先頭7バイト(='TSGCTF{') ・flag_content: flagの先頭7バイト以降末尾1バイト以前の文字列 ・flag_footer: flagの末尾1バイト(='}') ・flag_contentは同じ文字は使われていない。 ・c_list: flagの各文字のASCIIコード(byte)について、pow(byte, e, N)の配列 ・clues: c_listの末尾を除く配列の各値(x)と、先頭を除く配列の各値(y)でx * y % Nの配列 ・cluesをソートする。 ・N, e, cluesを出力
1文字ずつ、前の文字との組み合わせで条件を満たすものを探していく。
#!/usr/bin/env python3 with open('output.txt', 'r') as f: params = f.read().splitlines() N = int(params[0].split(' = ')[1]) e = int(params[1].split(' = ')[1]) clues = eval(params[2].split(' = ')[1]) chars = [bytes([c]) for c in range(32, 127)] chars.remove(b'{') chars.remove(b'}') flag_header = b'TSGCTF{' for i in range(len(flag_header) - 1): x = pow(flag_header[i], e, N) y = pow(flag_header[i + 1], e, N) c = x * y % N assert c in clues clues.remove(c) flag_contents = [b'{'] for i in range(len(flag_header), 33): tmp_flag_contents = [] for content in flag_contents: x = pow(content[-1], e, N) for code in range(33, 127): if bytes([code]) in content: continue y = pow(code, e, N) c = x * y % N if c in clues: tmp_flag_contents.append(content + bytes([code])) flag_contents = tmp_flag_contents for flag_content in flag_contents: flag = (flag_header + flag_content[1:]).decode() print(flag)
TSGCTF{OK,IsTHi5A_un1qUe-flag?XD}
Streamer (Crypto)
鍵は16バイトで、flagとXORしている。
flagは以下のような構成。
TSGCTF{XXXX...XXXX@<16バイトのbase64文字列>} →TSGCTF{XXXX...XXXX@YYYYYYYYYYYYYYYYYYYYYY==}
ブロック構成は以下のようなイメージ。
0123456789abcdef TSGCTF{XXXXXXXXX XXXXXXXXXXXXXXXX XXXXXXXXXXXXXXXX : XXXXXXXXXXXXXXXX XXXXXX@YYYYYYYYY YYYYYYYYYYYYY==}
これからわかる範囲で、XOR鍵を算出し、復号する。
0123456789abcdef TSGCTF{******n63 0 |2_+|-|******_th 1 e_saf3|******4`/ 2 _8e_as_******_4$ 3 _you_us******_a| 4 *pr0|*r******|\\| 5 cry|*+i******i$_ 6 ci|*|-|******4_0 7 ne_+i|\\******a|) 8 _ra+h3|******4|\\ 9 |_a_s+r******ph3 10 r,_but_******ins 11 3(u|2e_******e_i 12 t_us3s_******|\\/ 13 |e_r4nd******83r 14 $_re|*3******y._ 15 enjoy_h******:-) 16 -:)-:)@******Xid 17 RLICfdh******==} 18
10ブロック目の10-12バイト目は"_ci"またはそのリート文字であると推測し、復号する。さらに16ブロック目の7-9バイト目は"aha"またはそのリート文字であると推測し、復号する。
#!/usr/bin/env python3 from output import * import hashlib import base64 flag_header = b'TSGCTF{' flag_footer = b'==}' key = [-1] * 16 for i in range(len(flag_header)): key[i] = flag_header[i] ^ cipher[i] for i in range(len(flag_footer)): key[i+13] = flag_footer[i] ^ cipher[flag_length - 3 + i] flag = b'' for i in range(len(cipher)): if key[i % 16] != -1: flag += bytes([cipher[i] ^ key[i % 16]]) else: flag += b'*' print('[+] flag:', flag) ## guess1 ## key[10] = ord('_') ^ cipher[16 * 10 + 10] key[11] = ord('(') ^ cipher[16 * 10 + 11] key[12] = ord('i') ^ cipher[16 * 10 + 12] flag = b'' for i in range(len(cipher)): if key[i % 16] != -1: flag += bytes([cipher[i] ^ key[i % 16]]) else: flag += b'*' print('[+] flag:', flag) ## guess2 ## key[7] = ord('a') ^ cipher[16 * 16 + 7] key[8] = ord('h') ^ cipher[16 * 16 + 8] key[9] = ord('a') ^ cipher[16 * 16 + 9] flag = b'' for i in range(len(cipher)): if key[i % 16] != -1: flag += bytes([cipher[i] ^ key[i % 16]]) else: flag += b'*' flag_content = flag[7:-26] flag_hash = hashlib.md5(flag_content).digest() assert base64.b64encode(flag_hash) == flag[-25:-1] print('[*] flag:', flag)
復号結果は以下の通り。
[+] flag: b'TSGCTF{******n63|2_+|-|******_the_saf3|******4`/_8e_as_******_4$_you_us******_a|*pr0|*r******|\\|cry|*+i******i$_ci|*|-|******4_0ne_+i|\\******a|)_ra+h3|******4|\\|_a_s+r******ph3r,_but_******ins3(u|2e_******e_it_us3s_******|\\/|e_r4nd******83r$_re|*3******y._enjoy_h******:-)-:)-:)@******XidRLICfdh******==}' [+] flag: b'TSGCTF{***_l0n63|2_+|-|***la6_the_saf3|***+_m4`/_8e_as_***\\|g_4$_you_us***|\\|_a|*pr0|*r***3_3|\\|cry|*+i***_Thi$_ci|*|-|***i$_4_0ne_+i|\\***_|*a|)_ra+h3|***|-|4|\\|_a_s+r***_(iph3r,_but_***i$_ins3(u|2e_***ause_it_us3s_***_$4|\\/|e_r4nd***num83r$_re|*3***|)ly._enjoy_h***ha_:-)-:)-:)@***6sQXidRLICfdh***+IA==}' [*] flag: b'TSGCTF{The_l0n63|2_+|-|3_fla6_the_saf3|2_i+_m4`/_8e_as_lo|\\|g_4$_you_use_a|\\|_a|*pr0|*ria+3_3|\\|cry|*+i0n._Thi$_ci|*|-|3r_i$_4_0ne_+i|\\/|e_|*a|)_ra+h3|2_t|-|4|\\|_a_s+re4m_(iph3r,_but_it_i$_ins3(u|2e_be(ause_it_us3s_the_$4|\\/|e_r4ndom_num83r$_re|*34+3|)ly._enjoy_hahaha_:-)-:)-:)@TWp6sQXidRLICfdhOMY+IA==}'
\\のエスケープを解除する。
TSGCTF{The_l0n63|2_+|-|3_fla6_the_saf3|2_i+_m4`/_8e_as_lo|\|g_4$_you_use_a|\|_a|*pr0|*ria+3_3|\|cry|*+i0n._Thi$_ci|*|-|3r_i$_4_0ne_+i|\/|e_|*a|)_ra+h3|2_t|-|4|\|_a_s+re4m_(iph3r,_but_it_i$_ins3(u|2e_be(ause_it_us3s_the_$4|\/|e_r4ndom_num83r$_re|*34+3|)ly._enjoy_hahaha_:-)-:)-:)@TWp6sQXidRLICfdhOMY+IA==}
Survey (Cooldown)
アンケートに答えたら、フラグが表示された。
TSGCTF{Thank_you_for_your_participation!}