Datos del reto
| Campo | Valor |
|---|---|
| CTF | Nullcon CTF 2026 |
| Reto | 💯 (unicode magic) |
| Categoría | Misc |
| Flag | ENO{EM0J1S_UN1COD3_1S_MAG1C} |
Descripción del reto
Recuperar la flag a partir del único artefacto entregado: un README.md que contiene aparentemente solo un emoji (💯). A simple vista no hay nada más; el truco está en que el archivo incluye caracteres invisibles Unicode (Variation Selectors) que codifican la flag.
Reconocimiento
Muchos retos "misc" esconden información usando Unicode Variation Selectors: caracteres "invisibles" que modifican cómo se renderiza un carácter previo. En texto plano quedan pegados al final del emoji y no se ven. Se suelen usar como canal encubierto para codificar bytes.
En este reto, el emoji 💯 funciona como portador, y después vienen una serie de Variation Selectors invisibles que codifican la flag.
Formas típicas de verlo:
- Opción 1: Ver los codepoints con Python. Leyendo el archivo en UTF-8 y mostrando
ord()(ohex(ord())) aparecen caracteres con rangos comoU+FE00..U+FE0FoU+E0100..U+E01EF(Variation Selectors). - Opción 2: Herramientas CLI como
xxd,hexdump -Co editores que muestran Unicode invisible; se ve que el archivo NO es solo el emoji.
Análisis de la vulnerabilidad
Los Variation Selectors se mapean a valores numéricos (0..255), que luego se interpretan como bytes ASCII:
- Mapeo:
U+FE00..U+FE0F→ valores0..15;U+E0100..U+E01EF→ valores16..255(convalor = codepoint - 0xE0100 + 16). Cada valor se convierte a carácter conchr(valor)y se concatena.
Explotación
Script de solución (Python). Guardar como solve.py en la misma carpeta del README.md:
#!/usr/bin/env python3
def decode_variation_selectors(text: str) -> str:
if not text:
return ""
payload = text[1:] # El primer carácter es el emoji portador (💯). Lo ignoramos.
vals = []
for ch in payload:
cp = ord(ch)
if 0xFE00 <= cp <= 0xFE0F:
vals.append(cp - 0xFE00)
elif 0xE0100 <= cp <= 0xE01EF:
vals.append(cp - 0xE0100 + 16)
else:
pass
return "".join(chr(v) for v in vals)
def main():
with open("README.md", "r", encoding="utf-8") as f:
s = f.read().strip()
decoded = decode_variation_selectors(s)
print(decoded)
if __name__ == "__main__":
main()
Ejecutar: python3 solve.py
Flag
ENO{EM0J1S_UN1COD3_1S_MAG1C}
Lecciones aprendidas
- Los Variation Selectors Unicode son invisibles en la mayoría de editores pero siguen codificando información.
- Retos misc de esteganografía pueden usar el mismo alfabeto (emoji + selectores) para codificar bytes.