Table of Contents
Hello all! I played Google CTF 2023 which was happened from 24 June to 26 June.
Crypto - LEAST COMMON GENOMINATOR?
Attached File : [generate.py,dump.txt,flag.txt,public.pem]
generate.py
from secret import config
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, isPrime
class LCG:
lcg_m = config.m
lcg_c = config.c
lcg_n = config.n
def __init__(self, lcg_s):
self.state = lcg_s
def next(self):
self.state = (self.state * self.lcg_m + self.lcg_c) % self.lcg_n
return self.state
if __name__ == '__main__':
assert 4096 % config.it == 0
assert config.it == 8
assert 4096 % config.bits == 0
assert config.bits == 512
# Find prime value of specified bits a specified amount of times
seed = 211286818345627549183608678726370412218029639873054513839005340650674982169404937862395980568550063504804783328450267566224937880641772833325018028629959635
lcg = LCG(seed)
primes_arr = []
dump = True
items = 0
dump_file = open("dump.txt", "w")
primes_n = 1
while True:
for i in range(config.it):
while True:
prime_candidate = lcg.next()
if dump:
dump_file.write(str(prime_candidate) + '\n')
items += 1
if items == 6:
dump = False
dump_file.close()
if not isPrime(prime_candidate):
continue
elif prime_candidate.bit_length() != config.bits:
continue
else:
primes_n *= prime_candidate
primes_arr.append(prime_candidate)
break
# Check bit length
if primes_n.bit_length() > 4096:
print("bit length", primes_n.bit_length())
primes_arr.clear()
primes_n = 1
continue
else:
break
# Create public key 'n'
n = 1
for j in primes_arr:
n *= j
print("[+] Public Key: ", n)
print("[+] size: ", n.bit_length(), "bits")
# Calculate totient 'Phi(n)'
phi = 1
for k in primes_arr:
phi *= (k - 1)
# Calculate private key 'd'
d = pow(config.e, -1, phi)
# Generate Flag
assert config.flag.startswith(b"CTF{")
assert config.flag.endswith(b"}")
enc_flag = bytes_to_long(config.flag)
assert enc_flag < n
# Encrypt Flag
_enc = pow(enc_flag, config.e, n)
with open ("flag.txt", "wb") as flag_file:
flag_file.write(_enc.to_bytes(n.bit_length(), "little"))
# Export RSA Key
rsa = RSA.construct((n, config.e))
with open ("public.pem", "w") as pub_file:
pub_file.write(rsa.exportKey().decode())
dump.txt
2166771675595184069339107365908377157701164485820981409993925279512199123418374034275465590004848135946671454084220731645099286746251308323653144363063385
6729272950467625456298454678219613090467254824679318993052294587570153424935267364971827277137521929202783621553421958533761123653824135472378133765236115
2230396903302352921484704122705539403201050490164649102182798059926343096511158288867301614648471516723052092761312105117735046752506523136197227936190287
4578847787736143756850823407168519112175260092601476810539830792656568747136604250146858111418705054138266193348169239751046779010474924367072989895377792
7578332979479086546637469036948482551151240099803812235949997147892871097982293017256475189504447955147399405791875395450814297264039908361472603256921612
2550420443270381003007873520763042837493244197616666667768397146110589301602119884836605418664463550865399026934848289084292975494312467018767881691302197
And the flag.txt
is an encrypted flag.
Observations:
1. The actual flag was encrypted with the RSA
2. The primes are generated using a Linear Congruential Generator
3. The seed is known
4. First 6 generated random values of LCG are known
The LCG works on equation :
$$ X_{n+1} = (a \times X_{n}+c) mod p $$
Where,
- $$X(n)$$ is a sequence of pseudo random values.
- $$p$$ is modulo defined as $$0 < p$$
- $$a$$ is the multiplier defined as $$0 < a < p$$
- $$c$$ is the increment $$0 <= c < p$$ ( if $$c = 0$$ the LCG is called Multiplicative Congruential Generator)
We can see the generate.py implementation.
lcg_m = a lcg_c = c lcg_n = p
We have total generated values of lcg including seed.
We can find the n(modulus)
by making the 4 $$2 \times 2$$ matrices from $$ X_1,X_2,X_3,X_4,X_5,X_6,X_7$$ and finding the GCD of the determinant values of these 7 values.
The 4 $$2 \times 2$$ matrices, $$ \begin{bmatrix} X_1 - X_0 & X_2 - X_1\ X_2 - X_0 & X_3 - X_1 \end{bmatrix} \begin{bmatrix} X_2 - X_0 & X_3 - X_1\ X_3 - X_0 & X_4 - X_1 \end{bmatrix}$$
$$ \begin{bmatrix} X_3 - X_0 & X_4 - X_1\ X_4 - X_0 & X_5 - X_1 \end{bmatrix}, \begin{bmatrix} X_4 - X_0 & X_5 - X_1\ X_5 - X_0 & X_6 - X_1 \end{bmatrix} $$
Finding determinant of these all and then finding the GCD of them will give us the modulus(n) used in lcg.
With $$n$$ we can find $$a$$ by solving these equations.
$$ X_1 = (a \times X_0+c) mod p\ X_2 = (a \times X_1+c) mod p\ $$
$$ X_2 - X_1 = (a \times X_1+c - (X_0 \times a+c)) mod p\ X_2 - X_1 = (a \times X_1 - (X_0 \times a)) mod p\ X_2 - X_1 = (X_1 - X_0) \times a mod p\ \frac{X_2 - X_1}{X_1 - X_0} = a mod p \ a = \frac{X_2 - X_1}{X_1 - X_0} mod p\ a = ((X_2 - X_1)) \times InverseMod(X_1 - X_0,p) mod p\ $$
Lets solve for $$c$$,
$$ X_1 = (a \times X_0+c) mod p\ X_1 - c = (a \times X_0) mod p\ -c = (a \times X_0 - X_1) mod p\ c = (X_1 - a \times X_0) mod p $$
So, with n,c,m
we can generate entire series which is used to generate primes in the encryption.
The python implementation to find n,c,m
import math
def calc_det(i, j, X):
a1 = X[i] - X[0]
b1 = X[i + 1] - X[1]
a2 = X[j] - X[0]
b2 = X[j + 1] - X[1]
det = a1 * b2 - a2 * b1
return abs(det)
def GCD(a, b):
a = abs(a)
b = abs(b)
while a:
a, b = b % a, a
return b
def modInverse(a, m):
if GCD(a, m) != 1:
return None #if not releatively prime no modinv
u1, u2, u3 = 1, 0, a
v1, v2, v3 = 0, 1, m
while v3 != 0:
q = u3 // v3
v1, v2, v3, u1, u2, u3 = (
u1 - q * v1,
u2 - q * v2,
u3 - q * v3,
v1,
v2,
v3,
)
return u1 % m
def main():
while True:
try:
X = [
211286818345627549183608678726370412218029639873054513839005340650674982169404937862395980568550063504804783328450267566224937880641772833325018028629959635,
2166771675595184069339107365908377157701164485820981409993925279512199123418374034275465590004848135946671454084220731645099286746251308323653144363063385,
6729272950467625456298454678219613090467254824679318993052294587570153424935267364971827277137521929202783621553421958533761123653824135472378133765236115,
2230396903302352921484704122705539403201050490164649102182798059926343096511158288867301614648471516723052092761312105117735046752506523136197227936190287,
4578847787736143756850823407168519112175260092601476810539830792656568747136604250146858111418705054138266193348169239751046779010474924367072989895377792,
7578332979479086546637469036948482551151240099803812235949997147892871097982293017256475189504447955147399405791875395450814297264039908361472603256921612,
2550420443270381003007873520763042837493244197616666667768397146110589301602119884836605418664463550865399026934848289084292975494312467018767881691302197,
]
Det_X = []
Det_X.append(calc_det(1, 2, X))
Det_X.append(calc_det(2, 3, X))
Det_X.append(calc_det(3, 4, X))
Det_X.append(calc_det(4, 5, X))
found_p = math.gcd(math.gcd(Det_X[0], Det_X[1]), math.gcd(Det_X[2], Det_X[3]))
# To find 'a' and 'c' we need to solve the
mod_inv_a = modInverse((X[2] - X[3]), found_p)
found_a = ((X[3] - X[4]) * mod_inv_a) % found_p
found_c = (X[4] - found_a * X[3]) % found_p
print("n = %d\nm = %d\nc = %d\n" % (found_p, found_a, found_c))
break
except TypeError:
pass
if __name__ == "__main__":
main()
#Output
# n = 8311271273016946265169120092240227882013893131681882078655426814178920681968884651437107918874328518499850252591810409558783335118823692585959490215446923
# m = 99470802153294399618017402366955844921383026244330401927153381788409087864090915476376417542092444282980114205684938728578475547514901286372129860608477
# c = 3910539794193409979886870049869456815685040868312878537393070815966881265118275755165613835833103526090552456472867019296386475520134783987251699999776365
With these values as input we can find the modulus used in the encryption, n
, phi
and followed by d
.
Or we can just import the public.pem
to find e,n
used in the encryption.
Here is the final solution script to get the flag.
from Crypto.PublicKey import RSA
from Crypto.Util.number import bytes_to_long, isPrime,long_to_bytes
class LCG:
lcg_m = 99470802153294399618017402366955844921383026244330401927153381788409087864090915476376417542092444282980114205684938728578475547514901286372129860608477
lcg_c = 3910539794193409979886870049869456815685040868312878537393070815966881265118275755165613835833103526090552456472867019296386475520134783987251699999776365
lcg_n = 8311271273016946265169120092240227882013893131681882078655426814178920681968884651437107918874328518499850252591810409558783335118823692585959490215446923
def __init__(self, lcg_s):
self.state = lcg_s
def next(self):
self.state = (self.state * self.lcg_m + self.lcg_c) % self.lcg_n
return self.state
if __name__ == '__main__':
it = 8
bits = 512
# Find prime value of specified bits a specified amount of times
seed = 211286818345627549183608678726370412218029639873054513839005340650674982169404937862395980568550063504804783328450267566224937880641772833325018028629959635
lcg = LCG(seed)
primes_arr = []
dump = True
items = 0
primes_n = 1
while True:
for i in range(it):
while True:
prime_candidate = lcg.next()
if not isPrime(prime_candidate):
continue
elif prime_candidate.bit_length() != bits:
continue
else:
primes_n *= prime_candidate
primes_arr.append(prime_candidate)
break
# Check bit length
if primes_n.bit_length() > 4096:
print("bit length", primes_n.bit_length())
primes_arr.clear()
primes_n = 1
continue
else:
break
# Create public key 'n'
n = 1
for j in primes_arr:
n *= j
# Calculate totient 'Phi(n)'
phi = 1
for k in primes_arr:
phi *= (k - 1)
# Read the public key from the "public.pem" file
with open("public.pem", "rb") as pub_file:
rsa_key = RSA.import_key(pub_file.read())
# Extract the values of e and n from the RSA key
e = rsa_key.e
n = rsa_key.n
# Calculate private key 'd'
d = pow(e, -1, phi)
with open("flag.txt", "rb") as flag_file:
flag_data = flag_file.read()
_enc = int.from_bytes(flag_data, "little")
# decrypt flag
flag = pow(_enc,d,n)
print(long_to_bytes(flag))
# b'CTF{C0nGr@tz_RiV35t_5h4MiR_nD_Ad13MaN_W0ulD_b_h@pPy}'
Flag : CTF{C0nGr@tz_RiV35t_5h4MiR_nD_Ad13MaN_W0ulD_b_h@pPy}
Reference : https://teamrocketist.github.io/2019/03/31/Crypto-VolgaCtf2019-LG/