この大会は2025/3/1 0:30(JST)~2025/3/3 0:30(JST)に開催されました。
今回もチームで参戦。結果は300点で955チーム中202位でした。
自分で解けた問題をWriteupとして書いておきます。
RTFM : Infrastructure (Welcome)
Discordに入り、#informationsチャネルのメッセージを見ると、pwntoolsのコード例にフラグが書いてあった。
PWNME{Plz_r34d_tH3_d0c!!!}
ProfileEditor (Web)
パストラバーサルでflag.txtを読み取れる。
まず、以下のアカウントを作成し、ログインする。
Username: ../flag.txt Password: hogeFuga
Show Profileをクリックすると、フラグが表示された。
PWNME{afb7daa3a595522882f6a9efae30c4ec}
Easy Diffy (Crypto)
g が p - 1 になっているため、剰余環p上のgの奇数乗は必ずgとなる。つまり以下のようになる。
A = pow(g, a, p) = g B = pow(g, b, p) = g C = pow(B, a, p) = pow(g, a, p) = g
これでkeyは割り出せるので、AES暗号の暗号文を復号できる。
#!/usr/bin/env python3 from Crypto.Util.number import * from Crypto.Util.Padding import unpad from Crypto.Cipher import AES from hashlib import sha256 with open('output.txt', 'r') as f: params = f.read().splitlines() p = int(params[0].split(' ')[-1]) g = int(params[1].split(' ')[-1]) ciphertext = bytes.fromhex(params[2].split(' ')[-1]) C = g key = long_to_bytes(C) key = sha256(key).digest()[:16] cipher = AES.new(key, AES.MODE_ECB) flag = unpad(cipher.decrypt(ciphertext), AES.block_size).decode() print(flag)
PWNME{411_my_h0m13s_h4t35_sm411_Gs}
Square Power (Crypto)
暗号化処理の概要は以下の通り。
・n, g, k = generate_public_key() ・n: 素数の積 ・k: 2以上n-1以下ランダム整数 ・k, nのGCDが1以外の間、以下を実行 ・k: 2以上n-1以下ランダム整数 ・g = 1 + k * n ・n, g, kを返却 ・a: 2以上n-1以下のランダム整数 ・b: 2以上n-1以下のランダム整数 ・A = pow(g, a, n * n) ・B = pow(g, b, n * n) ・secret_key = pow(B, a, n * n) ・n, g, k, A, Bを出力 ・encrypt(flag, secret_key)を表示 ・hash_secret_key: secret_keyを文字列としたsha256ダイジェスト ・hash_secret_keyを鍵としてflagをAES暗号化して16進数文字列で表示
Aは以下のように式を変形できる。
A = pow(g, a, n * n) = pow(1 + k * n, a, n * n) = (1 + a * k * n) % (n * n)
このことから以下のようにしてaを割り出すことができる。
a = ((A - 1) // n) * inverse(k, n) % n
あとはsecret_keyが算出できるので、AES復号をすればよい。
#!/usr/bin/env python3 from Crypto.Util.number import * from Crypto.Cipher import AES from hashlib import sha256 with open('output.txt', 'r') as f: params = f.read().splitlines() n = int(params[0].split(' ')[-1]) g = int(params[1].split(' ')[-1]) k = int(params[2].split(' ')[-1]) A = int(params[3].split(' ')[-1]) B = int(params[4].split(' ')[-1]) enc = bytes.fromhex(eval(params[5].split(' ')[-1])) a = ((A - 1) // n) * inverse(k, n) % n assert pow(g, a, n * n) == A secret_key = pow(B, a, n * n) hash_secret_key = sha256(str(secret_key).encode()).digest() cipher = AES.new(hash_secret_key, AES.MODE_ECB) flag = cipher.decrypt(enc).decode() print(flag)
PWNME{Thi5_1s_H0w_pAl1ier_WorKs}
My Zed (Crypto)
暗号化処理の概要は以下の通り。
・file = openzed.Openzed(b'zed', os.urandom(16), 'flag.txt', len(FLAG))
・file.user = b'zed'
・file.password = ランダム16バイト文字列
・file.filename = 'flag.txt'
・file.size = FLAGの長さ
・file.generate_metadata()
・metadata = {}
・metadata["user"] = 'zed'
・metadata["password_hash"] = sha256(file.password).hexdigest()
・metadata["filename"] = 'flag.txt'
・metadata["size"] = FLAGの長さ
・file.metadata = metadataのjson文字列
・file.padding_len = 300 - len(file.metadata)
・file.metadataに300バイトになるようb"\x00"をパディング
・file.metadataを返却
・file.encrypt(FLAG)
・cipher = AES_CBC_ZED(file.user, file.password)
・cipher.user = b'zed'
・cipher.password = file.password
・cipher.derive_password()
・100回繰り返し
・cipher.key = sha256(cipher.password).digest()[:16]
・cipher.generate_iv()
・cipher.iv = (cipher.user + cipher.password)[:16]
・file.encrypted = cipher.encrypt(FLAG)
・iv = cipher.iv
・ciphertext = b""
・ecb_cipher = AES.new(key=cipher.key, mode=AES.MODE_ECB)
・FLAGを16バイトごとに以下の処理を実行
・chunk: FLAGのブロック
・最後のブロックの場合
・plaintextの長さが16以下の場合
・prev = iv
・plaintextの長さが16より大きい場合
・prev = ciphertext[pos-16:pos]
・prev = ecb_cipher.encrypt(prev)
・ciphertextにxor(chunk, prev)を結合
・最初のブロックの場合
・xored = bytes(xor(plaintext, iv))
・ciphertext += ecb_cipher.encrypt(xored)
・最初でも最後のブロックでもない場合
・xored = bytes(xor(chunk, ciphertext[pos-16:pos]))
・ciphertext += ecb_cipher.encrypt(xored)
・iv + ciphertextを返却
・file.encrypted = zlib.compress(file.encrypted)
・file.encryptedを返却
・file.generate_container()
・file.secure_container = b'OZED' + file.metadata + file.encrypted
・file.secure_containerを返却
・file.secure_containerを出力metadata["password_hash"]がわかっているので、AES暗号のkeyも算出できる。あとは逆算して復号すればフラグを割り出せる。
#!/usr/bin/env python3 import json import zlib from Crypto.Cipher import AES def xor(a: bytes, b: bytes) -> bytes: return bytes(x ^ y for x, y in zip(a, b)) with open('flag.txt.ozed', 'rb') as f: secure_container = f.read() assert secure_container[:4] == b'OZED' metadata = json.loads(secure_container[4:304].rstrip(b'\x00')) encrypted = secure_container[304:] key = bytes.fromhex(metadata['password_hash'])[:16] encrypted = zlib.decompress(encrypted) iv = encrypted[:16] ciphertext = encrypted[16:] ecb_cipher = AES.new(key=key, mode=AES.MODE_ECB) FLAG = b'' for pos in range(0, len(ciphertext), 16): chunk = ciphertext[pos:pos+16] if len(ciphertext[pos+16:pos+32]) == 0: if len(ciphertext) <= 16: prev = iv else: prev = ciphertext[pos-16:pos] prev = ecb_cipher.encrypt(prev) FLAG += xor(prev, chunk) elif not FLAG: xored = ecb_cipher.decrypt(chunk) FLAG += bytes(xor(xored, iv)) else: xored = ecb_cipher.decrypt(chunk) FLAG += bytes(xor(xored, ciphertext[pos-16:pos])) FLAG = FLAG.decode() print(FLAG)
PWNME{49e531f28d1cedef03103af6cec79669_th4t_v3Ct0r_k1nd4_l3aky}