Datos del reto
| Campo | Valor |
|---|---|
| CTF | Nullcon CTF 2026 |
| Reto | Going In Circles |
| Categoría | Crypto |
| Flag | ENO{CRC_is_just_some_modular_remainder} |
Descripción del reto
El reto nos da un script chall.py que:
- Lee la flag desde
flag.txty la convierte en un entero (interpretando los bytes en big-endian). - Genera un polinomio aleatorio
fde 32 bits (entero de hasta 32 bits, interpretado como polinomio en GF(2)[x]). - Aplica una función
reduce(flag, f)y nos devuelve el par(reduce(flag, f), f).
def reduce(a, f):
while (l := a.bit_length()) > BITS:
a ^= f << (l - BITS)
return a
Cada conexión al servicio nos da dos números: el "resto" de la flag módulo f y el propio f.
Reconocimiento
En GF(2)[x] los coeficientes están en {0, 1} y la suma es XOR. Un entero se interpreta como polinomio: el bit en posición i es el coeficiente de x^i. La función reduce(a, f) hace la reducción de a módulo f en GF(2)[x]. Por tanto, en cada muestra tenemos r = flag mod f y f = polinomio "módulo" (grado ≤ 31), es decir flag ≡ r (mod f) en GF(2)[x].
Análisis de la vulnerabilidad
El Teorema Chino del Resto (CRT) también vale en GF(2)[x]: si tenemos congruencias x ≡ r1 (mod f1) y x ≡ r2 (mod f2) con f1 y f2 coprimos, existe un único x módulo lcm(f1,f2) que cumple ambas.
Cada conexión nos da un nuevo par (r_i, f_i) con flag ≡ r_i (mod f_i). Combinando estas congruencias con CRT (empezando con R = r1, M = f1 y actualizando con cada nuevo (r, f) a R', M' = lcm(M, f)), cuando el grado de M sea mayor o igual que la longitud en bits de la flag, la solución módulo M será única y coincidirá con la flag. Entonces se puede decodificar R como bytes y buscar el formato ENO{...}.
Explotación
Aritmética en GF(2)[x]
Se implementan sobre enteros: grado, multiplicación sin acarreo, división con resto, MCD y MCD extendido (para CRT).
CRT en GF(2)[x]
La función crt_poly(r1, m1, r2, m2) resuelve x ≡ r1 (mod m1) y x ≡ r2 (mod m2), comprueba consistencia y devuelve (x, m) con m = lcm(m1, m2).
Bucle principal
- Conectar al servicio y recibir
(r, f). - Si
fes 0 o 1 se ignora. - La primera muestra válida inicializa
R = r,M = f. - Para las siguientes, combinar con CRT:
R, M = crt_poly(R, M, r, f). - Tras cada actualización, intentar decodificar
Rcomo bytes y buscar prefijo/sufijo de la flag; si se encuentra, imprimir y terminar.
Con suficientes conexiones, el módulo combinado M crece hasta acotar unívocamente la flag.
Flag
ENO{CRC_is_just_some_modular_remainder}
Lecciones aprendidas
| Concepto | Uso en el reto |
|---|---|
| reduce(a, f) | Resto de a módulo f en GF(2)[x] |
| Cada muestra | flag ≡ r (mod f) en GF(2)[x] |
| Solución | Combinar muchas muestras con CRT en GF(2)[x] hasta que el módulo combinado tenga grado ≥ longitud de la flag |
| Decodificación | Convertir el entero recuperado a bytes y buscar el formato de la flag |
El nombre "Going In Circles" encaja con dar vueltas al servidor (muchas conexiones) y con la estructura algebraica de polinomios sobre GF(2) y congruencias (CRT).