CakeCTF 2023 Writeup

この大会は2023/11/11 14:00(JST)~2023/11/12 14:00(JST)に開催されました。
今回もチームで参戦。結果は1617点で729チーム中18位でした。
自分で解けた問題をWriteupとして書いておきます。

Welcome (welcome)

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

CakeCTF{hav3_s0m3_cak3_t0_r3fr3sh_y0ur_pa1at3}

simple signature (crypto, warmup)

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

・p: 512ビット素数
・g = 2
・magic_word = "cake_does_not_eat_cat"
・skey, vkey = keygen()
 ・x: ランダム2以上p-1未満整数
 ・y: ランダム2以上p-1未満整数
 ・w: ランダム2以上p-1未満整数
 ・v = w * y % (p-1)
 ・vとp-1は互いに素でなければ、最初からやり直し
 ・u = (w * x - 1) * inverse(v, p-1) % (p-1)
 ・(x, y, u), (w, v)を返却
・p, g, vkeyを表示
・以下繰り返し
 ・choice: 入力
 ・choiceが"S"の場合
  ・message: 入力(magic_wordと同じ文字列はNG)
  ・sig = sign(h(message), skey)
   ・m: messageのsha512の数値化
   ・x, y, u = skey
   ・r: ランダム2以上p-1未満整数
   ・pow(g, x*m + r*y, p), pow(g, u*m + r, p)を返却
  ・sigを表示
 ・choiceが"V"の場合
  ・message: 入力
  ・s: 入力(2以上p-1未満)
  ・t: 入力(2以上p-1未満)
  ・verify(h(message), (s, t), vkey)でTrueの場合フラグを表示
   ・w, v = vkey
   ・pow(g, h(message), p) == pow(s, w, p) * pow(t, -v, p) % pを返却

Verifyの等式が成り立つように適当なsを決めて、まずpow(t, v, p)を求める。

pow(t, v, p) = pow(s, w, p) * pow(g, h(message), p)

これをCをとすると、RSA暗号の復号方法で復号する。

phi = p - 1
d = inverse(v, phi)
t = pow(C, d, p)

あとはこれらのデータをVerifyで渡してやればフラグが表示される。

#!/usr/bin/env python3
import socket
from hashlib import sha512
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 h(m):
    return int(sha512(m.encode()).hexdigest(), 16)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('crypto.2023.cakectf.com', 10444))

data = recvuntil(sock, b'\n').rstrip()
print(data)
p = int(data.split(' = ')[1])
data = recvuntil(sock, b'\n').rstrip()
print(data)
g = int(data.split(' = ')[1])
data = recvuntil(sock, b'\n').rstrip()
print(data)
w, v = eval(data.split(' = ')[1])

magic_word = 'cake_does_not_eat_cat'

s = 2

C = pow(s, w, p) * inverse(pow(g, h(magic_word), p), p) % p
d = inverse(v, p - 1)
t = pow(C, d, p)

data = recvuntil(sock, b': ')
print(data + 'V')
sock.sendall(b'V\n')
data = recvuntil(sock, b': ')
print(data + magic_word)
sock.sendall(magic_word.encode() + b'\n')
data = recvuntil(sock, b': ')
print(data + str(s))
sock.sendall(str(s).encode() + b'\n')
data = recvuntil(sock, b': ')
print(data + str(t))
sock.sendall(str(t).encode() + b'\n')
data = recvuntil(sock, b'\n').rstrip()
print(data)
data = recvuntil(sock, b'\n').rstrip()
print(data)

実行結果は以下の通り。

