Investigando la memoria RAM

El método utilizado por la ZX Spectrum para almacenar sus programas en la memoria RAM es un tanto complicado. Sin embargo, conocerlo puede ser una fuente inagotable de datos a la hora de desproteger programas o, por qué no, proteger nuestras propias creaciones.

Lo primero que debemos saber es en qué parte de la memoria RAM buscar un programa escrito en BASIC. Sabemos que la ZX Spectrum tiene 48 Kb. de RAM, pero no está muy claro dónde se encuentra el programa, ya que no toda la memoria está disponible para él. Luego, tendremos que poner en pantalla el programa en cuestión en forma de códigos, que deberán ser interpretados de la manera correspondiente. Finalmente, debemos hallar la relación entre estos códigos y el programa BASIC que podemos ver si hacemos un LIST del mismo.

Los programas de la Spectrum están almacenados en un área de memoria que se encuentra especificada por el contenido de dos variables del sistema de la máquina. Se trata de las variables PROG y VARS. Estas variables están almacenadas en las direcciones de memoria 23635-23636 (PROG) y 23627-23628 (VARS). Asimismo, los valores de estas variables pueden ser inspeccionados utilizando el comando PEEK.

Para identificar la dirección de memoria que contiene el primer byte de un programa BASIC hacemos:
PRINT PEEK 23635 + 256* PEEK 23636
El valor impreso será seguramente 23755, excepto cuando tengamos conectada una Interfase 1 o algún sistema de discos. Para obtener la dirección del último byte de la zona correspondiente a los programas BASIC hacemos:
PRINT PEEK 23627 + 256* PEEK 23628-1
El valor que obtengamos ahora variará, como es lógico, de acuerdo al largo del programa que estemos considerando. Una vez que tenemos determinada el área que estamos investigando, vamos a desarrollar una corta rutina escrita en BASIC que nos permitirá determinar los valores almacenados en esta zona e imprimirlos en la pantalla.

El siguiente programa será suficiente para esto:

10 REM programa peek
20 FOR I=23755 TO 23772
30 PRINT I, PEEK I
40 NEXT I

Esta rutina va a imprimir el contenido de los 19 primeros bytes del área de programas BASIC. Hemos elegido el valor 19 por dos motivos: primero, porque esta cantidad de información cabe en forma práctica en la pantalla de la Spectrum y, segundo, porque la primer línea del programa (la 10) ocupa exactamente 19 bytes en la zona de programas de la máquina. En la tabla 1 podemos ver el resultado de correr este programa:

TABLA 1:

23755: 0
23756: 10
23757: 15
23758: 0
23759: 241
23760: 97
23761: 97
23762: 61
23763: 491
23764: 52
23765: 49
23766: 51
23767: 14
23768: 0
23769: 0
23770: 133
23771: 5
23772: 0
23773: 13

Los dos primeros bytes son 0 y 10, y los mismos le dicen a la computadora que el número correspondiente a esta línea del programa es justamente 10.
Esto sale de aplicar la siguiente fórmula:
nl=0*256+10=10
Para probar esto podemos cambiar el valor de estas posiciones de memoria mediante la instrucción POKE. Si hacemos:
POKE 23755,14
Veremos que el número de la primer línea del programa ya no es 10, sino que pasó a ser la siguiente:
14*256+10=3594
Una vez que hemos probado esto hasta estar convencidos de que funciona, conviene que pongamos nuevamente el número de línea original, ya que de otro modo el programa podría no funcionar correctamente.

Para aquellos que estén al tanto de la programación del Z80, es interesante notar que el orden en que son almancenados estos números está invertido con respecto a como lo hace el microprocesador para trabajar con números de 16 bits. En este caso, el byte más significativo (el que determina que el resultado sea grande o chico) precede al menos significativo (el que apenas influye en la cifra total). Las próximas dos posiciones de memoria contienen la información relacionada con el largo de la línea que estamos analizando. Si bien el largo completo de la información que estamos poniendo en pantalla es de 19 bytes, no debemos olvidar que 4 de éstos son utilizados para avisarle a la máquina el número de la línea y el largo de la misma, con lo que el total de bytes útiles de información será de 15. Si analizamos el contenido de estos dos bytes veremos que:
15+256*0=15
En este caso la información es almacenada en la forma convencional, es decir con el byte más significativo al final.

