vsCTF 2024 writeup

2024/06/17に公開

SECCON に時間を費やしていたので、あんまり時間が使えなかったけれども、十分気休めになった。

crypto/not-quite-caesar [595 solves]

Caesar??? Couldn't be this!

import random
random.seed(1337)
ops = [
lambda x: x+3,
lambda x: x-3,
lambda x: x*3,
lambda x: x^3,
]

out = []
for v in flag:
out.append(random.choice(ops)(v))
print(out)

[354, 112, 297, 119, 306, 369, 111, 108, 333, 110, 112, 92, 111, 315, 104, 102, 285, 102, 303, 100, 112, 94, 111, 285, 97, 351, 113, 98, 108, 118, 109, 119, 98, 94, 51, 56, 159, 50, 53, 153, 100, 144, 98, 51, 53, 303, 99, 52, 49, 128]


ランダムのシード値が公開されているので、同じシードをセットし、opsの逆操作をすればいい。

import random
random.seed(1337)

ops = [
lambda x: x-3,
lambda x: x+3,
lambda x: x//3,
lambda x: x^3,
]

out = [354, 112, 297, 119, 306, 369, 111, 108, 333, 110, 112, 92, 111, 315, 104, 102, 285, 102, 303, 100, 112, 94, 111, 285, 97, 351, 113, 98, 108, 118, 109, 119, 98, 94, 51, 56, 159, 50, 53, 153, 100, 144, 98, 51, 53, 303, 99, 52, 49, 128]

flag = []

for v in out:
flag.append(random.choice(ops)(v))

print(''.join( chr(c) for c in flag))


vsctf{looks_like_ceasar_but_isnt_a655563a0a62ef74}

crypto/dream [104 solves]

I hear python MT can be broken with 624 outputs, but I only really need 8 random numbers. Surely you can't break it... right?

chall.py
#!/usr/local/bin/python
if __name__ != "__main__":
raise Exception("not a lib?")

from os import urandom
# check if seed.txt exists
try:
except:
seed = urandom(8)
open("seed.txt", "wb").write(seed)

seed = int.from_bytes(seed, "big")
import random
random.seed(seed)
from ast import literal_eval
idxs = literal_eval(input(">>> "))
if len(idxs) > 8:
print("Ha thats funny")
exit()
for idx in range(624):
rand_out = random.getrandbits(32)
if idx in idxs:
print(rand_out)

key = random.getrandbits(256)
nonce = random.getrandbits(256)
from Crypto.Cipher import AES
from hashlib import sha256
aes_key = sha256(str(key).encode()).digest()[:16]
aes_nonce = sha256(str(nonce).encode()).digest()[:16]
cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)
print(ct.hex())


pythonのrandomはメルセンヌツイスターが使われている。 624 個連続した乱数を手に入れることが出来ればシードを特定できる。

ただ、実験していて気付いたが、今回の場合もシード値が実質固定されている（何回も同じ要素を確認してみるとわかる）ため、\frac{624}{8} 回かけて連続した乱数を手に入れることによって、シード値を求めることができる。

シード値の特定計算はこのサイトを利用した。

from pwn import remote
import struct

HOST = 'vsc.tf'
PORT = 5001

def get_random_values(idxs):
conn = remote(HOST, PORT)
conn.recvuntil(b'>>> ')
conn.sendline(str(idxs).encode())
values = []
for _ in range(len(idxs)):
value = conn.recvline().strip()
values.append(int(value))
conn.close()
return values

xs1 = [None] * 624

for i in range(0, 624, 8):
idxs = list(range(i, i+8))
values = get_random_values(idxs)
for j, value in enumerate(values):
xs1[i + j] = value
mt_state = [untemper(x) for x in xs1]

flagの復元
import random
random.setstate((3, tuple(mt_state + [624]), None))

key = random.getrandbits(256)
nonce = random.getrandbits(256)

from Crypto.Cipher import AES
from hashlib import sha256

ct_hex = "cb3cc14d2f5eeac6b5645bb66fa268d88399d1654df20668110e1d04bf135db71930985b5eba307c0197b035f2e9203f"

aes_key = sha256(str(key).encode()).digest()[:16]
aes_nonce = sha256(str(nonce).encode()).digest()[:16]

ct = bytes.fromhex(ct_hex)

cipher = AES.new(aes_key, AES.MODE_GCM, nonce=aes_nonce)

print(pt.decode())


vsctf{dream_luck???_5e3ec2f2d338fc9f}

algo/baby-game [345 solves]

$N \times N$ の2次元平面上でプレイヤーA,Bがいる。片方がもう片方を囲めるように最適に動くので、どちらが囲めるかどうかを判定せよ。（みたいな感じの問題）

#include<bits/stdc++.h>
using namespace std;

int main(){
int n;
cin >> n;
int x1, y1;
int x2, y2;
cin >> x1 >> y1 >> x2 >> y2;
cout << ((abs(x1-x2)+abs(y1-y2))%2 ? "Caring Koala" : "Red Panda") << endl;
}


こういう系は結局座標の偶奇でどうにかなるよなーと思いながらガチャガチャしていた。証明は気が向いたら。

flagメモり忘れました。