MtSakaのブログ

競技プログラミングをします

SECCON Beginners CTF 2022 Crypto Writeup

SECCON Beginners CTF 2022に参加しました。CryptoはHardの一問以外解けたのでWriteupを雑に書こうかなと思ったので書いてる。

Coughing Fox

problem.pyが与えられて解読する。正直やるだけ。

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)

テキトーに一文字ごとに全探索すればいい。

import gmpy2
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:
    x=gmpy2.iroot(c-i, 2)
    if x[0]*x[0]==c-i:
      flag += chr(x[0]-i)
      break
print(flag)

できたー!

PrimeParty

server.pyが与えられて通信してflagあてる感じ。 デカい素数出力してやればいい。

from Crypto.Util.number import *
from secret import flag
from functools import reduce
from operator import mul

bits = 256
flag = bytes_to_long(flag.encode())
assert flag.bit_length() == 455

GUESTS = []

def invite(p):
    global GUESTS
    if isPrime(p):
        print("[*] We have been waiting for you!!! This way, please.")
        GUESTS.append(p)
    else:
        print("[*] I'm sorry... If you are not a Prime Number, you will not be allowed to join the party.")
    print("-*-*-*-*-*-*-*-*-*-*-*-*-")

invite(getPrime(bits))
invite(getPrime(bits))
invite(getPrime(bits))
invite(getPrime(bits))

for i in range(3):
    print("[*] Do you want to invite more guests?")
    num = int(input(" > "))
    invite(num)

n = reduce(mul, GUESTS)
e = 65537
cipher = pow(flag, e, n)

print("n =", n)
print("e =", e)
print("cipher =", cipher)

十分に大きい素数をゲットする。

from Crypto.Util.number import getPrime
print(getPrime(512))

あとは通信してe,cipherをゲットして普通に解読すればいい

from Crypto.Util.number import *
p=
e=65537
d=inverse(e, p-1)
cipher=
print(long_to_bytes(pow(cipher,d,p)))

おっけ~~~

Command

``chall.py''が与えられる。見るとAES暗号っぽいけどivを得られるから関係ないっぽい

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でもいいけどうまくxorとればいい。 コードなくした。パディングに気を付ける。

Unpredictable Pad

chall.pyが与えられる。乱数わかればいいのがわかる。

import random
import os

FLAG = os.getenv('FLAG', 'notflag{this_is_sample_flag}')

def main():
    r = random.Random()

    for i in range(3):
        try:
            inp = int(input('Input to oracle: '))
            if inp > 2**64:
                print('input is too big')
                return

            oracle = r.getrandbits(inp.bit_length()) ^ inp
            print(f'The oracle is: {oracle}')
        except ValueError:
            continue

    intflag = int(FLAG.encode().hex(), 16)
    encrypted_flag = intflag ^ r.getrandbits(intflag.bit_length())
    print(f'Encrypted flag: {encrypted_flag}')

if __name__ == '__main__':
    main()

624個の32bit整数分の乱数吐かせると乱数を特定で切るっぽいので、1個目は62232bit、2,3個目は32bitの乱数を出力させたい。 62232bitは-(2622*32-1)を出力すればif文のところも突破できる!!あとはやるだけ~ (参考:Mersenne Twisterの出力を推測してみる - ももいろテクノロジー)

import random
from Crypto.Util.number import *

def untemper(x):
    x = unBitshiftRightXor(x, 18)
    x = unBitshiftLeftXor(x, 15, 0xefc60000)
    x = unBitshiftLeftXor(x, 7, 0x9d2c5680)
    x = unBitshiftRightXor(x, 11)
    return x

def unBitshiftRightXor(x, shift):
    i = 1
    y = x
    while i * shift < 32:
        z = y >> shift
        y = x ^ z
        i += 1
    return y

def unBitshiftLeftXor(x, shift, mask):
    i = 1
    y = x
    while i * shift < 32:
        z = y << shift
        y = x ^ (z & mask)
        i += 1
    return y


payload1 = -(2**(32*622-1))
res1 =??
res1^=payload1
payload2 = 2**31
res2 =??
res2^=payload2
res3 = ??
res3^=payload2
value1=[]
for i in range(622):
  value1.append(res1&(2**32-1))
  res1>>=32
value1.append(res2)
value1.append(res3)
mt_state = tuple([untemper(x) for x in value1] + [624])
random.setstate((3, mt_state, None))

predicted =random.getrandbits(239)
print(predicted)
encrypted_flag = ??
flag=encrypted_flag^predicted
print(flag.bit_length())
print(flag)
print(long_to_bytes(flag))

これでできる~~

最後の問題242から探索範囲絞れるのか??になって終了 次のSECCON Beginners CTFのCrypto全完してえ~~~