Day 08
Attachment:
from Crypto.Util.number import getPrime, bytes_to_long
from secret import FLAG
def rotate(s):
return s[1:] + s[:1]
p = getPrime(256)
g = 2
print('p: ', p)
for _ in range(len(FLAG)):
x = bytes_to_long(FLAG.encode())
h = pow(g, x, p)
print('leak: ', h)
FLAG = rotate(FLAG)
p: 105891552782768273485439811488443354235545023673195353053856843893816480862271
leak: 74834831693148489075053741970537349864361072646438063822128523522624111096404
leak: 94637262384206414487913618520407895315549149001067603290715492338419957063638
leak: 26119415647812686814124878968032828750956202141747974248626227495752618826310
leak: 4679774184271053791528196045893972569586759227111353536533198888487507918565
leak: 18247264710550364407701539407739806326963229364366084285259750379893601861765
leak: 57915316904508068959154652733100932636576891111709785240327600448742235657223
leak: 48726379408397107751482798948037834170753506073391626907440435722452593080790
leak: 23985113650578780235980433910057970681859494895610853985857472866552793069375
leak: 79231016211481853602891211467430186947199626871048819648312025046281485147965
leak: 1741002057862179063516105232753064091765321308410446556962767548042727938590
leak: 45723548930603031191234669145592632467258468694483505689072179691228438466667
leak: 58359671511194341930052508512801343119287048085288142869480583778613556216586
leak: 36424897628935082604512568766716569943269039796337573854278402880102811617963
leak: 74124749341279711257845847090110538409556405630352757105373182076739304300759
leak: 66309699088883552645305984224828051437638962884903042514430099711503266344026
leak: 3162806651485565481013375199103880408462676315825209573632289159416883750178
leak: 38582718538740737041784799604514668813686704870272183643601445048856770639975
leak: 60816150467494956825800367310063997854145812297158134406617773585762215516843
leak: 12353526085228982771214476339845069964525380861405349768474892062968628975241
leak: 105582281937486445268146775233874434792742700624750876452894760571703935174919
leak: 13238662997179930853908762356537888264584682314391321882412877512234731316348
leak: 75875040959123636492014257513993145046661183624855640826292741601485403285328
leak: 12149947150174642792165286320748309835337741992502616190760701133669565860666
leak: 38650263185329843052214216132708910216830022007692554045491912295950867554552
leak: 92561547647296020081423003558701351791204280991353765906671600253604516771598
leak: 41349557324733447344437570514434312589869408427300508224464407246422794992797
leak: 86385180739170312287865404316483035605934760491085055759657242983443484861082
leak: 39348401982172687025128116440936094277482944374061555213943911942008513049037
leak: 58273481812023054183980778217247474508696785731672888895966172242039774561634
leak: 69932445711823749013619544296257448424270930795659726110506648765574551496961
leak: 11196151841801624492550269516119227591456953234837751240484968196644961160377
leak: 26041577455573938646072969268840197244828691423833869416465901173451675328995
The challenge:
- Generates a 256-bit prime
p - Uses generator
g = 2 - For each character in the FLAG (32 characters total):
- Converts the entire FLAG string to a long integer
x - Computes
h = 2^x mod p - Prints
has a "leak" - Rotates the FLAG string (moves first character to the end)
- Converts the entire FLAG string to a long integer
Let the FLAG be bytes: \(b_0, b_1, \cdots b_{n-1}\) where n = 32, the integer representation is:
\(x_1 = b_0 \times 256^{n-1} + b_1 \times 256^{n-2} + \cdots + b_{n-1}\)
After rotation:
\(x_2 = b_1 \times 256^{n-1} + b_2 \times 256^{n-2} + \cdots + b_{n-1} \times 256 + b_0\)
Therefore:
\(x_2 = 256x_1 - b_0(256^n-1)\)
We are given:
\(h_i = 2^{x_i} \bmod p\)
So
\(h_{i+1} = 2^{x_{i+1}} \bmod p = 2^{256x_i-b_i(256^n-1)} \bmod p = h_i^{256}\times2^{-b_i(256^n-1)} \bmod p\)
Since \(b_i\) is a byte (0-255), we can brute force it. Attack script:
import math
# Parse the data from out.txt
p = 105891552782768273485439811488443354235545023673195353053856843893816480862271
leaks = [
74834831693148489075053741970537349864361072646438063822128523522624111096404,
94637262384206414487913618520407895315549149001067603290715492338419957063638,
26119415647812686814124878968032828750956202141747974248626227495752618826310,
4679774184271053791528196045893972569586759227111353536533198888487507918565,
18247264710550364407701539407739806326963229364366084285259750379893601861765,
57915316904508068959154652733100932636576891111709785240327600448742235657223,
48726379408397107751482798948037834170753506073391626907440435722452593080790,
23985113650578780235980433910057970681859494895610853985857472866552793069375,
79231016211481853602891211467430186947199626871048819648312025046281485147965,
1741002057862179063516105232753064091765321308410446556962767548042727938590,
45723548930603031191234669145592632467258468694483505689072179691228438466667,
58359671511194341930052508512801343119287048085288142869480583778613556216586,
36424897628935082604512568766716569943269039796337573854278402880102811617963,
74124749341279711257845847090110538409556405630352757105373182076739304300759,
66309699088883552645305984224828051437638962884903042514430099711503266344026,
3162806651485565481013375199103880408462676315825209573632289159416883750178,
38582718538740737041784799604514668813686704870272183643601445048856770639975,
60816150467494956825800367310063997854145812297158134406617773585762215516843,
12353526085228982771214476339845069964525380861405349768474892062968628975241,
105582281937486445268146775233874434792742700624750876452894760571703935174919,
13238662997179930853908762356537888264584682314391321882412877512234731316348,
75875040959123636492014257513993145046661183624855640826292741601485403285328,
12149947150174642792165286320748309835337741992502616190760701133669565860666,
38650263185329843052214216132708910216830022007692554045491912295950867554552,
92561547647296020081423003558701351791204280991353765906671600253604516771598,
41349557324733447344437570514434312589869408427300508224464407246422794992797,
86385180739170312287865404316483035605934760491085055759657242983443484861082,
39348401982172687025128116440936094277482944374061555213943911942008513049037,
58273481812023054183980778217247474508696785731672888895966172242039774561634,
69932445711823749013619544296257448424270930795659726110506648765574551496961,
11196151841801624492550269516119227591456953234837751240484968196644961160377,
26041577455573938646072969268840197244828691423833869416465901173451675328995
]
n = len(leaks) # FLAG length = 32
def mod_inverse(a, m):
# Extended Euclidean Algorithm
def egcd(a, b):
if b == 0:
return (1, 0, a)
else:
x, y, g = egcd(b, a % b)
return (y, x - (a // b) * y, g)
x, y, g = egcd(a, m)
if g != 1:
raise Exception('Modular inverse does not exist')
else:
return x % m
def mod_pow(a, b, m):
return pow(a, b, m)
# Compute: R_i = h_i^{256} * h_{i+1}^{-1} mod p
# This should equal 2^{b_i * (256^n - 1)} mod p
print("Computing R_i = h_i^{256} * h_{i+1}^{-1} mod p")
print("This should equal 2^{b_i * (256^n - 1)} mod p")
print()
R_values = []
for i in range(n-1):
h_i_pow_256 = mod_pow(leaks[i], 256, p)
h_i1_inv = mod_inverse(leaks[i+1], p)
R_i = (h_i_pow_256 * h_i1_inv) % p
R_values.append(R_i)
print(f"R_{i} = {R_i}")
print("\n" + "="*80)
print("Now, note that R_i = 2^{b_i * (256^n - 1)} mod p")
print(f"Where n = {n}, so 256^n - 1 = 256^{n} - 1")
print()
# Compute K = 256^n - 1
K = pow(256, n) - 1
print(f"K = 256^{n} - 1 = {K}")
print()
# So we have: R_i = 2^{b_i * K} mod p = (2^K)^{b_i} mod p
# Let's compute base = 2^K mod p
base = mod_pow(2, K, p)
print(f"base = 2^K mod p = {base}")
print()
# Now we need to find b_i such that base^{b_i} mod p = R_i
# Since b_i are bytes (0-255), we can brute force!
print("Brute forcing b_i values (0-255) for all R_i:")
possible_b = []
for i, R_i in enumerate(R_values):
found = False
for b in range(256):
if mod_pow(base, b, p) == R_i:
print(f" R_{i:2d}: found b={b:3d} (chr='{chr(b) if 32 <= b < 127 else ' '}')")
possible_b.append(b)
found = True
break
if not found:
print(f" R_{i}: No b found in range 0-255")
possible_b.append(None)
print("\nAll possible bytes:", possible_b)
flag_chars = ''.join(chr(b) if b is not None and 32 <= b < 127 else '?' for b in possible_b)
print("As characters:", flag_chars)
print("Length:", len(flag_chars))
# We have n-1 bytes from the ratios, we need the last byte
# The FLAG has length n = 32, and we have bytes b0 through b30 from the ratios
# We need b31. We can get it from the fact that the rotation wraps around.
# Let's try to reconstruct the full flag
# We have: b0, b1, ..., b30 from the ratios
# We need b31. We can use the relationship for the last rotation:
# h_{31} = 2^{x_{31}} where x_{31} is FLAG rotated 31 times
# This should give us b31
print("\n" + "="*80)
print("Attempting to recover the full flag...")
# We have bytes b0..b30, let's try to find b31
# From the rotation formula: x_{i+1} = 256 * x_i - b_i * (256^n - 1)
# We know all b_i for i=0..30, and we know h_0 = 2^{x_0}
# We can try to compute x_0 from h_0 by solving discrete log, but that's hard
# Instead, let's use the fact that we have h_31 and the relationship should hold
# Actually, we have a cyclic relationship. After n rotations, we're back to the original:
# x_n = x_0
# So: x_n = 256^n * x_0 - (sum_{i=0}^{n-1} b_i * 256^{n-1-i}) * (256^n - 1) / (256 - 1)
# This is getting complex...
# Simpler: We have the bytes in order from the ratios: b0, b1, ..., b30
# The flag should be: b0 b1 ... b30 b31
# And from the rotation, b31 should be such that when we compute R_31 = h_31^{256} * h_0^{-1} mod p
# we get base^{b31}
# Compute R_31 (wrapping around)
h_31_pow_256 = mod_pow(leaks[31], 256, p)
h_0_inv = mod_inverse(leaks[0], p)
R_31 = (h_31_pow_256 * h_0_inv) % p
print(f"R_31 = {R_31}")
# Find b31
found_b31 = None
for b in range(256):
if mod_pow(base, b, p) == R_31:
found_b31 = b
print(f"Found b31 = {b} (chr='{chr(b) if 32 <= b < 127 else ' '}')")
break
if found_b31 is not None:
full_bytes = possible_b + [found_b31]
flag = ''.join(chr(b) if b is not None and 32 <= b < 127 else '?' for b in full_bytes)
print(f"\nFull flag ({len(flag)} chars): {flag}")
else:
print("Could not find b31")
NOTE: this attack is done by DeepSeek.