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- clave_proveedor_A.pem450 BClave pública RSA-2048 con la que se envolvió la clave de sesión.
- clave_proveedor_B.pem450 BClave pública de un segundo contrato del mismo proveedor.
- manifiesto_carga.enc1,9 KBEl manifiesto cifrado, en formato híbrido RSA + AES.
- NOTA_DEL_ANALISTA.txt1,5 KBFormato del fichero cifrado y la anotación sin verificar.
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
manifiesto_carga.enc (1936 bytes). Esquema no a escala.Paso a paso
- Inspeccionar las claves con
openssl rsa -pubin -in clave_proveedor_A.pem -noout -text: módulo de 2048 bits, exponente 65537. - Calcular
p = mcd(nA, nB). Sale un primo de 1024 bits, así que las claves comparten un factor. - Reconstruir la clave privada de A:
qA = nA / p,φ = (p−1)(qA−1)yd = e⁻¹ mod φ. - Separar el
.encen sus tres partes:[0:256],[256:272]y[272:]. - Descifrar los 256 primeros bytes con RSA-OAEP para obtener la clave AES. Ojo: el hash del OAEP es SHA-1, no SHA-256.
- 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}.
Ficheros
Descargar todo el material ZIP · 4,5 KB- persistencia.elf14,2 KBBinario del implante: ELF x86-64, PIE, sin símbolos.
- LEEME.txt1,0 KBContexto del incidente IR-2026-0209.
- triaje/etc_hostname.txt22 BNombre de host según el volcado.
- triaje/ps_aux.txt716 BProcesos en ejecución.
- triaje/mount.txt195 BMontajes del host.
- triaje/systemctl_list-timers.txt729 BTemporizadores de systemd, con una nota del analista.
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
- Llama a
gethostname(). - Calcula un hash FNV-1a de 64 bits con una base no estándar. Sus 8 bytes en little-endian son la clave.
- Descifra 548 bytes de
.rodata, en el offset0x2020. - Si el resultado empieza por
NAVSYNC, imprime el informe. Esememcmpes 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écnica | En este implante |
|---|---|
T1053.006 Systemd Timers | El temporizador lo relanza 120 s tras cada arranque y cada 900 s. |
T1543.002 Systemd Service | naval-syncd.service es la unidad que arranca el binario. |
T1036.004 Masquerade Task or Service | naval-syncd imita a naval-sync, el legítimo. |
T1480.001 Environmental Keying | La clave depende del hostname, y eso frustra el análisis en sandbox. |
Lecciones
- Revisa
/etc/systemd/systembuscando 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.