Home cakectf 2023
Post
Cancel

cakectf 2023

cakectf 2023

BunkyoWesternsに入れてもらったのでチームでやってました。

色々名言が生まれたので楽しかったです。

後買ってきたケーキをみんなで食べましたまる。

kanon on X: “cakectf終わったのでケーキ食べます https://t.co/Kj9xOJZACy” / X (twitter.com)

simple signature 88 solve

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import os
import sys
from hashlib import sha512
from Crypto.Util.number import getRandomRange, getStrongPrime, inverse, GCD
import signal


flag = os.environ.get("FLAG", "neko{cat_does_not_eat_cake}")

p = getStrongPrime(512)
g = 2
p = 11551622313922707550008678743003601832188036792175262302397935776595208393670456612224185992217183071863853189220628487330346803009314560368938395241129573

def keygen():
    while True:
        x = getRandomRange(2, p-1)
        y = getRandomRange(2, p-1)
        w = getRandomRange(2, p-1)
        v = w * y % (p-1)
        if GCD(v, p-1) != 1:
            continue
        u = (w * x - 1) * inverse(v, p-1) % (p-1)
        return (x, y, u), (w, v)


def sign(m, key):
    x, y, u = key
    r = getRandomRange(2, p-1)

    return pow(g, x*m + r*y, p), pow(g, u*m + r, p)


def verify(m, sig, key):
    w, v = key
    s, t = sig

    return pow(g, m, p) == pow(s, w, p) * pow(t, -v, p) % p


def h(m):
    return int(sha512(m.encode()).hexdigest(), 16)


if __name__ == '__main__':
    magic_word = "cake_does_not_eat_cat"
    skey, vkey = keygen()

    print(f"p = {p}")
    print(f"g = {g}")
    print(f"vkey = {vkey}")

    signal.alarm(1000)

    while True:
        choice = input("[S]ign, [V]erify: ").strip()
        if choice == "S":
            message = input("message: ").strip()
            assert message != magic_word

            sig = sign(h(message), skey)
            print(f"(s, t) = {sig}")

        elif choice == "V":
            message = input("message: ").strip()
            s = int(input("s: ").strip())
            t = int(input("t: ").strip())

            assert 2 <= s < p
            assert 2 <= t < p

            if not verify(h(message), (s, t), vkey):
                print("invalid signature")
                continue

            print("verified")
            if message == magic_word:
                print(f"flag = {flag}")
                sys.exit(0)

        else:
            break

solve

基本的に、公開鍵と秘密鍵は何か数学的な関連性が必要であるから解けないわけで、今回は$x,u$が公開鍵にしか使われていないため、なんでもおkとなる

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from pwn import *
from Crypto.Util.number import getRandomRange, getStrongPrime, inverse, GCD
from hashlib import sha512

io = remote("crypto.2023.cakectf.com" ,10444)
# io = process(["python3","server.py"])
p = int(io.recvline().decode().split("= ")[1])
g = int(io.recvline().decode().split("= ")[1])
w,v = eval(io.recvline().decode().split("= ")[1])
y = (v * pow(w,-1,p-1))%(p-1)
x = getRandomRange(2, p-1)
magic_word = "cake_does_not_eat_cat"
u = ((w * x - 1) * inverse(v, p-1)) % (p-1)



def h(m):
    return int(sha512(m.encode()).hexdigest(), 16)

def sign(m, key):
    x, y, u = key
    r = getRandomRange(2, p-1)

    return pow(g, x*m + r*y, p), pow(g, u*m + r, p)
def verify(m, sig, key):
    w, v = key
    s, t = sig
    print(pow(g, m, p) , pow(s, w, p) * pow(t, -v, p) % p)
    return pow(g, m, p) == pow(s, w, p) * pow(t, -v, p) % p

# sig = sign(h("message"),  (x, y, u), )
# print( verify(h("message"), sig, (w, v)))
# exit()

s,t = sign(h(magic_word), (x, y, u))
io.sendlineafter(b": ",b"V")
io.sendlineafter(b": ",b"cake_does_not_eat_cat")
io.sendlineafter(b": ",str(s).encode())
io.sendlineafter(b": ",str(t).encode())
io.interactive()
# io.sendlineafter(b": ",str(t).encode())
# CakeCTF{does_yoshiking_eat_cake_or_cat?}

janken vs yoshiking 2 43 solve

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import random
import signal
import os

HANDNAMES = {
    1: "Rock",
    2: "Scissors",
    3: "Paper"
}

