|
Spectrum
Investigando la memoria RAM
El método utilizado por
la Spectrum para almacenar sus programas en la memoria
es un tanto complicado, en especial si leemos el manual
de la misma. Sin embargo, saber esto puede ser una fuente
inagotable de datos a la hora de desproteger programas
o, por qué no, proteger los nuestros.
Lo primero que debemos saber es en qué parte de la memoria
buscar un programa escrito en BASIC. Sabemos que la
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 el mismo. 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.
POR DONDE BUSCAR
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). 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 Z-80, 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 cambiendo
la línea número 10 y correr el programa nuevamente para
ver cómo cambia la tabla de códigos en la pantalla.
LOS NUMEROS, UN CASO ESPECIAL
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 K-64 nº
42, septiembre 1988.
Recopilación: Diego Chiacchio.
|