The Strongest Exponent I Thought 2

Attachment:

from Crypto.Util.number import getPrime

flag = "TSGCTF{************REDACTED*************}"
assert len(flag) == 41

p = getPrime(1024)
q = getPrime(1024)
n = p * q
phi = (p - 1) * (q - 1)
# e = (p ** q) % phi
e = pow(p, q, phi)

m = int.from_bytes(flag.encode(), 'big')

c = pow(m, e, n)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")
n = 12304937106495019948935823747896177539282746445389847240573471819615183350782871344655736641424538327646203132786364331680803172672565881147275872891374004325125755563779284122723272535648080869675254890421953184730207640610391499587131846028642837930573002266641165653436488988851379423113634977604789919048336354635258936662005400938712244679354370283198939324907588785304516972967767660681327553320071078132881601695691855892384395407410833039284741805467662060074582416349864492118156987242250334988709485218902717356401467908841000040063209207292376700158480563758869353523722467860892726428807123723391659759129
e = 4671508298202473062908098501122739212494104135822769469172957660169210197809353803016143135118329312461442335945081361046400708303845794518393283847717962634490620864727105047957787663674174103273199546275870204405433321074872048541293929182467813762150496313476918071127459153748615896909138251615692934309061780761262625941584853051868040264550530943071064965322169702801685784672354619433050569448706427815793570967672380706998764482936197619503081883603989897593357214325915544887558829367659538199805711114070709896237126163581049975456559860839042551241198296925972794823104585546475842981577962176035100657125
c = 734159268912236870326906291519339441060443190729549798304173425093568801503632263909956754998145543587462956329598121470425917551153360082681204702617948060192715494367013944150441511630518697760762122142274058030831598925419796406992806841961744715847324329596313556581670687011807097996415174407805818109493635886581027682687266023134960157243158377876713374560244314688652713790833693878860837389203062889825767981000866786402777089465218863064978825889452354777968576215646119739501361612158664806323278269221278619558106196445546507144889290265009352562267920470295408931325837487026801823417636759968187821134

Solved by AI:

TSGCTF 2025: The Strongest Exponent I Thought 2 - Writeup

Category: Cryptography
Points: (Not specified)
Author: (Not specified)
Solved by: (Your name/team)

Challenge Description

But this time, it's for real! The actual strongest character EVER!

We're given a custom RSA-like cryptosystem with a special exponent construction. The challenge provides:

  • problem.py - The encryption script
  • output.txt - The encrypted output containing n, e, and c

Files

  • problem.py - Encryption script
  • output.txt - Contains n, e, and c values

Encryption Script Analysis

from Crypto.Util.number import getPrime

flag = "TSGCTF{************REDACTED*************}"
assert len(flag) == 41

p = getPrime(1024)
q = getPrime(1024)
n = p * q
phi = (p - 1) * (q - 1)
# e = (p ** q) % phi
e = pow(p, q, phi)

m = int.from_bytes(flag.encode(), 'big')

c = pow(m, e, n)

print(f"n = {n}")
print(f"e = {e}")
print(f"c = {c}")

The key difference from standard RSA is the exponent: - Standard RSA: e is typically small (like 65537) and coprime with phi - This challenge: e = p^q mod phi

Given Values

n = 12304937106495019948935823747896177539282746445389847240573471819615183350782871344655736641424538327646203132786364331680803172672565881147275872891374004325125755563779284122723272535648080869675254890421953184730207640610391499587131846028642837930573002266641165653436488988851379423113634977604789919048336354635258936662005400938712244679354370283198939324907588785304516972967767660681327553320071078132881601695691855892384395407410833039284741805467662060074582416349864492118156987242250334988709485218902717356401467908841000040063209207292376700158480563758869353523722467860892726428807123723391659759129
e = 4671508298202473062908098501122739212494104135822769469172957660169210197809353803016143135118329312461442335945081361046400708303845794518393283847717962634490620864727105047957787663674174103273199546275870204405433321074872048541293929182467813762150496313476918071127459153748615896909138251615692934309061780761262625941584853051868040264550530943071064965322169702801685784672354619433050569448706427815793570967672380706998764482936197619503081883603989897593357214325915544887558829367659538199805711114070709896237126163581049975456559860839042551241198296925972794823104585546475842981577962176035100657125
c = 734159268912236870326906291519339441060443190729549798304173425093568801503632263909956754998145543587462956329598121470425917551153360082681204702617948060192715494367013944150441511630518697760762122142274058030831598925419796406992806841961744715847324329596313556581670687011807097996415174407805818109493635886581027682687266023134960157243158377876713374560244314688652713790833693878860837389203062889825767981000866786402777089465218863064978825889452354777968576215646119739501361612158664806323278269221278619558106196445546507144889290265009352562267920470295408931325837487026801823417636759968187821134

Mathematical Analysis

Key Insight

Given e = p^q mod phi where phi = (p-1)*(q-1):

  1. e ≡ p^q (mod phi)
  2. Since phi = (p-1)*(q-1), we have e ≡ p^q (mod (p-1)) (when a = b (mod c * d), you known that a = b (mod c))
  3. Because p ≡ 1 (mod (p-1)), we get p^q ≡ 1^q ≡ 1 (mod (p-1))
  4. Therefore: e ≡ 1 (mod (p-1))

This means p-1 divides e-1.

Consequences

