I play VolgaCTF last weekend, could not solve any challenge because quite busy last weekend
But here is my writeup for Recurrent
Description
Attachment
Let’s look at the source code, it generate a random prime number and some random numbers:
p = random_prime(2 ** 512 - 1, False, 2 ** 511)
F = GF(p)
a = IntegerMod(F, randint(1, p))
b = IntegerMod(F, randint(1, p))
s0 = IntegerMod(F, randint(1, p))
n_a = IntegerMod(F, randint(1, p))
n_b = IntegerMod(F, randint(1, p))
Then pass into the recurrent
function
A = recurrent(s0, a, b, n_a)
B = recurrent(s0, a, b, n_b)
Then it uses the master_secret
to encrypt the flag using AES CBC mode:
master_secret = int(recurrent(A, a, b, n_b))
flag = open("flag.txt", "rb").read()
key = sha256(master_secret.to_bytes(64, 'big')).digest()[:16]
iv = sha256(key).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print("Encrypted:", iv + cipher.encrypt(pad(flag, 16)))
As you can see, we need to find out the value of master_secret
in order to decrypt the flag
All values are given in the params
file except n_a
and n_b
p = 11402052825540369388503481527295817481121560256343494229999703486843211603849210440171418919250976071577012333493508412960698029285144211123336209953546781
a = 2817802771147678250767821085169703611490909101765946538363694715392828746232646298983204110458876307183328568276931317661434830232022114467877231852642060
b = 9490450238579366303161630595850378445035492689429789647605534026537099144306960890264012619313399265591312506737704378948826242942974381162153121492302043
s0 = 8599613106490622815088840657327923452887480903614107996301188278431030143342810757070939643143362386195629333762665379031911566602662441910306436944668091
A = 986962953549041204542742968905526856564460530774514387254820641350803588102370143051893651800079411337299897527695463495894799510402443635863507908895240
B = 7400642646495721122069695917309138984119691728390992710126139372576214409335954108817954691349087032495947562990375228286533082455228820477398400954673663
Encrypted: b'\xd8\x95H\x97\x8b*\x06\x0fOs\xe5\xf0M\x96Z\xba\xed$\xa0[T\xc2D\x8b\xf4\xae\xb1\xd9\xc4\x19\xdb{o\xf7\x81-U\x1f\xce\xd6GF\xac\xb4\xb8\xe6\x01\xf8=\xed\x94:\xd0\xa1\x14(\xa1\x0fo\x1f\xa0\x88\x17\xf3'
Math
Let’s look at the function recurrent
def recurrent(s0, a, b, n):
s = s0
for i in range(1, n + 1):
s = s + a * i + b
return s
Basically it just add the seed value s0
n times with a*i
and b
, if written in math expression it looks like this:
\[s = s_0 + \sum_{i=1}^{n}ai + bn\]
The flag only needed \(n_b\) to decrypt so we only using \(B\) and \(n_b\), we can rewrite the math equation:
\[B = s_0 + \sum_{i=1}^{n_b}ai + bn_b\]
We know all the values except for \(n_b\), so how we calculate \(n_b\) by using this equation?
Find \(n_b\)
The summation part we can use the sum of arithmetic sequence formula where we learned in high school add maths
\[S_n = \frac{n}{2}[2a + (n–1)d]\]
Substitute the \(d\) with \(a\) (The difference is \(a\))
\[S_n = \frac{n}{2}[2a + (n–1)a]\]
Then simplify the equation
\[=\frac{n}{2}(2a+an-a)\] \[=\frac{2an+an^2-an}{2}\] \[=\frac{an+an^2}{2}\]
We can back to the recurrent equation and substitute it:
\[B = s_0 + \frac{an_b+a(n_b)^2}{2} + bn_b\]
Reconstruct the equation to form of \(ax^2+bx+c\) so we can find the roots:
\[2B = 2s_0 + an_b+a(n_b)^2 + 2bn_b\] \[2s_0 + an_b+a(n_b)^2 + 2bn_b - 2B =0\] \[a(n_b)^2 + 2bn_b + an_b + 2s_0 - 2B =0\] \[a(n_b)^2 + (2b+a)n_b + (2s_0 - 2B) =0\]
As you can see, we have \(a=a\), \(b=2b+a\) and \(c=2s_0-2B\) we can now find the roots of the quadratic equation to calculate \(n_b\)!
The number are too large so we have to use SageMath to help us calculate this! Also don’t forget the modular p
p = 11402052825540369388503481527295817481121560256343494229999703486843211603849210440171418919250976071577012333493508412960698029285144211123336209953546781
a = 2817802771147678250767821085169703611490909101765946538363694715392828746232646298983204110458876307183328568276931317661434830232022114467877231852642060
b = 9490450238579366303161630595850378445035492689429789647605534026537099144306960890264012619313399265591312506737704378948826242942974381162153121492302043
s = 8599613106490622815088840657327923452887480903614107996301188278431030143342810757070939643143362386195629333762665379031911566602662441910306436944668091
A = 986962953549041204542742968905526856564460530774514387254820641350803588102370143051893651800079411337299897527695463495894799510402443635863507908895240
B = 7400642646495721122069695917309138984119691728390992710126139372576214409335954108817954691349087032495947562990375228286533082455228820477398400954673663
n = var('n') # Declare symbol n
print(solve_mod([a*n^2 + (a+2*b)*n + 2*s -2*B == 0],p)) # solve in modular p
Unfortunately it show this error:
/usr/lib/python3/dist-packages/apport/report.py:13: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
import fnmatch, glob, traceback, errno, sys, atexit, locale, imp, stat
Traceback (most recent call last):
File "solve.sage.py", line 14, in <module>
print(solve_mod([a*n**_sage_const_2 + (a+_sage_const_2 *b)*n + _sage_const_2 *s -_sage_const_2 *B == _sage_const_0 ],p))
File "/usr/lib/python3/dist-packages/sage/symbolic/relation.py", line 1506, in solve_mod
solution =_solve_mod_prime_power(eqns, p, i, vars)
File "/usr/lib/python3/dist-packages/sage/symbolic/relation.py", line 1609, in _solve_mod_prime_power
possibles = cartesian_product_iterator([range(len(R)) for _ in range(len(vars))])
File "/usr/lib/python3/dist-packages/sage/symbolic/relation.py", line 1609, in <listcomp>
possibles = cartesian_product_iterator([range(len(R)) for _ in range(len(vars))])
File "sage/rings/ring.pyx", line 236, in sage.rings.ring.Ring.__len__ (build/cythonized/sage/rings/ring.c:3217)
OverflowError: Python int too large to convert to C ssize_t
Not sure why it complaining the value too large? Then I struggle until the competition ends…
Then I saw someone post the solution on telegram, I saw the solution script I realise need to use PolynomialRing
to find the roots…
p = 11402052825540369388503481527295817481121560256343494229999703486843211603849210440171418919250976071577012333493508412960698029285144211123336209953546781
a = 2817802771147678250767821085169703611490909101765946538363694715392828746232646298983204110458876307183328568276931317661434830232022114467877231852642060
b = 9490450238579366303161630595850378445035492689429789647605534026537099144306960890264012619313399265591312506737704378948826242942974381162153121492302043
s = 8599613106490622815088840657327923452887480903614107996301188278431030143342810757070939643143362386195629333762665379031911566602662441910306436944668091
A = 986962953549041204542742968905526856564460530774514387254820641350803588102370143051893651800079411337299897527695463495894799510402443635863507908895240
B = 7400642646495721122069695917309138984119691728390992710126139372576214409335954108817954691349087032495947562990375228286533082455228820477398400954673663
b = (a+2*b) % p
c = (2*s-2*B) % p
F = GF(p)
R.<x> = PolynomialRing(F)
quad_poly = a * x^2 + b * x + c
roots = quad_poly.roots()
print(roots)
# [(9843820933844649036327547942406712352816347215384538217313810946594825095037363480375700165926228786958138234308107215538911557037358884579012142374529722, 1), (1994023652190016323861372658827484904528548480921553267078233601451075663946468145138879986275822079751273076865795300424230748235691951213719042182521596, 1)]
Then using AES to decrypt it using master_secret
as the key:
from hashlib import sha256
from Crypto.Cipher import AES
p = 11402052825540369388503481527295817481121560256343494229999703486843211603849210440171418919250976071577012333493508412960698029285144211123336209953546781
a = 2817802771147678250767821085169703611490909101765946538363694715392828746232646298983204110458876307183328568276931317661434830232022114467877231852642060
b = 9490450238579366303161630595850378445035492689429789647605534026537099144306960890264012619313399265591312506737704378948826242942974381162153121492302043
A = 986962953549041204542742968905526856564460530774514387254820641350803588102370143051893651800079411337299897527695463495894799510402443635863507908895240
# Recoding recurrent to
def recurrent(s0, a, b, n):
return (s0 + (a*n + a*(n**2))//2 + b*n)%p
n_b = 9843820933844649036327547942406712352816347215384538217313810946594825095037363480375700165926228786958138234308107215538911557037358884579012142374529722
master_secret = int(recurrent(A, a, b, n_b))
key = sha256(master_secret.to_bytes(64, 'big')).digest()[:16]
iv = sha256(key).digest()[:16]
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
enc = b'\xd8\x95H\x97\x8b*\x06\x0fOs\xe5\xf0M\x96Z\xba\xed$\xa0[T\xc2D\x8b\xf4\xae\xb1\xd9\xc4\x19\xdb{o\xf7\x81-U\x1f\xce\xd6GF\xac\xb4\xb8\xe6\x01\xf8=\xed\x94:\xd0\xa1\x14(\xa1\x0fo\x1f\xa0\x88\x17\xf3'
print(cipher.decrypt(enc[16:]))
# b'VolgaCTF{y0u_r3_s0_g00d_1_s33_n0w}\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e\x0e'
Flag
VolgaCTF{y0u_r3_s0_g00d_1_s33_n0w}
Great math challenge!