FwordCTF 2021 Writeup

この大会は2021/8/28 2:00(JST)~2021/8/29 14:00(JST)に開催されました。
今回もチームで参戦。結果は4204点で428チーム中28位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (Welcome)

Discordに入り、#welcome-flagチャネルのメッセージを見ると、フラグが書いてあった。

FwordCTF{Welcome_To_FwordCTF_2021}

Leaky Blinders (Cryptography)

サーバの処理概要は以下の通り。

・key: ランダム32バイト文字列
・フラグを暗号化して表示
・以下繰り返し
 ・メニュー表示
 ・1を選択した場合
  ・msg: ランダム32バイト
  ・cipher: msgを暗号化
  ・cipherとkeyの各文字が異なる場合はcipherを表示
   そうでない場合は、leakのメッセージを表示
 ・2を選択した場合
  ・k: keyを16進表記で入力
  ・cipher: 暗号文を16進表記で入力
  ・復号(XOR -> AES-ECB復号)
   →FwordCTFが含まれていたら、フラグを表示
 ・3を選択した場合、終了

暗号化の処理概要は以下の通り。

・平文が16バイトの倍数でない場合、パディング
・AES-ECBでkeyを使って暗号化
・cipherとkeyでXOR(文字列が短い方は繰り返し文字列にする)

2で鍵と暗号文を指定できるので、適当な鍵で"FwordCTF"を暗号化したものを指定すればよい。

#!/usr/bin/env python3
import socket
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def xor(a, b):
    return bytearray([a[i % len(a)] ^ b[i % len(b)] for i in range(max(len(a), len(b)))])

def encrypt(msg):
    aes = AES.new(key, AES.MODE_ECB)
    if len(msg) % 16 != 0:
        msg = pad(msg, 16)
    cipher = aes.encrypt(msg)
    cipher = xor(cipher, key)
    return cipher

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('52.149.135.130', 4869))

for _ in range(3):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

