# Arranged Write-up > From: Cyber Apocalypse 2024, Hacker Royale > Tags: Crypto > Problem statement: > <Too lengthy and not very helpful, so I am not posting it here. I did not even read it when solving the challenge.> > Files attached: `main.sage`, `output.txt` > > Solved by: Jackylkk2003 ## Files attached: `main.sage`: ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import long_to_bytes from hashlib import sha256 from secret import FLAG, p, b, priv_a, priv_b F = GF(p) E = EllipticCurve(F, [726, b]) G = E(926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367, 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253) A = G * priv_a B = G * priv_b print(A) print(B) C = priv_a * B assert C == priv_b * A # now use it as shared secret secret = C[0] hash = sha256() hash.update(long_to_bytes(secret)) key = hash.digest()[16:32] iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(FLAG, 16)) print(encrypted) ``` `output.txt` ```! (6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997 : 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696 : 1) (4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734 : 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865 : 1) b'V\x1b\xc6&\x04Z\xb0c\xec\x1a\tn\xd9\xa6(\xc1\xe1\xc5I\xf5\x1c\xd3\xa7\xdd\xa0\x84j\x9bob\x9d"\xd8\xf7\x98?^\x9dA{\xde\x08\x8f\x84i\xbf\x1f\xab' ``` ## Code Analysis ```python! F = GF(p) E = EllipticCurve(F, [726, b]) G = E(926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367, 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253) ``` This code defines an Elliptic Curve here using the parameters $(GF(p), [726, b])$. In other words, the curve $E$ is: $$y^2 \equiv x^3 + 726x + b\ (mod\ p)$$ And $G$ is a point on $E$. ```python=12 A = G * priv_a B = G * priv_b print(A) print(B) C = priv_a * B assert C == priv_b * A # now use it as shared secret secret = C[0] ``` This code is a standard [Diffie-Hellman Key Exchange scheme](https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange). We know about `G`, `A`, `B`, and we want to know `((G * priv_a) * priv_b)` (equivalent to `A * priv_b`) or `((G * priv_b) * priv_a)` (equivalent to `B * priv_a`). This is a (supposingly) hard problem, that is, no one should be able to effiently find `C`, `priv_a` or `priv_b` by knowing only `G`, `A`, `B`, `E`, at least when this key exchange and cryptography is secure. From what is printed in the code, we know what $A$ and $B$ is. They are provided in the `output.txt` file. To avoid long long long numbers in this writeup, we will use the notation $G = (G_x, G_y)$. Similar notations are defined for points $A$, $B$. ## Straightforward approach A simple approach is to apply Sage discrete_log function directly to do the discrete log problem (ECDLP). However, there is a crucial problem: we do NOT have the whole curve yet. In this problem, $b$ and $p$ are unknown secrets. ## Solving for the unknowns What information can help us find out what $b$ and $p$ are? The only information about the curve is: * The equation stated [above](#Code-Analysis), but obviously this is not helpful. Looks like that is all, but actually there are some more. * $G$, $A$, $B$ are points on $E$. * $A, B$ can be obtained from $G$ by multiplying some integer constants. The first fact here is actually useful. Recall what we learnt in functions and graphs: `A point is on the graph if and only if it satisfies the equation of the graph.` In other words, we can put the points into the equation of $E$ and obtain some more equations. When we put $G$ into the equation, we have $$G_y^2 \equiv G_x^3 + 726 G_x + b\ (mod\ p)$$ We can express it in another form by making $b$ as the subject. $$b \equiv G_y^2 - G_x^3 - 726 G_x\ (mod\ p)$$ Similarly, we have $$b \equiv A_y^2 - A_x^3 - 726 A_x\ (mod\ p)$$ $$b \equiv B_y^2 - B_x^3 - 726 B_x\ (mod\ p)$$ We can calculate this using the following python/sage script. ```python! Gx = 926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367 Gy = 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253 Ax = 6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997 Ay = 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696 Bx = 4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734 By = 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865 print(f'b = {Gy ** 2 - Gx ** 3 - 726 * Gx} (mod p)') print(f'b = {Ay ** 2 - Ax ** 3 - 726 * Ax} (mod p)') print(f'b = {By ** 2 - Bx ** 3 - 726 * Bx} (mod p)') ``` We get the results: ```! b = -795681697752979021841640608005122212254481408059333389436461092784825702781732075202998355532776161687242583770626554226646700702822681028522622317139580412047249073777431186248756242053726846178683519895410827411480170487123529965570801762127312372615864877956046265119226609132011067488796059500296475165757221621606814766422659840111721465733339628985553330872697245012596263817584801121878502774373869888031674062739837325462968000518671538655034251350296 (mod p) b = -235389841633113518256718847719878733250125469551464659906955192105157068458892240496251208387305226684383977028692545364635616542911806071089784509634298138989466830088876060296948154657939206289908022191050803100386883123914692738193940269023567868365152264359631600117242932969716150281155438233466191095095609214364837462241270538753838231525244229345683421423856137208707912136351137768367596194932966862290676506665434291342481388359433898092926158324340379 (mod p) b = -75513297865381877165237329749121305404649863496562673628611547255472034783877319748412808667393686469886193277063058638505262905686384583987011362358457319536381368909650855340838565913232589885267322076370772598544068431036767525014189151469943189648194802751895203116369697087745722271774063256103313038301802715001727593484105594170399633466906647630238948981759128945460239877679475297123445626351905950631998326963214683279579832911791864195598564038489563 (mod p) ``` Again, for conciseness, we will name these 3 numbers as $r_1, r_2, r_3$. Note that all $r_i$ are equal after modulo $p$. (Otherwise $b\ mod\ p$ would have multiple values, which is obviously impossible.) To further simplify this, we can make use of the fact that $b = pq + r$ for some integers $q, r$ is equivalent to $b \equiv r\ (mod\ p)$. Combining these results, we have $$b = pq_1 + r_1 = pq_2 + r_2 = pq_3 + r_3$$ for some integers $q_1, q_2, q_3$. Applying more simple algebra, we have 3 more equations. $$p (q_1 - q_2) = r_2 - r_1$$ $$p (q_1 - q_3) = r_3 - r_1$$ $$p(q_2 - q_3) = r_3 - r_2$$ While these 3 equations seem to be useless, the main thing here is that the differences are multiples of $p$. In other words, $p$ is a factor of all $r_2-r_1, r_3-r_1, r_3-r_2$, that is, $p$ is a factor of $\gcd(r_2-r_1, r_3-r_1, r_3-r_2)$. A script for computing this is: ```python= r1 = Gy ** 2 - Gx ** 3 - 726 * Gx r2 = Ay ** 2 - Ax ** 3 - 726 * Ax r3 = By ** 2 - Bx ** 3 - 726 * Bx print(f'b = {r1} (mod p)') print(f'b = {r2} (mod p)') print(f'b = {r3} (mod p)') print(f'p is a factor of {gcd([r2 - r1, r3 - r1, r3 - r2])}') ``` The output is `p is a factor of 6811640204116707417092117962115673978365477767365408659433165386030330695774965849821512765233994033921595018695941912899856987893397852151975650548637533`. Additionally, we can check whether $p$ is a prime. If $p$ is a prime, then $p$ is directly the number above. Otherwise, we may have to factorize it. Luckily, the number above is a prime, so we know get $p$. Now that we know $p$, we can recalculate $b$. ```! b = -795681697752979021841640608005122212254481408059333389436461092784825702781732075202998355532776161687242583770626554226646700702822681028522622317139580412047249073777431186248756242053726846178683519895410827411480170487123529965570801762127312372615864877956046265119226609132011067488796059500296475165757221621606814766422659840111721465733339628985553330872697245012596263817584801121878502774373869888031674062739837325462968000518671538655034251350296 (mod p) ``` Computing the modulo, we have $b \equiv 42\ (mod\ p)$. The mod $p$ does not really matter here. ## Final Step We can finally apply our solution in the [straightforward approach](#Straightforward-approach) and use the discrete_log function in Sagemath to help us calculate $priv\_a$ or $priv\_b$. Here is the solve script: ```python= from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import long_to_bytes from hashlib import sha256 Gx = 926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367 Gy = 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253 Ax = 6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997 Ay = 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696 Bx = 4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734 By = 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865 r1 = Gy ** 2 - Gx ** 3 - 726 * Gx r2 = Ay ** 2 - Ax ** 3 - 726 * Ax r3 = By ** 2 - Bx ** 3 - 726 * Bx print(f'b = {r1} (mod p)') print(f'b = {r2} (mod p)') print(f'b = {r3} (mod p)') p = gcd([r2 - r1, r3 - r1, r3 - r2]) print(f'p is a factor of {p}') if is_prime(p): print(f'p = {p}') else: print('p is not prime. Please factorize before proceeding.') exit() b = r1 % p print(f'b = {b} (mod p)') F = GF(p) E = EllipticCurve(F, [726, b]) G = E(Gx, Gy) A = E(Ax, Ay) B = E(Bx, By) priv_a = discrete_log(A, G, operation='+') print(priv_a) C = priv_a * B secret = C[0] print(C) hash = sha256() hash.update(long_to_bytes(lift(secret))) key = hash.digest()[16:32] iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = b'V\x1b\xc6&\x04Z\xb0c\xec\x1a\tn\xd9\xa6(\xc1\xe1\xc5I\xf5\x1c\xd3\xa7\xdd\xa0\x84j\x9bob\x9d"\xd8\xf7\x98?^\x9dA{\xde\x08\x8f\x84i\xbf\x1f\xab' decrypted = cipher.decrypt(encrypted) print(decrypted) ``` And here is the output: `b'HTB{0rD3r_mUsT_b3_prEs3RveD_!!@!}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'` ## Alternative solution Credit: This solution idea comes from https://7rocky.github.io/en/ctf/other/htb-cyber-apocalypse/arranged/ After we have obtained the parameters $b, p$. We can investigate the order of $G$. ```python= F = GF(p) E = EllipticCurve(F, [726, b]) G = E(Gx, Gy) A = E(Ax, Ay) B = E(Bx, By) g_order = G.order() print(g_order) # Output: 11 ``` We can notice that $G$ has a very very very small order. We can simply loop through all the possible values of $n$. ```python= priv_a = 0 priv_b = 0 for i in range(g_order): if i * G == A: print(f'priv_a = {i}') priv_a = i if i * G == B: print(f'priv_b = {i}') priv_b = i ``` And the whole solve script is: ```python= # A detailed writeup is available at https://hackmd.io/@Jackylkk2003/Hyv5pCLlA from Crypto.Cipher import AES from Crypto.Util.Padding import pad from Crypto.Util.number import long_to_bytes from hashlib import sha256 Gx = 926644437000604217447316655857202297402572559368538978912888106419470011487878351667380679323664062362524967242819810112524880301882054682462685841995367 Gy = 4856802955780604241403155772782614224057462426619061437325274365157616489963087648882578621484232159439344263863246191729458550632500259702851115715803253 Ax = 6174416269259286934151093673164493189253884617479643341333149124572806980379124586263533252636111274525178176274923169261099721987218035121599399265706997 Ay = 2456156841357590320251214761807569562271603953403894230401577941817844043774935363309919542532110972731996540328492565967313383895865130190496346350907696 Bx = 4226762176873291628054959228555764767094892520498623417484902164747532571129516149589498324130156426781285021938363575037142149243496535991590582169062734 By = 425803237362195796450773819823046131597391930883675502922975433050925120921590881749610863732987162129269250945941632435026800264517318677407220354869865 r1 = Gy ** 2 - Gx ** 3 - 726 * Gx r2 = Ay ** 2 - Ax ** 3 - 726 * Ax r3 = By ** 2 - Bx ** 3 - 726 * Bx print(f'b = {r1} (mod p)') print(f'b = {r2} (mod p)') print(f'b = {r3} (mod p)') p = gcd([r2 - r1, r3 - r1, r3 - r2]) print(f'p is a factor of {p}') if is_prime(p): print(f'p = {p}') else: print('p is not prime. Please factorize before proceeding.') exit() b = r1 % p print(f'b = {b} (mod p)') F = GF(p) E = EllipticCurve(F, [726, b]) G = E(Gx, Gy) A = E(Ax, Ay) B = E(Bx, By) g_order = G.order() print(g_order) # Output: 11 priv_a = 0 priv_b = 0 for i in range(g_order): if i * G == A: print(f'priv_a = {i}') priv_a = i if i * G == B: print(f'priv_b = {i}') priv_b = i C = priv_a * B secret = C[0] print(C) hash = sha256() hash.update(long_to_bytes(lift(secret))) key = hash.digest()[16:32] iv = b'u\x8fo\x9aK\xc5\x17\xa7>[\x18\xa3\xc5\x11\x9en' cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = b'V\x1b\xc6&\x04Z\xb0c\xec\x1a\tn\xd9\xa6(\xc1\xe1\xc5I\xf5\x1c\xd3\xa7\xdd\xa0\x84j\x9bob\x9d"\xd8\xf7\x98?^\x9dA{\xde\x08\x8f\x84i\xbf\x1f\xab' decrypted = cipher.decrypt(encrypted) print(decrypted) ``` We have the same output as above: `b'HTB{0rD3r_mUsT_b3_prEs3RveD_!!@!}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'`