この大会は2023/7/7 23:00(JST)~2023/7/8 23:00(JST)に開催されました。
今回もチームで参戦。結果は134点で672チーム中141位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome!
問題にフラグが書いてあった。
CCTF{Unleash_Your_Cipher_Skills!}
Did it!
サーバの処理概要は以下の通り。
・_flag = False ・n = 127 ・l = 20 ・N: 0以上n未満の集合 ・K: 20個のランダム0以上n-1以下整数値の配列 ・cnt = 0 ・STEP = 2 * n // l - 1 ・以下繰り返し ・ans: 入力 ・_A: ansの","区切りの数値の配列 ・_Aの長さが20以下で、Nの部分集合の場合 ・DID = did(n, l, K, _A) ・A, K = set(A), set(K) ・R = [pow(_, 2, n) + randint(0, 1) for _ in A - K] ・Rを返却
Kを推測する問題。ランダム値を考慮しても衝突しない20個を指定して、該当する値があるかどうかをチェックする。該当する値がなければ、Kに含まれているので割り出すことができる。
#!/usr/bin/env python3 import socket def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('00.cr.yp.toc.tf', 11337)) for _ in range(3): data = recvuntil(s, b'\n').rstrip() print(data) n, l = 127, 20 N = list(range(0, n)) K = [] while True: answer = [] rs = [] dic = {} N2 = N for i in N2: r = pow(i, 2, n) if r not in rs and r + 1 not in rs: answer.append(str(i)) rs.append(r) rs.append(r + 1) dic[r] = str(i) dic[r + 1] = str(i) N.remove(i) if len(answer) == 20: break ans = ','.join(answer) print(ans) s.sendall(ans.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) DID = eval(data.split(' = ')[1]) non_K = [] for d in DID: non_K.append(dic[d]) k = list(set(answer) - set(non_K)) K += k if len(N) == 0: break ans = ','.join(K) print(ans) s.sendall(ans.encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + .:: Hi all, she DID it, you should do it too! Are you ready? ::. + ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 0,2,4,6,8,10,13,15,18,20,22,24,27,29,31,34,36,38,40,42 + DID = [1, 5, 17, 37, 64, 101, 43, 98, 71, 19, 104, 68, 94, 79, 73, 13, 26, 47, 76, 114] 1,5,9,12,17,21,25,28,32,37,41,45,47,49,52,54,56,58,60,63 + DID = [9, 2, 26, 82, 30, 18, 120, 51, 35, 116, 38, 61, 123, 88, 117, 62, 44] 3,11,16,23,30,35,43,46,50,53,57,61,64,67,69,72,78,80,85,87 + DID = [10, 83, 62, 105, 121, 71, 85, 2, 51, 87, 15, 113, 76, 75, 38, 11] 7,19,33,44,51,59,65,71,74,79,82,84,88,90,92,95,97,99,101,103 + DID = [73, 11, 41, 50, 68, 16, 19, 121, 107, 62, 71, 125, 99, 53, 83] 14,39,55,66,70,76,81,86,91,96,100,104,107,110,112,114,116,118,120,123 + DID = [73, 39, 94, 75, 124, 22, 69, 35, 98, 84, 43, 121, 30, 104, 81, 17] 26,62,73,77,89,94,102,106,109,115,119,122,125 + DID = [117, 122, 60, 71, 87, 18, 65, 35, 47, 41, 73] 48,75,93,105,111,117,124 + DID = [103, 37, 3, 18, 101, 9, 14] 68,98,113,126 + DID = [70, 53, 1] 83,121 + DID = [36, 32] 108 + DID = [108] 37,63,28,23,78,67,64,44,99,95,71,65,91,76,120,107,122,125,98 + DID = [] + Congrats! the flag: CCTF{W4rM_Up_CrYpt0_Ch4Ll3n9e!!}
CCTF{W4rM_Up_CrYpt0_Ch4Ll3n9e!!}
Blue Office
暗号化処理の概要は以下の通り。
・enc = encrypt(seed, flag) ・seedは2**32以下の値であることをチェック ・c, d = 0, seed ・enc, l = b'', len(flag) ・cがlより小さい間以下を実行 ・d = d * 214013 + 2531011 ・encにflag[c] ^ ((d >> 16) & 0xff)の文字を追加 ・encを返却 ・encの16進数表記を出力
フラグは"CCTF{"で始まることを前提に、z3でseedを求める。あとはseedからXORの鍵を求め、復号する。
#!/usr/bin/env python3 from z3 import * import binascii def reseed(s): return s * 214013 + 2531011 with open('output.txt', 'r') as f: enc = eval(f.read().split(' = ')[1]) enc = binascii.unhexlify(enc) x = BitVec('x', 32) s = Solver() flag_head = b'CCTF{' d = x for i in range(len(flag_head)): d = reseed(d) s.add(flag_head[i] ^ ((d >> 16) & 0xff) == enc[i]) r = s.check() assert r == sat m = s.model() seed = m[x].as_long() print('[+] seed =', seed) d = seed flag = b'' for i in range(len(enc)): d = reseed(d) flag += (enc[i] ^ ((d >> 16) & 0xff)).to_bytes(1, 'big') flag = flag.decode() print('[*] flag =', flag)
実行結果は以下の通り。
[+] seed = 10364460 [*] flag = CCTF{__B4ck_0r!F1c3__C1pHeR_!!}
CCTF{__B4ck_0r!F1c3__C1pHeR_!!}
Suction
暗号化処理の概要は以下の通り。
・r, nbit = 8, 128 ・PKEY, pkey = keygen(nbit, r) ・p, q: 128ビット素数 ・e: 16ビット素数 ・n = p * q ・GCD(e, phi) == 1の場合 ・N: nの2進数文字列で末尾8ビット切り落とし ・E: eの2進数文字列で末尾8ビット切り落とし ・PKEY = N + E ・pkey = (n, e) ・PKEY, pkeyを返却 ・PKEYの10進数表記を出力 ・FLAG: flagから'CCTF{'と'}'を削除した文字列 ・enc = encrypt(FLAG, pkey, r) ・m: FLAGの数値化したもの ・n, e = pkey ・c = pow(m, e, n) ・C: cの2進数文字列で末尾8ビット切り落とし ・Cを返却 ・Cの10進数表記を出力
PKEYから2進数文字列で末尾8ビットがEそれより前がNとなる。まずはNからブルートフォースでnの候補を列挙する。
[+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396571 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396577 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396583 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396589 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396603 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396613 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396633 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396643 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396667 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396723 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396751 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396783
それぞれfactordbで素因数分解した値のビット数を見て、128ビット2つに分解できるものを探す。以下の場合しかないことがわかる。
n = 55208723145458976481271800608918815438075571763947979755496510859604544396613
この場合、素因数分解したp, qは以下のようになる。
p = 188473222069998143349386719941755726311 q = 292926085409388790329114797826820624883
同様にEからブルートフォースでeの候補を出し、それぞれ復号し、printableなものになるものを探す。
#!/usr/bin/env python3 from Crypto.Util.number import * from sympy import * def is_printable(s): for c in s: if c < 32 or c > 126: return False return True with open('output.txt', 'r') as f: params = f.read().splitlines() PKEY = int(params[0].split(' ')[-1]) C = int(params[1].split(' ')[-1]) PKEY = bin(PKEY)[2:] N = int(PKEY[:-8], 2) E = int(PKEY[-8:], 2) ## enumeration of n candidates ## for i in range(256): n = N * 256 + i error = False for j in range(2, 2**20): if n % j == 0: error = True break if not error: print('[+] n?:', n) ## analysis result of n ## n = 55208723145458976481271800608918815438075571763947979755496510859604544396613 print('[+] ... n:', n) p = 188473222069998143349386719941755726311 q = 292926085409388790329114797826820624883 phi = (p - 1) * (q - 1) ## enumeration of e candidates and get flag ## found = False for i in range(256): e = E * 256 + i if isPrime(e): d = inverse(e, phi) for j in range(256): c = C * 256 + j m = pow(c, d, n) flag = long_to_bytes(m) if is_printable(flag): #found = True print('[+] ... e:', e) print('[+] ... c:', c) flag = 'CCTF{%s}' % flag.decode() print('[*] flag:', flag) break if found: break
実行結果は以下の通り。
[+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396571 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396577 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396583 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396589 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396603 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396613 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396633 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396643 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396667 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396723 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396751 [+] n?: 55208723145458976481271800608918815438075571763947979755496510859604544396783 [+] ... n: 55208723145458976481271800608918815438075571763947979755496510859604544396613 [+] ... e: 32983 [+] ... c: 32561828321881834735632894563707571322808353997859875093072961921724833474846 [*] flag: CCTF{6oRYGy&Dc$G2ZS}
CCTF{6oRYGy&Dc$G2ZS}