PAUL GUERRERO software engineer
← retos

Euskal Encounter 34 · Solve It 4

Solve It 4 — Moviplaya 2005

Resuelto
  • #ctf
  • #criptografía
  • #ia
Esta ROM esconde un misterio que no somos capaces de resolver.
Enunciado del reto

Cómo se resolvió

  1. La partida guardada no lleva nada

    Desencriptas su Deoxys y es una captura legítima, sin nada escondido dentro. El evento de 2005 tampoco regalaba ese bicho, sino el ticket para ir a por él. Media hora de comprobación y a otra cosa.

  2. El castellano delata lo inyectado

    El hash de la ROM no cuadra con el dump oficial, pero el diff de bytes son diez megas de ruido. La Esmeralda base es inglesa: diffea el texto decodificado y salen los diálogos que escribió el autor.

  3. Doce posiciones, no seis

    Contar antes de interpretar: la celda tiene el doble de puntos que el braille. Es night writing, el sistema militar de 1815. Y en el mismo cartel conviven las dos escrituras. Las señales dictan un camino a pasos.

  4. La contraseña se gana jugando

    La novena señal pide liberar el dulce aroma: se abre un pasadizo y dentro espera HÉCTOR. Hay que ganarle el combate; entonces una rutina inyectada la calcula en caliente. En el fichero no está escrita.

Contraseña 4roM4Noc7urNo

Tardamos en darnos cuenta de lo literal que era el enunciado: la frase la pronuncia un personaje dentro del juego. Los ficheros eran una ROM de Pokémon Esmeralda de 16 MB y una partida guardada, y el título apuntaba al verano de 2005, cuando Nintendo recorrió las playas españolas repartiendo eventos de Pokémon a quien se acercara con su Game Boy.

Con un save de Pokémon delante, el instinto es buscar el premio ahí: un mote, un nombre de entrenador, algo escondido en las cajas. Es justo lo que el autor esperaba, y por eso el save no lleva nada. El reto de verdad está inyectado en la ROM: una isla inventada, un ticket falso, una ruta de personajes que te la van contando y unas señales escritas en un sistema de puntos que parece braille y solo lo es a medias: la mayor parte está en su antepasado militar, veinte años más viejo.

Y el nivel está hecho para jugarse: descifras las señales, andas el recorrido que dictan, abres el pasadizo del final y ganas el combate que hay dentro; entonces el juego te dice la contraseña. No está escrita en ninguna parte del fichero — la calcula en caliente una rutina que el autor compiló dentro de la ROM. Esa era la puerta principal, y no es por donde entramos nosotros: durante el concurso la sacó un compañero inspeccionando el binario, saltándose el recorrido. Abajo va la cadena entera —el decoy, la palanca que lo abre todo, las señales y el keygen—, con los tropiezos incluidos, que aquí fueron caros.

El decoy: un save que no lleva nada

Descartar el save bien lleva su trabajo, porque los textos de la tercera generación van en un charset propietario y strings no ve nada. El formato son secciones de 4 KB con su ID y su checksum, y los Pokémon van cifrados con PID ^ TID salvo los motes y el nombre del entrenador. Con eso se localiza y desencripta el bicho temático del save: un DEOXYS de entrenador SHINY14, capturado en la zona 187, con el bit de fateful encounter puesto.

Para traducir ese 187 no hace falta creerse una wiki: la tabla de nombres de zona está en la propia ROM, y volcándola sale que 187 es Birth Island, Isla Origen. Es decir, una captura legítima, exactamente como la consigue cualquier jugador. El resto del save es un living dex bajado de internet, con nombres de broma y cajas por defecto. Ruido.

Aquí entra conocimiento del juego, no evidencia del binario: Moviplaya 2005 no regalaba un Deoxys. Regalaba el Aurora Ticket, el billete que desbloquea Isla Origen para que captures el Deoxys tú mismo. El Deoxys del save es coherente con el evento, pero no es el evento. El save no tiene contraseña; merecía comprobarse, no merecía más de media hora.

La palanca: castellano dentro de un juego inglés

El dato que decide el reto está a la vista desde el primer minuto: el MD5 de la ROM no coincide con el del dump oficial. Está modificada. El primer intento de explotarlo —diff byte a byte contra una copia limpia— no sirve: difieren unos 10 MB de 16, porque los datos recompilados divergen en bloque aunque el juego sea el mismo.

