Writeup
Ralia ASIS CTF 2025 Finals — Writeup
Ralia Challenge — Writeup
This writeup details the solution for the “Ralia” cryptography/PPC challenge from ASIS CTF 2025 Finals. The challenge involves interacting with a remote “weird machine” and solving a system of equations to generate integers that satisfy specific bit-length, primality, and perfect square constraints.
Summary
The target is a remote Python script accessible via netcat. To retrieve the flag, we must survive 19 levels of increasing difficulty.
In each level, the server provides a random bit length, bitel. We are required to provide three integers—, , and —that satisfy the following conditions:
- Bit Length: All three numbers () must have exactly
bitelbits. - Primality: and must be prime numbers.
- Perfect Squares: The following expressions must result in perfect squares:
Key Code Snippets
The core logic of the challenge is found in ralia.py.
ralia.py (Constraint Checking):
# The server parses our inputtry: x, y, z = [abs(int(_)) for _ in ans]except: die(border, f"The input you provided is not valid!")
_b = False
# The Mathematical Constraints# 1. Check if the sums are perfect squaresif is_persquat(x + y) and is_persquat(y + z + 1) and is_persquat(z + x + 5): # 2. Check if x and z are prime if isPrime(x) and isPrime(z): # 3. Check if they match the required bit length if x.bit_length() == y.bit_length() == z.bit_length() == bitel: _b = TrueAnalysis
This problem can be modeled as a system of linear equations. If we define the square roots of the required sums as integers , , and , we get:
We have a system of 3 equations with 3 variables (, , ). We can solve for , , algebraically in terms of , , .
Solving for : Sum equation (1) and (3), then subtract (2):
Solving for : Sum equation (1) and (2), then subtract (3):
Solving for : Sum equation (2) and (3), then subtract (1):
Integer Constraints
For , , to be integers, the numerators must be divisible by 2 (even). Looking at , since 4 is even, we need to be even. In modular arithmetic modulo 2, . Therefore, the constraint simplifies to:
The sum of the three roots must be even.
Exploitation and Flag Recovery
We cannot brute-force , , directly because finding primes that sum to squares is computationally expensive. However, generating , , from random values of , , is very fast.
1. Estimating the Range
We need . Since , and , then . So, . This gives us the center of the search range for our random squares.
2. The Solver Script
The script performs the following steps for each level:
- Calculates the search center based on
bitel. - Generates random integers and near the center.
- Calculates the required parity for (so that is even).
- Iterates through possible values of .
- Calculates , , and checks if they match the
biteland primality requirements.
#!/usr/bin/env python3from pwn import *from Crypto.Util.number import isPrimefrom math import isqrtimport random
def solve_level(bitel): # Calculate search center: sqrt(2 * 2^bitel) center = isqrt(2 * (1 << bitel)) width = center // 20
while True: # Pick A and B randomly around the target center A = random.randint(center - width, center + width) B = random.randint(center - width, center + width)
# C must satisfy (A + B + C) % 2 == 0 parity_target = (A + B) % 2
# Start searching for C C_start = random.randint(center - width, center + width) if C_start % 2 != parity_target: C_start += 1
# Iterate C to find valid primes for C in range(C_start, C_start + 5000, 2): # Calculate numerators num_x = A*A + C*C - B*B - 4 num_y = A*A + B*B - C*C + 4 num_z = B*B + C*C - A*A - 6
# Ensure positive results if num_x <= 0 or num_y <= 0 or num_z <= 0: continue
x = num_x // 2 y = num_y // 2 z = num_z // 2
# Check bit length constraints if x.bit_length() != bitel: continue if y.bit_length() != bitel: continue if z.bit_length() != bitel: continue
# Check primality (most expensive op, do last) if isPrime(x) and isPrime(z): return x, y, z
def main(): host = '65.109.194.34' port = 13377 r = remote(host, port)
try: while True: line = r.recvline().decode().strip() print(f"[Server] {line}")
if "Congratz" in line: print(f"\n[+] Flag: {line}") break
if "Please send" in line and "bitel =" in line: parts = line.split() try: idx = parts.index("bitel") bitel = int(parts[idx + 2].replace(":", "")) print(f"[*] Solving for bitel: {bitel}...")
x, y, z = solve_level(bitel)
r.sendline(f"{x},{y},{z}".encode()) except ValueError: print("[!] Error parsing") finally: r.close()
if __name__ == '__main__': main()3. Execution
We run the solver and wait for it to process all 19 levels.
[+] Opening connection to 65.109.194.34 on port 13377: Done[Server] ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓...[*] many levels and bitel are random for eac one and each level here but when solving level 19 you get the flag[Server] Congratz! You got the flag: ASIS{n1C3_IMO_fr0m_4U5Tr4LiA!}Final Result
Flag: ASIS{n1C3_IMO_fr0m_4U5Tr4LiA!}
Notes
The challenge demonstrates how arithmetic constraints can often be reversed using algebra to generate solutions efficiently, rather than filtering random numbers.
By controlling the generator variables (, , ), we ensured the perfect square constraints were met implicitly, leaving only the primality check as the computational hurdle.