p = 10456129348987557481254996071034143684530960425708760133818260187442073806339593567912840424398419865326063623147020711301242111753917733144639229108892563
g = 2
vkey = (447684014132427783133530646893568737263009822339544169775893952481211373586359060289482466832827091848685967445885854739716253040131700049201573721550677, 8075783722883597733009746086093828558359534391870759327760217320597468198350900837320339144395157219846639434878101311943221115540741827787657995188823465)
[S]ign, [V]erify: V
message: cake_does_not_eat_cat
s: 2
t: 6373152920261135618370503836807163046160391795156781879397274826756542381074926856233876326654834958417915167176491140943842127470211792996717173254879234
verified
flag = CakeCTF{does_yoshiking_eat_cake_or_cat?}
CakeCTF{does_yoshiking_eat_cake_or_cat?}

ding-dong-ting-ping (crypto)

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

・KEY: ランダム16バイト文字列
・IV: ランダム16バイト文字列
・aes: 鍵KEYを使ったAES-ECB暗号オブジェクト
・以下繰り返し
 ・choice: 入力
 ・choiceが1の場合
  ・register()
   ・username: base64で入力→デコード
   ・usernameに"root"が含まれていたら、NG
   ・cookie = "<PREFIX>|user=<username>|YYYY-MM-DD hh:mm:ss.xxxxxx"
   ・cookie = encrypt(cookie)
    ・plain: cookieをパディング
    ・blocks: plainをブロックごとの配列にしたもの
    ・ciphers = [IV]
    ・blocksの各blockについて以下を実行する。
     ・block: blockとciphers[-1]のmd5とのXOR
     ・blockをAES暗号化したものをciphersに追加
    ・ciphersを連結したものを返却
   ・cookie: cookieをbase64エンコードしたもの
   ・cookieを表示
 ・choiceが2の場合
  ・login()
   ・cookie: 入力
   ・cookie = decrypt(b64decode(cookie))
    ・cipher: cookieをbase64デコードしたもの
    ・blocks: cipherをブロックごとの配列にしたもの
    ・h = md5(blocks[0]).digest()
    ・plains = []
    ・blocksの2個目以降の各blockについて以下を実行する。
     ・plainsにblockを復号したものとhをXORしたものを追加
     ・h = md5(block).digest()
    ・plainsを連結し、アンパッドしたものを返却
   ・data = cookieを"|"区切りの配列にしたもの
   ・data[0]がPREFIXでdata[1]が"user="から始まる場合
    ・username: data[1]の"="区切りの2個目の文字列
    ・time: data[2]
   ・data[0]がPREFIXでない、またはdata[1]が"user="から始まらない場合
    ・認証失敗メッセージを表示して、ログイン処理は終了
   ・usernameとtimeを表示
   ・usernameが"root"の場合、フラグを表示

PREFIXの長さを調べる。

$ nc crypto.2023.cakectf.com 11111
===== MENU =====
[1]register [2]login: 1
username(base64): YQ==
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG1f61JT1JDR1MbtmoHS+StYRCqnu5gTslUG6z183w5LTHWTrAuhw9cce31/V0SWElw=

===== MENU =====
[1]register [2]login: 1
username(base64): YWE=
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG0kJ5ZLYV8vNCdmxf7B/Z8XytrtqLtjBpNQtYPplw86jBlFyUiIzE3rw/CqD2DgvWo=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFh
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG19ZS6ASjp0lC1NXPbWmwG1I8wgFh2zHNXoJEKY7gDFLr0N+c6zlKG+/IqdFu3ollw=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYQ==
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG1AXH1BvwFwcEIArdqBhn1Fmfbb8Z0d96XAQmjYFM0iVNHII/9PiTaruEUt8tBIiyo=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWE=
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG2pWvrTrc3Y21NNmtYWn7vQ0APqLKL+2TisGce2PYrJGZzBOnB3y0lKfoP5ackgk50=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFh
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3HF2sCx8+zOhQam82sIajxPaXgJMietq+yNVKAEHgs+GR4V53HOJRNmPu5Nh3k5k0=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYQ==
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG1jMihl9+0XLRa8MnwX2YavJ44Tl+1orpME2kjvuipz5QCOVfX5WieaOBk6b/gsg+U=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWE=
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG1WSz6CqKY/3o8d6e430YamQLZpqhfjEKegUFW1VNwz1Kef9puZbQAonNqrJGtJFh0=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFh
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6Dp+OGXkUcfH5VWxwnL7nUh6EkYGE5VNknYV36jQApwIU=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFhYQ==
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6Dr0mc3k4tRWL/aU9P0U2S+tyQBqxuv2QgzcoSPKwo+20=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFhYWE=
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6DWHlo/vwaD1j/+rrpvXH/WrsgQ2O2cUMz4fCnaBMQzVo=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFhYWFh
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6DoVhpSGIX4Hb6hcgEJif2ZcNIh08U6g5DUSkheXva45c=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFhYWFhYQ==
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6DIApsaAZIeoP7rcNx1VA9IfxSsCqJhM7Rcp/kf1VLdp4=