def commit(M, m):
    while True:
        r = random.randint(2, 2**256)
        if r % 3 + 1 == m:
            break
    return M**r, r


signal.alarm(1000)

flag = os.environ.get("FLAG", "neko{old_yoshiking_never_die,simply_fade_away}")
p = 1719620105458406433483340568317543019584575635895742560438771105058321655238562613083979651479555788009994557822024565226932906295208262756822275663694111
M = random_matrix(GF(p), 5)
print("[yoshiking]: Hello! Let's play Janken(RPS)")
print("[yoshiking]: Here is p: {}, and M: {}".format(p, M.list()))

round = 0
wins = 0
while True:
    round += 1
    print("[system]: ROUND {}".format(round))

    yoshiking_hand = random.randint(1, 3)
    C, r = commit(M, yoshiking_hand)
    print("[yoshiking]: my commitment is={}".format(C.list()))

    hand = input("[system]: your hand(1-3): ")
    print("")
    try:
        hand = int(hand)
        if not (1 <= hand <= 3):
            raise ValueError()
    except ValueError:
        print("[yoshiking]: Ohhhhhhhhhhhhhhhh no! :(")
        exit()

    print("[yoshiking]: My hand is ... {}".format(HANDNAMES[yoshiking_hand]))
    print("[yoshiking]: Your hand is ... {}".format(HANDNAMES[hand]))
    result = (yoshiking_hand - hand + 3) % 3
    if result == 0:
        print("[yoshiking]: Draw, draw, draw!!!")
        print("[yoshiking]: I'm only respect to win!")
        print("[system]: you can check that yoshiking doesn't cheat")
        print("[system]: here's the secret value: {}".format(r))
        exit()
    elif result == 1:
        print("[yoshiking]: Yo! You win!!! Ho!")
        wins += 1
        print("[system]: wins: {}".format(wins))

        if wins >= 100:
            break
    elif result == 2:
        print("[yoshiking]: Ahahahaha! I'm the winnnnnnner!!!!")
        print("[yoshiking]: You, good loser!")
        print("[system]: you can check that yoshiking doesn't cheat")
        print("[system]: here's the secret value: {}".format(r))
        exit()

print("[yoshiking]: Wow! You are the king of roshambo!")
print("[yoshiking]: suge- flag ageru")
print(flag)

solve

matrixでのDDH仮定みたいなものを判定しろってことみたいです。ただ今回は弱めで$M^t = Y$での$t$が3で割ったあまりがいくつになるかですね。

pがb-smoothなので固有値計算してdlpに持ち込もうとしたけど、固有値がなくどうしたものかと思っていたら、前に自作したもので絶対値に落としてdlpができることを思い出し(一部失敗するかもしれないが)やってみたら解けた。

solve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
from tqdm import tqdm
p = 1719620105458406433483340568317543019584575635895742560438771105058321655238562613083979651479555788009994557822024565226932906295208262756822275663694111

# io = process(["sage","server.sage"])
io = remote("crypto.2023.cakectf.com" ,"10555")
io.recvline()
M = eval(io.recvline().decode().split(": ")[-1])
M = matrix(GF(p),[[M[i+k*5] for i in range(5)] for k in range(5)])

Mdet = GF(p)(M.det())

for i in tqdm(range(100)):
    io.recvuntil(b"[system]: R")
    io.recvline()
    yoshi = eval(io.recvline().decode().split("=")[1])
    yoshi = matrix(GF(p),[[yoshi[i+k*5] for i in range(5)] for k in range(5)])
    
    yoshi_det = yoshi.det()

    nad = discrete_log(GF(p)(yoshi_det), Mdet)    

    if int((nad)%3)+1==1:
        io.sendlineafter(b": ",str(int(3)).encode())
    if int((nad)%3)+1==2:
        io.sendlineafter(b": ",str(int(1)).encode())
    if int((nad)%3)+1==3:
        io.sendlineafter(b": ",str(int(2)).encode())
    
io.interactive()    
        
# CakeCTF{though_yoshiking_may_die_janken_will_never_perish}

ding-dong-ting-ping 17 solve

chall

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
import os
from base64 import b64decode, b64encode
from hashlib import md5
from datetime import datetime
from Crypto.Cipher import AES

FLAG = os.environ.get("FLAG", "neko{cat_does_not_eat_cake}")
PREFIX = os.environ.get("PREFIX", "cakecakecakecake1").encode()

KEY = os.urandom(16)
IV = os.urandom(16)

aes = AES.new(KEY, AES.MODE_ECB)

