---
# System prepended metadata

title: AmateursCTF 2024 - cell-service writeup
tags: [' amateursCTF', ' ctf-writeup', ctf]

---

---
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}**