Datos del reto
| Campo | Valor |
|---|---|
| CTF | Nullcon CTF 2026 |
| Reto | Matrixfun II |
| Categoría | Crypto |
| Flag | ENO{l1ne4r_alg3br4_i5_ev3rywh3re} |
Descripción del reto
El servidor nos da un cifrado que funciona así:
- Alfabeto: Base64 estándar (65 caracteres:
a-z,A-Z,0-9,+,/,=). - Parámetros: Matriz aleatoria
A ∈ ℤ₆₅^(16×16)y vectorb ∈ ℤ₆₅^16. - Proceso: El mensaje se codifica en Base64, se rellena con
=hasta longitud múltiplo de 16, se divide en bloques de 16 símbolos (cada símbolo → índice 0–64). Para cada bloquex:c = A·x + b (mod 65).
Al conectar, el servidor imprime el cifrado de la flag y luego actúa como oráculo: podemos enviar mensajes en hex y nos devuelve su cifrado con la misma A y b.
Reconocimiento
El cifrado es afín por bloques: c = A·x + b (mod 65). Si recuperamos A y b, podemos despejar x = A⁻¹(c − b) (mod 65) y descifrar cualquier texto. La vulnerabilidad es tener oráculo de cifrado con la misma clave.
Análisis de la vulnerabilidad
- Recuperar
b: El vectorbes el cifrado del bloque cuyos 16 índices son 0 (16 veces'a'). Enviando un mensaje cuya Base64 tenga como primer bloque 16×'a', la respuesta del oráculo para ese bloque esb. - Recuperar
A(columna a columna): Para la columnajusamos el vectoreⱼ(0 salvo un 1 en la posiciónj). Enviando un mensaje con primer bloque con ese contenido, el oráculo devuelvecⱼ = (columna j de A) + b, luego columnajdeA = cⱼ − b (mod 65). Con 1 consulta paraby 16 para las columnas deAtenemos la clave. - Invertir
Amódulo 65: Con eliminación de Gauss–Jordan enℤ₆₅se obtieneA⁻¹. Si no hay pivote coprimo con 65,Ano sería invertible (en este reto no fue necesario manejarlo).
Explotación
- Conectar y guardar el ciphertext de la flag del banner.
- Enviar mensaje cuya Base64 empiece en 16×
'a'→ respuesta =b. - Para
j = 0..15, enviar mensaje con bloqueeⱼ(1 en posiciónj) →cⱼ − b= columnajdeA. - Calcular
A⁻¹ (mod 65)con Gauss–Jordan. - Para cada bloque
cdel ciphertext:x = A⁻¹(c − b), mapear a Base64, decodificar y ajustar padding.
Referencia del código: recover_Ab(oracle) obtiene b y A; mat_inv_mod(A, 65) calcula la inversa; decrypt(cipher_list, A, bvec) aplica x = A⁻¹(c − b) y decodifica Base64.
Flag
ENO{l1ne4r_alg3br4_i5_ev3rywh3re}
Lecciones aprendidas
- Un cifrado afín por bloques con oráculo de cifrado permite recuperar la matriz y el vector de desplazamiento enviando bloques elegidos (cero y vectores canónicos).
- La inversión de matrices en
ℤₙ(n compuesto) requiere pivotes coprimos con n en Gauss–Jordan.