😸

Cryptoverse 2023 writeup

2023/05/11に公開

0nePaddingのメンバーとして参加しました。非常に楽しいCTFでした。こういうのをまたやりたいです。

Crypto

Warmup 1

GmvfHt8Kvq16282R6ej3o4A9Pp6MsN
とても詰まった、CyberChefだけで頑張ろうとしたのが間違いで、Cipheyを使うと一撃で解ける。

Rot13->Base58をすると解けます。

Warmup 2

Felix Delastelleが考えたBifid cipher
Solverがオンラインにあるので使います。

https://www.dcode.fr/bifid-cipher
Cipherテキストを入力して、ヒントを行列に入れます。
CTFIS
GODAB
EHJKL
MNPQR
UVWXY

すると解けます。

Flag: CVCTFFUNBIFIDDECODING

Warmup 3

{}があるのに気が付くのと彳宀彳马辶{飞广口川彳门巾川彳大广彳飞彐大川巾}の初めの部分がおそらくcvcになっているのに着目します。
Subsutitute cipherが使えそうです。
pythonを使ってすべてアルファベットに直しました。

  
st = "女川弓彳己廾川 马己 马大川 口川彳己广巛 川巛飞马飞己广 己辶 彳巾山彐马己宀川巾口川 彳马辶. 弓艹口马 山川艹巾, 马川艹廾 廾艹彐弓川 屮艹彳己广 辶巾己廾 彳艹广艹巛艹 女己广 马大川 彳己廾彐川马飞马飞己广 屮山 口己弓宀飞广寸 艹弓弓 彳大艹弓弓川广寸川口. 飞辶 山己门 门广巛川巾口马己己巛 马大川 艹屮己宀川 彳大飞广川口川, 大川巾川 飞口 马大川 辶弓艹寸: 彳宀彳马辶{飞广口川彳门巾川彳大广彳飞彐大川巾}. 口门屮廾飞马 马大川 辶弓艹寸 飞广 弓己女川巾 彳艹口川."  

st = list(st)  
unique_characters = set(st)  
print(unique_characters)  
sp = ['}','{',' ','.',',']  
  
removed = list(set(sp) ^ set(unique_characters))  
    
alpha = "abcdefghijklmnopqrstuvwxyz"  
  
for i in range(len(removed)):  
    bf = removed[i]  
    af = alpha[i]  
    for j in range(len(st)):  
        if st[j] == bf:  
            st[j] = af  
    print(st)  
  
ans = ''.join(st)  
print(ans)

直した後の文字

ablfqgb hq hpb dbfqui bishsqu qv frcthqkbrdb fhv. ledh cber, hbeg getlb jefqu vrqg feueie aqu hpb fqgtbhshsqu jc dqlksun ell fpellbunbd. sv cqo ouibrdhqqi hpb ejqkb fpsubdb, pbrb sd hpb vlenm fkfhv{sudbforbfpufstpbr}. dojgsh hpb vlen su lqabr fedb.

https://quipqiup.com/
に入れます。

  
welcome to the second edition of cryptoverse ctf. last year, team maple bacon from canada won the competition by solving all challenges. if you understood the above chinese, here is the flagq cvctf{insecurechncipher}. submit the flag in lower case.

cvctf{insecurechncipher}がフラッグです。

Baby AES

かなり雑に解きました。

challenge.py

from secret import flag  
  
  
KEY_LEN = 2  
BS = 16  
key = pad(open("/dev/urandom","rb").read(KEY_LEN), BS)  
iv =  open("/dev/urandom","rb").read(BS)  
  
cipher = AES.new(key, AES.MODE_CBC, iv)  
ct = cipher.encrypt(pad(flag, 16))  
  
print(f"iv = {iv.hex()}")  
print(f"ct = {ct.hex()}")  
  
# Output:  
# iv = 1df49bc50bc2432bd336b4609f2104f7  
# ct = a40c6502436e3a21  
#      dd63c1553e481696  
#      7a75dfc0c7b90328  
#      f00af93f0094ed62

CBCモードのAESですね。普通に実装されているので解けなさそうです。
KEY_LENに注目すると実は2bytesをpaddingしたものをキーとして使っていることがわかります。
ブルートフォースで解けます。

Solve.py

from Crypto.Cipher import AES  
from Crypto.Util.Padding import pad  
import os  
  
KEY_LEN = 2  
BS = 16  

iv = bytes.fromhex("1df49bc50bc2432bd336b4609f2104f7")  
ct = bytes.fromhex("a40c6502436e3a21dd63c1553e4816967a75dfc0c7b90328f00af93f0094ed62")  

for i in range(100000):  
    result = os.urandom(2)  
    key = pad(result,BS)  
    cipher = AES.new(key, AES.MODE_CBC, iv)  
    m = cipher.decrypt(ct)  
    if b"cvctf" in m:  
        print(m)

b'cvctf{b4by_AES_s1mpL3}\n\n\n\n\n\n\n\n\n\n'がフラッグです。
面倒だったのでランダムに2バイト分作って投げ込みました。

Misc

Baby Calculator

pwn tools を使うのがあまり得意ではないので汚くてごめんなさい。
積分をsympyに解かせて値を提出しました。