===== MENU =====
[1]register [2]login: 1
username(base64): YWFhYWFhYWFhYWFhYWE=
your cookie => 5qR1+GZFJtyHTu+UCyKlnWrDhvz9OTuZW5igiAzscG3k/t1ykAROWQUjim/7Yh6DLQTcyR2/PMVXcC6Z3iR4AvMvit2PZhT+oEtHCXTsuldyW7RzSlvD9PS/RVuFDk1T

base64デコードしたusernameが14バイトのときに暗号化データをbase64デコードしたものがIVを含め96バイトになった。
"H"をPREFIX、"P"をパディング文字列と定義すると、以下のようなブロック構成のイメージになる。

    0123456789abcdef
PT0 HHHHHHHHHHHHHHHH
PT1 H|user=aaaaaaaaa
PT2 aaaaa|2023-11-12
PT3  hh:mm:ss.xxxxxx
PT4 PPPPPPPPPPPPPPPP

PT0 ^ md5(IV)  --(AES暗号)--> CT0
PT1 ^ md5(CT0) --(AES暗号)--> CT1
PT2 ^ md5(CT1) --(AES暗号)--> CT2
PT3 ^ md5(CT2) --(AES暗号)--> CT3
PT4 ^ md5(CT3) --(AES暗号)--> CT4

作りたい暗号は、例えば以下のブロック構成になる平文に対するもの。

    0123456789abcdef
PT0 HHHHHHHHHHHHHHHH
PT1 H|user=root|202P

rootは暗号化に指定できないので、roosで暗号化する。

    0123456789abcdef
PT0 HHHHHHHHHHHHHHHH -> CT0
PT1 H|user=roos|2023 -> CT1
PT2 -11-12 hh:mm:ss. -> CT2
PT3 xxxxxxPPPPPPPPPP -> CT3
PT0 ^ md5(IV)  --(AES暗号)--> CT0

この部分はくずせない。以下の形にしたい。

    0123456789abcdef
PT0 HHHHHHHHHHHHHHHH -> CT0
PTA H|user=root|202P -> CTa

PTBの値について以下のようになるようなものを作る。

PTA ^ md5(CT0) = PTB ^ md5(CT1)

以下のブロック構成にして、暗号化する。

    0123456789abcdef
PT0 HHHHHHHHHHHHHHHH -> CT0
PT1 H|user=roos|202P -> CT1
PTB ???????????????? -> CTb
PTC |2023-11-12 hh:m -> CTc
PTD m:ss.xxxxxxPPPPP -> CTd

この暗号結果から、IV + CT0 + CTbを指定し復号すれば、HHHHHHHHHHHHHHHHH|user=roos|202 になり、ログインできるはず。ただし、PT1の1バイト目は不明のため、ブルートフォースで復号する。

#!/usr/bin/env python3
import socket
from base64 import b64decode, b64encode
from hashlib import md5
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)

real_username = b'root'
dummy_username = b'roos'
b64_dummy_username = b64encode(dummy_username).decode()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.2023.cakectf.com', 11111))

