SECCON CTF 2022 Quals | BBB

#SECCON_CTF_2022_Quals

#xornet

from Crypto.Util.number import bytes_to_long, getPrime
from random import randint
from math import gcd
from secret import FLAG
from os import urandom


assert len(FLAG) < 100


def generate_key(rng, seed):
    e = rng(seed)
    while True:
        for _ in range(randint(10,100)):
            e = rng(e)
        p = getPrime(1024)
        q = getPrime(1024)
        phi = (p-1)*(q-1)
        if gcd(e, phi) == 1:
            break

    n = p*q
    return (n, e)


def generate_params():
    p = getPrime(1024)
    a = randint(0, p-1)

    return (p,a)


def main():
    p,a = generate_params()
    print("[+] The parameters of RNG:")
    print(f"{a=}")
    print(f"{p=}")
    b = int(input("[+] Inject [b]ackdoor!!: "))
    rng = lambda x: (x**2 + a*x + b) % p

    keys = []
    seeds = []
    for i in range(5):
        seed = int(input("[+] Please input seed: "))
        seed %= p
        if seed in seeds:
            print("[!] Same seeds are not allowed!!")
            exit()
        seeds.append(seed)
        n, e = generate_key(rng, seed)
        if e <= 10:
            print("[!] `e` is so small!!")
            exit()

        keys.append((n,e))

    flag = bytes_to_long(FLAG + urandom(16))
    for n,e in keys:
        c = pow(flag, e, n)
        print("[+] Public Key:")
        print(f"{n=}")
        print(f"{e=}")
        print("[+] Cipher Text:", c)


if __name__ == "__main__":
    main()

RSAだが、 eが可変で f(x) = x^2 + ax + b \mod pという式を用いて e = f(x)^rと計算される。この式のうち a, pが教えてもらえて、 bはこちらで事前に決められる

5回、それぞれ異なる xを渡すと、それを用いて (n_i, e_i)が計算され、その n_i, e_iを用いてフラグが暗号化される

平文の長さとの関係から e_iがすべて11になるように f, xを決められればHastad Broadcast Attackができる。したがって、 fとその回が11に収束する異なる5つの不動点を探すことがこの問題の本質

  • まず  b を決める。  b f(11) = 11 となるように定めたいので、  11^2 + 11a + b \equiv 11 \mod p を解けば決まる

  • 続いて  f の5つの不動点を求める。  f(11) = 11 は確実なので、seedの一つは  11 でよい

  • 適当に  f(x) = 11 となる  x を求めたら、次は  f(x') = x となるような  x' を求める……として次々にseedを求められる

  • そしてこれは  f^4(x) \equiv 11 \mod p を解けば全部求まるんじゃないか…?

  • なんかうまくイカなかったと思う

from ptrlib import Socket

sock = Socket("bbb.seccon.games", 8080)
a = int(sock.recvlineafter("a="))
p = int(sock.recvlineafter("p="))

PR.<b> = GF(p)[]
f = 11**2 + 11*a + b - 11
b = f.roots()[0][0]

PR.<x> = GF(p)[]
f = x**2 + x*a + int(b)
assert f(11) == 11

roots = [11]
unused = {11}
while True:
    rs = (f - unused.pop()).roots(multiplicities=False)
    for r in rs:
        if r not in roots:
            roots.append(r)
            unused.add(r)
    if len(roots) >= 5:
        break

print(roots)

sock.sendlineafter("[b]ackdoor!!: ", str(b))
for r in roots[:5]:
    sock.sendlineafter("seed: ", str(r))

ns = []
cs = []
for _ in range(5):
    ns.append(int(sock.recvlineafter("n=")))
    cs.append(int(sock.recvlineafter("Cipher Text: ")))

m11 = CRT(cs, ns)
m = m11.nth_root(11)

print(bytes.fromhex(hex(m)[2:]))