この大会は2022/12/24 1:00(JST)~2022/12/26 1:00(JST)に開催されました。
今回もチームで参戦。結果は350点で492チーム中123位でした。
自分で解けた問題をWriteupとして書いておきます。
4dm1n_c0ntR0L (Web)
Usernameに以下を入力して、Submitすると、ログインできる。
' or 1=1 -- -
Get flagボタンだけあるので、押してみると、フラグがポップアップ画面に表示された。
nitectf{w3nT_1nT0_Th3_s3rV3r}
itsybitsyrsa (Cryptography)
3つのファイルに分散されているが、それぞれRSA暗号のパラメータが書いてある。nが非常に大きく、eが小さいので、Low Public-Exponent Attackで復号する。
#!/usr/bin/env python3 from Crypto.Util.number import * import gmpy2 with open('e_value', 'r') as f: e = int(f.readline().rstrip().split(' ')[-1]) with open('c_value', 'r') as f: c = int(f.readline().rstrip().split(' ')[-1]) m = gmpy2.iroot(c, e)[0] flag = long_to_bytes(m).decode() print(flag)
nitectf{rsa_can_be_very_adaptable}
Basically, I locked up (Cryptography)
暗号・復号のスクリプトが提供されている。この暗号の処理概要は以下の通り。
・password: 8バイトの文字列(未知) ・plaintextにはb"HiDeteXT"が含まれている。 ・add_spice関数: 左シフト ・ciphertext: plaintextの各文字について、add_spice(c) ^ ord(password[i % len(password)])のバイト文字の連結 →ファイル書き込み
暗号の各位置から8バイトを復号した際にb"HiDeteXT"になることを前提にpasswordを求めることをブルートフォースで行う。
#!/usr/bin/env python3 add_spice = lambda b: 0xff & ((b << 1) | (b >> 7)) remove_spice = lambda b: 0xff & ((b >> 1) | (b << 7)) def decrypt(ciphertext, password): plaintext = bytes(remove_spice(c ^ ord(password[i % len(password)])) for i, c in enumerate(ciphertext)) return plaintext def is_printable(s): for c in s: if c < 32 or c > 126: if c != 10: return False return True with open('Important_encrypted', 'rb') as f: ciphertext = f.read() known = b'HiDeteXT' with open('Important_encrypted', 'rb') as f: ciphertext = f.read() for i in range(len(ciphertext) - 8 + 1): password = [''] * 8 for j in range(8): password[(i + j) % 8] = chr(add_spice(known[j]) ^ ciphertext[i + j]) plaintext = decrypt(ciphertext, password) if is_printable(plaintext): plaintext = plaintext.decode() print(plaintext) break
実行結果は以下の通り。
Oh, You Searching for HiDeteXT ?? NITE{BrUT3fORceD_y0uR_wAy_iN}
NITE{BrUT3fORceD_y0uR_wAy_iN}
Shoo-in (Cryptography)
サーバの処理概要は以下の通り。
・fn: "firstnames.py"のファイルオブジェクト ・ln: "lastnames.py"のファイルオブジェクト ・fp: "primes.py"のファイルオブジェクト ・fn_content: fnの行ごと(改行あり)の配列 ・ln_content: lnの行ごと(改行あり)の配列 ・prime_arr: fpの行ごと(改行あり)の配列 ・N: firstnames.pyとlastnames.pyの行数の少ない方の行数(=1300) ・seed, a, b: N未満のランダム整数 ・lcg = RNG(seed, a, b, N) ・lcg.seed = seed ・lcg.a = a ・lcg.b = b ・lcg.p = N ・gen=lcg.gen() ・以下10回繰り返し ・iが0の場合 ・name1: "[firstnamesのリストのa番目] [lastnamesのリストのb番目]" ・iが0以外の場合 ・name1: "[firstnamesのリストのnext(gen)番目] [lastnamesのリストのnext(gen)番目]" ・name2: "[firstnamesのリストのnext(gen)番目] [lastnamesのリストのnext(gen)番目]" ・name1, name2を表示 ・winner = next(gen) % 2 ・inp: 入力(1または2) ・winnerがinpと一致しない場合、終了 ・winnerがinpと一致する場合 ・iが9より小さい場合、正解メッセージを表示 ・iが9の場合 ・key=generate_keys() ・p: prime_arr[next(gen)] ・q: prime_arr[next(gen)] ・n = p * q ・g = n + 1 ・l = (p - 1) * (q - 1) ・mu = gmpy2.invert(l, n) ・n, g, l, muを返却 ・pallier_encrypt(key, int.from_bytes(flag, "big"), next(gen))の結果を表示 ・(pow(g, m, n**2) * pow(next(gen), n, n**2) % n**2を返却 ※m: フラグの数値化
最初のname1からa, bはわかる。さらにseedをブルートフォースし、name2になる条件からseedを求める。あとはnext(gen)の値がわかるので、各パラメータを算出できる。p, qがわかるので、Paillier暗号の暗号文を復号することができる。
#!/usr/bin/env python3 import socket from Crypto.Util.number import GCD, inverse def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) class RNG: def __init__ (self, seed, a, b, p): self.seed = seed self.a = a self.b = b self.p = p def gen(self): out = self.seed while True: out = (self.a * out + self.b) % self.p self.a += 1 self.b += 1 self.p += 1 yield out def getPrime(): prime = int(prime_arr[next(gen)].strip()) return prime def L(u, n): return (u - 1) // n def pallier_decrypt(c): p = getPrime() q = getPrime() n = p * q g = n + 1 l = (p - 1) * (q - 1) _lambda = l // GCD(p - 1, q - 1) m = L(pow(c, _lambda, n**2), n) * inverse(L(pow(g, _lambda, n**2), n), n) % n return m fn = open(r"firstnames.py", 'r') ln = open(r"lastnames.py", 'r') fp = open (r"primes.py", 'r') fn_content = fn.readlines() ln_content = ln.readlines() prime_arr = fp.readlines() N = (min(len(fn_content), len(ln_content))) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('34.90.236.228', 1337)) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) name1 = data.split('\t')[0] name2 = data.split('\t')[-1] a = fn_content.index(name1.split(' ')[0] + '\n') b = ln_content.index(name1.split(' ')[1] + '\n') next1 = fn_content.index(name2.split(' ')[0] + '\n') next2 = ln_content.index(name2.split(' ')[1] + '\n') found = False for seed in range(N): c1 = (a * seed + b) % N if c1 == next1: if ((a + 1) * c1 + (b + 1)) % (N + 1) == next2: found = True break assert found lcg = RNG(seed, a, b, N) gen = lcg.gen() next(gen) next(gen) winner = next(gen) % 2 if winner == 0: winner = 2 data = recvuntil(s, b'\n').rstrip() print(data) print(winner) s.sendall(str(winner).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) for i in range(1, 10): for _ in range(4): next(gen) data = recvuntil(s, b'\n').rstrip() print(data) winner = next(gen) % 2 if winner == 0: winner = 2 data = recvuntil(s, b'\n').rstrip() print(data) print(winner) s.sendall(str(winner).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) c = int(data) m = pallier_decrypt(c) flag = m.to_bytes((m.bit_length() + 7) // 8, "big").decode() print(flag)
実行結果は以下の通り。
== proof-of-work: disabled == Andre Reese vs Blaine Harmon Choose the winner: 1 or 2 1 That's correct, here is the next round Heath Francis vs Shiloh Heath Choose the winner: 1 or 2 2 That's correct, here is the next round Kash Avery vs Nikhil Lopez Choose the winner: 1 or 2 1 That's correct, here is the next round Margaret Schneider vs Bethany Selene Choose the winner: 1 or 2 2 That's correct, here is the next round Todd Estes vs Adrien Frye Choose the winner: 1 or 2 1 That's correct, here is the next round Rodrigo Moore vs Cadence Kent Choose the winner: 1 or 2 2 That's correct, here is the next round Jesse Beard vs Rodrigo Tobias Choose the winner: 1 or 2 1 That's correct, here is the next round Abigail Aryn vs Alberto Rodgers Choose the winner: 1 or 2 2 That's correct, here is the next round Jonathan Woods vs Daisy Hollyn Choose the winner: 1 or 2 1 That's correct, here is the next round Brice Schroeder vs Daisy Werner Choose the winner: 1 or 2 1 Congratulations! you made it Can you decode this secret message? 9247144570829358924841240967295610831251025340080430839001135462876063348979941518647185296879931786766958032992008977123505926544277369888568600621632355851505450281378555399544202335817356532297136697333575081814361295223072969220560307669233249687870270873089007634771695172315068511805588870393530505136577345386776211456988815465939654026602528062989553270554332170428745622557131002272007524816246732049457089239722213074314397985724894219764450273744264907017723444907563274142874472362285638387792413075013020704951426410641101658781288819482936333414155207342080191537216025570791495740491524447242675986564 niteCTF{n0T_sO_R@nd0m}
niteCTF{n0T_sO_R@nd0m}