from pwn import *  
import sympy as sy  
context.log_level ="debug"  
  
  
def prepare_ans(req):  
    eq = req.split(' ')  
  
    # given equation  
    ev = eq[-5]  
    ev = sy.parse_expr(eq[-5])  
  
    # integrate from  
    fm = int(eq[-3])  
  
    # to  
    to = int(eq[-1][:-2])  
    x = sy.Symbol("x")  
    result = sy.integrate(ev, (x, fm, to))  
  
    print("given equation:", ev)  
    print("from:", fm)  
    print("to:", to)  
  
    print("evaluated:", result)  
  
    submit = round(result.evalf(), 5)  
    print("float:", submit)  
    return submit  
  
  
p = remote('20.169.252.240', 4200)  
for i in range(2):  
    req = p.recvline()  
    print(req)  
  
for i in range(40):  
    req = str(p.recvuntil(b'.'))  
    submit = prepare_ans(req)  
    req = p.recvuntil(b'Answer:')  
    print(req)  
    #p.sendline(struct.pack('<f', submit))  
    p.sendline(str(submit))  
  
    req = p.recvline()  
    print(req)

b" Congrats! Here's your flag: cvctf{B4by_m@7h_G14n7_5t3P}\n"

Reset Options

画像が与えられます。なにかのガジェットらしいです。
よくわからないのでdelete apps, programs for singaporeとググったら出てきました。

Texas Instrumentsが出している計算機のバグのようです。
http://www.omnimaga.org/general-discussion/mem-clear-for-singapore/

2と8を同時押しして電源をつけるとバグるという話をしています。そうすると問題の画面が出てくるのでそれが答えです。

Flag: cvctf{TEXAS_INSTRUMENTS_2_8_ON}

Hoyoverse

非常に楽しい問題ばかりだった。

Hoyoverse 1

OSINT問題です。まず読める文字があるかどうか探しました。

  1. トラックの文字 HAYWARD
  2. Entrance 駐車場の黄色いところ
  3. Tarification 緑の恐らくは駐車場料金
  4. LOVE M? みたいな文字

しばらく悩んだ後で、フランスが関係ある???という話になったので英語かつフランス語の場所を探し始めました。
カナダあたりが怪しいので、ケベック、トロント、モントリオールあたりから探し始めました。

Montreal love meとgoogle 検索をすると
https://twitter.com/chrismtess/status/1163950493804224518
以上のtweetが出てきます。

ついでにyoutubeの動画も出てきます
https://www.youtube.com/watch?v=OdD7BGsInU4

Shot entirely on iphone on top of newcitygas rooftopと説明にあったのでそのあたりを探しました。

802 Ottawa St が答えです。

実はHoYoverse Canadaの目の前でした。

Hoyoverse 3 (解けなかったので指針のみ)

from Crypto.Util.number import *  
  
#from secret import FLAG  
  
assert FLAG[:6] == b'cvctf{'  
assert FLAG[-1:] == b'}'  
FLAG = FLAG[6:-1]  
assert len(FLAG) == 24  
  
class HoYoVault:  
    def __init__(self, u, v, w):  
        self.state = [u, v, w]  
        while True:  
            self.p = getPrime(64)  
            self.a = bytes_to_long(FLAG[:6])  
            self.b = bytes_to_long(FLAG[6:12])  
            self.c = bytes_to_long(FLAG[12:18])  
            self.d = bytes_to_long(FLAG[18:])  
            if self.p > max([self.a, self.b, self.c, self.d]):  
                break  
    def Generate(self):  
        data = (self.a * self.state[-1] + self.b * self.state[-2] + self.c * self.state[-3] + self.d) % self.p  
        self.state.append(data)  
        return data  
  
def main():  
    vault = HoYoVault(getRandomInteger(128), getRandomInteger(256), getRandomInteger(512))  
    print("data = " + str([vault.Generate() for _ in range(7)]))  
    print("p = " + str(vault.p))  
  
if __name__ == "__main__":  
    main()   
  
# data = [14169084828739113416, 12950362233651727953, 13081576751296291893, 11189892724250189745, 2366046383900978737, 1749792629103627315, 8575562236709928474]  
# p = 16200480981168924301

Flagを4つに分解、素数を適当に一つ準備します。
さらに、stateというリストに適当に作った数字をあらかじめ3つ入れておきます。
stateの値とflagの値をかけたものに素数でmodを取ったものをstateに追加します。
これを何回か繰り返すのですが、stateの値はstateリストの最後の3つであることがわかるので、7回繰り返されたころには最初の3つは完全に使用されないことがわかります。

data,pが与えられていることからフラグの値を逆算することができることがわかりました。

連立方程式をたてて解けば終わりなのですが、そのsolverを準備することができず解けませんでした。

途中まで頑張ったもの

以下が途中まで頑張ったものです。

from Crypto.Util.number import *  
import sympy as sy  
print(long_to_bytes(1738116809345971914))  
f0,f1,f2,f3 = sy.symbols("f0,f1,f2,f3")  
d = [14169084828739113416, 12950362233651727953, 13081576751296291893, 11189892724250189745, 2366046383900978737, 1749792629103627315, 8575562236709928474]  
p = 16200480981168924301  
  
eq = (f0*d[2]+f1*d[1]+f2*d[0]+f3)%p-d[3]  
eq2 = (f0*d[3]+f1*d[2]+f2*d[1]+f3)%p-d[4]  
eq3 = (f0*d[4]+f1*d[3]+f2*d[2]+f3)%p-d[5]  
eq4 = (f0*d[5]+f1*d[4]+f2*d[3]+f3)%p-d[6]  
  
#sol = sy.solve([eq,eq2,eq3,eq4],[f0,f1,f2,f3])  
#print(sol)  
print(eq)  
print(eq2)  
print(eq3)  
print(eq4)

Discussion