El
software actual viene normalmente protegido
con distintos métodos para impedir su copia. Números de
serie,
disquetes llave, anillos grabados en la superficie del CD-ROM... Pero
esta lucha contra la piratería no es nueva. Con el auge, en los
años
80, de los microordenadores domésticos proliferaron
también los
sistemas de protección.
En este
artículo pretendo enumerar los
que recuerdo por haberlos llegado a conocer en su dia. Me
centraré
exclusivamente en el ordenador ZX Spectrum de 48K. Fue el único
que
tuve antes de mi primer PC y, por tanto, el que mejor conozco. Espero
que la exposición os resulte interesante, y que os anime a
contarnos
los mecanismos de protección que se usaban en vuestros
ordenadores
favoritos.
Comenzaremos por
lo más sofisticado y ajeno al
ordenador en sí. E iremos avanzando hasta llegar a lo que, por
aprovechar las características más elementales de la
máquina, requiere
de menos capacidad de desarrollo. A lo largo del artículo, y
especialmente en el apartado 4, supondré que el lector tiene
unos
mínimos conocimientos sobre el funcionamiento del Spectrum.
1.
Cuando ya se ha logrado la copia.
Supongamos
que, por el método que sea, hemos conseguido copiar un juego
(sí, se
trataba principalmente de juegos) en una cinta que el Spectrum es capaz
de leer. ¿Qué es lo que nos impediría usarla?
Lo más
sencillo, y
utilizado, eran las "claves". Una vez cargado, el programa solicita una
información que el usuario debe consultar en la
documentación que se
incluye con el juego original. Esta información solía
venir en una hoja
impresa con muy bajo contraste, o contener diferentes colores. De esta
manera se hacía más difícil realizar un duplicado
mediante las
fotocopiadoras en blanco y negro de la época.
Más
elaborado era
el sistema Lenslock, de Firebird. En la pantalla aparecen unos
caracteres que debemos teclear, pero tan deformados que se necesita una
lente especial para poder leerlos. No tuvo mucho éxito porque
era
engorroso de utilizar y en más de una ocasión se pusieron
a la venta
partidas que incluían lentes defectuosas o correspondientes a
otro
programa, produciéndose las consiguientes quejas de los
compradores.
Como curiosidad podéis encontrar un emulador de Lenslock en homepage.ntlworld.com/simon.owen/lenskey/. Intentad leer con
él los caracteres de la imagen que sigue:
Protección
Lenslock del juego Elite, el primero que la utilizó.
Todavía
más sofisticados eran los dispositivos hardware necesarios para
el
funcionamiento del juego. Por ejemplo la primera edición del
"Camelot
Warriors", de la española Dinamic, venía con un
pequeño artilugio que
había que conectar al slot de expansión del Spectrum. En
su interior
había una simple resistencia, pero el juego no funcionaba sin
ella.
Por
supuesto todas estas protecciones (y las que veremos más
adelante) se
podían eliminar modificando el programa. Bastaba con acceder al
código
y evitar las rutinas que pedían las claves, que comprobaban si
había
algo en el slot, etc. Desproteger de esta forma los juegos no estaba al
alcance de cualquiera, pero siempre había alguien que lograba
hacerlo.
Me
atrevería a afirmar que un juego siempre podía ser
desprotegido con los
conocimientos adecuados. Salvo que dicho juego incluyera algo que
resultase demasiado caro de duplicar y fuera realmente imprescindible,
no un simple estorbo como el aparatito del Camelot. Este es el caso del
Mikro-plus, de Mikrogen. Consistía en un periférico que,
enchufado al
slot trasero, sustituía los 16Ks de ROM del Spectrum por una
nueva ROM
repleta de rutinas diseñadas para juegos. La
compañía promocionaba el
invento no como una protección anticopia, sino como una manera
de
conseguir juegos mejores al haber más memoria "útil"
disponible. En
realidad el programa no funcionaba sin el dispositivo adicional, y este
era sencillamente imposible de copiar para la mayoría de los
usuarios.
Que yo sepa sólo se sacó un juego con este sistema, el
"Shadow of the
Unicorn". Por desgracia para Mikrogen el programa no cumplió las
espectativas, el Mikro-plus lo encarecía demasiado, y fue un
fracaso.
2.
Copias "cassette a cassette".
Los
juegos con Mikro-plus eran imposibles de piratear, y métodos
como el
Lenslock había que desprotegerlos. Pero las claves se
podían, con la
suficiente paciencia, transcribir a mano. Bastaba entonces con copiar
la cinta en sí. Y, como un juego no contenía más
que ruiditos, si uno
disponía de dos magnetófonos se podía volcar en
una cinta virgen el
contenido del cassette original. Este método se conocía
como "copia de
cassette a cassette" y permitia duplicar cualquier cosa. ¿No?
Pues
no exactamente. Para empezar no era demasiado práctico.
Había que
disponer de la cinta original o de una copia muy buena. Porque los
duplicados de tercera o cuarta generación daban demasiados
errores de
carga. Sobre todo si se trataba de programas "turbo" (ver apartado
siguiente). Se hacían bastantes copias con este método,
pero no podían
propagarse demasiado. Una versión salvada directamente del
Spectrum era
siempre más fiable.
Se afirmaba
además, yo nunca llegué a
comprobarlo, que algunos juegos sólo se podían duplicar
empleando
magnetófonos buenos (platinas con control manual de nivel de
grabación). Y es que la mayoría de las grabadoras baratas
de la época
tenían control automático de ganancia. Es decir,
amplificaban
automáticamente la señal a grabar si esta era muy
débil, o la atenuaban
si era demasiado fuerte. Esto traía como consecuencia que en las
partes
silenciosas del original la grabadora amplificaba tanto su entrada que
en la copia se podía escuchar un notable zumbido donde
debería haber
silencio. Este zumbido no era otra cosa que el ruido de fondo presente
en todo aparato eléctrónico (sobre todo si es barato)
excesivamente
amplificado. Pues bien. Al parecer algunos juegos, como los de Ocean,
detectaban este zumbido y abortaban la carga. En consecuencia incluso
una copia de cassette a cassette realizada a partir de un original
podía fallar con determinados programas. A pesar de que los
magnetófonos utilizados funcionasen perfectamente.
3.
Copiando lo desconocido.
La
copia más fiel es, entonces, la realizada mediante el propio
ordenador.
Y los juegos constan de bloques de bytes. Se salvan con un 'SAVE
"nombre" CODE' y listo ¿no? Pues tampoco.
¿Cómo
impedir que un
usuario cargue en memoria el bloque del programa y salve luego una
copia de primera generación, eliminando de paso las
protecciones? Pues,
por ejemplo, no dejando memoria suficiente para hacerlo. Algunos juegos
constaban de un bloque de bytes tan grande que ocupaban toda la memoria
disponible para el BASIC. Cualquier programa tecleado para salvarlos
sería sobreescrito por el juego a copiar. A veces estos bloques
de
código incluían también la pantalla de
presentación (un volcado de la
memoria de vídeo). Ocupaban, pues, los 48Ks de la RAM. Y en
algunos
casos extremos se salvaba incluso la ROM. El sufrido Spectrum
tenía
entonces que leer 65536 bytes seguidos, a pesar de que los primeros
16Ks no servían más que para incordiar (intentar escribir
sobre la ROM
no provoca error, pero no tiene ningún efecto). Imposible, pues,
copiar
algo así desde el BASIC.
Otra
técnica consistía en usar bloques
sin cabecera. Todo lo que se salva desde el BASIC tiene un
pequeño
bloque de bytes (llamado cabecera) con información para el
sistema,
seguido de los datos propiamente dichos. Un pequeño programilla
en
código máquina que llame a la rutina de la ROM apropiada
puede, sin
embargo, leer desde la cinta sin necesidad de cabecera. Y el usuario
inexperto será incapaz de cargar desde el BASIC un bloque como
este.
Estos
métodos son, no obstante, fáciles de superar. Incluso los
programas
copiadores más sencillos podían leer (y salvar
posteriormente) un
bloque de casi 48K, tuviese o no cabecera. Y joyas de la
programación
como el TC7 de LERM eran capaces de contar en una primera pasada la
longitud del bloque, sacar sus cuentas, cargarlo en una segunda lectura
y salvarlo después. ¡Aunque ocupase los 64K direccionables
por el
Spectrum!
El problema de
estos sistemas de protección es que se
basan en las rutinas de cassette estandar, contenidas en la ROM. Dichas
rutinas son sobradamente conocidas: los datos (sean o no cabeceras) se
graban en bloques que comienzan por un tono guia de unos 800 Hz,
seguidos de una ristra de pulsos de 1000Hz (para representar un 1) o
2000Hz (para representar un 0). Esto da una velocidad de
transmisión
media de 1500 baudios (o bits por segundo).
El Spectrum leyendo
un tono guia estandar.
Carga
estandar de datos, a 1500 baudios.
Todas
estas especificaciones no son más que una convención, un
protocolo de
comunicaciones. Pero nada impide a una compañía
desarrollar sus propias
rutinas de carga, creando así un protocolo nuevo. Y las rutinas
de la
ROM, o los copiadores basados en ellas, serán incapaces de
interpretar
los datos grabados.
Aparecío
así la carga turbo, que en lugar de
a 1500 baudios leía los datos a otras velocidades (hasta 3600,
aunque
sobre 3000 era lo más común). Esto, además de
acelerar el proceso de
carga y dificultar la copia de cassette a cassette, impedía la
lectura
de los datos si no se conocían las especificaciones del sistema.
Dos
espectaculares ejemplos de carga turbo.
Además
de las características de los datos se podía modificar
también el tono
guía. Surgieron así los tonos pulsantes (en ocasiones mal
llamados
cabeceras pulsantes), que consistían en tonos guía
intermitentes.
También los tonos ultra-cortos (que las rutinas de la ROM no
lograban
detectar), o tonos de distintas frecuencias.
Tono guía de
Dinamic, más grave que el estandar.
Llegado
un momento se emplearon en un mismo juego combinaciones de todo lo
expuesto tan complejas que convertían el familiar sonido de la
carga
convencional en una cacofonía de chirridos, difíciles de
leer y
difíciles de reproducir. Aparecieron elaborados sistemas
anticopia con
nombre propio, como el Alkatraz o el Speedlock. La mayoría de
estos
sistemas incluían espectaculares efectos visuales mientras se
realizaba
la carga.
Speedlock: aunque
parezca un bloque de datos convencional esto
es una carga turbo
que incluye tonos pulsantes.
Alkatraz:
en este sistema turbo los bordes permancen en negro,
un
contador muestra cuánto queda de carga y la pantalla se dibuja
de
manera distinta a lo habitual (en este caso de forma secuencial).
Bleepload: este
sistema no es turbo. Pero consta de cientos de
pequeños
bloques de bytes separados por tonos tan breves que he
logrado capturar el
tono guía (arriba) y los datos (abajo) en una
sola
instantánea.
La
mayoría de los programas copiadores no consiguieron mantenerse
actualizados frente a la avalancha de métodos de
protección. Las
posibilidades eran infinitas, por lo que había que desproteger a
mano
cada nuevo sistema de carga a medida. Aunque las rutinas estaban muchas
veces encriptadas era posible en teoría analizarlas para
intentar la
desprotección. Bastaba con acceder al código, lo que nos
conduce al
último apartado del artículo.
4.
Protegiendo el BASIC.
Casi todas las
instrucciones de los juegos de Spectrum comienzan igual: Para jugar
teclee LOAD "" y pulse PLAY en el cassette.
En
efecto, por muy sofisticada que fuera la protección, lo primero
que se
leía era siempre un programa en BASIC. Dicho programa,
denominado
cargador, se ocupaba de ejecutar las rutinas de carga a medida si estas
estaban presentes. Si se quería evitar el análisis de
dichas rutinas
había que empezar por proteger el BASIC. Pero ¿puede un
programa en
BASIC ser protegido? Terminaremos este artículo contestando a
esta
pregunta mediante un ejemplo práctico.
Dado que el
BASIC es un
tema técnicamente más asequible que el código
máquina intentaremos
analizar el cargador del juego "Doomdark's Revenge", de Beyond
Software. Este programa consta de un cargador BASIC, un bloque sin
cabecera de 400 bytes (seguramente la rutina de carga a medida) y un
extenso bloque turbo.
Escribimos el
famoso 'LOAD ""' y, una vez
leído el cargador, pulsamos la tecla BREAK. Vaya, el Spectrum se
reinicia. Lo primero que suelen hacer los cargadores es realizar un
'POKE 23659,0', 'POKE 23613,0' o 'POKE 23613,82'. Cualquiera de estas
sentencias modifica el contenido de una de las varibles del sistema de
manera que el ordenador respectivamente se cuelga, se reinicia o
símplemente nos ignora cuando pulsamos BREAK. Debemos evitar que
esta
instrucción se ejecute si queremos acceder al listado. El
problema es
que todos los cargadores están grabados con "autorun", que
ejecuta el
programa BASIC tan pronto se completa la carga. ¿Cómo
evitar esto?
Para
cargar un programa en BASIC se puede emplear, a parte de LOAD, la
instrucción MERGE. Esta sentencia se utiliza para añadir
a un programa
que se encuentre en memoria un conjunto de nuevas líneas que se
cargan
de la cinta. Si en la memoria no hay ningún programa un MERGE
equivale
a un LOAD, con la diferencia de que MERGE ¡ignora el autorun!
Perfecto.
Leemos de nuevo el cargador, pero esta vez con 'MERGE ""'. Finalizada
la carga obtenemos un error "4 Out of memory, 0:1" ¿Qué
pasa ahora? Si
con LOAD funciona ¿por qué falla el MERGE? El problema es
que un MERGE,
debido a que intenta fundir el programa en memoria con el cargado del
cassette, realiza un análisis del listado más profundo
que el LOAD. Si
modificamos un programa de manera que contenga líneas con
numeración o
tamaño ilegal aún se podrá cargar con LOAD, pero
todo intento de usar
MERGE será desastroso. El intérprete BASIC no
logrará cargar el
programa, como en este caso, o sencillamente se colgará. Este
tipo de
protección se denomina "ANTIMERGE", por motivos obvios.
Afortunadamente
todavía no se nos han agotado los recursos. Comenzamos
nuevamente la
carga con LOAD. Pero esta vez pulsamos BREAK inmediatamente
después de
la cabecera, antes del tono guía del cuerpo del programa. Aunque
un
intento de examinar el listado nos muestra una pantalla en blanco el
sistema se ha preparado ya, al leer la cabecera, para recibir el
programa que intentabamos cargar. Dicho de otra forma: no hemos cargado
el programa en si (pulsamos BREAK antes de hacerlo) pero sí toda
la
información relativa a su tamaño. Si ahora hacemos un
'SAVE "programa"'
podremos grabar en el cassette una cabecera idéntica a la del
cargador
protegido ¡pero sin autorun! Hacemos pues un nuevo LOAD y
reproducimos
nuestra cabecera falsa recien creada, seguida del bloque de datos del
cargador original. Listo. Ya tenemos el programa en memoria. Veamos,
con la orden LIST, qué aspecto tiene.
Listado
del cargador del Doomdark's Revenge, una vez
evitado
el ANTIMERGE.
¿Qué
es esto? ¿Dónde están los números de
línea y las sentencias? Vamos a necesitar un análisis
más profundo.
Todo
programa en el BASIC del Spectrum consiste en una sucesión de
una o
varias líneas. Cada una de estas líneas comienza con dos
parejas de
bytes que indican el número de la línea y su longitud,
por este orden.
A estos cuatro bytes les sigue el contenido de la línea
propiamente
dicha, en formato ASCII. Y para finalizar, toda línea acaba con
un byte
de "fin de línea", de valor 13. Además, en un Spectrum
que no tenga
nada extraño conectado al slot de expansión, el primer
byte del
programa (es decir, el byte de mayor peso del número de la
primera
línea) se encuentra en la posición de memoria 23765. Con
todos estos
datos, y una tabla del código ASCII del Spectrum, se puede
examinar a
mano el contenido de cualquier listado. Un paciente análisis del
cargador que nos ocupa, a base de leer con PEEK las posiciones de
memoria pertinentes, nos desvela la siguiente información:
1) El programa
consta de 3 líneas de números 0, 10 y 100.
2)
El valor del campo longitud de la segunda línea es demasiado
grande, y
no coincide con la longitud real delimitada por el byte de valor 13 al
final de la misma.
3) El listado
está plagado de códigos de desplazamiento del cursor a la
izquierda, y de códigos de control de color.
La
existencia de una línea 0, imposible en el BASIC del Spectrum,
no
supone mayor problema. Se usa frecuentemente para impedir que dicha
línea sea borrada o editada por los métodos normales.
Pero basta un
simple 'POKE 23756,1' para convertirla en una línea 1. El valor
erróneo
de la longitud de de la línea 10 ya es más grave, y es el
motivo por el
que fallaba el MERGE. En cuanto a los códigos de control
diseminados
entre las sentencias, son los que nos impiden ver el listado. Un
"cursor a la izquierda" estratégicamente colocado sobreescribe
el
número de línea. Y usar códigos para poner el
fondo y los caracteres
del mismo color (en este caso el blanco) permite ocultar el resto del
listado. Por eso sólo podemos ver el irónico mensaje de
advertencia.
Podríamos
editar las líneas y borrar todos estos códigos molestos,
pero más
adelante veremos que eso no es una buena idea. En lugar de ello
sustituiremos dichos códigos, a base de POKEs, por espacios en
blanco
(32 en ASCII). Después de esta laboriosa sesión de PEEKs
y POKEs
podemos, por fin, contemplar el listado.
Listado
del cargador del Doomdark's Revenge, eliminados
los
caracteres que impiden su visualización.
Las
líneas que empiezan por REM son comentarios. La primera es un
simple aviso, pero la número 100 resulta muy sospechosa.
Tiene todo el aspecto de ser una pequeña rutina en código
máquina que
mostrada en formato ASCII resulta ininteligible. Seguramente se ocupe
de leer el bloque sin cabecera que sigue al cargador en la cinta del
juego.
Nos falta
sólo, para completar nuestro análisis del
BASIC, examinar la línea 10. El bucle con un BEEP se encarga de
realizar unos sonidos de aviso, los POKEs modifican los colores de la
pantalla e impiden el BREAK, y el 'RANDOMIZE USR 0' del final (que
provocaría un reinicio) nunca llega a alcanzarse si el juego se
carga
completamente. Nos interesan el 'CLEAR 57999' y el 'RANDOMIZE USR
24061'. En realidad es lo que uno busca en todo cargador. El primero le
dice al BASIC que reserve memoria para un bloque de datos, y el segundo
lanza la ejecución de una rutina en código
máquina. El valor 24061 cae
en la línea 100, tal y como esperábamos. ¿Tenemos
ya, entonces, lo que
queríamos? Mejor no fiarse todavía.
Para asegurarnos
sustituimos
(como no, a base de nuevos POKEs) los POKE, CLEAR y RANDOMIZE USR del
listado por simples PRINT. El Spectrum nos presenta los siguientes
valores: 23693,56,23624,63,48599,23659,0,24129,0. Los POKEs y el
último
RANDOMIZE están bien. ¡Pero el CLEAR y el RANDOMIZE que
nos interesan
no producen el resultado esperado! ¿Qué está
pasando? La respuesta es
que el Spectrum almacena los números que aparecen en un listado
de dos
maneras diferentes: como caracteres ASCII y como un 14 seguido de 5
bytes en el formato de punto flotante propio del ordenador. Al ver el
listado se nos muestra la representación ASCII, pero al ejecutar
el
programa se opera sobre el valor determinado por los 5 bytes.
Normalmente estas dos representaciones coinciden, pero si modificamos
una de ellas el valor del número para el Spectrum será
diferente del
que vemos nosotros.Y, lo que es peor, si editamos una línea
así
protegida el número en coma flotante se actualizará
automáticamente al
expresado en ASCII, por lo que perderemos el valor original. Por eso no
es buena idea editar una línea de un programa que intentamos
desproteger.
Podemos
comprobar todas estas afirmaciones
ejecutando la sentencia 'CLEAR 48599:RANDOMIZE USR 24129'. Si pulsamos
PLAY en el cassette (o emulador) la carga continua normalmente. Siempre
y cuando no hayamos eliminado ninguna línea, o modificado su
longitud,
pues esto cambiaría la posición en la que se encuentra la
rutina de la
línea 100.
Conseguido
entonces. Hemos "destripado" completamente
el cargador BASIC. Ahora sabemos que nuestro siguiente paso es analizar
la rutina en código máquina que se encuentra a partir de
la posición
24129, aunque esa es ya otra historia...
Como véis
sí es posible proteger un programa en BASIC. Y eliminar esa
protección puede llegar a resultar muy laborioso.
Las capturas de
pantalla fueron realizadas con el emulador Spectaculator 5.2 (www.spectaculator.com/)
Los juegos en
formato TZX (volcado digital perfecto de la cinta) fueron bajados del
archivo de W.O.S. (www.worldofspectrum.org/)
|