Since p-1 divides e-1, we can write e-1 = k*(p-1) for some integer k.

For any integer a coprime with n (like a=2): - By Fermat's little theorem: a^(p-1) ≡ 1 (mod p) - Therefore: a^(e-1) = a^(k*(p-1)) ≡ 1^k ≡ 1 (mod p)

Thus p divides a^(e-1) - 1.

Since p also divides n, we have:

gcd(a^(e-1) - 1, n) = p

This allows us to factor n!

Attack Plan

  1. Compute gcd(2^(e-1) - 1, n) to obtain p
  2. Compute q = n // p
  3. Compute phi = (p-1)*(q-1)
  4. Compute d = e^(-1) mod phi (modular inverse)
  5. Decrypt: m = c^d mod n
  6. Convert m to bytes to get the flag

Exploit Implementation

#!/usr/bin/env python3
from Crypto.Util.number import long_to_bytes
import math

# Given values
n = 12304937106495019948935823747896177539282746445389847240573471819615183350782871344655736641424538327646203132786364331680803172672565881147275872891374004325125755563779284122723272535648080869675254890421953184730207640610391499587131846028642837930573002266641165653436488988851379423113634977604789919048336354635258936662005400938712244679354370283198939324907588785304516972967767660681327553320071078132881601695691855892384395407410833039284741805467662060074582416349864492118156987242250334988709485218902717356401467908841000040063209207292376700158480563758869353523722467860892726428807123723391659759129
e = 4671508298202473062908098501122739212494104135822769469172957660169210197809353803016143135118329312461442335945081361046400708303845794518393283847717962634490620864727105047957787663674174103273199546275870204405433321074872048541293929182467813762150496313476918071127459153748615896909138251615692934309061780761262625941584853051868040264550530943071064965322169702801685784672354619433050569448706427815793570967672380706998764482936197619503081883603989897593357214325915544887558829367659538199805711114070709896237126163581049975456559860839042551241198296925972794823104585546475842981577962176035100657125
c = 734159268912236870326906291519339441060443190729549798304173425093568801503632263909956754998145543587462956329598121470425917551153360082681204702617948060192715494367013944150441511630518697760762122142274058030831598925419796406992806841961744715847324329596313556581670687011807097996415174407805818109493635886581027682687266023134960157243158377876713374560244314688652713790833693878860837389203062889825767981000866786402777089465218863064978825889452354777968576215646119739501361612158664806323278269221278619558106196445546507144889290265009352562267920470295408931325837487026801823417636759968187821134

# Step 1: Factor n using the vulnerability
pow_result = pow(2, e-1, n)
p = math.gcd(pow_result - 1, n)
q = n // p

print(f"Found p: {p}")
print(f"Found q: {q}")
print(f"Verification: n == p*q? {n == p*q}")

# Step 2: Compute phi and decryption exponent
phi = (p-1)*(q-1)
d = pow(e, -1, phi)  # Modular inverse

# Step 3: Decrypt
m = pow(c, d, n)

# Step 4: Convert to flag
flag = long_to_bytes(m)
print(f"Flag: {flag.decode()}")

Running the Exploit

$ python3 exp.py
Found p: 105937009553883862577967493979820300930239452369039712050865514405472077146174715149934438264344045317716403466474742353074706323059068441839265251729695958013724754515351900282908744725502755279101079244363313515315674647685808381377886490847262229336888471590571794486450616598202909282796888725875837701253
Found q: 116153336386527217696396836098940101996561406780282082275084959504295159477378988046243098201524326207914965592350955440641219375853263533368698528142124545547342363268572615298653923480284789760821739772188389370773347340174102358172495894629846892502629301625010753345575197711382718272022098628309152764293
Verification: n == p*q? True
Flag: TSGCTF{My_power_is_too_dangerous_to_use!}

Flag

TSGCTF{My_power_is_too_dangerous_to_use!}

Why This Works

The vulnerability stems from the custom exponent construction e = p^q mod phi. While this might seem strong at first glance, it creates a mathematical relationship e ≡ 1 (mod (p-1)). This is essentially leaking a multiple of (p-1), which is enough to factor n using the GCD attack.

In standard RSA, we choose e to be a small, fixed number (like 65537) that's coprime with phi. This challenge demonstrates why deviating from well-established cryptographic constructions can introduce vulnerabilities.

Alternative Approaches

  1. Using different a values: The attack works with any a coprime to n, not just a=2. If gcd(2^(e-1) - 1, n) = 1 (which doesn't happen here), we could try a=3, a=5, etc.

  2. Pollard's p-1 algorithm: Since we know e-1 is a multiple of p-1, we could potentially use Pollard's p-1 algorithm with e-1 as the bound. However, the GCD attack is simpler and more efficient in this case.

Lessons Learned

  1. Don't roll your own crypto: Custom cryptographic constructions often have hidden vulnerabilities.
  2. Mathematical relationships matter: The relationship e ≡ 1 (mod (p-1)) seems innocuous but is fatal for RSA security.
  3. GCD attacks are powerful: Knowing a multiple of (p-1) or (q-1) can lead to factorization.
  4. Fermat's little theorem is useful: a^(p-1) ≡ 1 (mod p) is a fundamental tool in many cryptographic attacks.

References

  • RSA cryptosystem
  • Fermat's little theorem
  • GCD-based factorization attacks
  • Pollard's p-1 algorithm