Competición MCCE · Talent4Cyber · 14-oct-2026

Retos 01 y 02

Enunciado, ficheros originales y resolución de los dos primeros retos del día 1. Puedes intentarlos por tu cuenta: las pistas y la solución están plegadas hasta que quieras verlas.

RETO 01 · PIR-01

Manifiesto Cifrado

«¿Qué se movía realmente en ese contenedor?»

Categoría
Criptografía
Dificultad
Normal
Puntos
10

Enunciado

Se ha interceptado tráfico cifrado de un proveedor logístico militar. Un manifiesto de carga no cuadra: la referencia de envío declarada en aduana no coincide con la ruta contratada.

El proveedor genera sus claves en un appliance de bajo coste durante el arranque del servicio. Se adjunta una segunda clave pública del mismo proveedor, emitida minutos después, «por si sirve», y una anotación de un analista sin verificar.

Misión: recuperar la clave privada, descifrar el manifiesto y extraer la referencia de envío real, con formato T4C{...}.

Ficheros

Descargar todo el material ZIP · 3,8 KB

SHA-256 del ZIP: f17f506c737650bac19be70778f71e8f30beadf516f574053ce08a8ccbd8e75a

Pistas

Pista 1 · ¿Qué tiene de raro ese appliance?

Un dispositivo barato que genera claves justo al arrancar tiene muy poca entropía, así que su generador de aleatorios puede repetir valores.

Pista 2 · ¿Para qué sirve la segunda clave?

Dos claves generadas casi a la vez con poca entropía pueden compartir uno de sus primos. ¿Qué operación muy barata lo comprobaría?

Pista 3 · La anotación del analista

Es una distracción. Factorizar RSA-2048 no es viable y tampoco hace falta ninguna base de datos externa.

Resolución

Ver la solución completa incluye la flag

La vulnerabilidad: primo compartido

En RSA el módulo es n = p · q. Si dos claves comparten un primo, su máximo común divisor lo revela al instante, aunque los números tengan 2048 bits:

nA = p · qA
nB = p · qB
mcd(nA, nB) = p

El fallo tiene precedente real: Mining Your Ps and Qs (Heninger et al., USENIX Security 2012) y Ron was wrong, Whit is right (Lenstra et al., 2012) encontraron miles de claves TLS y SSH de routers y dispositivos embebidos con primos compartidos.

Formato del fichero cifrado

Estructura de manifiesto_carga.enc (1936 bytes). Esquema no a escala.

Paso a paso

  1. Inspeccionar las claves con openssl rsa -pubin -in clave_proveedor_A.pem -noout -text: módulo de 2048 bits, exponente 65537.
  2. Calcular p = mcd(nA, nB). Sale un primo de 1024 bits, así que las claves comparten un factor.
  3. Reconstruir la clave privada de A: qA = nA / p, φ = (p−1)(qA−1) y d = e⁻¹ mod φ.
  4. Separar el .enc en sus tres partes: [0:256], [256:272] y [272:].
  5. Descifrar los 256 primeros bytes con RSA-OAEP para obtener la clave AES. Ojo: el hash del OAEP es SHA-1, no SHA-256.
  6. Descifrar el cuerpo con AES-256-CBC usando el IV y quitar el relleno PKCS#7.

El núcleo del script

p = math.gcd(A.n, B.n)
q = A.n // p
phi = (p - 1) * (q - 1)
dA = pow(A.e, -1, phi)
priv = rsa.RSAPrivateNumbers(
    p, q, dA, dA % (p - 1), dA % (q - 1), pow(q, -1, p), A).private_key()

blob = open("manifiesto_carga.enc", "rb").read()
wrapped, iv, body = blob[:256], blob[256:272], blob[272:]
k = priv.decrypt(wrapped, padding.OAEP(
    mgf=padding.MGF1(hashes.SHA1()), algorithm=hashes.SHA1(), label=None))

Resultado

MARITIME LOGISTICS IBERIA S.L. · DIVISIÓN CARGA ESPECIAL
MANIFIESTO DE CARGA — DOCUMENTO RESERVADO

Contrato .................. MLI-2026-ALG-0774
Buque ..................... M/V CABO DE GATA  (IMO 9438117)
Puerto de origen .......... Algeciras (ESALG)
Puerto de destino declarado Ceuta (ESCEU)

1x  Contenedor 40' HC   MLIU 774139-2   "Material de obra civil"
1x  Contenedor 20' DV   MLIU 550481-6   "Repuestos mecánicos"
3x  Palé refrigerado    -               "Suministro alimentario"

REFERENCIA DE ENVÍO REAL: T4C{f4ct0r_c0mun_ENV_4LG_77413}

Lecciones

  • Con varias claves RSA del mismo origen, prueba siempre el MCD entre todas. Si son muchas, con batch GCD.
  • No generes claves en el arranque de un dispositivo embebido sin entropía suficiente: usa getrandom() bloqueante o un TRNG/TPM por hardware.
  • Desconfía de las fuentes sin verificar: la anotación del analista llevaba a un camino sin salida.

