Challenge Objective
The goal of this challenge is to leverage the fact that d₁ and d₂ are close in value. We can factor N and decrypt the flag. The attack is based on techniques from this paper (https://eprint.iacr.org/2015/399.pdf), though one can derive the necessary equations independently.
What is Coppersmith’s method?
In simple terms, Coppersmith’s method can be used to solve polynomials f(x) = 0 mod N (where N = p × q). An extension of the Coppersmith algorithm allows us to solve for small roots modulo a factor of N.
Proof of Concept (PoC)
Working through the equations:
From the source file, we can observe that d₁ – d₂ is small.
Using RSA knowledge:
- e₁ × d₁ ≡ 1 mod φ (Equation 1)
- e₂ × d₂ ≡ 1 mod φ (Equation 2)
Note: the challenge uses a wrong formula for φ or “phi”, which is intentional. The challenge is still solvable
Multiplying Equation 1 by e₂ and Equation 2 by e₁ gives:
- (e₁ × d₁) × e₂ ≡ 1 × e₂ mod φ
- (e₂ × d₂) × e₁ ≡ 1 × e₁ mod φ
Now subtract the two equations:
- e₁ × e₂ × (d₂ – d₁) ≡ (e₁ – e₂) mod φ
Multiplying both sides by inverse(e₁ × e₂) yields:
- d₂ – d₁ ≡ (e₁ × e₂)⁻¹ × (e₁ – e₂) mod φ
Since φ in this case is p(p – 1)q, we can reduce the modulus:
- d₂ – d₁ ≡ (e₁ × e₂)⁻¹ × (e₁ – e₂) mod p
Let a be the constant (e₁ × e₂)⁻¹ × (e₁ – e₂), and let x represent d₂ – d₁. Thus, the final equation becomes:
Since x is small, we can use Coppersmith’s method to solve for it.
The following is the sage solve script:
from Crypto.Util.number import *
from sage.all import *
half1 = 78651793350576640257900270619795917529285047644781765616581951057657331607131680336334150941043270280956949519156262933098001212767520262811961846436456656232341708272687902930721771428435673461029160550713916008786765253837510226768726267021261591817547516595574853876261700889652868486451202805313650219035
half2 = 30521573044014797629905067010977428736108369836323048856638167547126098265619057810389376407142161783807725860136412967017301160328902290246130326937605121934755173596117979252594236685871178336014678635615730794208623869595900231140992001089801097773670072560758257753786495727148635061494576282052813437443
e1 = 48041843429846995439836089780466518990169717666500777185155099979742475823401831891726475465268908458890247473550715925911568215099863398806220424601608634294086687496014534365765293677751551370125683019404227658935319070779153770341163005699949953408333148903106238404181636497469874910954910839321212097377
e2 = 81321083568988906930621381366281884594209208525141702431451133589494246818051381307156037678664472921898900352239167226199151581081491752218518268840312768099135951875491714805539024406839288835828740432643135907728846419267437850846744605160826081609587285016495347365304540108695192262127687343324790311323
N = 85331786094363726303676262519644108650742699682084288027986403245313360478382722639360576783920875212775769669608267562536548019296066688463786544707608059141596098797246525476204652518715852830744533620825938766057674429338584968831567070056099772240180671323983938508942001779310674405066276411305766485357
a = (e2 - e1) * inverse(e1 * e2, N)
P = PolynomialRing(Zmod(N), "x")
x = P.gen()
f = x - a
root = ZZ(f.monic().small_roots(X=2 ** 80, epsilon=0.8, beta=0.66)[0])
p = N // GCD(int(f(root)), N)
q = N // (p ** 2)
phi = p * (p - 1) * q
d1 = inverse(e1, phi)
d2 = inverse(e2, phi)
half1 = long_to_bytes(int(pow(half1, d1, p * p)))
half2 = long_to_bytes(int(pow(half2, d2, p * p)))
flag = half1 + half2
print(flag)
In this code, we compute the difference d₂ – d₁ as x, solve for it using Coppersmith’s method, and recover p. Once p is known, we calculate φ, derive d₁ and d₂, and finally decrypt the two halves of the flag.
Flag:
MetaCTF{R5A_V4r14nt5_4r3_U5u411ly_5u5_Th15_i5_4_Pap3r_ch4113ng3_h4d_to_tw3ak_1t_th0}