この大会は2024/8/2 20:00(JST)~2024/8/4 20:00(JST)に開催されました。
今回もチームで参戦。結果は600点で924チーム中134位でした。
自分で解けた問題をWriteupとして書いておきます。
RULES (MISC, WARMUP)
Rulesのページにサンプルフラグが書いてあった。
TFCCTF{M4ny_ch4ng3s...m0r3_3ff0rt}
CCCCC (CRYPTO, WARMUP)
2バイト目にある"c"を除き、hexデコードする。
#!/usr/bin/env python3 with open('ccccc.txt', 'r') as f: enc = f.read() hex_enc = '' for i in range(0, len(enc), 2): hex_enc += enc[i] flag = bytes.fromhex(hex_enc).decode() print(flag)
TFCCTF{cshout_cout_ct0_cmy_cb0y_c4nd_cmy_cdog_cand_cmy_cc47}
GENETICS (CRYPTO, WARMUP)
フラグは"TFCCTF{"から始まることを前提にすると、以下のように2進数の変換をし、デコードすればよいことがわかる。
A: 00 C: 01 G: 10 T: 11
#!/usr/bin/env python3 enc = 'CCCA CACG CAAT CAAT CCCA CACG CTGT ATAC CCTT CTCT ATAC CGTA CGTA CCTT CGCT ATAT CTCA CCTT CTCA CGGA ATAC CTAT CCTT ATCA CTAT CCTT ATCA CCTT CTCA ATCA CTCA CTCA ATAA ATAA CCTT CCCG ATAT CTAG CTGC CCTT CTAT ATAA ATAA CGTG CTTC' enc = enc.replace('A', '00').replace('C', '01') enc = enc.replace('G', '10').replace('T', '11') enc = enc.split(' ') flag = '' for c in enc: flag += chr(int(c, 2)) print(flag)
TFCCTF{1_w1ll_g3t_th1s_4s_4_t4tt00_V3ry_s00n}
CONWAY (CRYPTO, EASY)
暗号化処理の概要は以下の通り。
・initial = 11131221131211131231121113112221121321132132211331222113112211 ・initial = generate_next_key(initial) ※generate_next_key関数は未知 ・initialを出力 ・initial = generate_next_key(initial) ・key: initialの文字列のsha256ダイジェスト ・keyを使って、flagをAES ECBモード暗号化したものを16進数表記で出力
11131221131211131231121113112221121321132132211331222113112211の次は以下の値になっている。
311311222113111231131112132112311321322112111312211312111322212311322113212221
どのように値が作られているかを推測する。
11131221131211131231121113112221121321132132211331222113112211
左から何個連続して値が構成されているかを見に行けば良さそうなので、確認してみる。
3個の1 → 31 1個の3 → 13 1個の1 → 11 2個の2 → 22 2個の1 → 21 :
途中まで見ていて正しそう。関数を実装して、keyを割り出し、復号する。
#!/usr/bin/env python3 import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import unpad def generate_next_key(n): s = str(n) + 'X' key = '' count = 1 pre = s[0] for i in range(1, len(s)): if pre == s[i]: count += 1 else: key += str(count) + pre count = 1 pre = s[i] return int(key) with open('output.txt', 'r') as f: params = f.read().splitlines() initial2 = int(params[0]) enc_flag = bytes.fromhex(params[1]) initial = 11131221131211131231121113112221121321132132211331222113112211 initial = generate_next_key(initial) assert initial == initial2 initial = generate_next_key(initial) h = hashlib.sha256() h.update(str(initial).encode()) key = h.digest() cipher = AES.new(key, AES.MODE_ECB) flag = unpad(cipher.decrypt(enc_flag), 16).decode() print(flag)
TFCCTF{c0nway's_g4me_0f_sequences?}
PADGROUNDS (CRYPTO, MEDIUM)
Padding Oracle Attackが使えそうだが、復号したデータに以下のような処理があり、確率的に判断するしかなさそう。
padding = True try: unpad(pt, 16) except: padding = False padding = (padding | (random.randint(1,10) > 7) ) & (random.randint(1,10) <= 7)
unpadに成功した場合、padding = 7/10の確率でTrue
unpadに失敗した場合、padding = 3/10 * 7/10 = 21/100の確率でTrue
フラグの形式はTFCCTF{[bdefgmnprsu012345_]+}で、{}の中身は18種の文字。
$ nc challs.tfcctf.com 31470 Lets see you decode this: 0UniKJi+ZZ66z8W3EiVZFLENEyKV5MigeiVArz1VrtKAXhEnpWWWVMHZcH+b6gVdvmSeC/3DIqPndg9Vu7NaVg== I made my unpad truly random, there is nothing you can do, just give up already
>>> from base64 import * >>> s = '0UniKJi+ZZ66z8W3EiVZFLENEyKV5MigeiVArz1VrtKAXhEnpWWWVMHZcH+b6gVdvmSeC/3DIqPndg9Vu7NaVg==' >>> len(b64decode(s)) 64
ivを含めて暗号ブロックは4ブロックのため、フラグは3ブロック。以下のような平文構成で、不明文字は40バイト。
0123456789abcdef
TFCCTF{xxxxxxxxx
xxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxx}同じ暗号文を20回試し、最もTrueが多いものがunpad成功したものとして判断し、Padding Oracle Attackを行う。なお、試行回数の制限があるため、ブロックごとに接続し、フラグの形式から制限できる文字のみを対象として、復号する。
#!/usr/bin/env python3 import socket from base64 import * from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) chars = b'bdefgmnprsu012345_}' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.tfcctf.com', 32038)) data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = b64decode(data.split(' ')[-1]) data = recvuntil(s, b'\n').rstrip() print(data) block_index = 0 size = 9 enc_blocks = [enc_flag[i:i+16] for i in range(0, len(enc_flag), 16)] curr_enc_block = enc_blocks[block_index] next_enc_block = enc_blocks[block_index + 1] xor_block = b'' for i in range(size): max_count = -1 ch = 0 for c in chars: try_c = bytes([c ^ curr_enc_block[-i-1] ^ (i + 1)]) to_unpad = b'\x00' * (16 - i - 1) + try_c + strxor(xor_block, bytes([i + 1]) * i) to_unpad += next_enc_block to_unpad = b64encode(to_unpad).decode() count = 0 for j in range(20): print(to_unpad) s.sendall(to_unpad.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) if data == 'True': count += 1 if count > max_count: max_count = count ch = c xor_block = bytes([ch ^ curr_enc_block[-i-1]]) + xor_block flag1 = 'TFCCTF{' + strxor(xor_block, curr_enc_block[-size:]).decode() print(flag1)
#!/usr/bin/env python3 import socket from base64 import * from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) chars = b'bdefgmnprsu012345_}' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.tfcctf.com', 32038)) data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = b64decode(data.split(' ')[-1]) data = recvuntil(s, b'\n').rstrip() print(data) block_index = 1 size = 16 enc_blocks = [enc_flag[i:i+16] for i in range(0, len(enc_flag), 16)] curr_enc_block = enc_blocks[block_index] next_enc_block = enc_blocks[block_index + 1] xor_block = b'' for i in range(size): max_count = -1 ch = 0 for c in chars: try_c = bytes([c ^ curr_enc_block[-i-1] ^ (i + 1)]) to_unpad = b'\x00' * (16 - i - 1) + try_c + strxor(xor_block, bytes([i + 1]) * i) to_unpad += next_enc_block to_unpad = b64encode(to_unpad).decode() count = 0 for j in range(20): print(to_unpad) s.sendall(to_unpad.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) if data == 'True': count += 1 if count > max_count: max_count = count ch = c xor_block = bytes([ch ^ curr_enc_block[-i-1]]) + xor_block flag2 = strxor(xor_block, curr_enc_block[-size:]).decode() print(flag2)
#!/usr/bin/env python3 import socket from base64 import * from Crypto.Util.strxor import strxor def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) chars = b'bdefgmnprsu012345_}' s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('challs.tfcctf.com', 32038)) data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = b64decode(data.split(' ')[-1]) data = recvuntil(s, b'\n').rstrip() print(data) block_index = 2 size = 16 enc_blocks = [enc_flag[i:i+16] for i in range(0, len(enc_flag), 16)] curr_enc_block = enc_blocks[block_index] next_enc_block = enc_blocks[block_index + 1] xor_block = b'' for i in range(size): max_count = -1 ch = 0 for c in chars: try_c = bytes([c ^ curr_enc_block[-i-1] ^ (i + 1)]) to_unpad = b'\x00' * (16 - i - 1) + try_c + strxor(xor_block, bytes([i + 1]) * i) to_unpad += next_enc_block to_unpad = b64encode(to_unpad).decode() count = 0 for j in range(20): print(to_unpad) s.sendall(to_unpad.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) if data == 'True': count += 1 if count > max_count: max_count = count ch = c xor_block = bytes([ch ^ curr_enc_block[-i-1]]) + xor_block flag3 = strxor(xor_block, curr_enc_block[-size:]).decode() print(flag3)
1ブロック目の実行結果は以下の通り。
Lets see you decode this: +55UZlXtwIGdO44nN4k6KkWxQoBbVaM6ZfYKK8DEtLZtvI5B+3pg0WTCC2n9QJKC3x8WNDS4Vtuis/hHfZJFEA==
I made my unpad truly random, there is nothing you can do, just give up already
AAAAAAAAAAAAAAAAAAAASUWxQoBbVaM6ZfYKK8DEtLY=
False
AAAAAAAAAAAAAAAAAAAASUWxQoBbVaM6ZfYKK8DEtLY=
False
:
:
AAAAAAAAAPWkAuNxTrRXR0WxQoBbVaM6ZfYKK8DEtLY=
False
AAAAAAAAAPWkAuNxTrRXR0WxQoBbVaM6ZfYKK8DEtLY=
False
AAAAAAAAAPWkAuNxTrRXR0WxQoBbVaM6ZfYKK8DEtLY=
False
TFCCTF{g00d_p4dd2ブロック目の実行結果は以下の通り。
Lets see you decode this: fyeoNP2LtVhsDxLSnIWuy4jkNbLL+imXuTRHbzBu64Ko98wu0Dwd5D6+t20WwVSDcxlPw01Zce80cJ7GM02FlQ==
I made my unpad truly random, there is nothing you can do, just give up already
AAAAAAAAAAAAAAAAAAAA4aj3zC7QPB3kPr63bRbBVIM=
False
AAAAAAAAAAAAAAAAAAAA4aj3zC7QPB3kPr63bRbBVIM=
False
:
:
5ZpC0YSHDdjPVmYaThrOzaj3zC7QPB3kPr63bRbBVIM=
False
5ZpC0YSHDdjPVmYaThrOzaj3zC7QPB3kPr63bRbBVIM=
False
5ZpC0YSHDdjPVmYaThrOzaj3zC7QPB3kPr63bRbBVIM=
True
1ngs_m4_fr1end5_3ブロック目の実行結果は以下の通り。
Lets see you decode this: BVBqoGm/O7krS807xFGrmZIlasp/7YQyvf+6r/U2FEntofI8gRuiT1FwcWlixDkBeJv4PGNEe3SakwcKwk7gCg==
I made my unpad truly random, there is nothing you can do, just give up already
AAAAAAAAAAAAAAAAAAAAYnib+DxjRHt0mpMHCsJO4Ao=
True
AAAAAAAAAAAAAAAAAAAAYnib+DxjRHt0mpMHCsJO4Ao=
False
:
:
gNSPSfxpgS0eUj4fB7oYbHib+DxjRHt0mpMHCsJO4Ao=
False
gNSPSfxpgS0eUj4fB7oYbHib+DxjRHt0mpMHCsJO4Ao=
False
gNSPSfxpgS0eUj4fB7oYbHib+DxjRHt0mpMHCsJO4Ao=
False
rememb3r_2_fun1}結合すると、フラグになる。
TFCCTF{g00d_p4dd1ngs_m4_fr1end5_rememb3r_2_fun1}