Humanos en problemas (II)

En la primera parte vimos algunas cosas básicas del debugger de DOSBox, y llegamos hasta el comienzo del programa principal ya desencriptado. Para retomar nuestro análisis, no hace falta volver sobre los mismos pasos. (Excepto abrir el DOSBox con debugger y activarlo escribiendo debug humans, claro). Habíamos hablado de que la sección Code Overview funciona como una ventana que podemos desplazar con las flechas del cursor. Pero también podemos apuntarla a una dirección determinada escribiendo C [segmento]:[desplazamiento]. Escribamos C CS:0132, que es donde sabemos termina la rutina de arranque, y coloquemos un freno con F9.

Aquí hace falta hacer un par de aclaraciones. En primer lugar, que CS no es otra cosa que un registro que indica el segmento de código actual. (Momento para mirar en Register Overview y ver que no miento, al menos sobre esta cuestión.) Venimos usándolo en lugar de un valor fijo porque éste puede cambiar, no sólo durante la ejecución, sino también de una máquina a otra, dependiendo de la configuración, controladores y programas residentes. En segundo lugar, sepamos que sólo apuntamos la ventana, pero el registro IP (instruction pointer) sigue indicando la dirección original (0000) y es desde ahí que el procesador va a continuar. Pulsemos F5 para ejecutar toda la rutina de arranque (desde CS:0000 hasta llegar a CS:0132), demos un paso más con F10, y estaremos nuevamente ante el comienzo del programa principal:

0000: call 0000816E
0003: call 000056BC
0006: call 00000677
0009: call 0000B43C         ;esta es la que nos interesa
000C: mov  [00EF4],0000

A diferencia de la vez anterior, ahora vamos a ejecutar sólo las tres primeras subrutinas (¿hace falta que aclare que tenemos que pulsar F10 tres veces? – bárbaro, gracias). Sabemos que en la cuarta está el código a desmenuzar así que, en vez de F10, pulsemos F11 (step into). ¿Cuál es la diferencia? Como su nombre lo sugiere, en vez de ejecutar la llamada call 0000B43C completa, dimos un paso “hacia adentro” de la subrutina. Una vez dentro, seguiremos con el habitual F10 (step over). Tomemos nota de que estamos en CS:B43C, que es nuestro nuevo punto de partida.

Empecemos a caminar esta nueva etapa y veremos aparecer la pantalla de carga (esa que dice «The Humans – Spanish Version»). Luego, en CS:B4C2 y CS:B4D1, aparecen dos saltos condicionales hacia atrás. Ya sabemos cómo lidiar con ellos (sí, se pueden saltear los dos juntos). Al llegar a CS:B505 pongamos un freno, pero antes borremos todos los anteriores (BPDEL *) para poder retomar directamente desde aquí cuantas veces haga falta:

B505: mov di,4894
B508: mov bp,0028       ;mostrar pantalla
B50B: mov bx,0000       ;solicitando la
B50E: mov cx,0100       ;página correspondiente
B511: call 00008C08     ;al dibujo
B514: call 00008BE7
B517: jmps 0000B574     ;ingreso y verificación (¿no vuelve?)
B519: mov di,4594
B51C: mov bp,0023       ;borrar pantalla
B51F: mov bx,0000       ;tras el ingreso correcto
B522: mov cx,0100       ;(nótese la simetría
B525: call 00008C08     ;con el bloque anterior)
B528: call 00008BE7

Acá está la papa. En estas líneas se muestra la pantalla con la señorita, se piden las claves y se vuelve a borrar la pantalla. Sin embargo, hay algo que no cierra. El salto en CS:B517 debería, en principio, ser una llamada, ya que cuando damos una respuesta correcta el flujo del programa continúa en CS:B519 (para comprobarlo sólo hace falta poner un freno ahí). Si nuestra respuesta no es satisfactoria, en cambio, iremos a parar a la consola del DOS con el mensaje “Program protection failure, Please re-run”, sin haber pasado por CS:B519.