RETO 02 · PIR-05

Persistencia Silenciosa

«¿Cómo sobrevive el implante a los reinicios?»

Categoría
Reversing / Forense
Puntos
10

Enunciado

Un implante instalado en el servidor de planificación sobrevive a los reinicios. Se adjunta el binario recuperado de /usr/lib/naval/ y un volcado de triaje tomado en caliente antes de aislar el host.

Misión: desofuscar el binario y acreditar el mecanismo exacto de persistencia. La flag es la ruta y el nombre de la unidad instalada.

Formato de la flag: se quita la barra inicial y cada barra, punto o guion pasa a guion bajo. Por ejemplo, /usr/lib/foo-bar.service queda como T4C{usr_lib_foo_bar_service}.

No ejecutes el binario. El análisis previsto es estático: fuera del host original no hace nada visible. Además, el triaje se tomó con el sistema ya alterado, así que trátalo como hipótesis y no como prueba.

Ficheros

Descargar todo el material ZIP · 4,5 KB

SHA-256 del ZIP: b92f2f1c768e13b7ade477086f87ea40b8c903eb0b38b222d452e3f7208e34ab

Pistas

Pista 1 · Mira los procesos con calma

En ps_aux.txt hay dos procesos casi idénticos. Solo uno pertenece al sistema de planificación.

Pista 2 · ¿Qué hace main?

Desensámblalo con objdump -d -M intel. Lee el hostname, deriva de él una clave de 8 bytes y descifra un bloque de .rodata. Después compara el resultado con una cadena mágica.

Pista 3 · ¿Y si el hostname no funciona?

No necesitas el hostname. Si sabes cómo empieza el texto descifrado, puedes despejar la clave directamente.

Resolución

Ver la solución completa incluye la flag

Qué hace el binario

  1. Llama a gethostname().
  2. Calcula un hash FNV-1a de 64 bits con una base no estándar. Sus 8 bytes en little-endian son la clave.
  3. Descifra 548 bytes de .rodata, en el offset 0x2020.
  4. Si el resultado empieza por NAVSYNC, imprime el informe. Ese memcmp es la única comprobación.
# Hash del hostname
base  = 0x14650fb0739d0383
primo = 0x100000001b3
h = (h ^ c) * primo   # por cada byte c, mod 2^64

# Descifrado, byte a byte (mod 256)
salida[i] = (rol8(clave[i & 7], i >> 3) ^ cifrado[i]) - i

El señuelo del hostname

Con el hostname del triaje, srv-planificacion-01, la salida no empieza por NAVSYNC: el dato no sirve. Sacar el hostname real invirtiendo el hash tampoco es práctico, y resulta que no hace falta.

La clave: texto conocido

Sabemos que el texto descifrado empieza por NAVSYNC. Para i < 8 la rotación es cero, así que los 7 primeros bytes de la clave se despejan directamente. clave[7] solo afecta a uno de cada ocho bytes: basta con probar sus 256 valores y quedarse con el que da texto imprimible.

need = [((b"NAVSYNC"[i] + i) & 0xff) ^ cipher[i] for i in range(7)]

for k7 in range(256):
    out = decode(cipher, need + [k7])
    if out[:7] == b"NAVSYNC" and all(9 <= b <= 13 or 32 <= b <= 126 for b in out):
        print(out.decode())
        break

Sale clave[7] = 0xcf, con lo que la clave completa es 93 17 f8 2a 53 73 11 cf.

Resultado: el mensaje oculto en el binario

NAVSYNC
[naval-syncd] mecanismo de persistencia instalado

  unidad temporizador : /etc/systemd/system/naval-syncd.timer
  unidad servicio     : /etc/systemd/system/naval-syncd.service
  ExecStart           : /usr/lib/naval/naval-syncd --quiet --no-banner
  OnBootSec           : 120
  OnUnitActiveSec     : 900
  WantedBy            : timers.target

  nota: el nombre imita al servicio legitimo naval-sync.service, que si
        pertenece al sistema de planificacion. Solo cambia una letra.

  marcador: T4C{etc_systemd_system_naval_syncd_timer}

Cómo se traduce en ATT&CK

TécnicaEn este implante
T1053.006 Systemd TimersEl temporizador lo relanza 120 s tras cada arranque y cada 900 s.
T1543.002 Systemd Servicenaval-syncd.service es la unidad que arranca el binario.
T1036.004 Masquerade Task or Servicenaval-syncd imita a naval-sync, el legítimo.
T1480.001 Environmental KeyingLa clave depende del hostname, y eso frustra el análisis en sandbox.

Lecciones

  • Revisa /etc/systemd/system buscando nombres casi idénticos a los de servicios legítimos.
  • No confíes en el triaje de un host comprometido: verifica siempre contra el artefacto original.
  • Una cabecera o cadena mágica conocida puede romper un cifrado casero sin necesidad de conocer su clave.