io = connect() io.recvuntil(b"of size ") p = ZZ(io.recvuntil(b"^2", drop=True).decode()) io.sendlineafter(b"$ ", b"-1") # (-1)^r-1=p^2-2 (mod p^2) if r is odd leak = ZZ(io.recvline().decode()) print(leak)
""" j1=A+0i # two unk j2=C+Di # two unk phi(j1,j2)=0 # two eq B=0 # one eq (C+Di)*(leak+Fi)=1 # one unk, two eq """
x = var("x") Fp = GF(p) Fp2 = GF(p ^ 2, "i", modulus=x**2 + 1) i = Fp2.gen() aa, cc, dd, ff = Fp2["aa,cc,dd,ff"].gens() PR_Fp = Fp["aa,cc,dd,ff"] f = mp3(aa + 0 * i, cc + dd * i) f_real = PR_Fp(f.map_coefficients(lambda c: c.polynomial()[0])) f_imag = PR_Fp(f.map_coefficients(lambda c: c.polynomial()[1])) g = (cc + dd * i) * (leak + ff * i) - 1 g_real = PR_Fp(g.map_coefficients(lambda c: c.polynomial()[0])) g_imag = PR_Fp(g.map_coefficients(lambda c: c.polynomial()[1])) aa, cc, dd, ff = PR_Fp.gens() f1 = f_real.sylvester_matrix(f_imag, dd).det() # function of a, c f2 = f_real.sylvester_matrix(g_real, dd).det() # function of a, c, f f3 = g_real.sylvester_matrix(g_imag, dd).det() # function of c, f f4 = f2.sylvester_matrix(f3, ff).det() # function of a, c g = f1.sylvester_matrix(f4, cc).det().univariate_polynomial() # function of a for r, _ in g.roots(): print(r, ZZ(r).bit_length()) if r > 0and ZZ(r).bit_length() < 200: target_j = ZZ(r) print("j", target_j)
defrecover(r): # r = sqrt(n) - floor(sqrt(n)) # set k = floor(sqrt(n)) is integer # (r+k)^2 = r^2 + 2rk + k^2 = n # n - 2rk - k^2 - r^2 = 0 # set m = n - k^2, which is approximately equal to k error = -4.22494828768973e-29# the error by experimenting k_ap = 2 ** (20 * 8 // 2) # approximate k
B = matrix( QQ, [[1, 1, 0, 0], [-2 * QQ(r), 0, 1, 0], [-QQ(r) ** 2, 0, 0, 1]] # m # k # 1 ) bounds = [QQ(abs(error)), k_ap, k_ap, 1] Q = diagonal_matrix([2**512 // b for b in bounds]) B *= Q T, mul = B._clear_denom() T = T.LLL() B = T.change_ring(QQ) / mul B /= Q for row in B: if row[-1] < 0: row = -row if row[-1] == 1: n_sub_k2 = row[1] k = row[-2] return k**2 + n_sub_k2
R = RealField(256) r = R(target_j) / 10 ** len(str(target_j)) # take the fractional part of sqrt(n) print(r) n = recover(r) print(n) guess = int(n).to_bytes(20, "big").decode() print(guess) io.sendline(guess.encode()) io.interactive() # n1ctf{0h_you_c4n_get_j_fr0m_th3_h4lf@#$%}
from Crypto.Util.number import * import os FLAG = os.environ.get('FLAG', b'n1ctf{XXXXFAKE_FLAGXXXX}') assert FLAG[:6] == b'n1ctf{'and FLAG[-1:] == b'}' FLAG = FLAG[6:-1]
defkeygen(nbits): whileTrue: q = getPrime(nbits) if isPrime(2*q+1): ifpow(0x10001, q, 2*q+1) == 1: return2*q+1
p = keygen(512) flag = bytes_to_long(FLAG) messages = [getRandomNBitInteger(flag.bit_length()) for i inrange(200)] enc = [encrypt(p, message, flag) for message in messages]
from sage.allimport * from Crypto.Util.number import * from subprocess import check_output from re import findall from output import message, enc # ln -s output.txt output.py from binteger import Bin
defflatter(M): # compile https://github.com/keeganryan/flatter and put it in $PATH z = "[[" + "]\n[".join(" ".join(map(str, row)) for row in M) + "]]" ret = check_output(["flatter"], input=z.encode()) return matrix(M.nrows(), M.ncols(), map(int, findall(b"-?\\d+", ret)))
defclean(n): for p in sieve_base: while n % p == 0: n //= p return n
""" c[i]=e^(flag xor msg[i]) % p =e^flag*prod((-1^flag[j])*(2^j)*msg[i][j]) =a*prod(k[i][j]^e[i][j]) """
# ref: https://affine.group/writeup/2022-01-TetCTF#fault bl = 159 mat = matrix( ZZ, [ Bin(e, bl).tuple[::-1] + (1,) for e, c inzip(message, enc) ], # 1 for the base b = 0x10001^flag ) L = mat.augment(identity_matrix(mat.nrows())) L[:, : bl + 1] *= 2**32 L = flatter(L) assert L[0][bl] == L[1][bl] == 0 xx = product([QQ(y) ** x for x, y inzip(L[0][bl + 1 :], enc)]) yy = product([QQ(y) ** x for x, y inzip(L[1][bl + 1 :], enc)]) p = clean(gcd(xx.numer() - xx.denom(), yy.numer() - yy.denom())) print(p) # p = 25070940894294854944213724808057610995878580501834029791436842569011975159898869595617649716256078890953233822913946718277574163417715224718477846001735447
deffield_exp(el, e): a, b = e.numerator(), e.denominator() el **= a q = el.parent().characteristic() // 2 d = inverse_mod(b, q) return el**d
F = GF(p) sol = mat.solve_left(vector([0] * (bl - 1) + [1, 1])) # pick a base that works y = product([field_exp(F(y), z) for y, z inzip(enc, sol)]) # y = F(0x10001) ** flag * F(0x10001) ** ((-1) ** flagbits[bl - 1] * 2 ** (bl - 1)) # also, flagbits[bl - 1] = 1 is known # so we can recover bits using `c[i]=e^flag*prod((-1^flag[j])*(2^j)*msg[i][j])`
recovered_bits = [0] * bl recovered_bits[bl - 1] = 1 for i inreversed(range(bl - 1)): base = vector([0] * (bl - 1) + [1, 1]) base[i] = 1 sol = mat.solve_left(base) x = product([field_exp(F(y), z) for y, z inzip(enc, sol)]) z = F(0x10001) ** (2**i) if x == y * z: recovered_bits[i] = 0 else: recovered_bits[i] = 1 flag = Bin(recovered_bits[::-1]).bytes print(flag) # n1ctf{s0o0_ezzz_dLLLp%$#@!}
#!/usr/bin/sage from Crypto.Util.Padding import pad from Crypto.Util.number import * from Crypto.Cipher import AES from hashlib import md5 import signal import random import os FLAG = os.environ.get('FLAG', 'n1ctf{XXXXFAKE_FLAGXXXX}')
io = connect() io.recvuntil(b"N = ") n = int(io.recvline().strip())
defdo_factor(n): # n=p*q*r=p*q*(2*q-1) # |GF(r^2)|=r^2-1=(r+1)*(r-1) # r+1=2*q -> 2*n is a multiple of r+1 # so we can apply algebraic group factorization R = Zmod(n)["x"] whileTrue: Q = R.quo(R.random_element(2)) rr = gcd(ZZ(list(Q.random_element() ^ (2 * n))[1]), n) if rr != 1: qq = (rr + 1) // 2 pp = n // qq // rr assert pp * qq * rr == n return pp, qq, rr
defpick_xy(primes): xs = [] ys = [] for i, p inenumerate(primes): # we want 4P=P, that is, 3P=0 # so we want to find a generator to the 3-torsion subgroup # we can't use P=0 as .xy() will throw at point at infinity # success rate is 1/27 :) E = EllipticCurve(GF(p), [3, 7]) assert E.order() % 3 == 0, f"unlucky at {i}-th prime" G = E.gen(0) P = G * (E.order() // 3) x, y = map(ZZ, P.xy()) xs.append(x) ys.append(y) x = crt(xs, primes) y = crt(ys, primes) return x, y
A = vector([PRq(a) for a in a_list]) t = PRq(t_list) c = PRq(c_list) z = vector([PRq(z) for z in z_list])
# pz[i]-pc*secret[i]=y[i] # so pz[i]-pz[j]-pc*(secret[i]-secret[j])=y[i]-y[j]=0 (we controlled the prng to make y[i]-y[j]=0) # and we can solve for secret[i]-secret[j] # note that secret[i] are binary polynomials (i.e. 0, 1) # so a `1` in secret[i]-secret[j] is only possible if secret[i]=1 and secret[j]=0 # so we can recover secret[i] by checking which bits are `1` in secret[i]-secret[j]
# another way to solve for the inverse using linear algebra # x = PRq.gen() # mat = matrix([vector((c * x**i % mod).padded_list(N)) for i in range(N)])
# this can be optimized by accounting for `-1` in secret[i]-secret[j] # from n*(n-1) to C(n, 2)=n*(n-1)/2 defget_secret(i): result = [0] * N for j inrange(m): if j == i: continue # another way to solve for the inverse using linear algebra # sol = mat.solve_left(vector(((z[i] - z[j]) % mod).padded_list(N))) sol = ((Rq(z[i]) - Rq(z[j])) / c).lift().padded_list(N) oneidxs = [i for i, v inenumerate(sol) if v == 1] for idx in oneidxs: result[idx] = 1 return PRq(result)
secret_prq = vector([get_secret(i) for i inrange(m)]) assert (A * secret_prq - t) % mod == 0 secret = vector([Rq(x) for x in secret_prq]) cipher = AES.new(md5(str(secret).encode()).digest(), mode=AES.MODE_ECB) flag = unpad(cipher.decrypt(ct), 16) print(flag) # n1ctf{A_Weak_RSIS_pr0bl3m_H373!!}