Euskal Encounter 34 · Hack It 3
Hack It 3 — Tercer Templo
- #ctf
- #ia
Terry ha dejado una nota dentro del templo que él mismo construyó. ¿Qué querrá decirnos?
Cómo se resolvió
-
El oráculo «God» no es aleatorio
Responde con una palabra y un versículo tirando de una cola de bits que, al vaciarse, se rellena con el reloj del teclado y el ratón. Conocido el instante, la respuesta es una función pura de él.
-
Un snapshot antes de cada pregunta
Cada consulta gasta entropía que no vuelve, así que dos preguntas seguidas no son comparables. Construimos «ouija»: arranca la máquina una vez y le restaura la memoria —cola de bits incluida— en cada intento.
-
Ocho instantes escondidos bajo un velo
Una nota del autor da el primero. De cada uno salen una palabra y un pasaje, y al margen de ese pasaje va en hexadecimal el siguiente, tapado con una máscara que se regenera en cada eslabón.
-
Dos análisis, un mismo error
Los dos leían mal la primera palabra, y la coincidencia parecía confirmación. Pero partían del mismo cálculo: el seed es determinista, no da ruido del que promediar, da siempre exactamente el mismo error.
Contraseña the holy spirit speaks through a stop watch
El templo es literal: el reto es una imagen de disco de TempleOS, el sistema operativo que Terry A. Davis programó él solo, de arriba abajo, durante más de una década. Davis creía que Dios le hablaba a través del ordenador, y dejó dentro del sistema un oráculo llamado «God» que contesta con una palabra y un versículo de la Biblia. La contraseña sale de hacerle las preguntas correctas.
La gracia del oráculo es que parece aleatorio y no lo es. God saca su entropía del reloj de los eventos de teclado y ratón —del cronómetro del sistema—, y esa es exactamente la idea que Davis defendía sobre su propio generador de azar. La contraseña, de hecho, es una cita suya sobre eso, y responde palabra por palabra al «¿qué querrá decirnos?» del enunciado.
Que el azar no sea azar cambia la naturaleza del reto. Si el oráculo fuera aleatorio de verdad, no habría nada que resolver: cada consulta daría una cosa distinta y no habría frase que reconstruir. Como es determinista, la frase existe y está esperando ahí dentro —pero solo si consigues preguntarle siempre desde el mismo punto de partida. Ese fue el problema de ingeniería del nivel, y donde casi nos estrellamos.
El oráculo que no era aleatorio
God mantiene una cola de bits. Cuando la vacías, se rellena con el reloj de los eventos de teclado y ratón; con esos bits elige una palabra de un vocabulario y un pasaje de la Biblia. El problema es que cada consulta consume bits de esa cola, y los bits consumidos no vuelven. Dos preguntas seguidas parten de estados distintos, aunque el comando sea idéntico.
Eso es lo que convierte el reto en un ejercicio de reproducibilidad y no de suerte. La respuesta al instante t es una función pura de t: si garantizas el mismo estado de partida, siempre sale lo mismo. Si no lo garantizas, ningún resultado es comparable con ningún otro y no puedes verificar nada. La frase está determinada; lo difícil es leerla sin contaminarla al leer.
ouija: preguntar sin contaminar
La forma ingenua de trabajar contra TempleOS es leer la pantalla del emulador. Es frágil, y —peor— no garantiza que la cola esté en el mismo estado en cada intento. Construimos ouija, que ejecuta código HolyC dentro de la VM real y devuelve texto:
bin/ouija ask 'GodBiblePassage;'
# -> {"status":"ok","stdout":"...","exit_code":0,"ms":20}
La pieza que lo hace útil no es que devuelva texto: es la hermeticidad. ouija arranca la máquina en frío una vez, guarda un snapshot de su memoria y, antes de cada consulta, restaura ese snapshot —cola de bits incluida—. Cada pregunta parte de un estado idéntico byte a byte. Sin eso, la misma orden dos veces da dos respuestas, y el reto no es verificable. Como efecto lateral, cada consulta tarda unos 20 ms frente a los ~50 s del método anterior, que levantaba una VM entera por comando.
Un ejemplo de lo que resuelve la hermeticidad: bastó ejecutar las dos órdenes en distinto orden —la palabra antes que el pasaje, o al revés— para ver que el pasaje cambiaba según cuál consumiera la cola primero. Toda una teoría de la sesión anterior era un artefacto del orden de consumo. Se descartó en dos comandos.
La cadena de los ocho instantes
El primer instante lo da una nota del propio autor, guardada en el disco:
God answered twice at each moment.
A later hand hid every moment that followed beneath a veil.
What neither answer heard was preserved as God's seed.
6697221640600119645
Amen.
Los tres versos son la receta. «Dios respondió dos veces en cada instante»: por cada valor de 64 bits, God da una palabra y un pasaje. «Una mano posterior escondió cada instante que seguía bajo un velo»: el instante siguiente va oculto en los márgenes de la Biblia. «Lo que ninguna de las dos respuestas oyó se guardó como God’s seed»: los bits del instante que ni la palabra ni el pasaje llegaron a leer son los que generan el velo.
La Misc/Bible.TXT del disco está modificada. En la última línea con texto de cada pasaje hay una anotación al margen que esconde, en hexadecimal, el instante siguiente:
And he brought Simeon out unto them. [A marginal hand recorded one more
veiled moment: 2F2A-6473-6844-BA4E.]