La palanca buena es diffear el texto decodificado, no los bytes. Y funciona por una razón muy concreta: la Esmeralda base es inglesa, así que todo texto en castellano dentro de esta ROM lo inyectó el autor. El decodificador son quince líneas —recorrer la ROM traduciendo cada byte con la tabla de caracteres de la generación 3 y quedarse con las tiradas largas que parezcan frase—, y en su salida el castellano inyectado convive con el inglés original:

El misterio de FAIL ISLAND? Ni IMOBILIS ni yo fuimos capaces de resolver esas
escrituras. Hay un viejo amigo que quizá pueda ayudarte. Hace tiempo que no le
vemos, pero debe de andar por MOSSDEEP. Pregunta por HÉCTOR.
| Este dashboard de Grafana no pinta bien, tendremos que revisar los switches...
| Hm? You don't seem to have any room for this POKeMON.

Ahí está el enunciado del reto, casi palabra por palabra, en boca de un personaje. Y de propina un NPC hablando de dashboards de Grafana, por si quedaba duda de quién escribió eso.

El tropiezo que nos costó una hora: esa herramienta la teníamos escrita desde mucho antes, pero la usamos para preguntar «¿esta ROM está en español?» — respuesta: no, es inglesa — y seguimos buscando por otro lado. La pregunta correcta, «¿qué texto hay aquí que no esté en la original?», usaba exactamente el mismo código. Tener el instrumento no sirve de nada si le haces la pregunta pequeña.

La búsqueda del tesoro

Con el texto inyectado delante, el reto se lee solo. Algunos offsets, verificados sobre el binario:

OffsetFragmento
0x270bcc«Ni IMOBILIS ni yo fuimos capaces»
0x270c59«debe de andar por MOSSDEEP.»
0x1e53cc«en el CENTRO ESPACIAL.»
0x2247ac«Haz sitio para el FAIL TICKET»
0x1e556f«CTOR te da un ticket raro,»
0x1e5660«FAIL ISLAND, seg»

Los cortes raros son del decodificador: no mapea vocales acentuadas ni ñ, así que cada acento parte la tirada — por eso «HÉCTOR» aparece como CTOR. La ruta, en claro: Centro Espacial de Mossdeep → HÉCTOR → FAIL TICKET → puerto de Calagua → barco → FAIL ISLAND, una isla que un NPC describe como «llena de señales». Cargando la ROM y el save en mGBA, la ruta funciona tal cual y la isla existe.

Otra nota de contexto, que no está en el binario: Fail Island y el Fail Ticket son la parodia de Isla Origen y el Aurora Ticket — exactamente lo que repartía Moviplaya. El chiste cierra.

Las señales: contar antes de leer

Cartel del juego con dos líneas de celdas de puntos: cada celda son dos columnas de barritas horizontales apiladas, oscuras las marcadas y claras las vacías; la segunda línea empieza con una celda de cuadrados gruesos, más grandes que el resto

Las nueve señales de la isla parecen braille, y ahí nos ganó el reto. Cada intento de leerlas como braille producía celdas imposibles y transcripciones con pinta de resultado que eran basura. El error no era de precisión: era de modelo. La comprobación más barata del reto —contar los puntos de una celda— la hicimos tarde, y a ojo: la celda tiene doce posiciones, seis filas por dos columnas. El braille tiene seis. Esto no es braille. (Con el tiempo resultó que la frase correcta era «esto no es solo braille», pero eso viene luego.)

Es night writing, la sonografía de Charles Barbier: un sistema militar de 1815 para que la tropa leyera órdenes de noche sin encender una luz que delatara la posición. Louis Braille partió de él y lo simplificó a las seis posiciones que conocemos. Y la mecánica de lectura es distinta de raíz: una celda de Barbier no es un dibujo que memorizar, es un par de números. Cuentas las marcas de la columna izquierda y eso te da la fila; cuentas las de la derecha y eso te da la columna; y el par indexa una tabla de 6×6 sonidos del francés:

123456
1aiouéè
2aninonuneuou
3bdgjvz
4ptqchfs
5lmnrgnll
6oioinianienionieu

En los píxeles, cada posición se dibuja siempre, como una barrita horizontal: gris oscuro si está marcada, gris claro si no. Un ejemplo concreto de la captura de arriba: la palabra de tres celdas que se repite por toda la isla da los pares 41 11 46 — cuatro marcas y una, una y una, cuatro y seis. Con la tabla: fila 4 columna 1 = p, fila 1 columna 1 = a, fila 4 columna 6 = s. «Pas». Ese mismo trío aparece en seis de las nueve señales, con encuadres distintos, y esa repetición es la mejor validación de la extracción: un detector con ruido no produce el mismo trío en seis fotos.