El próximo byte contendrá la primer pizca de información interesante, ya que se referirá al comando REM, que es la primer instrucción de esta línea. Si hojeamos el manual de la máquina, en la parte concerniente al set de caracteres de la misma, veremos que el correspondiente al código 234 es justamente la instrucción REM. El resto de los bytes que siguen a éste pueden ser decodificados siguiendo el mismo procedimiento, ya que cada byte corresponderá a una instrucción o «keyword», o bien a una letra como es el caso del mensaje «programa peek». El último byte es 13 y corresponde justamente al código del ENTER: es la tecla que debemos presionar para terminar de ingresar una línea de programa. Podemos probar cambiando la línea número 10 y corriendo el programa nuevamente para ver cómo cambia la tabla de códigos en la pantalla.

Supongamos que cambiamos la línea 10 por:
10 LET aa=1443
En la tabla 2 podemos ver el resultado de correr el programa:

TABLA 2:

23755: 0
23756: 10
23757: 15
23758: 0
23759: 234
23760: 112
23761: 114
23762: 111
23763: 103
23764: 114
23765: 97
23766: 109
23767: 97
23768: 32
23769: 112
23770: 101
23771: 101
23772: 107
23773: 13

Nuevamente, el número de línea es 10 y está almacenado en las posiciones de memoria 23755 y 23756. El largo de la línea es igual al de la anterior y por lo tanto va a caber en la pantalla perfectamente. Además, podemos ver que los bytes que indican el largo de la línea no han cambiado en absoluto. La quinta posición situada en la dirección de memoria 23759 contiene un byte que vale 241.

Si consultamos nuevamente con el manual de la máquina veremos que el código 241 corresponde a la instrucción LET del BASIC, que es justamente la que pusimos en el programa. Si seguimos investigando veremos que los próximos bytes contienen los códigos correspondientes a la letra «a», el signo igual y el número 1443. Esto no debe sorprendernos, pero lo que sí llama la atención es que una vez que terminamos de leer esto vemos que aunque para nosotros la línea ya terminó, en la memoria de la máquina aún continúa. Si miramos bien, la posición 23767 no contiene el código correspondiente al ENTER, sino que tiene un 14. Si nos referimos al manual de la máquina para ver de qué se trata el 14 observaremos que dice «número» sin mucha más información.

La explicación radica en que en la Spectrum los números están almacenados dos veces, en dos formas distintas. La primer forma de almacenarlos es tal y como la vemos en la pantalla, es decir, un byte por cada número. La segunda poco tiene que ver con lo que podamos entender, ya que es utilizada por la computadora para poder trabajar de manera más cómoda con los números. En esta segunda forma, cada número (independientemente de la cantidad de cifras que tenga) será almacenado ocupando 5 bytes en la memoria de la máquina. En este caso, el número 1443 está representado por los valores 0, 0, 163, 5 y 0. Esto redunda en un deperdicio de espacio, ya que por cada número que figure en nuestro programa tendremos no solo la representación del mismo tal cual, sino 5 bytes más para que la computadora lo entienda.

Es probable que se pregunten cuál es la ventaja de hacer esto. Bueno, lo que se pierde es espacio, pero lo que se gana es velocidad de operación, ya que la máquina necesita el número expresado de esta forma para poder hacer cálculos con el mismo. Se ahorra el tiempo de convertirlo cada vez que lo tiene que usar guardándolo convertido en la memoria de la máquina. Una aplicación interesante de todo lo que hemos visto es analizar un programa BASIC sin necesidad de ejecutarlo. Si no se nos ocurre para qué puede llegar a ser útil hacer eso, pensemos en la posibilidad de leer paso a paso un programa protegido que se autoejecuta una vez cargado y que no podemos «brekear». Una buena posibilidad de analizarlo es cargarlo como si fuese un grupo de bytes y cambiar las direcciones de inicio del programa «peek» por la dirección a partir de la cual hemos cargado el programa. Una vez que encontramos la traba en el código que analizamos la borramos de alguna forma (mediante un POKE, como hicimos con el número de línea) y grabamos nuevamente el programa en cinta o disco. De esa manera el programa queda sin protección.

Como podemos ver, analizar un programa en forma de códigos es una herramienta que va más allá de aprender simplemente algo nuevo.

Fuente: Revista K64 Nº 42, septiembre de 1988.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *