Cyber Apocalypse 2021 2/5 - Wii-Phit
Wii-Phit was the only Hard crypto challenge designed by CryptoHack for the Cyber Apocalypse 2021 CTF (there were also 4 challenges categorized as Insane though).
There is already an excellent writeup by the challenge organizers: one could recognize a well known equation related to the Erdős–Straus conjecture, some participants used Z3. We took a different approach.
The main difficulty is to find an integer solution to
∳∳ w(xz + yz - xy) == 4xyz ∳∳
where ∳w∳ is a given 512 bits integer.
More specifically, the flag is encrypted with RSA, the prime factors ∳p∳ and ∳q∳ are obviously secret, but we know that they pass the following assert:
w = 25965460884749769384351428855708318685345170011800821829011862918688758545847199832834284337871947234627057905530743956554688825819477516944610078633662855
x = p + 1328
y = p + 1329
z = q - 1
assert w*(x*z + y*z - x*y) == 4*x*y*z
Before really starting we can observe that ∳y∳ is ∳x + 1∳ and thus ∳x∳ and ∳y∳ are coprime. Moreover we know a few factors of ∳w∳ thanks to factordb.com.
If we rewrite the assert to put all the multiples of ∳z∳ on the same side we get:
∳∳ \begin{aligned} w(xz + yz) - 4xyz &= wxy\\ (w(x + y) - 4xy)z &= wxy \end{aligned} ∳∳
Thus ∳wxy∳ is a multiple of ∳z∳, in other words: ∳z∳ divides ∳wxy∳.
Similarly ∳x∳ divides ∳wyz∳, and ∳y∳ divides ∳wxz∳.
But ∳x∳ and ∳y∳ are coprime, thus:
- ∳x∳ divides ∳wyz∳ implies that ∳x∳ divides ∳wz∳
- similarly ∳y∳ divides ∳wz∳
Again, using the fact that ∳x∳ and ∳y∳ are coprime we can deduce that ∳xy∳ divides ∳wz∳, since they both divide ∳wz∳.
As a consequence, we know that there exist two integers ∳α∳ and ∳β∳ such that:
∳∳ \begin{aligned} wxy = αz\\ wz = βxy \end{aligned} ∳∳
Taking the product leads to ∳w^2xyz = αβxyz∳ or ∳w^2 = αβ∳ after simplification. We know some factors of ∳w^2∳, by distributing them over ∳α∳ and ∳β∳ we may find their values.
We now have a set of candidates for ∳α∳ and ∳β∳. By using ∳wxy = αz∳ and ∳y = x + 1∳ we can rewrite our original assert as a degree 2 equation in ∳x∳:
∳∳ \begin{aligned} w(xz + yz - xy) &= 4xyz\\ wxz + w(x+1)z - αz &= 4x(x+1)z\\ wx + w(x+1) - α &= 4x(x+1)\\ 4x^2 + (4 - 2w)x + α - w &= 0 \end{aligned} ∳∳
Now all we have to do is solve this equation for all the ∳α∳ we were able to build, looking for an integer solution. That is what we did and it found a suitable solution for ∳α = 1∳. It is then rather straightforward to get ∳p∳, ∳q∳, and the RSA private key (beware of the unusual ∳φ(N)∳), leading to the flag:
CHTB{Erdos-Straus-Conjecture}
Our script can be found below.
This is how we solved the challenge, but we were curious about the Erdős–Straus conjecture and looked back at our equation with ∳α = 1∳.
We have the following discriminant:
∳∳ \begin{aligned} Δ &= (4 - 2w)^2 - 4\times{}4(1-w)\\ Δ &= 16 - 16w + 4w^2 - 16 + 16w\\ Δ &= (2w)^2 \end{aligned} ∳∳
Which gives the following positive solution:
∳∳ \begin{aligned} x &= (- (4 - 2w) + √Δ) / (2\times{}4)\\ x &= (w-1)/2 \end{aligned} ∳∳
Then ∳y = (w+1)/2∳ and ∳z = w(w-1)(w+1)/4∳.
We actually did not need to know any factor of ∳w∳, we just needed it to be positive and odd. But knowing some factors and the fact that ∳y = x + 1∳ gently pushed us in the right direction to rediscover the decomposition mentioned in the wikipedia article on the Erdős–Straus conjecture.
Here is (a cleaned-up version of) the code we used to find ∳p∳ and ∳q∳ and get the flag:
from itertools import combinations
from math import isqrt, prod
# Challenge Data
cipher = 0x12F47F77C4B5A72A0D14A066FEDC80BA6064058C900A798F1658DE60F13E1D8F21106654C4AAC740FD5E2D7CF62F0D3284C2686D2AAC261E35576DF989185FEE449C20EFA171FF3D168A04BCE84E51AF255383A59ED42583E93481CBFB24FDDDA16E0A767BFF622A4753E1A5DF248AF14C9AD50F842BE47EBB930604BECFD4AF04D21C0B2248A16CDEE16A04B4A12AC7E2161CB63E2D86999A1A8ED2A8FAEB4F4986C2A3FBD5916EFFB1D9F3F04E330FDD8179EA6952B14F758D385C4BC9C5AE30F516C17B23C7C6B9DBE40E16E90D8734BAEB69FED12149174B22ADD6B96750E4416CA7ADDF70BCEC9210B967991E487A4542899DDE3ABF3A91BBBAEFFAE67831C46C2238E6E5F4D8004543247FAE7FF25BBB01A1AB3196D8A9CFD693096AABEC46C2095F2A82A408F688BBEDDDC407B328D4EA5394348285F48AFEAAFACC333CFF3822E791B9940121B73F4E31C93C6B72BA3EDE7BBA87419B154DC6099EC95F56ED74FB5C55D9D8B3B8C0FC7DE99F344BEB118AC3D4333EB692710EAA7FD22
exponent = 0x10001
w = 25965460884749769384351428855708318685345170011800821829011862918688758545847199832834284337871947234627057905530743956554688825819477516944610078633662855
# known factors of w*w
FACTORS = [
3,
3,
5,
7,
13,
29,
434042467,
2653449587,
829389339613,
83650191286538267,
2736375417317167558343187941866480708142084464122192435130859730622053555029655238941106280888782037819,
]
FACTORS += FACTORS
def decrypt_flag(p, q):
N = p ** 3 * q
phi = p ** 2 * (p - 1) * (q - 1)
d = pow(exponent, -1, phi)
m = pow(cipher, d, N)
flag = m.to_bytes((m.bit_length() + 7) // 8, "big")
print(f"{flag=}")
def check_candidate(alpha):
# we are trying to solve: 4*x² + (4 - 2*w)*x + alpha - w = 0
a = 4
b = 4 - 2 * w
c = alpha - w
delta = b ** 2 - 4 * a * c
if delta < 0:
return
# sqrt may fail if delta is too big, we use isqrt instead
# but we have to check if we got the actual square root
sqrt_delta = isqrt(delta)
if sqrt_delta ** 2 != delta:
return
# solutions are (-b +/- sqrt_delta) / (2*a)
# division may fail if operands are too big
for x in [-b - sqrt_delta, -b + sqrt_delta]:
if x <= 0 or x % (2 * a) != 0:
# x is not a positive integer
continue
x = x // (2 * a)
y = x + 1
if w * x * y % alpha != 0:
# z = (w * x * y) / alpha is not an integer
continue
z = (w * x * y) // alpha
p = x - 1328
q = z + 1
decrypt_flag(p, q)
# number of factors considered to build alpha
nbr_factors = 0
while nbr_factors <= len(FACTORS):
worklist = combinations(FACTORS, nbr_factors)
for alpha_factors in worklist:
check_candidate(prod(alpha_factors))
nbr_factors += 1