key = b'\x00' * 16
msg = b'FwordCTF'
cipher = encrypt(msg)
key_hex = key.hex()
cipher_hex = cipher.hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b': ')
print(data + key_hex)
s.sendall(key_hex.encode() + b'\n')
data = recvuntil(s, b': ')
print(data + cipher_hex)
s.sendall(cipher_hex.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

Welcome to Enc/Dec Oracle.
Here is the encrypted flag : fe4fe57726fd997b8973129764ad10c880a12d48f3f8604cb0ae3219a47ee019bf8670d05a1fb38da5fbd056f67aad4b2e14b32a565544fe41111a0ada03a8019b8ff6dc00caa62b700b6c2d82a9be75

1- Encrypt
2- Decrypt
3- Leave
> 2

Key : 00000000000000000000000000000000
Ciphertext : 94d0974bbcfc8739501c6985a3a50500
Well done ! Here is your flag : b'FwordCTF{N3v3r_x0r_w1thout_r4nd0m1s1ng_th3_k3y_0r_m4yb3_s3cur3_y0ur_c0d3}'
FwordCTF{N3v3r_x0r_w1thout_r4nd0m1s1ng_th3_k3y_0r_m4yb3_s3cur3_y0ur_c0d3}

Boombastic (Crypto)

サーバの処理概要は以下の通り。

・p: 1024bit素数
・secret: 1以上p-1以下ランダム整数
・以下繰り返し
 ・メニュー表示
 ・1を選択した場合
  ・magic_word: json形式で入力
  ・"Boombastic"のチケットと一致していたら、フラグを表示
 ・2を選択した場合
  ・word: ランダム16バイトの16進表記
  ・wordのチケットを表示
 ・3を選択した場合、終了

チケットの計算は以下の通り。

・y: msgのsha256ダイジェストの数値化
・r = ((y**2 - 1) * (inverse(secret**2, p))) % p
・s = ((1 + y) * (inverse(secret, p))) % p

inverse(secret, p) = Aとして、式を変形していく。

r = ((y**2 - 1) * (A**2)) % p
s = ((1 + y) * A) % p
    ↓
s**2 % p = ((y**2 + y*2 + 1) * (A**2)) % p
         = (r + 2 * (y + 1) * (A**2)) % p
         = (r + 2 * s * A) % p
    ↓
A = ((s**2 - r) * inverse(s * 2, p)) % p

Aを算出できるので、ここから"Boombastic"のチケットを計算できる。

#!/usr/bin/env python3
import socket
from Crypto.Util.number import inverse
from json import loads, dumps
import hashlib

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

def get_ticket(code, A):
    y = int(hashlib.sha256(code.encode()).hexdigest(),16)
    r = ((y**2 - 1) * (A**2)) % p
    s = ((1 + y) * A) % p
    return {'s': hex(s), 'r': hex(r), 'p': hex(p)}

sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sc.connect(('52.149.135.130', 4872))

data = recvuntil(sc, b'> ')
print(data + '2')
sc.sendall(b'2\n')
data = recvuntil(sc, b'\n').rstrip()
print(data)
data = recvuntil(sc, b'\n').rstrip()
print(data)
ticket = loads(data.split(' : ')[1])
s = int(ticket['s'], 16)
r = int(ticket['r'], 16)
p = int(ticket['p'], 16)
A = ((s**2 - r) * inverse(s * 2, p)) % p

magic_word = dumps(get_ticket("Boombastic", A))
data = recvuntil(sc, b'> ')
print(data + '1')
sc.sendall(b'1\n')
data = recvuntil(sc, b': ')
print(data + magic_word)
sc.sendall(magic_word.encode() + b'\n')
data = recvuntil(sc, b'\n').rstrip()
print(data)

実行結果は以下の通り。

                 ______________
               _(______________()
  ______     _- |              ||
 |      |_ _-   |              ||
 |      |_|_    |  Boombastic  ||
 |______|   -_  |              ||
    /\        -_|______________||
   /  \
  /    \
 /      \


1- Enter Cinema
2- Get a ticket
3- Leave
> 2

Your ticket : {"s": "0x82f654c827f93d5687273115817c50a5a5eb1f3f52d552a463319968622737864cd35d08ea1aeff856a6d2cd339dfa596944056787254324a395e224369d7ba619300720197935fa763d7aeb88c7475365d43496f75fb1fe819dbc7315c03897235fe7f7ff77e249107ab4be0cee3ce23b9beaa09f9f46eb39db12df4186ef6e", "r": "0xdbada907e4cd5fea81984ba32c377669e55cb1a732a98dc1e61baaeb785293dbff0d963f2c09531ee24019aafb46d7b180547c6b9a9c35f4abf227f08a159a19a90e49c076356ab53693d5ff4e387d780c2c5324e95ed20d33027685cd7c6ac54b692680eb2ec6779468e08e96d28a4dfcadee27437685d0ca5ba41194462a0f", "p": "0xddb53eca8ab4c59353a59b59f40f874105fa5ae78c8b6d0933f1c9fc068c2932e90d564e37f6db498e0c704e91253b5873480ca505907f2ec01ce56342e30248479501c7171c410c575bef1c0d00cf48ca98a968f786a42826e9ec59f8016e09d9421a7e41237831e92b0cb492b786609e53a5b4341e9ad87535a758fbd2b997"}

1- Enter Cinema
2- Get a ticket
3- Leave
> 1

Enter the magic word : {"s": "0x66f451a57a0aaa9fa295a49c3829d403deea227b165d94d6d20be8e05a450fa2a2ea7c1ccdbf4f2ef3662462ea6869fd561e2a211977197d9c6e0fc307e3b2b5a684dc615bb7838abe501008853dbf1d14a606fe690274cbecf400ad7a5a1dd54d36bfce7a5a4ebada04be4ea53369b0f2dd631e4c7455d8126ea9023c64c3af", "r": "0x423eb22fbcbce63aa40ea60bc79dd106819b083a2dd6aaf14f36cb8fa3162d94e473bae5b269fb886ec544ddf749433411aaad7efd1a4e8b7d76e889f414720136e75cc6948de85e27f57a925fa2a94ed5951205eb5c2c23bb1a134e3e8ef3df58a60d4bdc5fdff9085982ddcb1c4caf661b8f2b9e7a6fa29cf85fb15f8a851", "p": "0xddb53eca8ab4c59353a59b59f40f874105fa5ae78c8b6d0933f1c9fc068c2932e90d564e37f6db498e0c704e91253b5873480ca505907f2ec01ce56342e30248479501c7171c410c575bef1c0d00cf48ca98a968f786a42826e9ec59f8016e09d9421a7e41237831e92b0cb492b786609e53a5b4341e9ad87535a758fbd2b997"}
Here is your flag : FwordCTF{4ct_l1k3_a_V1P_4nd_b3c0m3_a_V1P}, enjoy the movie sir.
FwordCTF{4ct_l1k3_a_V1P_4nd_b3c0m3_a_V1P}