Lo que esto significa es que a partir de CS:B574 está la lectura del teclado y el control de autenticidad. Si algo falla, el programa no vuelve a su curso normal. Esto nos lleva a realizar un experimento casi cantado. Si el fragmento citado contiene todo el proceso, por qué no saltearlo y ver qué pasa. Salgamos al DOS pulsando Alt-X, volvamos a activar el debugger escribiendo debug humans, y pulsemos F5 para dejar correr el programa. Cuando la ejecución se detenga en CS:B505 escribamos SR IP B52B. Esta orden cambia el valor del registro IP que mencionábamos antes. Es decir que cuando dejemos correr nuevamente el programa, en vez de continuar en CS:B505 lo hará desde CS:B52B, justo después del bloque.

Pulsemos F5. Suena la melodía y vemos el logo de Mirage. ¡¿Lo logramos?! No cantemos victoria: ni bien tratemos de continuar nos encontraremos de regreso en la consola del DOS. ¡A no desalentarnos! No habremos logrado quebrar la protección, pero es evidente que estamos bien encaminados. Y además descubrimos que más adelante existen otros puntos en los que se controla el dato ingresado. En lugar de buscarlos uno por uno (sin saber cuántos son en total), nos conviene identificar y reproducir el efecto de ingresar el número de página correcto. Vamos a tener que zambullirnos en el código localizado en CS:B574, que es ahora nuestro nuevo punto de referencia.

Un maestro, una causa, un efecto

Volvamos a empezar, y al llegar a CS:B505 pongamos un freno en CS:B574, para ver qué pasa al ingresar el número de página. Las rutinas de lectura del teclado suelen consistir en un gran bucle que compara los códigos suministrados por una función del DOS o de la BIOS para determinar qué tecla ha sido pulsada, llevar la cuenta de los caracteres ingresados, y salir cuando el ingreso esté completo. Recorriendo el código veremos que en CS:B5AB hay un salto al comienzo, que cierra el bucle del que hablábamos. Pero también vemos la salida:

B5A2: mov  al,[457B]
B5A5: or   al,al
B5A7: jne  0000B5AD
B5A9: loop 0000B59F
B5AB: jmps 0000B574
B5AD: mov  b,[457B],00
B5B2: cmp  al,0D
B5B4: je   0000B5F2
B5B6: cmp  al,0E
B5B8: je   0000B5DC
B5BA: cmp  al,30
B5BC: jb   0000B574
B5BE: cmp  al,39
B5C0: ja   0000B574

A partir de CS:B5B2 comienza una serie de comparaciones sobre el caracter ingresado, que entre otras cosas descarta cualquier letra o símbolo que no sea un número del 0 al 9 (ver CS:B5BA y CS:B5BE, valores en ASCII). Pero la primera comparación de todas es contra el número hexadecimal 0Dh (¡bendito número 13 de la suerte!) que corresponde a la tecla Intro. Podemos estar seguros que esa es la salida que buscamos, así que pongamos un freno en CS:B5F2, dejemos correr el programa e ingresemos el número de página correcto.

Tal como lo previmos, el debugger retomó el control en CS:B5F2. Avancemos por el código para ver adónde nos lleva, y tras unos pocos pasos nos encontraremos saltando entre CS:B63A y CS:B637. Hemos venido a parar a un callejón sin salida, cuyo propósito es precisamente que le perdamos el rastro a la ejecución. La buena noticia es que no hace falta seguirlo.

B633: mov    [3ECD],bp
B637: mov    bx,B637
B63A: jmp    bx

Justo antes de estancarse, el código almacena el contenido del registro BP, resultado de unos cálculos hechos líneas antes, en la dirección DS:3ECD. Apuntemos la ventana Data Overview unas posiciones antes, para tener un contexto, escribiendo D DS:3EC0 y ver si podemos sacar alguna conclusión. El valor de 16 bits (word) almacenado en DS:3ECD (señalado con B en la captura) es idéntico al de la posición DS:3ECB (señalado con A). Y si repetimos el experimento, respondiendo correctamente a un dibujo diferente, veremos que el valor A cambia, y el correspondiente valor calculado (B) coincide con él. Es hora de repetir el mismo experimento de antes, pero con una variante: una vez detenidos en CS:B505 copiaremos el contenido de la memoria de A a B, antes de modificar el IP para saltear toda la rutina, siguiendo estos pasos:

  1. D DS:3ECB (apuntar la ventana de datos)
  2. SM DS:3ECD <byte> <byte> (copiar los dos primeros bytes de Data Overview)
  3. SR IP B52B (modificar el IP para saltear la rutina de claves)