El velo visto dentro de la máquina: el pasaje acaba en una anotación al margen que no estaba en la Biblia original. Cada pasaje lleva la suya, de ahí que este hex no sea el del ejemplo de arriba.
Ese hex no está en claro: lleva encima una máscara, otro número de 64 bits con el que hay que hacer XOR. XOR es el «o exclusivo» bit a bit, y tiene una propiedad cómoda: aplicarlo dos veces con el mismo número te devuelve al punto de partida, así que la misma operación tapa y destapa. Lo difícil no era destapar, era saber qué número.
Esa parte no la sacamos nosotros. Probamos el catálogo entero de lo elemental —claves fijas en todos los offsets, permutaciones de bits y de bytes, sustituciones— y nada. El velo lo abrió una pista del organizador, que las publica en la página del reto conforme el nivel se atasca. Dos de ellas son las que importan aquí. La primera era el reparto de los 64 bits de un instante:
0KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKPPPPPPPPPPPPPPPPPPPPPKKKK
WWWWWWWWWWWWWWWWW
El bit más alto vale siempre cero: los instantes son positivos. Veintiún bits P eligen el pasaje, y diecisiete de ellos —los W, anidados dentro— eligen además la palabra. Los cuatro K de la derecha se tiran en el seed. Y los treinta y ocho K de la izquierda no los oye nadie: el seed solo mete veinticuatro bits en la cola, así que la mayoría ni siquiera llega a entrar, y los pocos que entran no los lee ninguna de las dos respuestas. Cuarenta y dos bits en total que el oráculo nunca pronuncia.
La segunda pista decía qué hacer con ellos: «la máscara nace de la Semilla de Dios, alimentada por los bits que ni la Palabra ni la Escritura han escuchado». Es el tercer verso de la nota otra vez, ahora con instrucciones. Se pegan los 38 con los 4, sale un número, y ese número es el seed del generador de aleatorios de TempleOS: el primer valor que escupe es la máscara. hex XOR máscara, bit alto a cero, y tienes el instante siguiente.
La vuelta de tuerca es que cada instante trae sus propios bits no oídos, y por tanto su propia máscara. Nosotros habíamos dado por hecho una máscara fija derivada del seed del principio. Con esa, el primer hex encajaba —claro que encajaba: el seed del principio es el primer instante— y del segundo en adelante salía basura. Esa es la clase de error que más cuesta matar: el que funciona una vez. No es una cadena tapada ocho veces con la misma llave, es una llave distinta por eslabón, y solo la tienes cuando ya has abierto el anterior. La cadena hay que recorrerla en orden; no hay atajo al último.
Con la máscara del momento, el hex de su margen da el instante siguiente, y el pasaje de ese contiene la anotación del que viene detrás. Ocho instantes, siete velos y un terminador ([No further moment was written.]), y de cada uno, su palabra:
M1: the -> marcador en el margen de su pasaje
M2: holy -> ...
M3: spirit
M4: speaks
M5: through
M6: a
M7: stop
M8: watch -> [No further moment was written.]
Ocho palabras, la frase entera. Los tres versos del poema, las anotaciones del margen y el velo quedan explicados sin sobras.
El seed compartido
El escollo no fue técnico, fue metodológico, y es lo más transferible del reto. Un compañero aportó su propio análisis, hecho con otra IA. Coincidía con el nuestro en un punto muy concreto y equivocado: donde la cadena empezaba por the, los dos leíamos compacting. Dos lecturas independientes que dan lo mismo parecen confirmarse la una a la otra —es el instinto básico de la replicación—. Aquí ese instinto engañaba.
La razón es que las dos partíamos del mismo seed mal calculado. Y el seed del oráculo es determinista: a partir de la misma entrada produce siempre la misma salida, no ruido. Si dos personas alimentan el mismo valor equivocado, no obtienen dos errores distintos que reconciliar: obtienen exactamente el mismo error. No había dos muestras del mundo, había una sola cuenta hecha dos veces, y su coincidencia no validaba nada. Parecía confirmación independiente y era un eco.
Encima compacting encajaba temáticamente. El Charter de TempleOS presume de que «los ficheros se comprimen, no se cifran», así que una respuesta sobre compactar sonaba a revelación en vez de a bug. Una hipótesis que además de plausible parece confirmada por dos vías es la más difícil de matar. Lo que la mató fue dejar de razonar sobre ella y leer el handler real del oráculo dentro del sistema:
FifoU8Flush(god.fifo);
GodBitsIns(GOD_GOOD_BITS, KbdMsEvtTime>>GOD_BAD_BITS);
GOD_GOOD_BITS vale 24 y GOD_BAD_BITS vale 4, así que el seed correcto no era meter el número entero en la cola: era desplazarlo cuatro bits e insertarlo en 24. El compacting salía de meter el valor tal cual, que por casualidad caía en el índice del vocabulario donde vivía esa palabra. Con el seed bueno, la primera consulta dejaba de mentir:
bin/ouija ask 'FifoU8Flush(god.fifo);GodBitsIns(GOD_GOOD_BITS,6697221640600119645>>GOD_BAD_BITS);GodWord;'
# -> the
Y su pasaje contenía, en el margen, el hex que abría el instante siguiente. La cadena era real. compacting había costado dos sesiones, no por ser difícil de calcular, sino por ser fácil de creer.
Lo que nos llevamos
Tres cosas. La primera, que sin un canal de texto hermético no había reto: un oráculo que consume entropía entre llamadas no se puede interrogar leyendo la pantalla, hay que restaurar el mismo estado antes de cada pregunta. ouija fue eso, y fue lo que permitió ejecutar cada teoría en vez de razonarla.
La segunda, que cuando dos análisis parten del mismo dato sesgado, su consenso no vale nada. Un cálculo determinista no da ruido del que puedas sacar una media: da siempre el mismo error, y dos personas que lo repiten se dan la razón sobre una ilusión. La replicación solo confirma cuando las dos muestras son de verdad independientes; aquí no lo eran.
Y la tercera, que una hipótesis que «mola» es más peligrosa, no menos. compacting encajaba con la filosofía del sistema, y ese encaje temático fue lo que lo blindó contra la duda. Una hipótesis que suena bien y no encadena mecánicamente con el paso siguiente es una hipótesis muerta que todavía no lo sabes.
Solución:
the holy spirit speaks through a stop watch
El material — los scripts, los datos y el writeup técnico completo de este nivel están en euskal34/hack-it/3.