Por qué el braille daba basura convincente: cada línea de texto se ve como tres bandas horizontales, y la tentación es leer cada banda como una línea. No lo son — las tres bandas juntas son una línea de celdas de seis filas. Si las separas, cada conteo sale a un tercio de su valor… y sigue cayendo dentro del rango 1–6, así que mapea a sonidos válidos y produce una transcripción completa. Un modelo equivocado no falla limpio: falla a medias, y eso lo disfraza de ruido.

Lámina con las nueve señales de la isla, etiquetadas S1 a S9 y apiladas una debajo de otra. Cada una es un cartel blanco con dos líneas de celdas de puntos en dos tonos de gris; se repiten grupos de celdas idénticos entre señales distintas

Las nueve señales de Fail Island, recortadas de las capturas del emulador y etiquetadas para poder citarlas. El trío de celdas que se lee «pas» aparece en seis de ellas, con encuadres distintos.

Con la agrupación correcta, las señales hablan francés —lógico: la tabla de Barbier codifica fonemas franceses, y el autor la usó tal cual— y son instrucciones de un recorrido: un pas vers…, deux pas vers…, trois pas vers…, regardez. Las señales no llevan la contraseña: llevan el camino. Y aquí la pieza de diseño que hace bueno al reto, otra vez conocimiento del juego y no del binario: caminar un patrón preciso por una isla vacía es exactamente la mecánica del evento oficial de Deoxys en Isla Origen. El autor no inventó un puzzle cualquiera: replicó el evento que da nombre al reto.

El segundo sistema

Aun así el decode se quedaba a medias. Salía un / deux / trois … pas vers … y ahí se acababa: la dirección no se leía, y vers salía como véts. El diagnóstico que dimos fue que el extractor mide mal las celdas de conteo alto —«cuando una columna lleva cinco o seis marcas, las barras se funden y la cuenta baja»—, que es una explicación cómoda porque no obliga a revisar el modelo.

Era falso, y esta es la parte buena del reto. Esas celdas no son night writing mal medido: en el mismo cartel conviven dos sistemas de escritura. Y la diferencia no se interpreta, se mide.

barras por columnaalto de barrasistemacómo se lee
celda fina66–7 pxnight writingtally por columna → par (fila, columna)
celda gruesa318–19 pxbraille de seis puntosbitmap 1-2-3 / 4-5-6

Las «celdas de cuadrados gruesos» que habíamos apartado como ornamento son braille, y llevan dato. Son las letras que la tabla de Barbier no cubre —c, h, x— más las cuatro de su fila 5 —l, m, n, r—. Ninguna celda de night writing de las nueve señales baja de la fila 4 en su columna izquierda: el autor cortó ahí y para lo demás se pasó al braille. La r de vers nunca fue un conteo mal medido; era braille [1235] leído con la tabla del otro sistema. Estábamos leyendo con una sola tabla un texto escrito en dos.

Con las dos, las nueve señales se leen enteras:

francéscastellano
S1sous le cristal bleu, commencébajo el cristal azul, empieza
S2un pas vers le midiun paso hacia el sur
S3quatre pas vers l’aubecuatro pasos hacia el este
S4trois pas vers le miditres pasos hacia el sur
S5six pas vers le couchantseis pasos hacia el oeste
S6un pas vers le nordun paso hacia el norte
S7deux pas vers l’aubedos pasos hacia el este
S8regardez en hautmirad arriba
S9libérez le doux parfumliberad el dulce aroma

Las direcciones van en francés poético, no en nord/sud/est/ouest: midi es el mediodía, el sur; aube es el alba, el este; couchant es el poniente, el oeste. Por eso ninguna palabra obvia encajaba mientras probábamos cribs contra la tabla mala.

Y la transcripción es fonética, que es como funciona el night writing: la tabla de Barbier indexa sonidos, no letras. parfun es p·a·r·f·[un], con [un] como un único glifo; regarde an haut es regardez en haut dicho en voz alta. No son erratas del extractor, es el sistema.

La comprobación cruzada: píxeles contra bytes

El texto de las señales también está guardado en la ROM, en nueve bloques contiguos a partir de 0x2a73c2. Eso permite una comprobación que no depende de los píxeles: sacar los glifos de los PNG por un lado, los bytes del binario por otro, y ver si se corresponden.

