Lately, I haven't been playing CTFs much... So I was kinda dumb on everything. Anyways, I encouter a nice RSA Crypto challenge on MidnightCTF.
12 April, 2023
843 words
4 mins

Challenge Source

p = random_prime(2**1024) q = random_prime(2**1024) a = randint(0, 2**1024) b = randint(0, 2**1024) def read_flag(file='flag.txt'): with open(file, 'rb') as fin: flag = fin.read() return flag def pad_flag(flag, bits=1024): pad = os.urandom(bits//8 - len(flag)) return int.from_bytes(flag + pad, "big") def generate_keys(p, q): n = p * q e = 0x10001 return n, e def encrypt_message(m, e, n): return pow(m, e, n) flag = read_flag() m = pad_flag(flag) n, e = generate_keys(p, q) assert m < n c = encrypt_message(m, e, n) print(c) print(n) print(p + b^2 * q) print(a^2 * p + q)

Idea

Well, this is just classic RSA encryption. On its own, this scheme is definitely not breakable. However, the author provides some additional values h0,h1h_0, h_1 so that somehow, we can use them to decrypt the encrypted value cc:

h0=p+qb2h1=q+pa2h_0 = p + qb^2 \\ h_1 = q + pa^2

where aa, bb is some random value [0,21024)\in [0, 2^{1024}).

My teammate immediately recognized (god 😢) that:

h0h1n is a close approximation to ab\sqrt{\frac{h_0h_1}{n}} \text{ is a close approximation to } {ab}

For which you can reason it by doing the following calculations:

h0h1n=(p+qb2)(q+pa2)pq=p+qb2qq+pa2p=(pq+b2)(qp+a2)=a2b2+1+b2qp+a2pq=a2b2+1+abbaqp+ababpq=a2b2+1+ab(baqp+abpq)\begin{aligned} \frac{h_0h_1}{n} & = \frac{(p + qb^2)(q + pa^2)}{pq} \\ & = \frac{p + qb^2}{q} \frac{q + pa^2}{p} \\ & = (\frac{p}{q} + b^2)(\frac{q}{p} + a^2) \\ & = a^2b^2 + 1 + b^2\frac{q}{p} + a^2\frac{p}{q} \\ & = a^2b^2 + 1 + ab\frac{b}{a}\frac{q}{p} + ab\frac{a}{b}\frac{p}{q} \\ & = a^2b^2 + 1 + ab(\frac{b}{a}\frac{q}{p} + \frac{a}{b}\frac{p}{q}) \end{aligned}

Let's set baqp+abpq\frac{b}{a}\frac{q}{p} + \frac{a}{b}\frac{p}{q} to be kk.

a2b2+1+kab a^2b^2 + 1 + kab

If kk is small enough to aa and bb, we can approximate it to be

h0h1na2b2+kab+(k2)2(ab+k2)2\begin{aligned} \frac{h_0h_1}{n} & \approx a^2b^2 + kab + (\frac{k}{2})^2 \\ & \approx (ab + \frac{k}{2})^2 \end{aligned}

Since qp,ba\frac{q}{p}, \frac{b}{a} and their reciprocals involve in the sum making up kk, the bigness of kk depends on max(p,q)min(p,q)\frac{max(p, q)}{min(p, q)} and max(a,b)min(a,b)\frac{max(a, b)}{min(a, b)}. And you can reason that for any two numbers x,yx, y generated at random in the same interval, the ratio max(x,y)min(x,y)\frac{max(x, y)}{min(x, y)} is expected to be small.


During local test, I realized that kk is very small (like, 22 or 33 or something...). In fact, let's plot kk for different values of p,q,a,bp, q, a, b and see how it goes 🙂

Probability density graph of k.
Probability density graph of k.

The left column represents the percentage of occurances for a certain kk. And you can see for ALMOST ALL cases, kk will just land around on some value 6\le 6. (yes, while you can argue that the spike of 22 is just 5%, so the rest could be much bigger, however, the probability of kk landing >20> 20 is just 12%)

Also, you can notice that the plot is flat before a sudden spike around k=2k = 2. This happens because kk can be represented in the form of 1x+x\frac{1}{x} + x, and from AM-GM Inequality, 1x+x21xx=2\frac{1}{x} + x \ge 2\sqrt{\frac{1}{x}x} = 2 for any x>0x > 0.


Anyways, this means in most cases, we can expect:

h0h1n(ab+3)2h0h1nab+3 \frac{h_0h_1}{n} \le (ab + 3)^2 \\ \sqrt{\frac{h_0h_1}{n}} \le ab + 3 \\

or if you are really skeptical, you can bound it to be ab+10000\le ab + 10000, just to be sure 🙂 This means that we can find abab just by bruteforcing 10000 values around h0h1n\sqrt{\frac{h_0h_1}{n}}.


Get pp

Knowing abab, we now have 4 equations with 4 variables to solve:

pq=nab=(whatever ab is)h0=p+qb2h1=q+pa2 pq = n \\ ab = \text{(whatever } ab \text{ is)} \\ h_0 = p + qb^2 \\ h_1 = q + pa^2

for which we can lend the power of magic Sagemath to solve for p,qp,q.

from sage.all import * from tqdm import trange c = 8572900207835762766259060305548427890100883047397634495461699978853157157614537323169347948562539226943681557963690343113654640872734234635812404134737383783029729221882136594985076147296741783975976308542646646810930077508306094278881958489094577466122474005274780847502484006774122175180575134906406965261990134809789771668692230279546111615898299677351494730026393299745116126441043262641839846131458109835024756668882646302817842268403187061058095410531259459671076936340425779509762997737719853997033122063887141701633633320429028875760558509090378244944304521100714575766987495025277855338621083017737516612418 n = 11144809766924753211164169820974462795856999866851443978190116700121363041989551071283939406281584000345497130693285334786351068731714844065338178589052164965325272282738320690462179192218231489414187602007189279178640751309024526964298676986141201862905207755202946987885824563821850086298674083250354794989520315263319887676335677763156987501376451785566994901716198026893601133060745072362354091538721508265964958576574770694885291955086273571799403020416375990066994984333344295830201448319348356984141129557424755917133168216662244421360263592451413336939492881342257615928593787731918025125347411493314907250137 h0 = 3133548371772245794593916200216569789009977503838749561227383433777720559941789482302916747603629163277628624218728934067099751860342707727093284736792795911695291350493714661780427322085098620600442730601203727048863482544984150704785568558089024822242137835533188983145916911039381864946181510766072421365244229323632933214703949194559838270346065808390788292338719229269524606818971098272185892178470535466837645119304408225231332458404102608745644737712878232270144653748091436259421085576899894410825994764509434190271250735054775427479336540747164022736063724088385024013450114201117597406901351692873529770371805217775170905068961082711810613758277323468498024046033975544974993657043847859384646871490664090364968788225608324873580922409806934637562732699242491794243842510137720412106011270126856983601691379316962610115528724097947971496018172633530779698135626404996346034214279586492856257103552654185991624401974 h1 = 1668414440918184768102355070614199463515793278471647551081913918452877485930426726226888602387669498229921420169090265893271687426728329309931304042833872472475849661618647319286811295064264833639171767419811286840565958014252378459070198913214095511167368116072393143874905742547670514329775410132929858061858466466966832673233837546616958733459536386756690536101619509572857744943940172226354331174483476689882385303341939713713582201065151215959817799687574284712464653518663899972395427884233952547367314997783361776439415679136448860553029913121320416809081351610252174264463832509457873454643897905542501703122708259182482820112852708896076939481486564165447979041966168147292062612542988326875351534914932148857901377696892000822979439668349934754312002375192855438370671607139791178130484207096854031824894563191176002437196868004348552012031601430061559392941224723263095744921218764294325481602675909958988713011638 P = PolynomialRing(QQ, names=('b', 'q')) b, q = P.gens() ab_approx = Integer(isqrt(Integer(h0*h1) / Integer(n))) for delta in trange(0, 1000): ab = ab_approx - delta # Here, I substitude a*b = ab and p*q = n # to reduce number of equations & vars eqs = [ n + b**2 * q*q - h0*q, ab**2 * n + q*q * b**2 - h1 * b**2 * q ] # Copied from somebody's Coppersmith multivariate solver :v H = Sequence(eqs, P) I = H.ideal() if I.dimension() == -1: H.pop() elif I.dimension() == 0: for root in I.variety(ring=ZZ): print(root) # q = 109813633407350440684752357104087423730270808390261227637462376676953120335377705330415425210793126533507813859480119893163026376723863463313335381388502153679899248105338398482841127131263152039296390164162491463575378945424202721526857339615769738614581032689687091169497613753054146273393581209744585087263

Get Flag

Since we have p,qp,q, we can now recover the private exponent and solve for flag 🙂

from Crypto.Util.number import * c = 8572900207835762766259060305548427890100883047397634495461699978853157157614537323169347948562539226943681557963690343113654640872734234635812404134737383783029729221882136594985076147296741783975976308542646646810930077508306094278881958489094577466122474005274780847502484006774122175180575134906406965261990134809789771668692230279546111615898299677351494730026393299745116126441043262641839846131458109835024756668882646302817842268403187061058095410531259459671076936340425779509762997737719853997033122063887141701633633320429028875760558509090378244944304521100714575766987495025277855338621083017737516612418 n = 11144809766924753211164169820974462795856999866851443978190116700121363041989551071283939406281584000345497130693285334786351068731714844065338178589052164965325272282738320690462179192218231489414187602007189279178640751309024526964298676986141201862905207755202946987885824563821850086298674083250354794989520315263319887676335677763156987501376451785566994901716198026893601133060745072362354091538721508265964958576574770694885291955086273571799403020416375990066994984333344295830201448319348356984141129557424755917133168216662244421360263592451413336939492881342257615928593787731918025125347411493314907250137 h0 = 3133548371772245794593916200216569789009977503838749561227383433777720559941789482302916747603629163277628624218728934067099751860342707727093284736792795911695291350493714661780427322085098620600442730601203727048863482544984150704785568558089024822242137835533188983145916911039381864946181510766072421365244229323632933214703949194559838270346065808390788292338719229269524606818971098272185892178470535466837645119304408225231332458404102608745644737712878232270144653748091436259421085576899894410825994764509434190271250735054775427479336540747164022736063724088385024013450114201117597406901351692873529770371805217775170905068961082711810613758277323468498024046033975544974993657043847859384646871490664090364968788225608324873580922409806934637562732699242491794243842510137720412106011270126856983601691379316962610115528724097947971496018172633530779698135626404996346034214279586492856257103552654185991624401974 h1 = 1668414440918184768102355070614199463515793278471647551081913918452877485930426726226888602387669498229921420169090265893271687426728329309931304042833872472475849661618647319286811295064264833639171767419811286840565958014252378459070198913214095511167368116072393143874905742547670514329775410132929858061858466466966832673233837546616958733459536386756690536101619509572857744943940172226354331174483476689882385303341939713713582201065151215959817799687574284712464653518663899972395427884233952547367314997783361776439415679136448860553029913121320416809081351610252174264463832509457873454643897905542501703122708259182482820112852708896076939481486564165447979041966168147292062612542988326875351534914932148857901377696892000822979439668349934754312002375192855438370671607139791178130484207096854031824894563191176002437196868004348552012031601430061559392941224723263095744921218764294325481602675909958988713011638 e = 65537 q = 109813633407350440684752357104087423730270808390261227637462376676953120335377705330415425210793126533507813859480119893163026376723863463313335381388502153679899248105338398482841127131263152039296390164162491463575378945424202721526857339615769738614581032689687091169497613753054146273393581209744585087263 p = n // q d = pow(e, -1, (p-1)*(q-1)) print(long_to_bytes(pow(c, d, n))) # b"midnight{1_kN0w_3n0ugh_4b0ut_RSA}\n\xa9\xbd\xd17\xec'\xed\x9c\x19RD\x8f\x9c8\xe4\xb9\x11\xf3\xb5/\xdeC\xff\xe5c\x89_3\xd7F\x1f^\x12 +A\xc3\xc7\\HC\xd9!\xd6`\x86\xa8B\xc0A)4q\x85x<\xbd\xe7@\xa9\xa3\x08\xc2\xb0\xdf\x03\xb9\xbd.\x8f\xa3\xe5\xd8\x8b\xf0\xadw7\xb7\xe1\xa2j\x95\xd9\xe5!\xee\xf2u\xf32\xf1V\x1c"
table-of-content
::: Made with (❤️ && 😭) by Mistsu :::
Your browser sucks, you should consider getting a new one (one that supports display: grid)
>.<