Protection des jeux sur cassettes MO5

Vingt ans après, on peut estimer que parler de la protection des jeux Thomson n'est plus du piratage, mais de l'histoire.

Pour commencer le plus simple : mettre une checksum non standard, et modifier l'algorithme de contrôle pour qu'il l'accepte. 

Il faut connaitre la structure des fichiers cassette (voir les FAQ du forum http://dcmoto.free.fr). Le dernier octet de chaque bloc est la checksum, calculée par une formule simple : la somme modulo 256 des octets utiles du bloc (y compris la checksum) doit être nulle. 

Le principe est de recopier dans le programme la routine système de lecture d'un bloc, et de changer les 2 ou 3 instructions de contrôle de la checksum. Par exemple on dit qu'elle doit être la somme modulo 256 des octets précédents. Pour charger le programme on appelle cette routine modifiée au lieu d'appeler la routine officielle. 
Tous les autres programmes de chargement ou de copie utilisant les routines de lecture standard trouvent la checksum mauvaise et s'arrêtent avec la fameuse erreur 53 (erreur de lecture).

Une petite anecdote sur la protection Infogrames

Dès la sortie de DCMO6, William Hennebois a voulu exécuter Prohibition (son chef-d'oeuvre) avec l'émulateur. Il a donc essayé de détourner la protection (géniale) qu'il a lui-même inventée, probablement en 1985. Mais il avait oublié un de ses pièges, et n'a pas réussi. Ne sachant pas comment faire, il a demandé à Dianiel Coulom de le convertir. 

Protection des programmes Basic 

Les programmes Basic ont l'avantage d'être faciles à écrire et l'inconvénient d'être lents. 

Mais quand la vitesse d'exécution n'est pas recherchée, ce n'est pas une honte d'écrire quelques lignes de Basic, et de très bons programmeurs l'ont fait. Pour vous en convaincre, regardez le concours de deuligne à http://dcmo5.free.fr 

Pour protéger un programme Basic, on utilise souvent la technique suivante : en mémoire, les lignes du programme sont chaînées : chaque ligne est précédée d'un pointeur contenant l'adresse du début de la ligne suivante. La longueur d'une ligne n'est limitée que par la taille mémoire disponible. 

Par contre, l'éditeur Basic ne peut pas traiter les lignes de plus de 255 octets. S'il en trouve une, il n'affiche que les 255 premiers, et si on cherche à la modifier il la tronque à 255. L'astuce consiste à écrire tout son programme dans une seule ligne, qui aura la longueur du programme, par exemple 25000 octets. Si on le liste, on ne voit que les 255 premiers, et si on le modifie on perd les 24745 suivants. 

Heu ... mais ça empêche les GOTO/GOSUB et autres joyeusetés non ?

Oui, c'est juste, ça rend l'exercice très très difficile. Mais avec des FOR...NEXT on arrive presque à faire de la programmation structurée. Par exemple on fait une boucle FOR I=0 TO 1 STEP 0, et quand on veut en sortir on fait I=1. Evidemment ce n'est pas très pratique. Le pire, c'est pour les IF. Le MO5 ne connait pas l'instruction END IF, c'est la fin de la ligne qui en tient lieu. Donc avec une seule ligne dans le programme on galère pas mal (heureusement il y a le ELSE). On peut aussi faire des variantes : par exemple 5 lignes d'environ 5000 octets au lieu d'une de 25000, et ça t'ouvre les possibilités de GOTO et de GOSUB. Quand tu vois qu'on peut écrire Pictor en "deuligne", on doit pouvoir faire un éducatif de français en 5. 

Les codes opération invalides

Les instructions du 6809 sont codées sur un octet, ou sur deux octets avec un pré-octet 10 ou 11 (hexa). Au total 768 possibilités. 

La bible du programmeur 6809 est le "MC6809-MC6809E Microprocessor Programming Manual" (voir http://dcmoto.free.fr (page Documentation)). Il décrit environ 300 instructions. Les 470 codes restants sont qualifiés de "code opération invalide". Si le programmeur étourdi en glisse un dans son programme, il a de fortes chances de se planter. Mais ces codes opérations ne sont pas si invalides qu'on le dit. Ils font tous quelque chose, au minimum ils incrémentent le compteur ordinal d'une ou plusieurs unités. D'après mes tests, chaque code opération invalide fait une opération valide. Le problème, c'est que même les bons programmeurs ne savent pas quoi. Certains codes invalides sont décrits (voir le très bon site d'Arto Salmi sur le 6809), mais il en reste des centaines qu'aucun désassembleur ne connaît. D'où l'idée des programmeurs de protections : utiliser des codes opération invalides dans leur programme, pour que personne (sauf les initiés) ne puisse le comprendre.

L'utilisation de la mémoire vidéo

Quand on écrit un programme de protection, on cherche souvent à le cacher : s'il n'est pas visible, il sera beaucoup plus difficile à comprendre. Une bonne cachette dans le MO5 est la mémoire vidéo (idem pour les autres Thomson, mais sur les TO l'adresse est différente). Elle est composée de deux banques de 8 Ko se partageant les adresses 0000-1FFF. La première banque est utilisée pour stocker les couleurs, la deuxième pour les "formes" (dans le language de Thomson la "forme" définit si un pixel a la couleur d'écriture ou la couleur de fond). Mais les couleurs, comme les formes, n'occupent que 8000 octets. Il reste donc 8K - 8000 = 192 octets inutilisés dans chaque banque, soit au total 384 octets. De quoi loger un joli petit programme. Il faut toutefois se méfier de la sélection de la banque vidéo, qui peut intempestivement commuter d'une banque à l'autre au gré du bit 0 du registre A7C0. Mais le programmeur Thomson connait bien ce mécanisme, et s'en accommode sans problème ! 

Quand la couleur d'écriture et la couleur de fond sont identiques, on ne voit pas ce qui est écrit à l'écran (dans la mémoire "forme"). On n'y voit que du bleu (ou du noir, du jaune, du vert etc.). On peut donc en profiter pour mettre ce que l'on veut dans cette banque video, et pourquoi pas un programme de protection. Et là, plus de limite à 384 octets, on dispose d'un espace confortable de 8 Ko. Bien évidemment ce programme ne peut rien afficher, sinon il s'auto-détruit. Mais il peut sans problème lire la cassette, charger des modules en RAM, les crypter, les décrypter, bref faire tout ce qu'il veut sans crainte d'être découvert. Pour finir il lance un effacement d'écran et il n'y a plus aucune trace. 

Comment arrêter les petits malins ? 

Quand mon programme de protection est chargé, même bien caché, il y aura toujours des petits malins pour le trouver, le désassembler et le comprendre. Ces petits malins vont utiliser des PEEK pour lire la mémoire, et c'est une instruction Basic. Je vais donc empêcher le Basic de fonctionner, et privés de PEEK même les plus rusés seront totalement impuissants. Rien de plus simple : le Basic ne peut pas fonctionner sans sa zone de travail en RAM (Thomson appelle celà les "vecteurs" du Basic). J'implante mon programme à la place des vecteurs Basic, et le Basic est mort. On peut le ressusciter (en appuyant sur le bouton blanc miraculeux marqué INITIAL. PROG), mais dans ce cas il écrase mon code qu'on ne peut donc jamais voir. 

Le bloc FF : un cheval de Troie !

Les fichiers cassette sont terminés par un bloc de fin de fichier (voir http://dcmoto.free.fr). 
Ce bloc a normalement une longueur 2, et contient le type de bloc (FF) et la checksum (00). 
Rien n'interdit de créer un bloc FF plus long, dans la limite des 256 octets autorisés. 
Il joue toujours son rôle d'indicateur de fin de fichier, mais peut contenir jusqu'à 254 octets de code. A la lecture, il est stocké dans le buffer de lecture du magnétophone, qui se trouve à une adresse fixe. Il suffit alors de se brancher à cette adresse pour lancer le programme. Pour empêcher l'accès au code, le procédé du bloc FF n'est pas suffisant.

Le cryptage 

Quand une cassette contient un programme de protection, au lieu de le charger et de l'exécuter, on peut aussi l'ouvrir en lecture et le lire. Et le désassembler, et le comprendre. Pour éviter cela, la bonne technique est de crypter le programme. Sur la cassette il devient incompréhensible. Au lancement du jeu, le programme crypté est chargé, puis décrypté avant d'être lancé. Sur MO5 les cryptages sont généralement simples. En général un simple XOR de chaque octet avec une valeur fixe, ou avec plusieurs valeurs fixes revenant cycliquement (par exemple le cryptage imaginé par Microsoft pour les programmes Basic). C'est donc facile à décrypter, et en plus les routines de cryptage et de décryptage sont identiques (le XOR est "réversible"). 

On peut objecter qu'il faut bien avoir quelque part un décrypteur pour le décrypter. Il serait aussi possible de crypter le décrypteur (certains le font !), mais en bout de chaîne il y aura toujours un programme non crypté qu'on pourra analyser et comprendre. C'est là qu'interviennent les fameux codes opérations invalides. Si on truffe le décrypteur de code opérations invalides, il devient incompréhensible pour le non initié. William Hennebois utilisait le code opération invalide 01, que tout le monde (ou presque) connaît. Il fait un BRN. Mais si on vous plante de ci de là quelques 1041, ou 52, ou 61, ou autres, même le génial concepteur du 6809 ne saura pas ce que ça fait, ni même de combien d'octets progresse le compteur ordinal. 

La protection par blocs non standard.

Le système MO5 reconnaît plusieurs types de fichiers logiques : Basic (compacté ou texte ASCII, crypté ou non crypté), Contenu mémoire (par exemple les programmes en langage machine), Données... On peut aussi inventer de nouveaux types, mais ça ne présente pas d'intérêt particulier pour la protection. Pour être stockés sur cassette, ces fichiers logiques sont découpés en blocs physiques. Dans chaque bloc il y a une entête de bloc (16 octets 01), un identificateur de début de bloc (3C5A), le type du bloc (1 octet), la longueur du bloc (1 octet), les données, la checksum (1 octet). Les routines du moniteur MO5 ne savent lire que ce type de bloc. Il y a donc un moyen simple pour protéger un fichier cassette : utiliser une structure de bloc différente, non lisible par les routines standard, mais lisible par un petit programme appelé "loader". Le loader peut-être caché dans un bloc FF et chargé en mémoire vidéo pour passer inaperçu. C'est lui qui va charger le programme stocké dans les blocs non standard. Infogrames a inventé plusieurs types de blocs non standard. 

Encore plus fort : les bits sont raccourcis 

Pour empêcher la lecture par les routines standard, le changement de la structure des blocs est largement suffisant. Mais on peut faire encore mieux : modifier la période du signal enregistré sur la bande. Le procédé d'enregistrement des cassettes MO5 s'appelle MFM (Modified Frequency Modulation). Sous se terme barbare se cache un codage très simple, expliqué dans les FAQ du forum de http://dcmoto.free.fr. Ce qu'il faut retenir, c'est qu'un bit 0 est représenté par un créneau long, et un bit 1 par deux créneaux courts. La largeur de ces créneaux est déterminée par des boucles de temporisation dans la routine d'écriture de la cassette. Le programme de lecture s'attend à retrouver des créneaux de même taille. S'il trouve n'importe quel autre signal ne correspondant pas à un créneau de bit 0 ou à 2 créneaux de bit 1, il considère que c'est un parasite et l'ignore. Si j'écris la cassette avec un programme spécial qui utilise des temporisations différentes, par exemple des créneaux 45% plus courts, la lecture standard ne trouvera pas un seul bit sur la cassette. C'est exactement comme quand on cherche à lire une cassette TO7 sur un MO5 : la bande défile jusqu'au bout et aucun programme n'est trouvé. Même pas un bit !

Les intervalles entre les blocs

Nous avons vu qu'un bloc standard contient au maximum 275 octets (en-tête 16, indicateur 2, longueur 1, type 1, données 254, checksum 1). Après sa lecture, il faut le traiter. Par exemple, pour un programme Basic en mode texte ASCII, chaque mot-clé du Basic est reconnu et remplacé par un code, chaque ligne Basic est stockée en mémoire, et le pointeur vers la ligne suivante est calculé. Ce traitement dure un certain temps, c'est pourquoi le bloc suivant sur la cassette ne peut pas être juste à la suite du précédent. L'intervalle entre les blocs (ou gap inter-blocs) est différent selon le type du fichier. Un fichier de type contenu mémoire a des gaps courts, car le stockage du bloc en mémoire est très rapide. Un programme Basic a des gaps plus longs, car le traitement est plus complexe. 

On peut penser qu'en arrêtant le moteur du magnétophone à la fin d'un bloc, et en le redémarrant quand le traitement est terminé, on pourrait éviter le gap. Cette technique est parfois utilisée, mais l'inertie du moteur et du cabestan du magnétophone ne permet pas d'enchaîner directement les blocs. Le temps d'arrêt et le temps de stabilisation de la vitesse au démarrage nécessitent dans ce cas un gap d'environ 1 seconde, on n'a donc pas intérêt à arrêter le moteur si le traitement dure moins longtemps. 

Pendant le gap, il n'y a plus de signal sur la bande, et le programme de lecture se désynchronise. C'est ce qui rend nécessaire l'en-tête de bloc. Les 16 octets à 01 permettent de synchroniser à nouveau la lecture avant de lire les données du bloc suivant. Au total, avec le gap inter-blocs et les octets de synchronisation, l'enregistrement des données utiles peut représenter moins de la moitié du temps total. Si, comme certains programmes Loriciels, on utilise des blocs plus courts, le pourcentage de temps utile est encore plus défavorable. Ce qui les rend d'autant plus longs à charger. 

Le son d'un programme MO5 sur cassette n'est pas très agréable à l'oreille. Sans être mélomane, on peut cependant percevoir toutes ces différences : blocs longs ou courts, gaps très réduits ou anormalement longs, arrêt et redémarrage du moteur, et surtout la mélodie très reconnaissable des blocs Infogrames.

Liens

Site des émulateurs Thomson de Daniel Coulom

retour à la page principale