Se corresponden. 29 símbolos distintos, biyección perfecta, ni una colisión, y las longitudes cuadran señal por señal. Es más: de las 362.880 formas de emparejar las nueve capturas con los nueve bloques, solo una es consistente — la identidad. Cada cartel queda atado a su bloque de bytes por fuerza bruta, no por buena voluntad.

De propina sale cómo lo guardó el autor: cada byte es un bitmap braille de la letra latina, con pesos dot1=1, dot2=4, dot3=16, dot4=2, dot5=8, dot6=32. Decodificados así, los bloques de la ROM dicen:

S1   sys le cristal | blw commence.
S2   z pas vers | le midi.
S5   six pas vers | le cykjt.
S9   libere le | dyx parfz.

Las letras raras son justo las que al francés hablado le sobran del alfabeto latino: z=un, y=ou, w=eu, j=an, k=ch. Así sys es sous, blw es bleu, cykjt es couchant y parfz es parfum. El autor se montó un alfabeto de 29 símbolos —26 letras, espacio, punto y salto de línea—, recicló cinco letras ociosas como los dígrafos que le faltaban, y luego lo pintó mezclando los dos sistemas.

El orden importa, porque lo que se lee son instrucciones, y no hay que deducirlo: los símbolos están escritos a lo largo de la pared de la cueva, y se leen de izquierda a derecha como cualquier texto. Ese es el orden de los pasos. Que coincida con S1…S9 —el orden de los ficheros, que es también el de los bloques en la ROM— es una comprobación de más, no la fuente.

Y sale bien: andando dentro del juego la secuencia que dictan —un paso al sur, cuatro al este, tres al sur, seis al oeste, uno al norte, dos al este— y usando entonces el Dulce Aroma, el camino secreto se abre.

La llave: Dulce Aroma

¿Y qué haces al final del recorrido? También está escrito en la ROM, en el mismo dump de texto inyectado:

«El cristal azul responde al DULCE AROMA. Se abre un pasadizo.»

Este fue el error más caro del nivel. Estuvimos un buen rato buscando qué significaba Dulce Aroma (Sweet Scent) en wikis de Pokémon, como si fuera una mecánica del juego base. No lo es: la frase la escribió el autor, y estaba en un fichero que habíamos generado nosotros mismos horas antes — el mismo diff de texto que destapó a HÉCTOR y el Fail Ticket. Se comprueba en un grep: la ROM entera contiene dieciséis tiradas de texto con «aroma»; quince están en inglés y son del juego base, y una sola está en castellano, la del cristal, en 0x291362. Cuando alguien ha inyectado contenido en un binario, la fuente autorizada sobre ese contenido es el binario. No internet.

Y hay un segundo golpe, que solo se ve ahora que las señales están cerradas: S9 dice «liberad el dulce aroma». La instrucción estaba escrita en el propio puzzle desde el principio. Mientras buscábamos en wikis qué era Dulce Aroma, el último cartel de la isla lo estaba pidiendo a la cara — en night writing y en francés, pero pidiéndolo. El recorrido no acaba en una pista: acaba en una orden.

Captura del emulador: una sala de roca marrón con el suelo de tierra vacío, una entrada oscura al fondo y, en la pared, un cristal azul brillante

El cristal azul de S1, en el juego. «Sous le cristal bleu, commencé»: el recorrido empieza aquí, y aquí es donde se responde al Dulce Aroma.

La contraseña no existe: se genera

Tras el pasadizo hay una cueva oculta, y dentro un entrenador —HECTOR, otra vez: el mismo nombre que abre la ruta la cierra—, y su diálogo de derrota, en 0x22e05a, dice «La contraseña es» seguido de un placeholder de variable que el juego rellena en caliente. La contraseña no está en el fichero, se busque como se busque: grep en ASCII da cero resultados, y codificarla al charset propietario de la generación 3 y buscar esos bytes también.

Quien la produce es una rutina inyectada que los scripts del juego invocan por número —la special 0x1F4—, y desensamblada es un keygen pequeño y completo:

seed  = estado del juego (dos variables)
gate  : exige dos flags puestos  Y  seed == 0x3D40563B   (si no, no genera)
state = seed ^ 0xC3A5F17D
para i = 0..12:                       # 13 caracteres
    state  = xorshift32(state)        # desplazamientos 13, 17, 5
    buf[i] = (state >> 24)                     # byte alto del estado
           ^ TABLA[i & 3][ idx(i) % 19 ]       # sustitución por posición
           ^ ((29*i + 0x37) & 0xFF)            # máscara que depende de i