Dejemos correr el programa y esta vez pasamos sin problemas a la segunda presentación, al menú y podemos disfrutar del juego, nivel tras nivel. Esta vez sí hemos quebrado la protección.

¿Y ahora qué pasa, eh?

El método funciona pero está claro que todavía tenemos pendiente elaborar un parche que lo implemente. Dicho parche tendrá que hacer su trabajo una vez que el juego esté desencriptado en memoria. Y algún lector sagaz se habrá percatado de una complicación adicional: el valor correspondiente al dibujo (aquel que señalamos con A) se calcula aleatoriamente cada vez que el juego se pone en funcionamiento. Una posible solución es localizar la rutina que calcula un nuevo valor y anularla.

Dado que esta segunda parte también se ha extendido bastante, prefiero dejar la implementación del parche para la próxima (y última) entrega. Mientras tanto, recomiendo a quien haya leído esta guía practicar localizando la rutina que hay que anular para que el valor original no cambie. También puede experimentar con la edición de GameTek de este juego, donde la protección fue modificada levemente, agregándole una complicación adicional.

PS: Existe también una versión en CD-ROM que contiene el juego original combinado con los niveles del disco de expansión. Si bien en este caso la protección es distinta, al tratarse de un cd-check, la rutina de arranque es idéntica y podemos llegar al inicio del programa con los metodos aquí explicados.

Humanos en problemas (I)

Días atrás, en pleno encierro forzoso “aislamiento social preventivo y obligatorio”, colaboré en abandonsocios con un crack para la versión en castellano de The Humans. De ahí surgió la idea de hacer una guía paso a paso explicando el procedimiento, cosa que resultó bastante más difícil que el propio crack. Como la protección no es demasiado compleja, decidí orientar la guía a quienes ya tengan algún conocimiento de lenguaje ensamblador y quieran incursionar en el apasionante mundo de la desprotección de juegos antiguos. En esta primera entrega vamos a familiarizarnos con las funciones principales de un debugger, a medida que avancemos sobre el código de arranque del juego. ¿Qué es un debugger? Una herramienta interactiva que nos permite analizar un programa mientras éste se ejecuta.

Los pasos de la guía están descriptos para el debugger de DOSBox, porque fue la herramienta que utilicé al desproteger el juego y porque cualquier aficionado actual a los juegos de DOS seguramente está familiarizado con dicho emulador, ya sea que utilice Windows o Linux. Cabe aclarar que la instalación estándar no incluye el debugger; es necesario descargar un ejecutable alternativo y agregárselo. Si utilizamos Windows, debemos descargar el ejecutable correspondiente y moverlo a la carpeta donde tengamos instalado DOSBox. En caso de usar Linux, en esta página están disponibles los paquetes correspondientes, e instrucciones de instalación desde consola. Ambos enlaces corresponden a la versión vigente a la fecha (0.74-3).

Un pantallazo al debugger

Si iniciamos DOSBox con el nuevo ejecutable (dosbox-debug), en lugar de la habitual ventana de estado se abrirá otra llamada “DOSBox Debugger”. Pulsemos alt-pausa para activarla y observemos que está compuesta por cinco secciones, cada una encabezada por una barra color celeste con su título:

  • Register Overview: contenido de los registros del procesador
  • Data Overview: contenido de la memoria, en hexadecimal y ASCII
  • Code Overview: contenido de la memoria interpretado como código ejecutable
  • Variable Overview: contenido de variables definidas por el usuario
  • OutPut/Input: mensajes informativos

Las secciones Data, Code e Input/Output funcionan como ventanas que pueden desplazarse pulsando las teclas indicadas en el título («Scroll»).

