この大会は2021/12/10 21:00(JST)~2021/12/12 21:00(JST)に開催されました。
今回もチームで参戦。結果は1370点で331チーム中72位でした。
自分で解けた問題をWriteupとして書いておきます。
baby reversing (rev)
$ strings baby_rev : b@by_R3v enter the password to get the flag: congratulations! here's the flag: nite{ wrong password! r3@l_ :
パスワードが分断されているように見える。
$ ./baby_rev enter the password to get the flag: r3@l_b@by_R3v congratulations! here's the flag: nite{baby_r3v_champ}
nite{baby_r3v_champ}
Qurious Case (Forensics)
Stegsolveで開き、Red plane 0を見ると、QRコードが一部欠けた状態になっている。
不明な箇所は"?"にし、テキストに落とし、読み込む。
$ cat qr.txt XXXXXXX_????_XXXX_XXXXXXX X_____X_????X__X__X_____X X_XXX_X_????__X___X_XXX_X X_XXX_X_????_XX_X_X_XXX_X X_XXX_X_????XXX_X_X_XXX_X X_____X_????___X__X_____X XXXXXXX_????X_X_X_XXXXXXX ????????????X_X__________ ????????????X_X_XX_X_X_X_ ????????????_XX_X______X_ ????????????X__XXXX__X_XX __X_XX___XXX__XX_X_X___XX X_____XX_X_X_XXX__XX_XX__ X__XXX_XX_X_XXX_X__X_____ X___XXX_X_X____X_XX____XX X_X__X____X_X_X___X______ X__XXXX_XX__XXXXXXXXX_XX_ ________X____XX_X___XXX__ XXXXXXX_X__XX_XXX_X_X_XXX X_____X__XXX__X_X___X____ X_XXX_X_XXXX___XXXXXX_XXX X_XXX_X_X_X_XX___XX____XX X_XXX_X_XX___XXX_XXX_X__X X_____X_X___XX__XX______X XXXXXXX_X_X_X_X__X__XX_XX $ python sqrd.py qr.txt nite{tH@T'$_qRazzYyYy}
nite{tH@T'$_qRazzYyYy}
Variablezz (Crypto)
各文字について、以下の式で暗号化している。
a * pow(ord(x), 3) + b * pow(ord(x), 2) + c * ord(x) + d
まずフラグの先頭4文字で方程式にしてa, b, c, dを求める。あとはブルートフォースでフラグを復号する。
#!/usr/bin/env python3 from sympy import * with open('ciphertext.txt', 'r') as f: enc = eval(f.read().split(' = ')[1]) a = symbols('a') b = symbols('b') c = symbols('c') d = symbols('d') flag_head = 'nite' eq = [] for i in range(len(flag_head)): x = flag_head[i] eq.append(Eq(a * pow(ord(x), 3) + b * pow(ord(x), 2) + c * ord(x) + d, enc[i])) sol = solve(eq, [a, b, c, d]) a = int(sol[a]) b = int(sol[b]) c = int(sol[c]) d = int(sol[d]) flag = '' for i in range(len(enc)): for code in range(32, 127): if a * pow(code, 3) + b * pow(code, 2) + c * code + d == enc[i]: flag += chr(code) break print(flag)
nite{jU5t_b45Ic_MaTH}
Rabin To The Rescue (Crypto)
サーバの処理概要は以下の通り。
・p: 256bit素数(4で割った余りが3) ・q: pの次の素数(4で割った余りが3) ・n = p * q ・e = int(mixer(q - p)) * 2 ・以下繰り返し ・choice: "E", "G", "X"選択 ・"E"の場合 ・m: 16進表記入力→10進数数値化 ・ciphertext = pow(m, e, n) →16進表記で表示 ・"G"の場合 ・flag: フラグの数値化 ・t: 2~10のランダム整数 ・ciphertext = pow(flag, e, n) →16進表記で表示 ・"X"の場合、終了
eは必ず2になる。p, qの条件から暗号はRabin暗号になる。以下の方針でフラグを復号する。
・"E"で2乗した値がnの数倍を超える値に抑えるよう指定し、暗号化結果との差分がnの倍数になることを利用して、nを求める。 ・p, qは近い数値のため、Fermat法でnを素因数分解する。 ・"G"でフラグの暗号化を取得し、Rabin暗号の復号方法で復号する。
#!/usr/bin/env python3 import socket from Crypto.Util.number import * def recvuntil(s, tail): data = b'' while True: if tail in data: return data.decode() data += s.recv(1) def isqrt(n): x = n y = (x + n // x) // 2 while y < x: x = y y = (x + n // x) // 2 return x def fermat(n): x = isqrt(n) + 1 y = isqrt(x * x - n) while True: w = x * x - n - y * y if w == 0: break elif w > 0: y += 1 else: x += 1 return x - y, x + y def egcd(a, b): if a == 0: return b, 0, 1 else: gcd, y, x = egcd(b % a, a) return gcd, x - (b // a) * y, y s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('rabin.challenge.cryptonite.team', 1337)) data = recvuntil(s, b'E[x]it\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'>>') print(data + 'E') s.sendall(b'E\n') data = recvuntil(s, b':\n').rstrip() print(data) m = 2**256 - 1 print(hex(m)[2:]) s.sendall(hex(m)[2:].encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) c = int(data, 16) n = m**2 - c for i in range(100, 1, -1): if n % i == 0: n = n // i p, q = fermat(n) assert p % 4 == 3 assert q % 4 == 3 data = recvuntil(s, b'>>') print(data + 'G') s.sendall(b'G\n') data = recvuntil(s, b'\n').rstrip() print(data) data = recvuntil(s, b'\n').rstrip() print(data) c = int(data, 16) r = pow(c, (p + 1) // 4, p) s = pow(c, (q + 1) // 4, q) gcd, c, d = egcd(p, q) x = (r * d * q + s * c * p) % n y = (r * d * q - s * c * p) % n plains = [x, n - x, y, n - y] for plain in plains: flag = long_to_bytes(plain) if flag.startswith(b'nite{'): print(flag.decode()) break
実行結果は以下の通り。
== proof-of-work: disabled == ________________________________________________________________________________________________ ----------------------------------- ----------------------------------- ----------------------------------- /\ __/__\__ \/ \/ /\____/\ ``\ /`` \/ ----------------------------------- ----------------------------------- ----------------------------------- Welcome to the Encryption challenge!!!! Menu: => [E]ncrypt your message => [G]et Flag => E[x]it ________________________________________________________________________________________________ >>E Enter message in hex: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff Your Ciphertext is: 245de4b42302114a104d2f73f16b4667120f562d1bfa6c79880c93c33dba2eefec35ecf57e80f950013a58492990a9d053f0a5f634204bacce2c35077fb64bff >>G Your encrypted flag is: 4d34144fc1b3d401b93cd06e4090cf583548b6f12480c79cb16c05d53805778069c14fa934bac7b109164bd97cf3826e05ab58a7cc63476c63b8bf5b08f3c5d3 nite{r3p34t3d_r461n_3ncrypt10n_l1tr4lly_k1ll5_3d6f4adc5e}
nite{r3p34t3d_r461n_3ncrypt10n_l1tr4lly_k1ll5_3d6f4adc5e}
Flip Me Over (Crypto)
サーバの処理概要は以下の通り。
・KEY: ランダム16バイト ・entropy: ランダム128バイトのmd5ダイジェスト ・username: 16進表記で入力 ・token = generate_token(username) ・iv: ランダム16バイト ・pt: usernameのhexデコード ・ptに'gimmeflag'が含まれていたら終了 ・ct: ptをパディングして、AES-CBC暗号化 ・tag: ctをブロックごとに分け、すべてXORする。 ・tag: さらにiv, entropyをXORする。 ・tag + ctの16進文字列を返す。 ・tokenを表示 ・以下繰り返し ・token: 16進表記で入力 ・tag: 16進表記で入力 ・verify(tag, token)の結果に'gimmeflag'が含まれている場合、フラグを表示 ・verify(tag, token)の結果に'gimmeflag'が含まれていない場合、その値を表示 ・entropy: entropyのmd5ダイジェスト
平文1ブロック目 ^ iv --(AES-CBC暗号化)--> 暗号1ブロック目 tag = 暗号1ブロック目 ^ iv ^ entropy
適当な16バイト未満のusername('a' * 9)を指定し、次にivを変えて、'gimmeflag'が含まれるようにしたい。このとき、tagを変えることによって、ivを変える。
iv ^ entropy = tag ^ ct new_iv = pad('a' * 9) ^ iv ^ pad('gimmeflag') → new_iv ^ pad('a' * 9) ^ pad('gimmeflag') ^ entropy = tag ^ ct → new_iv ^ entropy = (pad('a' * 9) ^ pad('gimmeflag') ^ tag) ^ ct → new_tag = pad('a' * 9) ^ pad('gimmeflag') ^ tag, new_ct = ct
#!/usr/bin/env python3 import socket from Crypto.Util.Padding import pad, unpad 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) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('flipmeover.chall.cryptonite.team', 1337)) pt = b'a' * 9 username = pt.hex().encode() data = recvuntil(s, b'hex():\n').rstrip() print(data) print(username) s.sendall(username + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) tag = bytes.fromhex(data[:32]) ct = bytes.fromhex(data[32:]) new_pt = b'gimmeflag' new_tag = strxor(strxor(pad(pt, 16), pad(new_pt, 16)), tag) new_ct = ct data = recvuntil(s, b'hex():\n').rstrip() print(data) print(new_ct.hex()) s.sendall(new_ct.hex().encode() + b'\n') data = recvuntil(s, b'hex():\n').rstrip() print(data) print(new_tag.hex()) s.sendall(new_tag.hex().encode() + b'\n') for _ in range(4): data = recvuntil(s, b'\n').rstrip() print(data)
実行結果は以下の通り。
== proof-of-work: disabled == Hello new user We shall allow you to generate one token: Enter username in hex(): b'616161616161616161' 542b5c295fc6baa997cd90cac24c72f12bd4852ae0d03813acebe02d665f71df Validate yourself :) Enter token in hex(): 2bd4852ae0d03813acebe02d665f71df Enter tag in hex(): 522350255bc1b7a991cd90cac24c72f1 Oh no u flipped me... I am now officially flipped... Here's ur reward... nite{flippity_floppity_congrats_you're_a_nerd}
nite{flippity_floppity_congrats_you're_a_nerd}