El gate es lo que ata el keygen al recorrido: solo produce algo cuando has hecho el camino (los dos flags) y el estado del juego vale exactamente ese seed. Las cuatro tablas de sustitución, 76 bytes en total, viven en 0x5F14A0 y son datos del autor, no de la Esmeralda comercial. Con todo eso, keygen.py reproduce la rutina sin necesitar la ROM, y su salida es literalmente esta:

$ python3 keygen.py
bytes Gen 3 : a5 e6 e3 c7 a5 c8 e3 d7 a8 e9 e6 c8 e3
contrasena  : 4roM4Noc7urNo

Aviso de método: todo lo de arriba es reversing estático, deducido sobre el binario sin tocar el juego. La cadena está cerrada por las dos puntas —el recorrido se ha andado dentro del juego y la contraseña sale en pantalla—, pero conviene decir cuál de las dos era la puerta que el autor puso: la de jugar. Por el binario se llega antes, y el nivel no estaba pensado para eso; es leer la partitura en vez de tocarla. La ROM y el save siguen sin redistribuirse, que son un juego comercial; los offsets están para quien los tenga.

NIGHT CAVE: el reto, jugado

Captura del juego: el jugador dentro de una cueva de roca marrón y, arriba a la izquierda, el rótulo blanco de entrada de zona con el texto NIGHT CAVE

La zona donde pasa todo esto tiene nombre, y el rótulo de entrada lo canta: NIGHT CAVE. Ahí está la tercera pata del chiste del autor. Las señales están en night writing, la escritura nocturna de Barbier; la cueva se llama night cave; y la contraseña es «aroma nocturno». La palabra que ata todo el reto está puesta hasta en el cartel de la puerta.

Y al terminar, en la misma cueva, un «Enhorabuena, solver.» — el autor rompiendo la cuarta pared para saludar a quien está jugando.

HÉCTOR va en serio

Pantalla de combate Pokémon: el sprite del rival, un entrenador con los brazos en alto, y el cuadro de texto que dice HECTOR would like to battle!

Al fondo de la cueva espera HÉCTOR, y conviene avisar de que el combate no es un trámite narrativo. Es un especialista de tipo eléctrico con el equipo entre los niveles 55 y 65 —por ahí remata la Liga la Esmeralda original— y cierra con un METAGROSS que no es eléctrico y que pega como un camión. Después de todo el camino a ciegas, todavía hay que ganar.

Captura del juego dentro de la cueva: el jugador de pie y el cuadro de diálogo mostrando el texto 4roM4Noc7urNo

La contraseña, dicha por el juego después del combate. Es exactamente la salida de keygen.py: la rutina reconstruida en Python y la rutina compilada dentro de la ROM producen el mismo string.

Lo que nos llevamos

Cuatro cosas. Que un binario modificado se ataca por el diff semántico, no por el de bytes: la asimetría de idioma convirtió 10 MB de ruido en una lista de diálogos del autor. Que la pista que te falta suele estar dentro del propio artefacto — antes de salir a buscar a internet, grepea lo que ya tienes, sobre todo si lo generaste tú; y si el artefacto es un puzzle, acábalo, que S9 llevaba horas dándonos la respuesta. Que ante un sistema de símbolos hay que medir la estructura antes de asumir el sistema por su aspecto: contar doce posiciones donde el braille tiene seis costaba un minuto. Y la que más cara ha salido: cuando algo se lee a medias, desconfía del diagnóstico que no te obliga a tocar el modelo. «El extractor mide mal las celdas grandes» era cómodo y era falso; lo que pasaba es que había dos escrituras en el mismo cartel, y se veía con una regla — seis píxeles contra diecinueve.

Y una nota sobre el proceso, porque el orden de este writeup no es el orden en que pasó. Durante el concurso las señales sí se transcribieron a ojo —de ahí salió la geometría real de doce posiciones, que ningún OCR nuestro veía—, pero eso no produjo la contraseña: la sacó un compañero por el binario, y el decode completo llegó después. Es lo que explica el reto, no el camino que seguimos.

Solución:

4roM4Noc7urNo

El material — los scripts, los datos y el writeup técnico completo de este nivel están en euskal34/solve-it/4.