--- tags: ctf,ctf-writeup,amateursCTF --- # AmateursCTF 2024 - cell-service writeup - Category: Crypto - Score: 472/500 - Solve: 7 ## Source code - We were given 2 files: Problem source code in *cell-service.py* and the data in *out.txt* (compressed in *out.zip*). However, due to the enormous size of *out.txt*, I'll not show it here. ```python def num_to_ascii(n): a = 0 while n % p == 0: n //= p a += 1 return chr(a) def unhex(n): return int(n, 16) def eval_tower(tower): while tower: b = tower.pop() if isinstance(b, str): x, y = map(unhex, tower.pop()) a = unhex(b) else: x, y = map(unhex, b) a = (pow(x, a) + pow(y, a)) // p return num_to_ascii(a) def eval_towers(towers): cat = "" for tower in towers: cat += eval_tower(tower) return cat with open('out.txt') as f: p = int(f.readline()) print(eval_towers(eval(f.readline()))) ``` ## Challenge Analysis We can summarize the problem as follows: - In *out.txt*, we were given a list of 64 elements: $lst = [e_0, e_1, ..., e_{63}]$ and a 128-bit prime $p$. - Each element $e_i$ corresponds to a character in the flag and is represented by a list contains 9999 pairs of number $(x_{ij}, y_{ij})$ and a number $a_i$: $e_i = [a, (x_{i,0}, y_{i,0}), (x_{i,1}, y_{i,1}), ..., (x_{i,9998}, y_{i,9998})]$ (*I reversed the list for the ease of representation.*) - An interesting point is that $p$ divides sum of each pair: $$p|x_{ij} + y_{ij}$$ - The flag is computed as follow: - Denote $v_p(x)$ to be the greatest power in which a prime $p$ divides $x$. - Character $i$ of the flag is $v_p(t_{9998})$ where sequence $t_n$ is computed as following: $$t_j = \begin{cases} \frac{x_{ij}^{t_{j - 1}} + y_{ij}^{t_{j - 1}}}{p} & \text{ if } j >= 1,\\ \frac{x_{i, 0}^a + y_{i,0}^a}{p} & \text{ if j = 0} \end{cases}$$ ## My Approach - In the first version of the challenge (which is said to be broken by Neobeo), $a$ (or $t_j$) is not divided by $p$ after each iteration. ![image](https://hackmd.io/_uploads/rJd5LLBgA.png) - My first thought is to the **Euler's theorem**. The idea is to calculate $t_{9998}\space mod\space p$, $t_{9998}\space mod\space p^2$, ... and so on. However, this requires me to calculate Euler's totient function 9999 times on $p, p^1, p^2, ..., p^{127}$. Sage cannot automatically handles this due to the size of $p$. - I then shifted the approach to **Carmichael function** $\lambda(x)$ - a reduced version of Euler's totient function, which requires the factorization of $x$. I stole the Sage's implementation of Carmichael function and tweaks it a little bit to work in this case. However, it didn't work. - The problem is that at some step, $t_j$ is divisible by $p$, preventing us from applying the property of Carmichael function (and Euler's theorem too). - I also thought of **Hensel's lemma** but stopped diving further. Sadly, it is closely related to the intended solution of this challenge (FML). - After some unknown period of time, the author announced that a fix had been delivered for *cell-service*. ![image](https://hackmd.io/_uploads/HJpnILre0.png) - This time, $a$ (or $t_j$) is not by $p$ after each iteration. This further ensures that my original approach was not correct. However, I failed to find a new approach until the end of competition :(. ## The Intended Solution - After the CTF, I dived into the CTF's Discord server to see how other teams solved it. ![image](https://hackmd.io/_uploads/H10LdUBlR.png) - **Lifting-the exponent Lemma**, hmm. Never heard of it. Anyway, I'll give it another shot. - In short, we need the following result to solve the problem: Let $p$ be a prime number and let $x$ and $y$ be two integers that are not divisible by $p$. Then for an odd positive integer $n$: if $p | x + y$, then $v_p(x^n + y^n) = v_p(x + y) + v_p(n)$ - From these, we can rewrite the $v_p(t_j)$ as: $$v_p(t_j) = v_p(\frac{x_{ij}^{t_{j - 1}} + y_{ij}^{t_{j - 1}}}{p}) = v_p(x_{ij} + y_{ij}) + v_p(t_{j - 1}) - 1 $$ - Continuously expand the above equation gave us the way to compute between $v_p(t_j)$ from $v_p(t_0), v_p(t_1), ..., v_p(t_{j - 1})$. - However, the equation no longer holds if $t_{j - 1}$ is even, which is equivalent to the fact that $x_{ij} + y_{ij}$ is even. In this case, $v_p(t_j) = 0$. (Need proof or not?). - Eventually, in order to solve the challenge, we just need to iterate through $e_i$, calculate $v_p(x_{ij} + y_{ij})$ and add it to the current power until we encountered an even case. - Solve script: ```python from out import output # import the array from out.txt def calc_power(a, p): if a == 0: return 0 power = 0 while a % p == 0: power += 1 a //= p return power p = 183905317677500011457945744395125586799 flag = '' # Convert hex string to integers for i in range(len(output)): output[i][-1] = int(output[i][-1], 16) for j in range(9999): x, y = output[i][j] x, y = int(x, 16), int(y, 16) output[i][j] = (x, y) for i in range(len(output)): power = 0 a = output[i][-1] for j in range(9999): x, y = output[i][j] if (x + y) % 2 == 0: break power += calc_power(x + y, p) - 1 # Apply the LTE Lemma here flag += chr(power) print(f'{flag = }') ``` **Flag: amateursCTF{3xponents_4re_cool_or_s0mething?!?_4d767c85f66f530c}**