data = recvuntil(s, b': ')
print(data + '1')
s.sendall(b'1\n')
data = recvuntil(s, b': ')
print(data + b64_dummy_username)
s.sendall(b64_dummy_username.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
ct = b64decode(data.split(' ')[-1])

ct_blocks = [ct[i:i+16] for i in range(0, len(ct), 16)]

for code in range(32, 127):
    pta = bytes([code]) + b'|user=root|202\x01'
    key0 = md5(ct_blocks[1]).digest()
    key1 = md5(ct_blocks[2]).digest()
    ptb = strxor(strxor(pta, key0), key1)

    try_username = b'roos|2023' + ptb
    b64_try_username = b64encode(try_username).decode()
    data = recvuntil(s, b': ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b': ')
    print(data + b64_try_username)
    s.sendall(b64_try_username.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    ct = b64decode(data.split(' ')[-1])

    ct_blocks = [ct[i:i+16] for i in range(0, len(ct), 16)]

    cookie = b''.join(ct_blocks[:2] + [ct_blocks[3]])
    cookie = b64encode(cookie).decode()
    data = recvuntil(s, b': ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b': ')
    print(data + cookie)
    s.sendall(cookie.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if 'unsuccessful' not in data:
        break

data = recvuntil(s, b'\n').rstrip()
print(data)
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vcw==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vtWFwAo5tcXZ4Q8MeplSFD8+XCeHeYudix1bPnxLj+pkg=

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIz6whoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vtBum2woJgdEV9L4qc7NULbw4I99nGMu2uFqjKXPERaMeHYmDes6N9D/7Tsol01I7G

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmYG6bbCgmB0RX0vipzs1Qtv
Authentication unsuccessful...

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIz6ghoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vtZXqMefkT5H6bbVwFX9VEK9hU/r7QNdbQFWeWv3DCl0Cjut3vtZE7MUe5RPpQMzE9

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmZleox5+RPkfpttXAVf1UQr
Authentication unsuccessful...

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIz6QhoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vt6aTON+SbcWu6aMVl8J8eSG3rJlMTXGpNYoAio8f+4CzYs9R71s+aq/mngH2yjQum

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmbppM435Jtxa7poxWXwnx5I
Authentication unsuccessful...

        :
        :

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIziQhoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vti4xWOzOCdY3mGr+r21ztMa4KDDzlyDfr4kf8kWB3wphpZCxLp38ffuqaRwix5Mgu

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaLjFY7M4J1jeYav6vbXO0x
Authentication unsuccessful...

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIziAhoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vtEPS/ZBbSlO307a6cut8oTpP1TYmeIAmPqeA4Lq3+fDE4soy17RW2zhGHpUAaKr8w

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmYQ9L9kFtKU7fTtrpy63yhO
Authentication unsuccessful...

===== MENU =====
[1]register [2]login: 1
username(base64): cm9vc3wyMDIzjwhoLcNIiupkzFEYm3N8mA==
your cookie => TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmaJ3HFAAdILXhA8zuS6n7vt16JnvbUEQJEIXKJqEcnhEF64mst2xL8jFgXbEGcVLG8ulRzxGjc6H7gEZn2r6GyT

===== MENU =====
[1]register [2]login: 2
cookie: TQh+VTNmPnEIRbTcpgmJpUcAcETB+LojSJRCgK2qqmbXome9tQRAkQhcomoRyeEQ
Hi, root! [registered at 202]
Ding-Dong, Ding-Dong, Welcome, root. The ultimate authority has logged in.
This is for you =>  CakeCTF{dongdingdongding-dingdong-dongdingdong-ding}
CakeCTF{dongdingdongding-dingdong-dongdingdong-ding}

Survey (survey)

アンケートに答えたら、フラグが表示された。

CakeCTF{thank_y0u_4_tasting_0ur_n3w_cak3s_this_y3ar}