Usa el árbol para saltar entre colecciones sin salir del lector.

archivo Seleccionar un writeup Abrir árbol
NullconCTF2026/WriteupEmoji.md READ_ONLY

💯 (unicode magic)

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

El único archivo entregado era un README.md que, a simple vista, parecía contener solo un emoji, 💯. El truco estaba en que ese emoji no venía solo. Detrás llevaba caracteres Unicode invisibles que escondían el mensaje real.


Reconocimiento

Cuando un archivo "no tiene nada" pero claramente debería esconder algo, revisar los codepoints suele ser un muy buen primer paso. En este caso, al leer el contenido en UTF-8 y mostrar ord() de cada carácter aparecían valores en los rangos U+FE00..U+FE0F y U+E0100..U+E01EF.

Esos rangos corresponden a Variation Selectors, caracteres que normalmente no se ven al mostrar el texto pero siguen presentes en el archivo. El emoji hacía de base y los selectores que venían después eran los que llevaban la información.


Análisis

El esquema del reto era directo. Cada Variation Selector representaba un valor entre 0 y 255, y esos valores se interpretaban como bytes ASCII.

La conversión que hacía falta era esta.

  • U+FE00..U+FE0F se convertía en 0..15
  • U+E0100..U+E01EF se convertía en 16..255

Una vez convertido cada selector a su valor numérico, ya solo quedaba concatenar esos bytes para obtener el texto oculto.

No había cifrado ni compresión. El contenido estaba en los caracteres invisibles que acompañaban al emoji visible.


Resolución

Con un script corto en Python alcanzaba para ignorar el emoji inicial y decodificar los selectores.

def decode_variation_selectors(text: str) -> str:
    payload = text[1:]
    values = []

    for ch in payload:
        cp = ord(ch)
        if 0xFE00 <= cp <= 0xFE0F:
            values.append(cp - 0xFE00)
        elif 0xE0100 <= cp <= 0xE01EF:
            values.append(cp - 0xE0100 + 16)

    return "".join(chr(v) for v in values)

with open("README.md", "r", encoding="utf-8") as f:
    print(decode_variation_selectors(f.read().strip()))

Al ejecutarlo, la salida devolvía la flag en claro.


Flag

ENO{EM0J1S_UN1COD3_1S_MAG1C}