import math from Crypto.Hash import CMAC from Cryp...
Creado el: 7 de diciembre de 2025
Respondido usando GPT-5.1 Thinking por Chat01
Creado el: 7 de diciembre de 2025
Respondido usando GPT-5.1 Thinking por Chat01
import math
from Crypto.Hash import CMAC
from Crypto.Util.number import long_to_bytes, getPrime, bytes_to_long, isPrime
from Crypto.Cipher import AES
from hidden import power_tower_mod, flag
assert bytes_to_long(flag).bit_length() == 1263
"""
power_tower_mod -> takes x, n and some data and returns
.
.
.
x
x
x
x
x mod n
i.e. infinite power tower of x modulo n
x^(x^(x^(x^(x.......))))) mod n
There are no vulnerabilities in that function trust me!!
"""
class bigMac:
def init(self, security = 1024):
textself.security = security self.n, self.data = self.generator() self.base = getPrime(security) * getPrime(security) self.message = bytes_to_long(flag) self.process() self.verified = False self.bits = 96 self.keys = [] for i in range(self.bits): self.keys.append(getPrime(self.bits)) print("My m4c&ch3353:", self.mac) print("My signature: ", self.getSignature(self.base)) self.next() def generator(self): chunk = 128 while 1: data = [] n = 1 for i in range(2 * self.security // chunk): data.append([getPrime(chunk), 1]) n *= data[-1][0] data.append([2, 2 * self.security - n.bit_length()]) while n.bit_length() < 2 * self.security: n *= 2 if n.bit_length() == 2 * self.security: return n, data def process(self): x = long_to_bytes(self.n) cc = CMAC.new(x[:16], ciphermod=AES) self.mac = cc.update(x).hexdigest() def getSignature(self, toSign): return (toSign * toSign) % (1 << (toSign.bit_length() - (self.security // 250))) def verify(self, N, data): self.next() if self.verified: print("ALREADY VERIFIED") return False if N.bit_length() != 2 * self.security: print("size of N is not correct.") return False prev = self.n mac = self.mac self.n = N self.process() x = 1 maxPrime = 0 for i in range(len(data)): data[i][0] = int(data[i][0]) data[i][1] = int(data[i][1]) if not isPrime(data[i][0]): self.n = prev self.mac = mac print("Gimme primesssssss onlyyyy!!") return False x *= pow(data[i][0], data[i][1]) maxPrime = max(maxPrime, data[i][0]) if self.mac != mac or x != N or maxPrime.bit_length() > self.security // 5: self.n = prev self.mac = mac print("Failed to verify.") return False print("Yayyyyyyyy! big mac got verified! for n =", prev) print("Data =", self.data) self.data = data self.n = N self.verified = True return True def next(self): self.base = power_tower_mod(self.base, self.data, self.n) def obfuscateSmall(self, m): obs = m & ((1 << self.bits) - 1) m ^= obs final = 0 for i in range(self.bits): if ((obs >> i) & 1): final += self.keys[i] return m + final def communicate(self): self.next() if self.verified: x = self.obfuscateSmall(bytes_to_long(flag)) while math.gcd(x, n) != 1: x += 1 while math.gcd(self.base, self.n) != 1: self.base += 1 print(f"Here is your obfuscated c: {pow(x, self.base, self.n)}") else: print("Verification needed.") def power_tower(self, x): self.next() if self.verified: print("WTF(What a Freak), you have n do it yourself.") return -1 return power_tower_mod(x, self.data, self.n)
if name == "main":
big = bigMac()
textsteps = 90 while steps > 0: print("1: Communicate.") print("2: Verify.") print("3: Obfuscating.") print("4: Quit.") steps -= 1 x = int(input("Choose: ")) if x == 1: big.communicate() elif x == 2: n = int(input("Give me the MY modulus : ")) *inp, = input("Enter prime factorization in format [prime1, count1]-[prime2, count2]-[...: ").split('-') data = [] for i in inp: curr = i[1:-1].split(", ") data.append([int(curr[0]), int(curr[1])]) big.verify(n, data) elif x == 3: x = int(input("Enter your message : ")) print("Here is your obfuscated message : ", big.obfuscateSmall(x)) elif x == 4: print("Goodbye.") quit() else: print("Wrong input.")
ANALYZE THIS FULLY IN DEPTH AND JUST EXPLAIN WHAT TYPE OF PROBLEM IS THIS AND WHAT IS EXPECTED TO SOLVE IT - LIKE WHAT METHODOLOGY OR CONCEPT TO USE - I THINK ITS SOMETHING WITH PRIMES AND WE HAVE TO USE ELLIPTIC CURVES
Short version:
This is a CTF-style number-theory / protocol-design crypto challenge, not an elliptic-curve cryptography problem.
You’re supposed to:
obfuscateSmall (a knapsack/subset-sum-style thing) using chosen inputs.Elliptic curves might show up only as “one possible factoring algorithm”, not as the core concept of the problem.
What the script does conceptually:
n as a product of many primes (generator()).n and prints it (My m4c&ch3353:).My signature:) that leaks some bits of base.flag actually gets used is in communicate():
obfuscateSmall(flag) is applied → gives some “obfuscated” integer x.c = x^base mod n (after some tweaks to ensure gcds are 1).N with a prime factorization you supply.So there are really three moving parts:
n, process, verify).power_tower_mod (next, power_tower).obfuscateSmall, communicate).Your job (at a conceptual level) is to get enough control / information that from the final ciphertext you can reconstruct flag.
generator)pythondef generator(self): chunk = 128 while 1: data = [] n = 1 for i in range(2 * self.security // chunk): data.append([getPrime(chunk), 1]) n *= data[-1][0] data.append([2, 2 * self.security - n.bit_length()]) while n.bit_length() < 2 * self.security: n *= 2 if n.bit_length() == 2 * self.security: return n, data
security = 1024, so 2*security = 2048.n is exactly 2048.data is essentially the prime factorization: [p1, 1], ..., [p16, 1], [2, k].Concepts here:
self.data).n (process)pythondef process(self): x = long_to_bytes(self.n) cc = CMAC.new(x[:16], ciphermod=AES) self.mac = cc.update(x).hexdigest()
n (x[:16]).n (x).Important: AES-CMAC here is being treated as a black-box secure MAC.
You are not expected to break AES or CMAC. That would be wildly out of scope for a CTF problem.
pythondef getSignature(self, toSign): return (toSign * toSign) % (1 << (toSign.bit_length() - (self.security // 250)))
security//250 with security=1024 is 4.toSign (initially self.base) has bitlength L ≈ 2048, the modulus is 2^(L-4).base² (top ~4 bits chopped off).Conceptually this is:
base via its square modulo a power of two.verify)pythondef verify(self, N, data): self.next() if self.verified: ... if N.bit_length() != 2 * self.security: ... prev = self.n mac = self.mac self.n = N self.process() x = 1 maxPrime = 0 for i in range(len(data)): data[i][0] = int(data[i][0]) data[i][1] = int(data[i][1]) if not isPrime(data[i][0]): self.n = prev self.mac = mac ... return False x *= pow(data[i][0], data[i][1]) maxPrime = max(maxPrime, data[i][0]) if self.mac != mac or x != N or maxPrime.bit_length() > self.security // 5: self.n = prev self.mac = mac ... return False print("Yayyyyyyyy! big mac got verified! for n =", prev) print("Data =", self.data) self.data = data self.n = N self.verified = True return True
Key points:
self.next() (so the internal base is advanced).n and mac in prev, mac.self.n = N and recomputes self.mac.x equals N.N is 2048 bits.security//5 = 204 bits.self.mac equals the old mac.If all that passes:
prev and self.data).N and data.self.verified = True.So:
verify is a kind of “certify this modulus” gate, guarded by a MAC over the modulus value.This is a protocol / logic-design target, not a “break AES” challenge.
You’re meant to notice where it leaks and how you can abuse that, not to attack CMAC directly.
next, power_tower, power_tower_mod)pythondef next(self): self.base = power_tower_mod(self.base, self.data, self.n) def power_tower(self, x): self.next() if self.verified: print("WTF(What a Freak), you have n do it yourself.") return -1 return power_tower_mod(x, self.data, self.n)
power_tower_mod is described as computing an infinite power tower:
using self.data (factorization) and self.n.self.base evolving in some complicated but deterministic way.Notice:
power_tower(x), so you get black-box access to power_tower_mod with the server’s modulus and data.power_tower just refuses to tell you anything useful.The comment in the code says “There are no vulnerabilities in that function trust me!!” – which usually means for CTF purposes: don’t try to cryptanalyze the power tower, it’s just a state-update PRF-ish thing. The real bugs are elsewhere.
obfuscateSmall)pythondef obfuscateSmall(self, m): obs = m & ((1 << self.bits) - 1) m ^= obs final = 0 for i in range(self.bits): if ((obs >> i) & 1): final += self.keys[i] return m + final
self.bits = 96.self.keys is a list of 96 randomly chosen 96-bit primes.Mechanics:
obs = the lowest 96 bits of m.m ^= obs → this clears those 96 low bits (because m_low ⊕ m_low = 0); high bits stay the same.i that is 1 in obs, it adds keys[i] to final.m' + final, where m' is m with low 96 bits zeroed.So you can think of this as:
m intact.keys[i], depending on the original low bits.This is exactly a knapsack / subset-sum-style encoding of the low 96 bits.
Now look at menu option 3:
pythonelif x == 3: x = int(input("Enter your message : ")) print("Here is your obfuscated message : ", big.obfuscateSmall(x))
You get a full chosen-plaintext oracle for this obfuscation with the same keys. That’s super important.
And it turns out to be hilariously weak:
obfuscateSmall(1 << i) for 0 ≤ i < 96, you get:
obs = 1 << i, so final = keys[i].m' = 0.keys[i].So with at most 96 chosen messages, you can recover all the secret primes keys[0..95].
Once you know them, obfuscation is just a linear system over the integers: you can invert it and recover the original 96 low bits of any obfuscated m.
Conceptually: this part is a broken knapsack / subset-sum scheme, defeated by a chosen-plaintext attack, not by elliptic curves.
communicate)pythondef communicate(self): self.next() if self.verified: x = self.obfuscateSmall(bytes_to_long(flag)) while math.gcd(x, n) != 1: x += 1 while math.gcd(self.base, self.n) != 1: self.base += 1 print(f"Here is your obfuscated c: {pow(x, self.base, self.n)}") else: print("Verification needed.")
After verification:
self.next() advances base again.x = obfuscateSmall(flag) – the same obfuscation you can query in menu option 3, just applied to the secret flag.x upward until gcd(x, n) == 1.self.base upward until gcd(self.base, self.n) == 1.c = x^base mod n.This is essentially RSA-style encryption:
n (the current self.n).e = self.base.x = obfuscateSmall(flag).If you know:
n and its factorization → you can get φ(n) or λ(n).e (or enough to make gcd(e, φ(n)) = 1) → you can compute a decryption exponent d = e^{-1} mod φ(n).c → you can get x = c^d mod n.obfuscateSmall (using the recovered keys) to get flag.So the crypto concepts here are:
Elliptic curves are not involved in any of that structure.
Putting it together, this is a multi-stage CTF cryptography challenge built from:
A MAC-protected modulus switch and leak.
verify, see what information it leaks, and how/when you can control which modulus is in use and (possibly) know its factorization.A toy knapsack / subset-sum “obfuscation”.
keys[i].x.RSA-style modular arithmetic.
c = x^base mod n.n is known and factorable (or chosen by you).base to invert the exponentiation (or otherwise recover x).Optional: standard integer factoring.
So: this is best thought of as a “weird RSA + bad knapsack + buggy verification” problem, not an ECC problem.
If you’re planning how to tackle it, the toolbox is roughly:
Careful code auditing / protocol reasoning
self.n, self.data, self.base, self.mac, and self.verified change.n, original factorization) and under what conditions.Number theory
pow(x, e, n)).Integer arithmetic / 2-adic reasoning (if you go after getSignature)
x² mod 2^k.x from partial information.Knapsack / subset-sum reasoning
obfuscateSmall as: low bits → subset of primes → sum.1<<i to recover keys[i].flag from the known subset sums.(Maybe) standard factoring
But: elliptic curves are only relevant if you choose to use ECM for factoring.
They are not being used in the cryptosystem itself, and you’re not expected to do elliptic-curve cryptanalysis.
If you want, next step we can zoom in on one part (for example, inverting obfuscateSmall, or understanding the verify logic in detail) and I can walk through how you’d systematically attack just that piece.