Mientras tanto, el programa en ejecución ha quedado en suspenso, cosa que podemos comprobar viendo que el apuntador del DOS ya no parpadea. Para retomar la actividad y devolver el foco a la ventana principal, debemos indicarle al debugger, pulsando la tecla F5 (run), que deje correr libremente el código. Otra forma, quizás más útil, de tomar el control desde el debugger es hacerlo desde el comienzo del programa a analizar. Escribamos debug humans desde el directorio donde tenemos instalado el juego. Esta orden le dice a DOSBox que cargue el juego en memoria y que en vez de ejecutarlo le transfiera el control al debugger.

Quiero ver, quiero entrar

The Humans está protegido con un doc-check, es decir, la verificación de que el usuario tiene en su poder algún elemento adicional difícil de duplicar (instrucciones, rueda de códigos, etc.). Ni bien cargamos el juego, una señorita nos pregunta en qué página del manual aparece un dibujo determinado. Como esto ocurre incluso antes de la presentación, vamos a intentar seguir el flujo del programa hasta llegar a ese punto. Prestemos atención a la sección Code Overview. Estamos en la dirección cero del segmento de código actual (CS:0000) ante la instrucción jmps 0176. Cada vez que pulsamos F10 (step over), el debugger deja que se ejecute la instrucción actual (señalada en color verde) y avanza a la siguiente. Esto se refleja también en el apuntador de instrucción (registro EIP en la sección Code Overview).

Debugger screenshotVayamos avanzando paso a paso, sin profundizar demasiado en los detalles pero intentando seguir el hilo del programa. Nuestro objetivo es llegar rápidamente hasta el fragmento de código en que se encuentra la protección. Al llegar a CS:01CA encontramos un salto condicional hacia atrás. Si continuáramos como hasta ahora, pasaríamos cientos de veces por el mismo fragmento de código. ¡Podemos emplear mejor nuestro tiempo, incluso en cuarentena! Bajemos a la siguiente instrucción utilizando el cursor (CS:01CC, ver captura) y pulsemos F9 (breakpoint) para poner un freno, que el debugger señala en color rojo. Si ahora dejamos correr el programa (F5), como hicimos al principio, DOSBox ejecutará reiteradamente las instrucciones precedentes hasta que la condición deje de cumplirse. Recién ahí pasará a CS:01CC y al toparse con nuestro freno volveremos a tomar el control desde el debugger.

Continuemos paso a paso hasta CS:011D, donde encontraremos otro salto hacia atrás. La manera de saltearlo es la misma, y es una excelente oportunidad para poner en práctica lo que acabamos de aprender. Un poco más adelante, más precisamente en CS:0132, encontramos la última instrucción de este fragmento: jmp far word cs:[0138]. Pulsemos F10 una vez más y preparémonos para la siguiente etapa.

Del corazón pa’ dentro

Observemos que estamos otra vez en la dirección CS:0000, pero de un segmento de código distinto. Aunque no lo parezca, hemos avanzado bastante. Sin demasiado esfuerzo atravesamos la rutina de arranque y tenemos ante nuestros ojos el comienzo del programa principal ya desencriptado:

0000: call 0000816E
0003: call 000056BC
0006: call 00000677
0009: call 0000B43C         ;¿lobo, estás?
000C: mov  [00EF4],0000

De entrada nomás llama a cuatro subrutinas. Vayamos ejecutándolas una por una, pulsando F10. Las tres primeras terminan sin novedades, pero al pasar a la cuarta vemos la pantalla de carga y llega la señorita a pedirnos el número de página. Contestemos el dato correcto y veremos el logo de Mirage y el comienzo de la presentación antes de que el debugger recupere el control en CS:000C.

Con esto ya podemos dirigir nuestro análisis a la subrutina que comienza en CS:B43C. Y así debemos seguir como si se tratase de una muñequita rusa, pelando capa tras capa hasta llegar al meollo de la cuestión. Pero como esto se ha hecho ya bastante largo, vamos a dejarlo aquí hasta la próxima entrega.

Delirios de pinball