xor = lambda a, b: bytes([x^y for x, y in zip(a, b)])

def pad(data: bytes):
    l = 16 - len(data) % 16
    return data + bytes([l]*l)

def unpad(data: bytes):
    return data[:-data[-1]]

def encrypt(plain: bytes):
    plain = pad(plain)
    blocks = [plain[i:i+16] for i in range(0, len(plain), 16)]
    ciphers = [IV]
    for block in blocks:
        block = xor(block, md5(ciphers[-1]).digest())
        ciphers.append(aes.encrypt(block))
    return b"".join(ciphers)

def decrypt(cipher: bytes):
    blocks = [cipher[i:i+16] for i in range(0, len(cipher), 16)]
    h = md5(blocks[0]).digest() # IV
    plains = []
    for block in blocks[1:]:
        plains.append(xor(aes.decrypt(block), h))
        h = md5(block).digest()
    return unpad(b"".join(plains))    

def register():
    username = b64decode(input("username(base64): ").strip())
    if b"root" in username:
        print("Cannot register as root user!")
    else:
        cookie = b"|".join([PREFIX, b"user="+username, str(datetime.now()).encode()])
        cookie = encrypt(cookie)
        cookie = b64encode(cookie)
        print("your cookie =>", cookie.decode())
    return

def login():
    cookie = input("cookie: ").strip()
    cookie = decrypt(b64decode(cookie))
    data = cookie.split(b"|")
    if (data[0] == PREFIX) and data[1].startswith(b"user="):
        username = data[1].split(b"=")[1]
        time = data[2]
    else:
        print("Authentication unsuccessful...")
        return
    print(f"Hi, {username.decode()}! [registered at {time.decode()}]")
    if username != b"root":
        print("You're not the root user...")
    else:
        print("Ding-Dong, Ding-Dong, Welcome, root. The ultimate authority has logged in.")
        print("This is for you => ", FLAG)
    return

while True:
    print("===== MENU =====")
    choice = int(input("[1]register [2]login: ").strip())
    if choice == 1:
        register()
    elif choice == 2:
        login()
    else:
        print("Invalid choice")
    print()

solve

AES-CBCのxor演算にmd5を用いてるもので、エラーを吐かない限りいくらでもoracleは使える。

さらに、timeの部分はcheckされてないことからうまく復元されればなんでも表示してくれる。けど、使わなかった…orz

方針として任意の暗号文を暗号化してくれるoracleを構成し、そこから暗号文を組みたてるという作業をおこなえばおk

問題としては、prefixの長さによってメンドサが変わるが今回は運よく、17文字?だったので1文字をブルートフォースして求めればいいことになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import os
from base64 import b64decode, b64encode
from hashlib import md5
from datetime import datetime
from Crypto.Cipher import AES
from tqdm import tqdm
from pwn import *
from itertools import product
Print = str
context.log_level = "debug"


def encrypt(io,m):
    io.sendlineafter(b": ",b"1" )
    io.sendlineafter(b": ",b64encode(m))
    return b64decode(io.recvline(None).split(b"=> ")[1])

def decrypt(io,m):
    io.sendlineafter(b": ",b"2" )
    io.sendlineafter(b": ",b64encode(m))
    return io.recvline(None).decode()

def ecb_oracle(io,m):
    user = b"A"*9 + b"A"*16
    ret = encrypt(io,user)
    b = md5(ret[3*16:4*16]).digest()
    ret = encrypt(io,user + xor(b,m))
    return ret[4*16:5*16]
    


io = remote("crypto.2023.cakectf.com" ,"11111")
# io = process(["python3","server.py"])

enc_PREFIX = encrypt(io,b"1")[:2*16]
ret = encrypt(io,b"AAAAAAAAA")[2*16:3*16]
for i in range(256):
    if ret == ecb_oracle(io,xor(bytes([i]) + b"|user=AAAAAAAAA", md5(enc_PREFIX[-16:]).digest())):
        print(i)
        break
else:
    print("NOT found")
    exit()

pad = b"\x01"*16
body1 = bytes([i])+b"|user=root|1234"
body1 = ecb_oracle(io,xor(body1, md5(enc_PREFIX[-16:]).digest()))
body2 = ecb_oracle(io,xor(pad, md5(body1).digest()))


aim = enc_PREFIX + body1+ body2
print(decrypt(io,aim))
io.interactive()
# CakeCTF{dongdingdongding-dingdong-dongdingdong-ding}
This post is licensed under CC BY 4.0 by the author.