この大会は2021/12/18 0:00(JST)~2021/12/20 0:00(JST)に開催されました。
今回もチームで参戦。結果は85点で150チーム中127位でした。
自分で解けた問題をWriteupとして書いておきます。
gipfel (CRY)
$ nc 65.108.176.66 1088 please give S such that sha256(unhex("8f35e21761b7c327" + S)) ends with 30 zero bits (see pow-solver.cpp). 01000000157e6a3e pubA = 0x1c0cbb33dc0f5ead27915ff41cc2b744d312a86fb19f573c50a58ff0a35dfd988e5a101f6db9c24d7f4c207126d41f8b7aaed1ded4a1f6a7e6e2bc6deef653aa81e3f0bccb574e865aaa5a5ca3a87e908925a8865d88840722243871758845e
サーバの処理概要は以下の通り。
・q: 固定値 ・password: 10**6未満整数 ・go() ・g = int(H(password).hex(), 16) ・privA: 40*[2**999未満整数値] ・pubA = pow(g, privA, q)→表示 ・pubB: 2以上q未満整数入力 ・shared = pow(pubB, privA, q) ・verA = pow(g, shared**3, q)→表示 ・verB: 整数値入力 ・verBとpow(g, shared**5, q)が一致している場合 ・key = H(password, shared) ・flag: flag.txtから読み込み ・flagのAES-CTR(nonce=b'')暗号化したものを16進表記で表示 ・verBとpow(g, shared**5, q)が一致していない場合 ・sharedを16進数で表示 ・go() ・go()
最初のPoWはソルバーをコンパイルしたものをそのまま使う。
1回目のgo()で適当な値を入力し、sharedを入手する。verAの値からRSA暗号の復号の方法でgを計算できる。
2回目のgo()でBの秘密鍵(=privB)を適当に指定して、sharedを求める。sharedがわかったら、verBを算出できるので、フラグの暗号化データを入手できる。
鍵はpasswordとsharedから生成されるので、passwordがわかったらフラグを復号できることになる。passwordはブルートフォースでgが一致するものを探し、割り出すことができる。
#!/usr/bin/env python3 import socket import re import subprocess from Crypto.Hash import SHA256 from Crypto.Cipher import AES 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 enc(a): f = {str: str.encode, int: int.__str__}.get(type(a)) return enc(f(a)) if f else a def H(*args): data = b'\0'.join(map(enc, args)) return SHA256.new(data).digest() q = 0x3a05ce0b044dade60c9a52fb6a3035fc9117b307ca21ae1b6577fef7acd651c1f1c9c06a644fd82955694af6cd4e88f540010f2e8fdf037c769135dbe29bf16a154b62e614bb441f318a82ccd1e493ffa565e5ffd5a708251a50d145f3159a5 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('65.108.176.66', 1088)) #### PoW #### data = recvuntil(s, b'\n').rstrip() print(data) pattern = 'unhex\(\"(.+)\" \+ S\)\) ends with (\d+) zero' m = re.search(pattern, data) pre = m.group(1) bits = m.group(2) cmd = './pow-solver %s %s' % (bits, pre) S = subprocess.check_output(cmd.split(' ')).rstrip() print(S.decode()) s.sendall(S + b'\n') #### 1st (& 2nd) go() #### data = recvuntil(s, b'\n').rstrip() print(data) pubA = int(data.split(' ')[-1], 16) pubB = 3 print(pubB) s.sendall(str(pubB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) verA = int(data.split(' ')[-1], 16) verB = 2 print(verB) s.sendall(str(verB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) shared = int(data.split(' ')[-1], 16) phi = q - 1 if GCD(shared, phi) == 1: d = inverse(shared**3, phi) g = pow(verA, d, q) assert pow(g, shared**3, q) == verA print('[+] g =', g) else: data = recvuntil(s, b'\n').rstrip() print(data) pubA = int(data.split(' ')[-1], 16) pubB = 5 print(pubB) s.sendall(str(pubB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) verA = int(data.split(' ')[-1], 16) verB = 2 print(verB) s.sendall(str(verB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) shared = int(data.split(' ')[-1], 16) phi = q - 1 d = inverse(shared**3, phi) g = pow(verA, d, q) assert pow(g, shared**3, q) == verA print('[+] g =', g) #### last go() #### data = recvuntil(s, b'\n').rstrip() print(data) pubA = int(data.split(' ')[-1], 16) privB = 1 pubB = pow(g, privB, q) print(pubB) s.sendall(str(pubB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) verA = int(data.split(' ')[-1], 16) shared = pow(pubA, privB, q) verB = pow(g, shared**5, q) print(verB) s.sendall(str(verB).encode() + b'\n') data = recvuntil(s, b'\n').rstrip() print(data) enc_flag = data.split(' ')[-1] for password in range(10**6): try_g = int(H(password).hex(), 16) if try_g == g: break print('[+] password =', password) key = H(password, shared) aes = AES.new(key, AES.MODE_CTR, nonce=b'') flag = aes.decrypt(bytes.fromhex(enc_flag)).decode() print('[*] flag:', flag)
実行結果は以下の通り。
please give S such that sha256(unhex("a6da72cadcab0304" + S)) ends with 30 zero bits (see pow-solver.cpp). 0100000005e3ea41 pubA = 0x257cae78f3f23cba016200abdc09d2e5007392ff4b0c63aec1dbbf92cb4e3db8e50d9faeb418643828fa5a6050b7e00db0fbc7a770a5bcd1d53b39674d9b2a2d1007f642ce54b811a4d16eee711f3d3984a6dab5be1dce36bc28b78a8b30387 3 verA = 0xffce09f70b1ed7e3d24ba7651d6059d4e5c50938a391710ef3ef963ffad2b08db9898ecb1bfb45abee869fd854ae2f48eb92b0ca85a9fb5c3f7b1a4572539b3cc07b58448548b8e21cf20ab0d6402e6df90b123fac84ffd9fa6175bf44f62d 2 nope! 0xf965ff0d6b22e6dd9a47cc6d392913062654de223a67b5ee878afe3dbcd726b5e31450f4b6212fcac76a00e8bacc0e232430c1b17078a4a17de7d70c2bd3fcd3715e65c4feba983b4673a1fe2c3b7269f91bcbf9529fb04b8df86dba5289b [+] g = 13493793633820603780901075325722880484676950487827758055252724828867100710213 pubA = 0x2eb0dfeaf601ef3993db9b3a8b5f872608073b3efa794b600893bbe43162aa14b0025c2d382afeca8c8816f5aeab978b09c45df30a6c16d35ff661b08c0be54295d5865ca6151d96231f35be623ceddaed334f8e979cd52e52a85bdbc275cc 13493793633820603780901075325722880484676950487827758055252724828867100710213 verA = 0x1182826b9fbd0d4add2b52fc64f392b2701b9c61d7479886ea9caa8d58db4d27c654760a5c46ccccf488bd20469210de8aa2887fba4d4cbd654de3510985f86be8c12ac8af032b84b5b80f4e360e2cd61497b9d8d4034462d44db707f623a87 10222498644341506938343598224304245593466464162217677047877898700877722581416404301594557632764667021937154267283258709061638868653792738409785033440829078565783119877648318555176791890932495702335158882959608977932523567241840462 flag: 91be4c89e2b9f610d072f04b3efabeb1a598b89421b4a478509dfe4a42fc015783f93f65d0640acc [+] password = 346041 [*] flag: hxp{ju5T_k1ddIn9_w3_aLl_kn0w_iT's_12345}
hxp{ju5T_k1ddIn9_w3_aLl_kn0w_iT's_12345}