Durante muchos años, probablemente debido a la complejidad técnica, las máquinas de pinball estuvieron a salvo de sus pares electrónicos. Muchas fueron las adaptaciones que intentaron capturar, con escaso éxito, la esencia del juego. Algunos, aceptando la imposición del formato horizontal de los televisores, ensayaron con mesas más anchas que largas; otros respetaron la proporción natural, pagando el costo con una pantalla dividida o reduciendo considerablemente el tamaño de la mesa.

Night Mission
La versión 3.0 de 1988

Incluso hubo ideas ingeniosas como el Pinball Construction Set (una especie de Meccano de los pinball), pero los resultados no llegaban muy lejos: salvo honrosas excepciones como el Night Mission Pinball de SubLOGIC, no recuerdo haberme entusiasmado con ninguno.

No fue hasta 1992, y no es de sorprender que el milagro haya ocurrido en la Amiga, que la entonces desconocida Digital Illusions creó el genial Pinball Dreams. Por primera vez estaban todos los ingredientes necesarios para producir un juego adictivo, comenzando por una mecánica suficientemente realista, siguiendo con una mesa vertical que se desplaza suavemente por la pantalla, y terminando con un acompañamiento musical de primera. A partir de ahí fue «un partidito más» mañana, tarde y noche…

Party LandLa propia Digital Illusions demostró, sólo unos meses después, que no estaba dicha la última palabra destronando a su propio juego con el increíble Pinball Fantasies. Tras sólo unas horas me convertí en un cobayito cuya única satisfacción era efectuar sucesivos tiros a las rampas opuestas para escuchar el «Five million!» y así seguir aumentando la cuenta. Es el día de hoy que la mesa Party Land sigue siendo una de mis favoritas.

No hay dos sin tres

Entonces llegó 1995 y un día como cualquier otro mi entonces amigo y compañero de estudios Bosterix me pasó el dato de que el flamante Pinball Illusions estaba disponible en uno de los BBS que frecuentábamos. Conectado a las 5 de la mañana terminé de descargar el lanzamiento de Hybrid de la versión en CD: un enorme ejecutable de 50MB, una presentación cinematográfica donde Digital Illusions se jactaba con toda razón de su impecable trayectoria, cuatro mesas distintas y al elegir cualquiera de ellas… silencio absoluto.

Así fue la «era dorada» del CD-ROM: los cráneos de las editoriales vieron al nuevo soporte y sus inconmensurables 650MB como una nueva variante de llave anti-piratería, para lo cual a todo había que agregarle animaciones en 3D, voces (¡como en las odiosas versiones parlantes de las aventuras de Lucas Arts!), fragmentos fílmicos de dudosa calidad o simplemente, como en este caso, poner la música como pistas de audio.

Para colmo, 21st Century Entertainment sacó también una versión en sólo tres diskettes que traía toda su música sintetizada, pero no incluía la cuarta mesa (Viking Tales) que, lógicamente, se convirtió en mi favorita. Y no fue solamente por ser la «figurita difícil» sino también porque la canción de apertura, coreada por una manga de vikingos ebrios (bueno, cada uno…) a la manera en que hoy se corea el Himno Nacional Argentino en los estadios, era francamente irresistible.

Pero volviendo a la cuestión que nos ocupa, ocurrió que una vez, en medio de alguno de esos baches auditivos, tuve que poner pausa para atender un llamado y cuando retomé el partido aconteció algo inesperado: el silencio se volvió música. Era evidente que el ejecutable de Pinball Illusions CD incluía toda la música original, sólo que en algunas circunstancias ésta era enmudecida para ceder el micrófono (¡je!) a su versión alternativa en CD-Audio.

Pasó el tiempo, estudios, trabajo y familia, y hace algunos días caí en cuenta que se cumplen nada menos que 20 años de la aparición de este juego, ¿y qué mejor para conmemorarlo que hacer una edición que funcione al 100% sin necesidad del CD?

DPMI o no DPMI. ¿Es ésa la cuestión?

A diferencia de sus antecesores, Pinball Illusions viene empaquetado en un solo archivo de unos 4MB (trabajé sobre la versión incluída en el Pinball Power Pack, para no arrastrar los 40 y pico de megabytes de relleno de la versión original). El juego trabaja en modo protegido y se rehusa a funcionar bajo Windows, lo cual nos cierra la posibilidad de usar SoftICE para inspeccionarlo -o quizás no.

Después de una pequeña rutina en CS:0070 que desencripta el stub, podemos hacer un vuelco de 5210h bytes a partir CS:0100, y observar el código tranquilos. Lo primero que vemos, en texto plano, son algunos mensajes interesantes sobre DPMI y compatibilidad con Windows.

Efectivamente, en CS:16C9 está la cadena «DPMI detected» y es mostrada por una rutina huérfana que empieza en CS:1667. Veamos si podemos rehabilitarla. Las subrutinas de detección de distintas API de gestión de memoria colocan en CS:503F el tipo detectado, que luego es comparado en la rutina CS:01BA. Es ahí donde deberíamos interponer la llamada a esta subrutina olvidada. Pero hay algo más: si leemos la API de DPMI nos daremos cuenta de que la subrutina da por hecho que existe el DPMI y comienza por guardar los valores que devolvería la llamada para tantear la presencia del servicio, pero esta no aparece por ninguna parte. Una manera de rehabilitar esta opción, entonces, es desviar la llamada en CS:01EC hacia una rutina que haga:

mov ax, 1687h
int 2Fh
test ax, ax
je 1667 ;hay DPMI, llamo a la rutina
jmp 15B6 ;no pasó nada, sigo con lo que venía

Esto me permitió finalmente usar el debugger (bajo Windows 98), pero entre la lentitud con que funciona y una placa de video poco compatible, lo que parecía prometedor se redujo a pequeños avances: sólo adelanté en eliminar el cd-check, que no tenía nada de especial, excepto por la cantidad de checksums que lo protegen.

Entonces empecé a seguirle el rastro a las llamadas al MSCDEX, y encontré un patrón que me permitió ubicar la API de sonido en CS:98A0 y con ello los puntos en los que se silenciaba la música sintetizada: al volver al menú de selección de mesas, en el attract mode, y ante los eventos (mini juegos y fanfarrias al entrar en el marcador). Pero había algo en el modo de sortear este último caso que no me terminaba de convencer…

Las comparaciones no siempre son odiosas

Exultante por lo conseguido hasta acá, y con un panorama general de la situación en la cabeza, quería cerciorarme de que la solución fuera la correcta. Entonces recurrí al IDA (en su versión gratuita) para generar listados del código de las versiones en diskette y en CD, y poder así cotejar las diferencias en la rutina que me inquietaba. Después de algunas horas de concentración, apareció clarito ante mis ojos el punto ideal para evitar por completo toda la rutina adicional y disfrutar, ahora sí, del juego con toda su música.

Ya envalentonado le anulé también la rutina que grababa la configuración en un archivo externo y la necesidad de anteponer una barra (/) a las opciones (ver con ILLUSION ? la lista). Para terminar, restaba implementar el parche. En el stub, cuando estaba rehabilitando el DPMI, me crucé con el siguiente fragmento:

mov ah, 000h
int 094
call 1112 ;llamada inútil a un RET
call 1308 ;descompresión del juego en memoria

La primera llamada es la clave para intercalar las modificaciones al código. Invirtiendo el órden para que quede a continuación de la descompresión, nos queda un call libre para aplicar los cambios. Sólo debemos buscar un lugar no utilizado para escribir el parche y asunto terminado.

El resultado final de todo este trabajo puede aplicarse sobre cualquier versión original (i.e. no modificada) de Pinball Illusions CD, ya sea la de 1995 que ocupaba unos 50MB ó, mejor aún, la que se incluyó en las recopilaciones Pinball Power Pack y Pinball Gold Pack, que no están infladas y ocupan sólo 4MB, aproximadamente. Para eso, les dejo para descargar el parche completo. Un crack.

Pinball Illusions 20th Anniversary

Dedicado a Anita