Gelfand FeIt Strauch Krsnik
Das Anti-Cracker-Buch
DATABECKER
ISBN 3-89011-253-6 2. unveränderte Auflage Copyright
...
135 downloads
1242 Views
95MB Size
Report
This content was uploaded by our users and we assume good faith they have the permission to share this book. If you own the copyright to this book and it is wrongfully on our website, we offer a simple DMCA procedure to remove your content from our site. Start by pressing the button below!
Report copyright / DMCA form
Gelfand FeIt Strauch Krsnik
Das Anti-Cracker-Buch
DATABECKER
ISBN 3-89011-253-6 2. unveränderte Auflage Copyright
©
1988
DATA BECKER GmbH Merowingerstraße 30 4000 Düsseldorf
Alle Rechte vorbehalten . Kein Teil dieses Buches darf in irgendeiner Form (Druck, Fotokopie oder einem anderen Verfahren) ohne schriftliche Genehmigung der DATA BECKER GmbH reproduziert oder unter Verwendung elektronischer Systeme verarbeitet, vervielfältigt oder verbreitet werden:
Wichtiger Hinweis : Die in diesem Buch wiedergegebenen Schaltungen, Verfahren und Programme werden ohne Rücksicht auf die Patentlage mitgeteilt. Sie sind ausschließlich für Amateur- und Lehrzwecke bestimmt und dürfen nicht gewerblich genutzt werden . Alle Schaltungen , technischen Angaben und Programme in diesem Buch wurden von dem Autoren mit größter Sorgfalt erarbeitet bzw. zusammengestellt und unter Einschaltung wirksamer Kontrollmaßnahmen reproduziert. Trotzdem sind Fehler nicht ganz auszuschließen. DAT A BECKER sieht sich deshalb gezwungen, darauf hinzuweisen, daß weder eine Garantie noch die juristische Verantwortung oder irgendeine Haftung für Folgen, die auf fehlerhafte Angaben zurückgehen, übernommen werden kann . Für die Mitteilung eventueller Fehler ist der Autor jederzeit dankbar.
Zu diesem Buch Da dieses Buch sich mit einem recht heiklen Thema beschäftigt, dessen Brisanz vor allem in letzter Zeit immer mehr ansteigt, hielten wir es für notwendig, einige einleitende Worte zu diesem Thema zu schreiben. Während der gesamten Entstehungszeit dieses Buches haben wir uns immer an dem Ziel orientiert, möglichst jedem C64-Benutzer einen Einblick in die aktuellsten Schutz- bzw. Kopierschutzsysteme zu ermöglichen. Wir versuchen hier, unser Wissen über diese Systeme zu vermitteln, um Ihnen die Möglichkeit zu geben, Ihre eigenen Programme vor Fremdzugriffen und Raubkopierern zu schützen. Darüber hinaus haben wir neue, geradezu sensationelle Kopierschutzverfahren entdeckt und entwickelt, die von keinem derzeit bekannten Kopierprogramm überwunden werden können. Da wir versucht haben, alle zur Zeit auf dem Markt befindlichen Techniken zusammenzustellen und diese zu erklären, ist es Ihnen mit diesem Buch auch möglich, fremde Software zu untersuchen und zu verstehen. Das soll Sie aber nicht dazu animieren, diese Software in irgendeiner Art zu modifizieren. Ausdrücklich warnen (!) wir davor, den Kopierschutz von fremder Software zu entfernen und diese zu kopieren. In diesem Sinne wünschen wir Ihnen viel Spaß und vor allem viel Erfolg!
Die Autoren
DüsseldorJ. im Februar 1987
Inhaltsverzeichnis 1.
Einleitung .............................................................. 13
2.
BASIC-Programmschutz .......................................... 15
2.1 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.1.7 2.2 2.2.1 2.2.2 2.3 2.4. 2.4.1
Listschutz ......................................................................... 15 Der Listschutz mit "SHIFT -L" ........................................ 15 LIST-Schutz mit Steuerzeichen ...................................... 16 LIST-Schutz über Sprungvektoren ................................. 17 Nur Zeilennummern ........................................................ 18 Simuliertes Programmende .............................................. 33 Der Linke(r)-Trick .......................................................... 36 Der Sys-Bluff ..................................................: ............... 38 Änderungsschutz ............................................................. 42 Abfrage des BASIC-Endes ............................................. 42 Änderungsschutz durch übergroße Zeilennummer .................................................................. 43 RESET- und RUN-STOP/RESTORE-Schutz ............. .48 Warum eigentlich Compilieren? ..................................... 50 Warum der Compilercode ............................................... 51
3.
Programmschutz für Profis ..................................... 55
3.1 3.1.1 3.1.2 3.1.3 3.1.4 3.1.5 3.1.6 3.1.7 3.1.8 3.1.9 3.2 3.3 3.3.1
Autostart .......................................................................... 55 Warum eigentlich Autostart? ................................... ....... 55 Der einfachste Autostart ................................................. 56 Autostart im Tastaturpuffer ........................................... 57 Autostart über Sprungvektoren ...................................... 62 Stapel-Autostart. .............................................................. 71 Autostart während des Ladens ....................................... 75 Autostart über Interrupt ................................................. 79 Autostart über die CIAs .................................................. 80 Autostart mit Adreßverschiebung .................................. 82 Prüfsummen und Selbstzerstörung .... ..................... ........ 84 Codierung von Programmen ........................................... 87 Verwendung der EXOR-Verknüpfung .......................... 87
3.3.2 3.3.3 3.3.4 3.3.5 3.4 3.4.1 3.4.2 3.4.3 3.5 3.5.1 3.5.2 3.5.3 3.6
Codierung mit verstecktem Einsprung ........................... 90 Codierung mit Timer ...................................................... 92 Einzelschritt-Decodierung .............................................. 97 Codierung über ASCII-Code ............... ......................... 105 Die Illegal-Codes ........................................................... 110 Erklärung der Illegal-Opcodes ..................................... 110 Anwendung der Illegal-Opcodes .................................. 114 Taktzyklen der Illegal-Codes ........................................ 118 Dongle als Programmschutz ................................... ....... 119 Wie arbeitet ein Dongle? .............................................. 120 Eine einfache Dongle-Abfrage ........... ...................... .... 121 Dongle mit IC ................................................................ 122 Password-Abfrage ......................................................... 125
4.
Directory-Schutz .................................................. 135
4.1 4.2 4.3 4.4 4.5 4.6 4.7
Versteckter Filename .................................................... 135 Verstecken der Filenamen durch Steuerzeichen ......... 136 Der "blinde" Block ....... .................................................. 137 Der Trick mit den Nullen ............................................ 140 Das geteilte Directory ................................................... 142 Die Tarnung des Filetyps ............................................. 144 Gelöschte Files .............................................................. 145
5.
Kopierschutz mit der Datasette .............................. 149
5.1 5.2
Sinn und Unsinn eines Kassettenkopierschutzes ......... 149 Laden und Abspeichern von Programmen und Daten ...................................................................... 150 Was man von BASIC aus machen kann ................... .... 150 Laden und Speichern in Maschinensprache ........ ......... 159 Autostart als Kopierschutz .............. ............................. 166 Eingriffe in die LOADjSA VE-Routine ...................... 166 Selbststartende Programme ........................................... 168 Der Kassettenpuffer ..................... ................................. 173 Entwicklung eines eigenen Aufzeichnungsformats ......................... .......................... 177
5.2.1 5.2.2 5.3 5.3.1 5.3.2 5.4 5.5
5.5.1
5.5.4
Direkte Ansteuerung der DatasettenFunktionen ..................................................................... 177 Signalerzeugung auf dem Band als Kopierschutz .................................................................. 182 Ein neues Aufzeichnungsformat am Beispiel des "KKS" ........... ............................... ................................... 192 Wichtige Betriebssystemadressen und -routinen ......... 212
6.
Diskettenkopierschutz ...............•....•..•......•........•... 215
6.1 6.l.2 6.l.3 6.1.4 6.2 6.2.1 6.2.2 6.2.3 6.2.4 6.2.5 6.2.6 6.3 6.4 6.5 6.6 6.6.1 6.6.2 6.6.3 6.6.4 6.7
Unser "Handwerkszeug" ................................................ 215 Das Disketten-Aufzeichnungsverfahren ...................... 223 Einführung in die Lese- und Schreibtechnik .............. 228 Registerbeschreibung der VIA 2 .................................. 239 Die Formatroutine ......................................................... 241 Formatieren eines einzelnen Tracks ............................. 242 Formatieren der Spuren 36 bis 41.. .............................. 245 Doppelte Spuren ............................................................ 250 Änderung der Headerparameter: Read-Errors ............ 256 Das Arbeiten mit zerstörten Blöcken ........................... 265 Gleiche Blöcke auf einem Track .................................. 270 Half tracks ....................................................................... 277 Mit Nullen beschriebene Spuren .................................. 286 Überprüfen eines kompletten Tracks ........................... 292 SYNC-Manipulationen .................................................. 299 Killertracks .................................................................... 299 Verlängerte SYNC-Markierungen ................................ 300 Der 3000-SYNC-Schutz ................................................ 304 Daten ohne SYNC-Markierung ....... ............................. 312 Der "Disketten-Killer" .................................................. 318
7.
Wie man sich gegen Knackmodule schützt.. ............ 321
7.1 7.2 7.2 7.3
Einleitung ....................................................................... 321 Vorläufer der Knackmodule ........................................ 321 Knackmodule neueren Datums ..................................... 324 Knackmodule: Zukunftsaussichten ............................... 331
5.5.2 5.5.3
8.
Gerüchteküche ....................••....................•........... 333
8.1 8.2
Der rechnerzerstörende Kopierschutz .......................... 333 Viren .............................................................................. 334
9.
Allgemeine Tips ..................................................... 337
10.
Komplettlösung ....................•.•.•.•..............•........... 339
11.
Was ein Software-Haus über "Cracker" wissen sollte ................................................................... 347
12.
Kopierschutz auf dem C128 .•...............•.........•....... 353
12.1
Übereinstimmungen und Unterschiede zum C64 ......................................................................... 353
Index ................................................................... 377
Einleitung
1.
13
Einleitung
Der C64 ist ein Computer, für den tausende von Programmen zur Verfügung stehen. Man kann diese Software einerseits im Laden kaufen, andererseits ist nahezu jedes Programm auch als Raubkopie auf dem schwarzen Markt erhältlich. Dieses Potential hat zwar zur weiten Verbreitung des Rechners geführt, aber man kann heute kein Programm mehr auf den Markt bringen, befürchten zu müssen, daß es binnen einer Woche als Raubkopie vorliegt und somit sehr viel seltener verkauft wird. Aus diesem Grunde werden die meisten Programme von den Firmen mit einem Kopierschutz ausgestattet. Wir wollen in diesem Buch kein Urteil über das Raubkopieren abgeben. Vielmehr wollen wir aufzeigen, wie man sein Programm vor den unberechtigten Zugriffen anderer schützt und welche Kopierschutzmethoden auf dem C64 möglich sind. Die Palette der von uns beschriebenen Systeme reicht von einfachen, aber nichtsdestoweniger wirkungsvollen, Tricks zum Schutz eines BASIC-Programmes bis zu Anwendungen, die die von professionellen Entwicklern um einiges übertreffen. Als Programmierer können Sie durch dieses Buch einen Großteil der Arbeit, die Sie sonst auf den Kopierschutz verwenden müßten, einsparen. Sie können allerdings auch als Heimanwender Ihre Programme vor dem Zugriff durch Bekannte schützen. Wir haben uns bemüht, unsere Beschreibungen auch für einen Anfänger verständlich zu halten. Die meisten abgedruckten Programme können Sie benutzen, ohne sich bis ins Detail mit ihnen zu beschäftigen. Andererseits wollten wir natürlich auch dem erfahrenen Anwender etwas bieten. Daher sind alle ProgrammListings, insbesondere die Maschinensprache-Listings, ausführlich erläutert und dokumentiert. Wenn Sie, aufbauend auf den Beispielen, zu Eigenentwicklungen übergehen wollen, kann es an einigen Stellen hilfreich sein, schon mit der 6510-Assemblersprache vertraut zu sein oder sich zumindest mit der Bedienung eines Maschinensprachemonitors ein wenig auszukennen. Wie gesagt, ist das allerdings keine Grundvoraussetzung, um mit diesem Buch etwas anzufangen.
14
Das große Anti-Cracker-Buch
Um einen Kopierschutz zu überwinden, kann ein Kopierer prinzipiell zwei Dinge tun. Entweder versucht er, das Programm mit Hilfe eines Kopierprogrammes zu vervielfältigen, oder er entfernt die Abfrage des Kopierschutzes. Daher mußten wir in diesem Buch auch eine klare Grenze zwischen Programm- und Kopierschutz ziehen. Kopierschutz ist alles, was das Kopieren eines Programms verhindert. Ein Programmschutz dagegen dient dazu, Änderungen an einem Programm zu verhindern, worunter natürlich auch das "Knacken", also das Entfernen eines Kopierschutzes, fällt. Beim Kopierschutz mußte eine weitere Unterteilung in Datasetten- und Disketten-Kopierschutz vorgenommen werden. Die Möglichkeiten, die eine Diskettenstation bietet, unterscheiden sich stark von denen eines Datasetten-Rekorders. Auch beim Programmschutz haben wir eine Unterscheidung getroffen, nämlich zwischen BASIC- und Assembler-Programmen. Für beide Fälle finden Sie in diesem Buch zahlreiche Beispiele. In neuerer Zeit ist eine Kopiermethode sehr beliebt geworden, die einer gesonderten Behandlung bedarf: das "Knacken" mit einem "Knackmodul". Selbstverständlich haben wir auch für dieses Schutzproblem eine Lösung gefunden. Betrachten Sie dieses Buch nicht nur als eine Ansammlung aller möglichen Schutzsysteme, sondern lassen Sie sich auch dazu anregen, die angeführten Beispiele zu erweitern und eigene Ideen zu verwirklichen.
BASIC -Programmschutz
2. 2.1
15
BASIC-Programmschutz Listschutz
Wir werden hier einige Methoden demonstrieren, wie man sein eigenes BASIC-Programm vor dem LIST -Befehl schützt. Alle diese Möglichkeiten, die wir Ihnen hier aufführen, können natürlich mit anderen Schutz-Methoden kombiniert werden. Zum Beispiel ein BASIC-Programm, das einen LIST -Schutz enthält, der nur ein Listen der Zeilennummern ermöglicht, RUNSTOP-RESTORE ignoriert und nach RESET ein automatisches Starten des Listings oder ein Zerstören des Programms veranlaßt. Wir werden zunächst mit den einfachsten Verfahren beginnen und gehen anschließend zu den etwas anspruchsvolleren über. Wir hoffen, alle BASIC-LIST -Schutzmethoden erfaßt zu haben. Wir beginnen zunächst mit dem LIST -Schutz. Anschließend folgen dann die anderen Schutz-Möglichkeiten wie RESET -Schutz usw. 2.1.1
Der Listschutz mit "SHIFT-L"
Wir wollen Ihnen hier eine einfache Methode demonstrieren, mit der Sie BASIC-Programme vor dem Listen schützen können. Diesen LIST -Schutz zu installieren, ist nicht sehr schwer. Man muß hinter einer BASIC- Zeile nur noch einen zusätzlichen Befehl eingeben, und zwar ein REM hinter dem direkt noch ein [SHIFT]-L folgt. Dies könnte folgendermaßen aussehen: 10 FOR 1=1 TO 20:REM[SHIFTlL
Mit [SHIFT]-L ist das Zeichen gemeint, das durch gleichzeitiges Drücken der SHIFT - und der L-Taste erzeugt wird. Dieses Zeichen muß direkt hinter dem REM-Befehl folgen. Falls man nun versucht, das Programm zu listen, wird ab dieser Zeile die Fehlermeldung "SYNTAX ERROR" folgen, und der List-Vorgang wird unterbrochen.
Das große Anti-Cracker-Buch
16
Der Nachteil bei diesem Schutz ist allerdings, daß er nur auf dem Original-Betriebssystem funktioniert. Außerdem wird die Zeile 10, in der sich das REML befindet, zuerst gelistet, und dann erst folgt die Fehlermeldung. Das heißt, daß die erste Zeile immer sichtbar bleibt. Also ist es ratsam, vor dem eigentlichen Programm noch ein paar REM-Zeilen zu setzen, damit man den Programm beginn nicht sieht: 5 REML 6 REML 7 REML 10 FOR 1=1 TO 20:REML
Beim Versuch das Programm zu listen, sieht man nur folgendes: 5 REM ?SYNTAX ERROR READY.
Das restliche Programm bleibt unsichtbar. Noch ein Hinweis: Dieser LIST -Schutz läßt sich sehr leicht wieder ausbauen, indem man die REM-Zeilen nacheinander löscht. So kann man sich langsam, aber sicher in das Programm hineintasten. Möchten Sie ein Programm effizient schützen, ist dieser Schutz nicht zu empfehlen. 2.1.2
LIST -Schutz mit Steuerzeichen
Dieser LIST -Schutz funktioniert ähnlich wie der schon beschriebene. Der Unterschied besteht nur darin, daß statt eines [SHIFT]-L Steuerzeichen eingesetzt werden. Für den LISTSchutz eignet sich eigentlich nur das DEL-Zeichen. Es hat die gleiche Funktion wie die DELETE-Taste auf der Tastatur. Wenn
BAS Je - Programmschut z
17
man nun das Zeichen in eine BASIC-Zeile mit einem REMBefehl kombiniert, lassen sich BASIC-Zeilen verstümmeln bzw. ganz unsichtbar machen: 10 FOR 1=1 TO 20:REM"TTTTTTTTTTTTTTTTT
Beim Listen wird für jedes reverse T ein Zeichen der BASICZeile gelöscht. Bei unserem Beispiel wird bei einem LIST - Vorgang alles bis auf die Zeilennummer gelöscht. Ist dieser Schutz einmal eingebaut, läßt er sich schlecht wieder ausbauen. Um ein solches reverses T zu erzeugen, muß wie folgt vorgegangen werden: 10 FOR 1=1 TO 20:REM"" [DEL] 17 MAL [INS] UND 17 MAL [DEL]
Sofort hinter dem REM werden zwei Hochkommata gesetzt. Anschließend wird eines davon mit der DELETE-TASTE wieder entfernt, um den Hochkomma-Modus auszuschalten. Dann drücken Sie 17mal die INSERT-TASTE und anschließend 17mal die DELETE-TASTE. Jetzt haben Sie 17 reverse T erzeugt. Die Anzahl kann je nach Bedarf gewählt werden. Wenn Sie über SPEEDDOS oder ein anderes System verfügen und dieses auch benutzen, ist es durchaus möglich, daß diese Schutz-Methode nicht funktioniert, weil diese Systeme über eine veränderte LIST -Routine verfügen. 2.1.3
LIST -Schutz über Sprungvektoren
Einen guten LIST -Schutz kann man auch durch Ändern des LIST - Vektors erreichen. Dieser Vektor, der in $0306 (774) und $0307 (775) liegt, wird bei jeden LIST -Befehl angesprungen, um das BASIC-Programm in Klartext zu verwandeln. Er dient nur als Zeiger in LOW- und HIGH-Byte-Darstellung, der auf diese Betriebssystem-Routine zeigt.
18
Das große Anti-Cracker-Buch
Man kann nun diesen Vektor mittels POKE oder über einen Maschinensprachemonitor auf eine andere Routine verzweigen lassen. Das heißt, daß bei jedem LIST -Befehl diese Routine angesprungen wird. Geben Sie nun einmal diese beiden POKEs ein: POKE 774,226: POKE 775,252
Mit diesen POKEs haben Sie den LIST-Vektor auf die RESETRoutine $FCE2 (64738) verzweigen lassen. Wenn man nun LIST eingibt, wird automatisch ein RE SET ausgeführt. Das funktioniert aber nur, wenn Sie ein BASIC-Programm im Speicher haben. Auf diese Weise kann man auch zu seiner eigenen Maschinensprache-Routine verzweigen, wo zum Beispiel das BASIC-Programm automatisch wieder gestartet oder zerstört werden kann. Einen kleinen Nachteil hat dieses Verfahren. Man muß immer nach dem Einschalten des Computers diese POKEs eingeben. Dieses Problem läßt sich lösen, indem man einen Autostart in das Programm einbaut, der diese Vektoren automatisch umstellt. 2.1.4
Nur Zeilen nummern
Dieser LIST -Schutz eignet sich am besten zum Schutz von BASIC-Programmen. Wenn man hier bei eingebautem LISTSchutz versucht, das BASIC-Programm zu listen, sieht man nur die jeweiligen Zeilennummern der BASIC-Zeilen. Der Nachteil ist, daß man jede BASIC-Zeile einzeln mit dem Schutz versehen muß. Nun zum eigentlichen Schutz! Geben Sie folgende Zeile ein: 10 :::::PRINT "HALLO"
Diese fünf Doppelpunkte dienen als Platzhalter. Sie werden später mit dem Maschinensprachemonitor überschrieben. Aber
19
BAS JC - Programmschutz
schauen wir uns erst einmal diesen BASIC-Befehl im Maschinensprachemonitor an: .:0800 00 13 08 OA 00 3A 3A 3A .:0808 3A 3A 99 22 48 41 4C 4C .:0810 4F 22 00 00 00
::."HALL 0" ..•...
Die ersten beiden Werte in Zeile $0800 zeigen in LOW- und HIGH-Byte-Darstellung auf die nächste BASIC-Zeile. Falls keine weitere BASIC-Zeile vorhanden ist, weist dieser Zeiger auf drei hintereinander folgende Nullen, die das BASIC-Programmende markieren. Dieser Zeiger heißt Linker. Die nächsten beiden Werte stellen auch in LOW- und HIGHByte-Darstellung die Zeilennummer dar. Im LOW-Byte steht $OA und im HIGH-Byte $00, das heißt, daß in diesem Fall die Zeilennummer 10 beträgt. Eine LOW-HIGH-Byte-Darstellung ist unbedingt erforderlich, da man sonst nur Zeilennummern bis 255 darstellen könnte. In diesem Fall aber lassen sich Zeilennummern bis 65535 darstellen. Anschließend folgen unsere fünf Doppelpunkte, die, wie schon erwähnt, nur als Platzhalter dienen. Sie sind im Maschinensprachemonitor mit dem ASCII-Wert $3F abgelegt. Hinter den Doppelpunkten folgt dann das Token $99, was nichts anderes als PRINT im BASIC-Listing bedeutet. Für jeden BASIC-Befehl ist ein bestimmtes Token vorhanden. Die ASCII-Zeichen hinter dem Token sind die Zeichen, die über dem PRINT -Befehl ausgegeben werden sollen. Falls eine weitere BASIC-Zeile existiert, würde dann $00 folgen, und der Vorgang würde sich wiederholen. Die Null signalisiert, daß eine neue BASIC-Zeile folgt. Wenn aber keine weitere BASIC-Zeile folgt, wie es in unserem Beispiel der Fall ist, stehen dort drei Nullen, die das BASIC-Programmende anzeigen.
20
Das große Anti-Cracker-Buch
Soweit zum Aufbau der BASIC-Zeile. Nun wollen wir uns aber mit dem eigentlichen LIST -Schutz beschäftigen. Wir ändern die Doppelpunkte, also die Platzhalter, mit dem Maschinensprachemonitor: .. 0800 00 13 08 DA 00 00 FF FF .. 0808 FF FF 99 22 48 41 4C 4C .. 0810 4F 22 00 00 00 •
... "HALL 0" ••.•••
Anstelle der Doppelpunkte $3A $3A $3A $3A $3A haben wir jetzt $00 $FF $FF $FF $FF gesetzt. Wenn wir nun LIST eingeben, sehen wir: 10 READY.
Die Zeilen nummer bleibt sichtbar, aber der BASIC-Befehl ist verschwunden. Er ist natürlich nicht richtig "verschwunden", sondern er läßt sich einfach nicht mehr listen, weil der Interpreter direkt hinter der Zeilennummer eine Null findet, diese als Zeilenende interpretiert und zur nächsten Zeile springt. Das Programm aber läßt sich immer noch ganz normal mit RUN starten. Der LIST -Schutz funktioniert auch, wenn mehrere BASIC-Befehle in einer Zeilen stehen. Da es sicher umständlich ist, jede BASIC-Zeile einzeln zu schützen, haben wir zwei Programme für Sie vorbereitet: Das erste ist ein Packer-Programm für BASIC-Zeilen. Es packt bis zu 245 Zeichen pro Zeile. Programmzeilen, die von GOTO, GOSUB oder THEN angesprungen werden, werden nicht gebunden. Auch Zeilen, in denen REM-Zeilen vorkommen, bleiben unverändert, weil sonst der nachfolgende Befehl als REM-Text anerkannt werden würde. Alle PRINT - und OPEN-Befehle müssen mit Anführungszeichen abgeschlossen sein, da sonst vom Compaktor "SYNTAX ERROR" ausgegeben wird.
BASIC -Programmschutz
21
Wenn man nun ein BASIC-Programm packen möchte, muß man den Packer vorher in den Speicher laden. Das Packer-Programm befindet sich nun ab der Adresse $COOO (49152) im Speicher. Danach muß ein NEW eingegeben werden, um die Zeiger wieder richtig zu stellen. Anschließend muß das zu packende BASICProgramm in den Speicher geladen werden. Das Packerprogramm wird mit SYS 49152 gestartet. Der PackVorgang dauert ca. 3 Sekunden. Das BASIC-Programm kann nun wieder auf Diskette gespeichert werden . Es ist um einige Bytes kürzer als vorher. Das zweite Programm , das direkt hinter dem Packerprogramm im Speicher liegt, ist das Listschutz-Programm. Es kann "normale" und gepackte BASIC-Programme schützen. Wenn sich das zu schützende BASIC-Programm im Speicher befindet, kann die Schutz-Routine mit SYS 49730 ($C247) aufgerufen werden. Nach ca . einer Sekunde sind alle BASIC-Zeilen geschützt, das heißt, es sind nur die Zeilennummern sichtbar. Das BASIC-Programm kann auch normal mit SA VE wieder auf Diskette oder Kassette gespeichert werden. Es ist durch den Schutz um einiges länge r als vorher. Wenn man aber das BASICProgramm vorher packt, kommt man ungefähr wieder auf die gleiche Länge. Ein gepacktes BASIC-Programm ist auch schwer zu ändern, daher kann man das Packen auch als Änderungsschutz verwenden. Vorsicht! Wenn Sie einmal ein BASIC-Programm LIST-geschützt haben, wird es kaum möglich sein , es wieder sichtbar zu machen.
22
Das große Anti - Cracker-Buch
Nun das Maschinenlisting des Packer- Programms: COOO C002 C004 C007 C009 COOA COOC CO OE C010 C012 C014 C016 C01B COlA C01C COlD C01F C02l C022 C024 C025 C026 C02B C02B C02D C02F C031 C032 C034 C035 C037 C03B C03A C03C C03E C040 C042 C044
LDA STA JSR LDA SEC SBC STA STA LDA SBC STA STA LDY LDA INY ORA BEC INY LDA TAX INY LDA JSR BEQ LDY LDA SEC SBC SEC SBC CLC ADC BCS CMP BCS STA LDY LDA
#$36 $01 $COA7 $2B #$01 $AB $A9 $2C #$00 $AC $AA #$01 ($A9) Y I
($A9),Y $C05E ($A9), Y
($A9),Y $C213 $C079 #$01 ($A9), Y $A9 #$05 $AD $C079 #$F5 $C079 MD #$00 #$3A
BASIC-Interpreter ausblenden Tabe l le angesprungen. Zeilen erstellen BASIC-Anfang LOW-Byte Carry für Subtrakt i on setzen minus Eins nach Zeiger für Zielspeicher LOW-Byte nach Zeiger auf Programm LOW -Byte BASIC-Anfang HIGH-Byte minus Übertrag nach Zeiger für Zielspeicher HIGH-Byte nach Zeiger auf Programm HIGH-Byte Programmzeiger auf Link-Zeiger setzen Link-Byte LOW holen Programmzeiger erhöhen mit Link -Byte HIGH verknüpfen verzweige, wenn Null Programmzeiger erhöhen Zeilenummer LOW laden nach X schieben Programmzeiger erhöhen Zeilenummer HIGH laden prüfen, ob Zeilennummer in Tabelle verzweige, wenn ja Programmzeiger auf Linker setzen Linker LOW-Byte ho len Carry für Subtraktion setzen minus Programmzeiger LOW -Byte Carry für Subtraktion setzen minus 5 ergibt gekürzte Zeilen länge Carry für Addit i on löschen plus bisherige Zeilenlänge verzweige, wenn größer 255 größer gleich $F5 (=245)? verzweige, wenn ja Summe als neue Zei lenlänge spe i chern Zähler für Verschiebeschleife auf Null ASCI I 00.0'
BASIC -Programmschutz C046 STA ($AB),Y C048 INY C049 LDA $A9 C04B CLC C04C ADC #$04 C04E STA $A9 C050 BCC $C054 C052 INC $AA C054 LDA ($A9),Y C056 BEQ $C090 C058 STA ($AB),Y C05A INY C05B JMP $C054 C05E TAY C05F STA ($AB),Y C061 INY C062 CPY #$03 C064 BNE $C05F C066 TYA C067 CLC C068 ADC $AB C06A STA $2D C06C LDA $AC C06E ADC #$00 C070 STA $2E C072 LDA #$37 C074 STA $01 C076 JMP $E1AB C079 LDY #$00 C07B LDA ($A9),Y C07D STA ($AB),Y C07F INY C080 CPY #$05 C082 BNE $C07B C084 LDA ($A9),Y C086 BEQ $C08E C088 STA ($AB),Y C08A INY C08B JMP $C084 C08E STY $AD
in Zielspeicher schreiben Zeiger erhöhen Programmzeiger LOW-Byte Carry für Addition löschen plus 4 nach Programmze i ger LOW-Byte verzweige, wenn kein Übertrag HIGH-Byte Programmzeiger erhöhen Programm-Byte holen verzweige, wenn nächste Zeile erreicht in Zielspeicher schreiben Zäh l er erhöhen Sprung zum Schleifenanfang Zeiger für Zielbereich auf Null Null ans Programmende schreiben Zeiger erhöhen schon drei Nullen geschrieben? verzweige, wenn nein V-Register nach Akku Carry für Addition löschen Programmende LOW-Byte berechnen in Programmendezeiger LOW speichern Programmende HIGH-Byte Übertrag addieren in Programmendezeiger HIGH speichern BASIC-Interpreter einschalten CLR, Programmzeilen binden, Rücksprung Zähler auf Null setzen erste fünf Programm-Bytes verschieben Zähler erhöhen schon fünf Bytes verschoben? verzweige, wenn nein Programm-Byte holen verzweige, wenn nächste ZeiLe erreicht Programm-Byte speichern ZähLer erhöhen Sprung zum Schleifenanfang Programmzeilenlänge speichern
23
24 C090 C091 C092 C094 C096 C098 C09A C09B C09C C09E COAO COA2 COA4 COA7 COA9 COAA COAC COAE COBO COB2 COB4 COB6 COB8 COBA COBC COBE COCO COC1 COC2 COC4 COC7 COC8 COCA COCC CO CD COCE CODO COD2 COD4 COD6
Das große Allti-Cracker-Buch TYA CLC ADC STA BCC INC TYA CLC ADC STA BCC INC JMP LDA SEC SBC STA LDA SBC STA LDY LDA STY STA LDY LDA TAX INY LDA JSR INY LDA BNE TYA CLC ADC STA BCC INC LDY
SA9 SA9 SC09A SAA
SAB SAB $COA4 SAC SC018 $2B #$01 SAB S2C #$00 SAC #SDO #$AO SA5 $A6 #$03 (SAB),Y
($AB),Y SC193 ($AB),Y $COF6
$AB $AB $COD6 SAC #$01
Zähler nach Akku Carry für Addition löschen Programmzeiger LO~-Byte berechnen und speichern verzweige, wenn kein Übertrag Programmzeiger HIGH-Byte erhöhen Zähler nach Akku Carry für Addition löschen Zielzeiger LO~-Byte berechnen und speichern verzeige, wenn kein Übertrag Zielzeiger HIGH-Byte erhöhen nächste Zeile bearbeiten BASIC-Anfang LO~-Byte Carry für Subtraktion setzen minus 1 in Programmzeiger LO~-Byte speichern BASIC-Anfang HIGH-Byte minus Übertrag in Programmzeiger HIGH-Byte speichern LO~-Byte von $AOOO HIGH-Byte von $AOOO in Tabellenendezeiger LO~ speichern in Tabellenendezeiger HIGH speichern Programmzeiger auf erste Zeilennummer Zeilennummer LO~-Byte holen und nach X-Register schieben Programmzeiger erhöhen Zeilennummer HIGH-Byte holen in Tabelle eintragen Programmzeiger erhöhen Programm-Byte holen verzweige, wenn nicht am Zeilenende Zeiger nach Akku schieben Carry für Addition löschen Programmzeiger berechnen und speichern verzweige, wenn kein Übertrag Programmzeiger HIGH-Byte erhöhen Zeiger auf Linker setzen
BAS Je - Programmschutz COD8 CODA CODB CODD CODF COE1 COE3 COE5 COE6 COE8 COEA COEC COEE COF1 COF3 COF6 COF8 COFA COFC COFE C100 C102 C104 C106 C108 C10A C10C C10E C110 C112 C114 C116 C117 C11A C11B C11D C11F C121 C122 C124
LDA INY ORA BNE STA LDA STA RTS LDA BEQ LDA STA JMP LDY JMP CMP BEQ CMP BEQ CMP BEQ CMP BEQ CMP BEQ CMP BEQ CMP BEQ CMP BEQ INY JMP INY LDA CMP BEQ INY LDA CMP
($AB),Y ($AB),Y $COE6 $A7 #$AO $A8 $02 $COF1 #$00 $02 $COBC #$05 $COC8 #$80 $C121 #$89 $C121 #$CB $C11A #$8B $C17A #$A7 $C121 #$22 $C182 #$8F $C17A #$91 $C17A $COC8 ($AB),Y #$20 $C11A ($AB),Y #$20
Linker LO~-Byte holen Programmzeiger erhöhen Akku mit Linker HIGH-Byte verknüpfen verzweige, wenn nicht am Programmende Tabellenzeiger LO~ auf Null setzen HIGH-Byte von $AOOO in Tabellenzeiger HIGH speichern Rücksprung Flag für IF-Befehl testen verzweige, wenn nicht gesetzt Flag für IF-Befehl löschen Zeile eintragen Zeilennummer Überspringen nächstes Programm-Byte testen GOSUB-Token? verzweige, wenn ja GOTO -Token? verzweige, wenn ja GO-Token? verzweige, wenn ja IF-Token? verzweige, wenn ja THEN-Token? verzweige, wenn ja ASCII: Anführungszeichen? verzweige, wenn ja REM-Token? verzweige, wenn ja ON-Token? verzweige, wenn ja Programmzeiger erhöhen nächstes Programm-Byte testen Programmzeiger erhöhen Programm-Byte holen Leerzeichen? verzweige, wenn ja GOTO- bzw. TO-Token überspringen Programm-Byte holen Leerzeichen?
25
Das große Allti-Cracker-Buch
26 C126 C128 C12A C12C C12E C12F C130 C132 C134 C136 C138 C13A C13C C13E C140 C142 C144 C145 C148 C149 C14A C14B C14D C14F C152 C155 C156 C157 C159 C15B C15D C15F C162 C164 C165 C167 C169 C16A C16C C16E
BEQ LDA STA STY TYA CLC ADC STA LDA ADC STA LDA CMP BCC CMP BCS INY JMP TYA PHA SEC SBC BEQ JSR JSR PLA TAY LDA STA LDX LDA JSR LDA INY CMP BEQ DEY CMP BEQ JMP
$C121 #$37 $01 $AE
$AB $22 $AC #$00 $23 ($AB),Y #$30 $C148 #$3A $C148 $C13A
$AE $C171 $B7B5 $B7F7
#$36 $01 $14 $15 $C193 ($AB) , Y #$20 $C162 #$2C $C121 $COC8
verzweige, wenn ja BASIC-Interpreter einschalten V-Register zwischenspeichern Zeiger in Akku schieben Carry für Addition löschen Programmzeiger LOW-Byte berechnen und speichern Programmzeiger HIGH-Byte holen übertrag addieren und speichern Programm-Byte holen kleiner als ASCII "O"? verzweige, wenn ja größer als ASCII "9"? verzweige, wenn ja Zeiger erhöhen Sprung zum Schleifenanfang V-Register auf Stapel retten Carry für Subtraktion setzen Länge der Ziffernfolge berechnen verzweige , wenn gleich Null Zi f fern-String in Fließkommazahl wandeln Fließkommazahl in Integerzahl wandeln V-Register vom Stapel zurückholen BASIC-Interpreter wieder abschalten Zeilennummer LOW-Byte Zeilennummer HIGH-Byte in Tabelle eint r agen nächtes Programm-Byte holen Programmzeiger erhöhen Leerzeichen? verzweige, . wenn ja Programmzeiger verringern ASCII : " , " (bei ON . . . GOTO/GOSUB)? verzweige , wenn ja nächstes Programm -Byte testen
BAS Je -Programmschutz C171 C172 C173 C175 C177 C17A C17C C17E C17F C182 C183 C185 C187 C189 C18B C18C C18F C190 C193 C195 C197 C199 C19B C19D C19F C1A1 C1A3 C1A5 C1A7 C1A9 C1AA C1AC C1AE C1AF C1BO C1B2 C1B4 C1B5 C1B7 C1B9
PLA TAY LDA STA JMP LDA STA INY JMP INY LDA BEQ CMP BNE INY JMP DEY JMP STX STA STY LDY STY LDA STA CPY LDA SBC BEQ SEC LDA SBC TAX INY LDA SBC INY BNE INC BCC
#$36 $01 $COC8 #$01 $02 $COC8 ($AB),Y $C18F #$22 $C182 $COC8 $C17A $A9 $AA $AE #$00 $A7 #$AO $A8 $A5 $A8 $A6 $C1CD ($A7),Y $A9
($A7),Y $AA $C1B9 $A8 $C1A1
V-Register vom Stapel zurückholen BASIC-Interpreter wieder abschalten nächstes Programm-Byte testen Flag für IF, REM oder ON setzen Token überspringen nächstes Programm-Byte testen Anführungszeichen überspringen Programm-Byte holen verzweige, wenn Zeile zu Ende zweites Anführungszeichen erreicht? verzweige, wenn nein Programmzeiger erhöhen nächstes Programm-Byte testen Programmzeiger verringern IF-Flag setzen Zeilennummer LOW Zeilennummer HIGH V-Register zwischenspeichern LOW-Byte von $AOOO nach Tabellenzeiger LOW schreiben HIGH -Byte von $AOOO nach Tabellenzeiger HIGH schreiben Tab_-zeiger LOW mit Tab_-ende vergleichen Tabellenzeiger HIGH mit Tabellenende HIGH vergleichen verzweige, wenn Ende erreicht Carry für Subtraktion setzen Zeilennummer LOW aus Tabelle mit neuer Zeilennummer vergleichen Differenz zwischenspeichern Tabellenzeiger erhöhen Zeilennummer HIGH aus Tabelle mit neuer Zeilennummer vergleichen Programmzeiger LOW-Byte erhöhen verzweige, wenn kein Übertrag Programmzeiger HIGH-Byte erhöhen verzweige, wenn neue Zeilennr. größer
27
28 C1BB C1BD C1BF C1CO C1C2 C1C4 C1C5 C1C6 C1CB C1CA C1CC C1CD C1CF C1D1 C1D3 C1D5 C1D7 C1D9 C1DB C1DD C1DF C1E1 C1E3 C1E5 C1E7 C1E9 C1EB C1ED C1EF C1F1 C1F3 C1F4 C1F6 C1FB C1FA C1FD C1FF C201 C203 C204
Das große Anti-Cracker-Buch CMP BNE TXA BNE lDY RTS DEY CPY BNE DEC DEY STY lDA STA lDA STA lDA CMP lDA SBC BCS DEC lDA CMP BNE DEC lDY lDA lDY STA DEY lDA lDY STA JMP lDY lDA STA INY lDA
#$00 $C1C5 $C1C5 $AE
#$FF $C1CC $A8 $A7 $A5 $FB $A6 $FC $A7 $FB $A8 $FC $C1FD $FB $FB #$FF $C1EB $FC #$00 ($FB),Y #$02 ($FB),Y ($FB),Y #$03 ($FB),Y $C1D7 #$00 $A9 ($FB),Y $AA
HIGH-Byte der Zeilennummern gleich? verzweige, wenn nein lOW-Byte der Zeilennummern gleich? verzweige, wenn nein V-Register zurückholen Rücksprung Programmzeiger lOW -Byte verringern Übertrag? verzweige, wenn nein Programmzeiger HIGH-Byte verringern Programmzeiger lOW-Byte verringern V-Register in Programmz_ lOW schreiben Tabellenende lOW in Verschiebezeiger LOW schreiben Tabellenende HIGH in Verschiebezeiger HIGH schreiben Tabellenzeiger LOW mit Verschiebezeiger lOW vergleichen Tabellenzeiger HIGH mit Verschiebezeiger HIGH vergleichen verzweig~, wenn Tab_-Zeiger größer Verschiebezeiger LOW-Byte verringern und holen Übertrag? verzweige, wenn nein Verschiebezeiger HIGH-Byte verr i ngern Verschiebezeiger auf LOW-Byte vorher Zeilennummer LOW holen Verschiebezeiger auf LOW-Byte nachher Zeilennummer lOW speichern Verschiebezeiger auf HIGH-Byte vorher Zeilennummer HIGH holen Verschiebezeiger auf HIGH-Byte nachher Zeilennummer HIGH speichern Sprung zum Schleifenanfang Zeiger auf lOW-Byte in Tabelle setzen neue Zeilennummer LOW holen und in Tabelle schreiben Zeiger auf HIGH-Byte in Tabelle setzen neue Zeilennummer HIGH holen
BAS Je - Programmschutz C206 C208 C20A C20C C20E C210 C213 C215 C217 C219 C21B C21D C21F C221 C223 C225 C227 C229 C22A C22C C22E C230 C232 C234 C236 C238 C23A C23C C23D C23F C241
STA INC INC BNE INC JMP STY STX STA LDA CMP LDA SBC BCS LDY LDA CMP INY LDA SBC BCC INC INC BNE INC LDY LDA RTS LDY LDA RTS
($FB),Y $A5 $A5 $C1C2 $A6 $C1C2 $AE $FB $FC $A7 $A5 $A8 $A6 $C23D #$00 $FB ($A7),Y $FC ($A7),Y $C23D $A7 $A7 $C238 $A8 $AE #$00 $AE #$01
und in Tabelle schreiben Tabellenende LOW erhöhen Tabellenende LOW erhöhen verzweige, wenn kein Übertrag Tabellenende HIGH erhöhen Rücksprung V-Register zwischenspeichern Zeilennummer LOW-Byte Zeilennummer HIGH-Byte Tabellenzeiger LOW mit Tabellenende LOW vergleichen Tabellenzeiger HIGH mit Tabellenende HIGH vergleichen verzweige, wenn Ende erreicht Zeiger auf Null setzen Zeilennummer LOW holen mit Tabellenwert vergleichen Zeiger erhöhen Zeilennummmer HIGH holen mit Tabellenwert vergleichen verzweige, wenn nicht in Tabelle Tabellenzeiger LOW erhöhen Tabellenzeiger LOW erhöhen verzweige, wenn kein übertrag Tabellenzeiger HIGH erhöhen V-Register zurückholen Zero-Flag setzen Rücksprung V-Register zurückholen Zero-Flag löschen Rücksprung
Listschutz: C242 C244 C246 C248 C24A
LDY LOA STA LOA STA
#$00 $20 $AB $2E $AC
V-Register auf Null setzen BASIC-Programmende LOW-Byte nach Verschiebezeige r 1 LOW -Byte BASIC-Programmende HIGH-Byte nach Verschiebezeiger 1 HIGH-Byte
29
30 C24C C24E C250 C252 C254 C256 C258 C25A C25C C25E C260 C262 C264 C266 C268 C26A C26C C26E C270 C272 C274 C276 C278 C27A C27B C27D C27F C281 C283 C285 C286 C288 C28A C28C C28D C28F C291 C293 C295 C297
Das große Anti-Cracker-Buch LDA STA LDA STA DEC LDA CMP BNE DEC DEC LDA CMP BNE DEC LDA STA LDA CMP LDA SBC BCS LDY LDA INY ORA BEQ LDY LDA STA INY CPY BNE LDA CLC ADC STA BCC INC LDY TYA
S37 SA9 S38 SAA SAB SAB #$FF SC25E SAC SA9 SA9 #$FF SC268 SAA (SAB), Y (SA9),Y SAB S2B SAC S2C SC254 #$01 (SA9), Y (SA9),Y $C2C2 #$00 (SA9), Y (SAB),Y #S05 SC281 SAB #$05 SAB $C295 SAC #SOO
BASIC-Speicherende LOY-Byte nach Verschiebezeiger 2 LOY-Byte BASIC-Speicherende HIGH-Byte nach Verschiebezeiger 2 HIGH-Byte Verschiebezeiger 1 LOY verringern und holen Übertrag? verzweige, wenn nein Verschiebezeiger HIGH verringern Verschiebezeiger 2 LOY verringern und holen Übertrag? verzweige, wenn nein Verschiebezeiger 2 HIGH verringern Programm-Byte holen und ans Speicherende verschieben Verschiebezeiger 1 LOY mit BASIC-Anfang LOY vergleichen Verschiebezeiger 1 HIGH mit BASIC-Anfang HIGH vergleichen verzweige, wenn Anfang nicht erreicht Programmzeiger auf Linker setzen Linker LOY-Byte holen Programmzeiger erhöhen mit Linker HIGH-Byte verknüpfen verzweige, wenn Programmende erreicht Programmzeiger auf Zeilenanfang Zeilenanfang holen und in Zielbereich verschieben Programmzeiger erhöhen schon fünf Bytes verschoben? verzweige, wenn nein Zielzeiger LOY ho len Carry für Addition löschen plus 5 in Zielzeiger LOY schreiben verzweige, wenn kein Übertrag Zielzeiger HIGH erhöhen Zielzeiger-Index um fünf verringern Akku gleich Null setzen
BASIC - Programmschutz C298 C29A C29B C29D C29F C2A1 C2A3 C2A5 C2A7 C2A8 C2AB C2AC C2AD C2AF C2B1 C2B3 C2B5 C2B6 C2B7 C2B9 C2BB C2BD C2BF C2C2 C2C4 C2CS C2C7 C2C8 C2CA C2CC C2CD C2CE C2DO C2D2 C2D4 C2D6 C2D8
STA INY LDA CPY BNE LDA BEC STA INY JMP TYA CLC ADC STA BCC INC TYA CLC ADC STA BCC INC JMP LDY TYA STA INY CPY BNE TYA CLC ADC STA LDA ADC STA JMP
($AB),Y #$3A #$05 $C298 ($A9),Y $C2AB ($AB),Y $C2A1
$A9 $A9 $C2B5 $AA
$AB $AB $C276 $AC $C276 #$00 ($AB),Y #$03 $C2CS
$AB $20 $AC #$00 $2E $E1AB
und in Zielbereich schreiben Zielzeiger erhöhen ASCII: ":" schon fünf Doppelpunkte eingefügt? verzweige, wenn nein Programm-Byte holen verzweige, wenn Zeilenende erreicht Byte in Zielbereich schreiben Zeiger erhöhen nächstes Programm-Byte verschieben V-Register in Akku schieben Carry für Addition löschen Programmzeiger LOW berechnen und speichern verzweige, wenn kein Übertrag Programmzeiger HIGH erhöhen V-Register in Akku schieben Carry für Addition löschen Zielzeiger LOW berechnen und spei chern ,. verzweige, wenn kein Übertrag Zielzeiger HIGH erhöhen nächste Zeile bearbeiten Zeiger auf Null setzen Null in Akku laden und an Programmende schreiben Zeiger erhöhen schon drei Nullen geschrieben? verzweige, wenn nein V-Register in Akku schieben Carry für Addition löschen Programmende LOW berechnen und in Programmendezeiger LOW speichern Programmende HIGH holen übertrag addieren und in Programmendezeiger HIGH speichern CLR, Programmzeilen binden, Rücksprung
31
32
Das große Allli-Cracker-Buch
Nachfolgend der BASIC-Loader: 100 FORI=1T0731STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATAA9,36,85,01,20,A7,CO,A5,2B,38,E9,01,85,AB,85, 147 301 DATAA9,A5,2C,E9,00,85,AC,85,AA,AO,01,B1,A9,C8,11, 151 302 DATAA9,FO,3D,C8,B1,A9,AA,C8,B1,A9,20, 13,C2,FO,4C, 245 303 DATAAO,01,B1,A9,38,E5,A9,38,E9,05,18,65,AD,BO,3D, 254 304 DATAC9,F5,BO,39,85,AD,AO,00,A9,3A,91,AB,C8,A5,A9, 174 305 DATA18,69,04,85,A9,90,02,E6,AA,B1,A9,FO,38,91,AB, 147 306 DATAC8,4C,54,CO,A8,91,AB,C8,CO,03,DO,F9,98,18,65, 117 307 DATAAB,85,2D,A5,AC,69,00,85,2E,A9,37,85,01,4C,AB, 39 308 DATAE1,AO,00,B1,A9,91,AB,C8,CO,05,DO,F7,B1,A9,FO, 181 309 DATA06,91,AB,C8,4C,84,CO,84,AD,98, 18,65,A9,85,A9, 183 310 DATA90,02,E6,AA,98,18,65,AB,85,AB,90,02,E6,AC,4C, 130 311 DATA18,CO,A5,2B,38,E9,01,85,AB,A5,2C,E9,OO,85,AC, 229 312 DATAAO,00,A9,AO,84,A5,85,A6,AO,03,B1,AB,AA,C8,B1, 95 313 DATAAB,20,93,C1,C8,B1,AB,DO,2A,98,18,65,AB,85,AB, 45 314 DATA90,02,E6,AC,AO,01,B1,AB,C8,11,AB,DO,07,85,A7, 168 315 DATAA9,AO,85,A8,60,A5,02,FO,07,A9,00,85,02,4C,BC, 172 316 DATACO,AO,05,4C,C8,CO,C9,8D,FO,27,C9,89,FO,23,C9, 212 317 DATACB,FO,18,C9,8B,FO,74,C9,A7,FO,17,C9,22,FO,74, 81 318 DATAC9,8F,FO,68,C9,91,FO,64,C8,4C,C8,CO,C8,B1,AB, 30 319 DATAC9,20,FO,F9,C8,B1,AB,C9,20,FO,F9,A9,37,85,01, 46 320 DATA84,AE,98,18,65,AB,85,22,A5,AC,69,OO,85,23,B1, 172 321 DATAAB,C9,30,90,08,C9,3A,BO,04,C8,4C,3A,C1,98,48, 226 322 DATA38,E5,AE,FO,22,20,B5,B7,20,F7,B7,68,A8,A9,36, 38 323 DATA85,01,A6,14,A5,15,20,93,C1,B1,AB,C8,C9,20,FO, 107 324 DATAF9,88,C9,2C,FO,B3,4C,C8,CO,68,A8,A9,36,85,01, 98 325 DATA4C,C8,CO,A9,01,85,02,C8,4C,C8,CO,C8,B1,AB,FO, 181 326 DATA08,C9,22,DO,F7,C8,4C,C8,CO,88,4C,7A,C1,86,A9, 148 327 DATA85,AA,84,AE,AO,00,84,A7,A9,AO,85,A8,C4,A5,A5, 176 328 DATAA8,E5,A6,FO,24,38,B1,A7,E5,A9,AA,C8,B1,A7,E5, 20 329 DATAAA,C8,DO,02,E6,A8,90,E6,C9,OO,DO;06,8A,DO,03, 68 330 DATAA4,AE,60,88,CO,FF,DO,02,C6,A8,88,84,A7,A5,A5, 54 331 DATA85,FB,A5,A6,85,FC,A5,A7,C5,FB,A5,A8,E5,FC,BO, 54
BASIC-Programmschutz 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
DATA1C,C6,FB,A5,FB,C9,FF,DO,02,C6,FC,AO,00,B1,FB, DATAAO,02,91,FB,88,B1,FB,AO,03,91,FB,4C,D7,C1,AO, DATAOO,A5,A9,91,FB,C8,A5,AA,91,FB,E6,A5,E6,A5,DO, DATAB4,E6,A6,4C,C2,C1,84,AE,86,FB,85,FC,A5,A7,C5, DATAA5,A5,A8,E5,A6,BO,1A,AO,00,A5,FB,D1,A7,C8,A5, DATAFC,F1,A7,90,OD,E6,A7,E6,A7,DO,02,E6,A8,A4,AE, DATAA9,00,60,A4,AE,A9,01,60,AO,00,A5,2D,85,AB,A5, DATA2E,85,AC,A5,37,85,A9,A5,38,85,AA,C6,AB,A5,AB, DATAC9,FF,DO,02,C6,AC,C6,A9,A5,A9,C9,FF,DO,02,C6, DATAAA,B1,AB,91,A9,A5,AB,C5,2B,A5,AC,E5,2C,BO,DE, DATAAO,01,B1,A9,C8,11,A9,FO,43,AO,00,B1,A9,91,AB, DATAC8,CO,05,DO,F7,A5,AB,18,69,05,85,AB,90,02,E6, DATAAC,AO,00,98,91,AB,C8,A9,3A,CO,05,DO,F7,B1,A9, DATAFO,06,91,AB,C8,4C,A1,C2,98,18,65,A9,85,A9,90, DATA02,E6,AA,98,18,65,AB,85,AB,90,B9,E6,AC,4C,76, DATAC2,AO,00,98,91,AB,C8,CO,03,DO,F9,98,18,65,AB, DATA85,2D,A5,AC,69,OO,85,2E,4C,AB,E1,A4,A4,A4,A4,
2.1.5
33 37 21 99 84 108 253 172 54 41 112 230 210 177 37 31 74 135
Simuliertes Programmende
Einen perfekten LIST-und RUN-Schutz gleichzeitig erhält man, wenn man dem BASIC-Interpreter "vorgaukelt", daß das im Speicher befindliche Programm zu Ende sei, obwohl es noch gar nicht zu Ende ist. Der BASIC-Interpreter erkennt anhand seiner Markierung (drei hintereinander folgende Nullen), ab welcher Speicheradresse das BASIC-Programm zu Ende ist. Wird nun ein BASIC-Programm gelistet oder gerade abgearbeitet, prüft der BASIC-Interpreter nach jedem Befehl, ob das BASIC-Programm zu Ende ist. Hinter einem BASIC-Programm stehen immer drei Nullen als Endmarkierung. Trifft nun der BASIC-Interpreter beim Listen oder beim Abarbeiten eines BASIC-Programms auf diese Markierung, unterbricht er automatisch den jeweiligen Vorgang.
34
Das große Al7li-Cracker-Buch
Wir möchten Ihnen anhand dieses kleinen BASIC-Programms den Schutz demonstrieren: 10 PRINT "HALLO" 20 REM 30 GOTO 10
Unser LIST -Schutz basiert nun auf dieser Tatsache. Es ist durchaus möglich, diese Markierung mitten in ein BASIC-Programm zu setzen, ohne daß dieses zerstört wird. Und zwar ist das nur am Ende einer BASIC-Zeile möglich, weil dort eine Markierung für das Zeilenende steht. Die Markierung für Zeilenende besteht nur aus einer Null. Hinter dieser Null folgt nun der Linker,der auf die nächste Zeile des BASIC-Programms zeigt. Im Maschinensprachemonitor sieht das BASIC-Programm folgendermaßen aus: 0800: 0808: 0810: 0818:
00 48 08 00
OF 41 14 89
08 4C 00 20
OA 4C 8F 31
00 4F 00 30
99 22 1E 00
20 00 08 00
22
"
15
HALLO" ••
1E 00
.. 10 ••••
Wenn man sich nun den Wert dieser bei den Speicherzellen, die den Linker darstellen, auf ein Blatt Papier aufschreibt und sie anschließend mit dem Wert Null überschreibt, haben wir mit der Zeilenendmarkierung drei Nullen mitten im BASIC-Programm stehen. Wichtig ist auch, daß man sich die Adresse des geänderten Linkers aufschreibt, um später die richtigen Werte per POKE wieder hineinzuschreiben. Der Anfang der nächsten Zeile ist daran zu erkennen, daß hinter der Null vier weitere Bytes folgen. Die ersten beiden stellen, wie schon erwähnt, den Linker dar und die anderen beiden Bytes die Zeilennummer.
BAS Je - Programmschutz
35
Hier die BASIC-Zeile ohne LINKER: 0800: DOOF OBOB: 4B 41 0810: 00 14 0818: 00 89
08 4C 00 20
DA 4C BF 31
00 4F 00 30
99 22 1E 00
20 22 00 00 OB 1E 00 00
" HALLO" .. .. 10 ....
Wenn man nun das veränderte BASIC-Programm listen möchte, sieht man es nur bis zu der Adresse, an der der BASIC-Interpreter auf diese Nullen trifft. Dort wird dann der LIST-Vorgang automatisch unterbrochen. Genauso verhält es sich, wenn man versucht, das Programm zu starten . Es wird nur bis zu der Stelle laufen, an der die Nullen beginnen, und nicht weiter. Bei unserem BASIC-Programm sieht das folgendermaßen aus: LIST
10 PRINT "HALLO" READY. RUN HALLO READY.
Das Programm läßt sich auch in diesem Zustand auf Diskette speichern. Wenn man es wieder in den Speicher lädt, zeigt es die gleichen Symptome wie vorher. Um nun diese wieder zu entfernen (falls man sein Programm wieder starten möchte), muß man die Originalwerte, die wir vorher gelöscht haben, wieder ins BASIC-Programm hineinschreiben. Dazu brauchen wir einfach nur zwei POKEs im Direktmodus einzugeben. Poken Sie nur die Adresse des Linkers und die Adresse des Linkers+l mit den entsprechenden Werten in den Speicher. Nun läßt sich das Programm wieder listen und auch starten. Noch ein wichtiger Hinweis: Im geschützten Zustand des BASIC-Programms darf man auf keinen Fall Änderungen am BASIC-Programm vornehmen, da sonst die Adressen verschoben
36
Das große Anti-Cracker-Buch
werden und die Adresse des Linkers nicht mehr stimmt. Beim Zurückstellen könnten Sie dann das BASIC-Programm zerstören .
2.1.6
Der Linke(r)- Trick
Man erhält einen hervorragenden LIST -Schutz, wenn man den ersten LINKER des BASIC-Programms in eine Endlosschleife zeigen läßt. Man muß nur den LINKER wieder auf $0801 zeigen lassen . Das kleine BASIC-Programm vorher: 10 PRINT "HALLO" 20 REM 30 GOTO 10 0800: 0808: 0810: 0818:
00 48 08 00
OF 41 14 89
08 4C 00 20
OA 4C 8F 31
00 4F 00 30
99 22 1E 00
20 00 08 00
22 15 1E 00
" HALLO" .. .. 10 ... •
Jetzt braucht man nur in die Speicherzellen $080 I und $0802 die Werte $01 und $08 hineinzuschreiben . Damit haben Sie den LINKER in eine Endlosschleife zeigen lassen . Wenn man nun LIST eingibt, sieht dieses folgendermaßen aus: 10 10 10 10 10 10 10
PRINT PRINT PRINT PRINT PRINT PRINT PRINT
"HALLO" "HALLO" "HALLO" "HALLO" "HALLO" "HALLO" "HALLO"
BASIC-Programmschutz
37
Sie werden feststellen, daß der BASIC-Interpreter immer nur ein und dieselbe Zeile listet und nicht zu den anderen kommt. Bei diesem Schutz kann das Programm normal mit RUN gestartet werden. Es ist auch nicht möglich, die dahinterliegenden Zeilen zu löschen. Wenn man aber die erste Zeile löscht, normalisiert sich das Ganze wieder von selbst. Von daher ist es besser, mit dieser Methode Programmteile zu überbrücken, das heißt, den LINKER so zu stellen, daß er ein paar BASIC-Zeilen überspringt und dann erst weiter listet. In unserem Programmbeispiel könnte das so aussehen: 10 PRINT "HALLO" 30 GOTO 10
Hier haben wir nur den LINKER auf die übernächste Zeile verzweigen lassen. Die REM-Zeile ist aber dennoch vorhanden, sie wird nur nicht gelistet. Beim RUN würde diese Zeile, falls dort ein anderer Befehl stehen würde, trotzdem ausgeführt werden. Diese Methode ist vollkommen unauffällig. Das hat den Vorteil, daß man wichtige BASIC-Zeilen, in denen zum Beispiel ein Password abgefragt oder überprüft wird, einfach überspringt und damit unsichtbar macht. Noch ein wichtiger Hinweis: Die Zeilen, die man überspringt, also unsichtbar macht, dürfen keinesfalls von einem GOTO, GOSUB oder THEN-Befehl angesprungen werden, da der BASIC-Interpreter die Zeile nicht finden kann und man die Fehlermeldung "UNDEF'D STATEMENT ERROR" erhält. Das BASIC-Programm würde dann nicht einwandfrei laufen.
38 2.1.7
Das große Allli-Cracker-Buch
Der Sys-Bluff
Hier möchten wir Ihnen noch eine sehr wirkungsvolle LISTSchutz-Methode demonstrieren: es läßt sich ein hervorragender LIST -Schutz erreichen, indem man den BASIC-Start verschiebt. Aber dazu später. Der LIST -Schutz soll wie folgt aussehen: 10 sys 2064
Beim Listen erscheint nur eine SYS-Zeile, so daß der Fremdbenutzer denken muß, es handle sich hier um ein Maschinenprogramm. Wir haben Ihnen hier ein Schutzprogramm erstellt, das den Einbau der SYS-Zeile selbst vornimmt. Das Maschinenprogramm wird mit SYS 49152 ($COOO) gestartet. Das . zu schützende BASIC-Programm muß sich auf Diskette befinden. Es muß sich um ein reines BASIC-Programm handel~, das keine Maschinenprogramm- Unterroutinen besitzt, weil die Gefahr besteht, daß diese nicht mehr aufgerufen werden können. Das Schutzprogramm fragt nach dem Start nach dem Filenamen des zu schützenden BASIC-Programms. Anschließend wird dieses geladen, mit dem Schutz versehen und direkt wieder auf Diskette gespeichert. Der alte Filename wird beibehalten. Es wird nur ein Zusatz dahintergehängt, damit man erkennt, daß es sich um das geschützte Programm handelt. Deshalb sollte der Filename des zu schützenden BASIC-Programms nicht länger als 14 Zeichen sein.
Nun zur Funktionsweise: Nach dem Eingeben des Filenamens kopiert das Schutzprogramm die SYS-Zeile und das dazugehörige Maschinenprogramm in den BASIC-Speicher ab der Adresse $0801 (2049). Anschließend wird das zu schützende BASIC-Programm direkt dahinter geladen. Beide Teile, also die SYS-Zeile und das BASIC-Programm, werden wieder auf Diskette gespeichert. Beim Listen sieht man nur noch die SYS-Zeile. Nach einem RUN führt die SYS-Zeile das dahinterliegende Maschinenpro-
BASIC - Programm schutz
39
gramm aus, das die BASIC-Zeiger auf das dahinterliegende BASIC-Programm setzt. Vor dem Programmstart werden noch die BASIC-Linker neu gebunden, um diese an den neuen Speicherbereich anzupassen. Nun das versprochene Schutzprogramm in Maschinencode und BASIC- Loader:
COOO LDX C002 LDA COOS STA C008 INX C009 CPX COOB BNE COOD LDA COOF JSR C012 LDX C014 LDA C017 JSR C01A INX C01B CPX C01D BNE C01F LDX C021 JSR C024 STA C027 INX C028 CMP C02A BNE C02C DEX C02D STX C02F LDX C031 LDY C033 JSR C036 . LDA C038 LDX C03A LDY C03C JSR C03F LDA C041 LDX
#$00 Zähler auf Null setzen SC080,X Erstes Byte der BASIC'Zeile holen S0801,X und in BASIC'Speicher schreiben Zähler erhöhen #$28 Falls ganze BASIC'Zeile geschrieben, $C002 dann weiter #$93 Bildschirm SFFD2 löschen #$00 PROGRAMMNAME $COA8,X auf den $FFD2 Bildschirm bringen #$OE Falls alle Zeichen, $C014 dann weiter #$00 Zähler auf Null SFFCF Zeichen holen $COB8,X und speichern Zähler erhöhen #$OD Falls RETURN gedrückt weiter $C021 ansonsten nächstes Zeichen holen Byte-Anzahl des Filenamen S02 speichern #$08 Geräteadresse #$00 Sekundäradresse File-Parameter setzen $FFBA $02 Byte-Anzahl des Filenamen holen Anfangsadresse des #$B8 Filenamen setzen #$CO $FFBD Filenamenparameter setzen #$00 Zeichen für LOAD setzen #$29 LOW-Byte Anfangsadresse
40 C043 C04S C048 C04A C04C C04F COS1 COS4 COSS COS6 COS8 COSA COSC COSF C061 C063 C06S C068 C06A C06C C06E C070 C072 C074 C076 C079
Das große Allli-Cracker-Buch LDY JSR LDX LDA STA LDA STA INX INX STX LDX LDY JSR LDA LDX LDY JSR LDX LDY STX STY LDA LDX LDY JSR RTS
#$08 $FFDS $02 #$2F $COB8,X #$S3 $COB9,X
$02 #$08 #$01 $FFBA $02 #$B8 #$CO $FFBD #$01 #$08 $FB $FC #$FB $AE $AF $FFD8
HIGH-BYTE Anfangsadresse LOAD-Befehl Byte-Anzahl des Filenamen holen Code für "/" laden und hinter Filenamen speichern Code für "S" laden und hinter Filenamen speichern Byte-Anzahl des Filenamen um zwei erhöhen und speichern Geräteadresse Sekundäradresse File-Parameter setzen Byte-Anzahl des Filenamens laden Anfangsadresse des Filenamens laden Filenamenparameter setzen Anfangsadresse setzen und speichern Zeiger auf Anfangsadresse setzen Endadresse holen SAVE-Befehl Rücksprung
C080 OC 08 OA 00 9E 20 32 30 BASIC-Zeile C088 36 34 00 00 00 00 00 18
Maschinenprogramm hinter der SYS-Zeile C08F C090 C092 C094 C096 C098 C09A
CLC LDA ADC STA LDA ADC STA
#$28 $2B $2B #$00 $2C $2C
Carry-Flag löschen Zeichen-Anzahl mit BASIC-Start-LOW-Byte addieren und speichern Null laden, falls Übertrag: 1 laden BASIC-Start-HIGH-Byte addieren und speichern
BAS Je - Programmschutz C09C C09F COA2 COA5 COA6 COA7
JSR SA659 JSR $A533 JMP $A7AE BRK BRK BRK
41
CHRGET-Zeiger auf Programmstart und CLR BASIC-LINKER anpassen in die Interpreterschleife springen
COAB 50 52 4F 47 52 41 40 40 PROGRAMM COBO 4E 41 40 45 3A 20 00 00 NAME: __
Nachfolgend der dazugehörige BASIC-Loader: 100 FORI=1T0184STEP15:FORJ=OT014:REAOA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-4B:IFA>9THENA=A-7 110 B=ASC(B$)-4B:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AN0255:POKE49151+I+J,A:NEXT:REAOA: I FC=ATHENC=O:NEXT:ENO 130 PRINT"FEHLER IN ZEILE:"iPEEK(63)+PEEK(64)*256:STOP 300 OATA A2,00,BO,80,CO,90,01,08,E8,EO,28,00,F5,A9,93, 54 301 OATA 20,02,FF,A2,00,BO,AB,CO,20,02,FF,EB,EO,OE,00, 79 302 OATA F5,A2,00,20,CF,FF,90,BB,CO,EB,C9,00,00,F5,CA, 231 303 OATA 86,02,A2,OB,AO,00,20,BA,FF,A5,02,A2,BB,AO,CO, 12 304 OATA 20,BO,FF,A9,00,A2,29,AO,OB,20,05,FF,A6,02,A9, 61 305 OATA 2F,90,BB,CO,A9,53,90,B9,CO,EB,EB,86,02,A2,08, 88 306 OATA AO,01,20,BA,FF,A5,02,A2,B8,AO,CO,20,BO,FF,A2, 89 307 OATA 01,AO,08,86,FB,84,FC,A9,FB,A6,AE,A4,AF,20,08, 237 30B OATA FF,60,00,00,00,00,00,00,OC,08,OA,00,9E,20,32, 109 309 OATA 30,36,34,00,00,00,00,00,18,A9,28,65,2B,85,2B, 195 310 OATA A9,00,65,2C,85,2C,20,59,A6,20,33,A5,4C,AE,A7, 163 311 OATA 00,00,00,50,52,4F,47,52,41,40,40,4E,41,40,45, 134 312 OATA 3A,20,00,00,48,41,4C,4C,4F,2F,53,00,00,00,00, 76
Es ist auch vorteilhaft, vor dem Schützen seines BASIC-Programms in der ersten Zeile mit POKE 808,225 die RUN-STOPRESTORE- Taste außer Betrieb zu setzen, um ein späteres Listen zu vermeiden. Zusätzlich kann im BASIC-Programm selbst noch ein zusätzlicher Listschutz eingebaut werden, um sicher zu gehen, daß wirklich niemand das Programm listen kann.
42
Das große Allli-Cracker-Buch
2.2
Änderungsschutz
2.2.1
Abfrage des BASIC-Endes
Eine einfache, aber wirkungsvolle Methode, das Ändern eines BASIC-Programms zu verhindern, ist folgende: Man kann innerhalb eines BASIC-Programms das BASIC-Programmende mittels PEEK ermitteln und es nachher im BASIC-Programm abfragen. Das Ende eines BASIC-Programms ermittelt man folgendermaßen: PRINT PEEK(45)+PEEK(46)*Z56
Da das BASIC-Programmende, das in den Speicherzellen $2D (45) und $2E (46) in LOW- und HIGH-Byte Darstellung verzeichnet ist, muß man, wie oben gezeigt, in eine ganze Zahl umrechnen, damit man es später im BASIC-Programm abfragen kann. Bevor man sich nun das BASIC-Programmende holt und berechnet, muß man die Programmzeile, in der die Abfrage stattfinden soll, vorher in das zu schützende BASIC-Programm einbauen, we il sich beim Einfügen einer neuen BASIC-Zeile das BASICProgrammende wieder verschieben würde. Dies hätte zur Folge, daß die berechnete Prüfsumme nicht mehr stimmen würde. Die Abfrage könnte so aussehen: 10 A=PEEK(45)+PEEK(46)*Z56 20 IF A<>OOOOO THEN POKE 776,1
Wenn man diese Zeilen in das zu schützende BASIC-Programm einbaut und dann im Direktmodus das BASIC-Programmende berechnet, erhält man den korrekten Wert, den man in Zeile 20 eintragen kann. Man darf auf keinen Fall beim Ändern der Prüfsumme eine Zahl löschen oder hinzufügen, da sich die Prüfsumme wieder verändern würde.
BAS IC- Programmschutz
43
Wenn die Prüfsumme zum Beispiel 2543 betragen würde, müßte man diese in Zeile 20 folgendermaßen eintragen: 20 IF A<>02543 THEN POKE 776,1
Es ist vorteilhafter, wenn man diese Abfrage irgendwo mitten im BASIC-Programm einbaut, um sie besser zu verbergen. Am besten versieht man das BASIC-Programm anschließend noch mit einem guten LIST -Schutz. Falls nun eine Änderung an diesem BASIC-Programm erfolgt, das heißt, eine Zeile bzw. ein Zeichen eingefügt oder gelöscht wird, verschiebt sich das BASIC-Programmende automatisch. Dadurch würde der Wert in Zeile 20 nicht mehr mit dem BASIC-Programmende übereinstimmen, was zur Folge hätte, daß der dahinterliegende POKE-Befehl ausgeführt würde. Dieser POKE-Befehl würde den Vektor für "BASICBefehlsadresse holen" umändern, so daß dann kein BASICBefehl ausgeführt werden kann. Das BASIC-Programm könnte also weder gestartet, gelistet noch per NEW gelöscht werden. Dies hätte zur Folge, daß man einen Systemreset ausführen müßte.
2.2.2
Änderungsschutz durch übergroße Zeilennummer
Wir haben hier für Sie ein kleines Programm vorbereitet, das einen sehr guten Änderungsschutz in Ihr BASIC-Programm einbaut. Das Programm funktioniert folgendermaßen: Das zu schützende Programm darf keine Zeilennummern I und 2 enthalten, weil diese vom Schutz-Programm generiert werden. Das Schutzprogramm liegt sowohl im Maschinencode als auch als BASIC-Loader vor. Nach . dem Start steht das Maschinenprogramm ab der Adresse $COOO (49152) zur Verfügung. Es wird vom BASIC-Loader direkt mit SYS 49152 gestartet. Nach dem Start fragt das Programm nach dem Filenamen des zu schützenden BASIC-Programms. Nach der Eingabe des Filenamens wird
44
Das große Allli-Cracker-Buch
das BASIC-Programm von Diskette geladen und in geschützter Form wieder auf Diskette gespeichert. Das mit dem Änderungsschutz versehene Programm ist um nur wenige Bytes verlängert.
Nun zur Funktionsweise: Das Maschinenprogramm generiert zwei BASIC-Zeilen mit den Zeilennummern I und 2. Die Zeile I ist eine REM-Zeile, hinter der noch zwei kurze Maschinenprogramme stehen, deren Funktion im folgenden noch erläutert wird. In der zweiten Zeile steht ein SYS-Befehl, der eines der beiden Maschinenprogramme in Zeile 0 startet. Sind diese beiden Zeilen nun erzeugt, wird die Zeilennummer 0 durch eine höhere, eigentlich unerlaubte Zeilennummer (größer als 64000) ersetzt. Diese Zeile kann daher auch nicht gelöscht werden. Da alle nun folgenden Zeilen kleiner sind als die erste, können diese vom Computer nicht mehr erkannt werden. Ein Sprung in eine solche Zeile führt zu der Fehlermeldung: "UNDEF'D STATEMENT ERROR". Es kann daher keine Zeile gelöscht werden, da diese für den Interpreter nicht mehr vorhanden ist. Der einzige Nachteil ist, daß es nicht nur ein perfekter Löschschutz, sondern auch ein RUN-Schutz ist, weil Sprungziele innerhalb des Programms nicht mehr gefunden werden können. Wird das BASIC-Programm nun gestartet, trifft der Interpreter zuerst auf den SYS-Befehl in Zeile 2. Es folgt ein Sprung in das Maschinenprogramm in der REM-Zeile. Dort wird die Zeilennummer wieder auf I gesetzt, und der Vektor auf EINGABE EINER ZEILE wird auf die zweite Maschinenroutine gesetzt. Nun kann das BASIC -Programm ohne Fehler ausgeführt werden. Wird der Programmablauf zum Beispiel durch die STOP-Taste, Fehlermeldungen, Programmende und so weiter unterbrochen, wird das zweite Maschinenprogramm über den geänderten Vektor angesprungen. Dort wird die Zeilennummer wieder erhöht,
BASIC -Programmschutz .
45
der Vektor für "Eingabe einer Zeile" wieder auf den normalen Wert gebracht und die Routine für "Eingabe einer Zeile" angesprungen. Das Programm liegt nun wieder in der geschützten Form vor.
Hier nun das Schutz-Programm: COOO C002 CO OS C008 C009 COOB COOD C010 C012 C01S C018 C019 C01B C01D C01F C022 C02S C026 C028 C02A C02B C02D C02F C031 C034 C036 C038 C03A C030 C03F C041 C043
LDX LDA STA INX CPX BNE JSR LDX LDA JSR INX CPX BNE LDX JSR STA INX CMP BNE OEX STX LDX LDY JSR LDA LDX LDY JSR LDA LDX LDY JSR
#$00 Zähler auf Null stellen $C080,X BASIC-Zeile ins S0801,X BASIC-RAM kopieren Zähler erhöhen #$3B Falls alle Zeichen, dann weiter ansonsten nächstes Zeichen holen SC002 $ES44 Bildschirm löschen #$00 Zähler auf Null $COCO,X PROGRAMMNAME auf SFFD2 Bildschirm bringen Zähler erhöhen Falls alle Zej chen, dann weiter #$OE $C012 ansonsten nächstes Zeichen holen #$00 Zähler auf Null stellen $FFCF Byte von Tastatur holen $CODO,X und speichern Zähler erhöhen Falls noch kein RETURN, dann nächstes #$OD $C01F Zeichen holen Zähler erniedrigen und speichern $02 Geräteadresse laden #$08 Sekundäradresse laden #$00 File-Parameter setzen $FFBA Byte-Anzahl des Filennamens laden $02 Position des #$00 Filenamens laden #$CO $FFBD Filenamenparameter setzen Flag für LOAD setzen #$00 #$3C Startadresse festlegen #$08 LOAD-Befehl SFF05
46 C046 C048 C04A C04E C050 C052 C055 C056 C058 C05B C05C C05E C060 C062 C065 C067 C069 C06B C06E C070 C072 C074 C076 C078 C07A C07C C07F
Das große Anti-Cracker-Buch LOA STA LOA LOX LOA STA INX LOA STA INX STX LOX LOY JSR LOA LOX LOY JSR LOX LOY STX STY LOA LOX LOY JSR JMP
LOII- und HIGH-Byte für BASIC-_,C04C STA $2E Ende setzen Byte-Anzahl des Filenamens laden Code für li/li laden und hinter Filenamen speichern Zähler erhöhen #$41 Code für "A" laden $COOO,X und hinter Filenamen speichern Zähler wieder erhöhen $02 und speichern #$08 Geräteadresse laden #$01 Sekundäradresse laden $FFBA File-Parameter setzen $02 Byte-Anzahl des Filenamens laden #$00 Position des Filenamens #$CO laden Filenamen-Parameter setzen $FFBO #$01 Startadresse #$08 laden $FB und $FC speichern #$FB Zeiger auf Startadresse laden $20 Endadresse $2E laden $FF08 SAVE-Befehl $A533 BASIC-Zeilen neu binden und Rücksprung
$AE $20 $AF $02 #$2F $COOO,X
C080 33 A5 FF FF 8F 20 22 A2
Erste BASIC-Zeile
Maschinenprogramm hinter REM-Zeile: C087 C089 C08C C080 C090 C092
LOX STX OEX STX LOA STA
#$01 $0803 $0804 #$1C $0302
LOII-Byte der Zeilennummer setzen Byte auf Null setzen HIGH-Byte der Zeilennummer speichern Vektor für Eingabe einer Zeile auf
BASIC -Programmschutz C095 C097 C09A C09B C090 COAO COA3 COA5 COA8 COAA COAO
LOA STA RTS LOX STX STX LOA STA LOA STA JMP
#$08 $0303 #$FF $0803 $0804 #$83 $0302 #$A4 $0303 $A483
47
$081C stellen Rücksprung Zeilennummer der REM-Zeile auf 65535 setzen Vektor auf Originalwert stellen Befehl ausführen und Rücksprung
COBO 00 3C 08 02 00 9E 32 30 COB8 35 36 00 00 00 00 00 00
Zweite BASIC-Zeile
COCO 50 52 4F 47 52 41 40 40 PROGRAMM COC8 4E 41 40 45 3A 20 00 00 NAME:
Nachfolgend nun der entspechende BASIC-Loader: 100 FORI=1T0208STEP15:FORJ=OT014:REAOA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AN0255:POKE49151+I+J,A:NEXT:REAOA: IFC=ATHENC=O:NEXT:SYS 49152 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 OATA A2,00,BO,80,CO,90,01,08,E8,EO,3B,00,F5,20,44, 113 301 OATA E5,A2,00,BO,CO,CO,20,02,FF,E8,EO,OE,00,F5,A2, 242 302 OATA 00,20,CF,FF,90,00,CO,E8,C9,OD,00,F5,CA,86,02, 240 303 OATA A2,08,AO,00,20,BA,FF,A5,02,A2,DO,AO,CO,20,BD, 121 304 OATA FF,A9,00,A2,3C,AO,08,20,05,FF,A5,AE,85,2D,A5, 204 305 OATA AF,85,2E,A6,02,A9,2F,9D,DO,CO,E8,A9,41,9D,00, 78 306 OATA CO,E8,86,02,A2,08,AO,01,20,BA,FF,A5,02,A2,00, 109 307 OATA AO,CO,20,BO,FF,A2,01,AO,08,86,FB,84,FC,A9,FB, 44 308 OATA A6,20,A4,2E,20,08,FF,4C,33,A5,FF,FF,8F,20,22, 143 309 OATA A2,01,8E,03,08,CA,8E,04,08,A9, 1C,8D,02,03,A9, 160 310 OATA 08,80,03,03,60,A2,FF,8E,03,08,8E,04,08,A9,83, 251
48
Das große Allti-Cracker-Buch
311 DATA 8D,02,03,A9,A4,8D,03,03,4C,83,A4,OO,3C,08,02, 43 312 DATA OO,9E,32,30,35,36,OO,OO,OO,OO,OO,OO,50,52,4F, 92 313 DATA 47,52,41,4D,4D,4E,41,4D,45,3A,20,OO,OO,OO,OO, 239
2.3
RESET- und RUN-STOP/RESTORE-Schutz
Gegen einen RESET gibt es nur eine einzige Schutzmöglichkeit, und zwar mit CBM80. CBM80 ist nichts weiter als ein Erkennungsmerkmal, das vom Betriebssystem benutzt wird, um festzustellen , ob sich ein Modul im Modul-Schacht befindet. Die Byte-Folge CBM80 darf nur im Bereich $8004 (32772) stehen, da es an anderer Stelle nicht anerkannt wird. Von $8000 bis $8003 stehen noch vier Bytes, die in LOW- und HIGH-Byte Darstellung auf die Adresse zeigen, wo der Einsprung, nach Drücken von RESTORE oder nach der Ausführung von RESET stattfinden soll. Die Bytes in $8000 und $800 I sind für die RESTORE- Taste bestimmt. Nach Drücken von RESTORE springt das Betriebssystem über diese Vektoren in den gewünschten Bereich. Die Vektoren in $8002 und $8003 sind für den RE SET bestimmt. Falls ein RESET ausgeführt wird, springt das Betriebssystem eben über die Vektoren in diesen bestimmten Bereich. Da man aber ein Modul simulieren kann, indem man diese Bytes in den Bereich von $8000 bis $8008 hineinschreibt, kann dadurch ein sehr guter RESET - und RUN-STOP/RESTORE-Schutz erzielt werden. Wir haben hier für Sie ein Programm vorbereitet, das wieder in ein BASIC-Programm springt und es erneut startet, falls RESET oder RESTORE ausgeführt wurden . Es eignet sich gut als Schutz-Methode, da man nicht ohne weiteres aus dem BASICProgramm herauskommt.
49
BASIC-Programmschutz
Hier nun das Programm im Assemblerlisting und der BASICLoader: 8000: 09 80 09 80 C3 C2 co 38 8008: 30 • • •. 8009 800A 8000 80'0 8012 8015 8018
CL! JSR JSR LDA STA JSR JMP
$FF5B $FOA3 #$EF $0328 $A659 $A7AE
CBM80 UND SPRUNGVEKTOREN NACH $8009
Interrupt freigeben Video-RESET ausführen Interrupt vorbereiten STOP-Taste sperren CHRGOT-Zeiger auf Programmstart und CLR in die Interpreter-Schleife springen
Dazu der BASIC- Loader: 5 POKE 808,225:POKE 56,'28:CLR 10 FOR 1=0 TO 26: REAO A: POKE 32768+I,A: S=S+A: NEXT I 20 IF S<>3182 THEN PRINT "FEHLER IN DATA-ZEILEN": END 100 DATA 9,128,9,128,'95,194,205,56,48,88,32,9',255,32,'63,253 "0 DATA 169,239,'41,40,3,32,89,'66,76,'74,'67
Der erste POKE-Befehl in Zeile 5 sperrt die RUN/STOP-Taste, weil vor dem Drücken von RUN/STOP-RESTORE oder RESET noch kein Schutz vorhanden ist und man das BASIC-Programm ohne weiteres unterbrechen kann. Der nachfolgende Befehl setzt das Variablenende in $8000 ab, weil bei längeren Programmen das CBM80 und die Maschinenroutine von den Variablen überschrieben werden können. Dadurch hat man aber erheblich weniger Speicherplatz, weil normalerweise der BASIC-Speicherplatz bis $AOOO reicht_ Das Programm funktioniert folgendermaßen: von $8000 bis $8008 sind das CBM80 und die Zeiger, die auf das kleine Programm zeigen, abgelegt_ Falls ein RESET oder ein RESTORE ausgeführt wird, springt das Betriebssystem über die Vektoren in das kleine Assemblerprogramm. Dort passiert dann folgendes:
50
Das große Allti-Cracker-Buch
In der ersten Zeile wird der Interrupt, der bei einem RE SET maskiert wird, wieder freigegeben. Anschließend wird ein Video-RESET ausgeführt. Das heißt, der VIDEO-Controller wird wieder in den Normalzustand versetzt. Diese Funktion ist nicht unbedingt notwendig und kann auch weggelassen werden. Wir haben sie hier nur eingebaut, weil unter anderem auch der Bildschirm gelöscht wird. In der nächsten Zeile wird die Betriebssystem-Routine 'INTERRUPT VORBEREITEN' aufgerufen. Das ist nötig, weil sonst nach dem Drücken des RESET -Knopfes keine Eingaben von der Tastatur mehr möglich wären und daher auch keine INPUTBefehle im BASIC-Programm funktionieren würden. In den nächsten beiden Zeilen wird die STOP- Taste gesperrt, da man sonst das BASIC-Programm einfach mit der STOP-Taste wieder unterbrechen könnte. Das geschieht, indem das LOWByte des STOP- Vektors um einen Befehl im Betriebssystem heruntergesetzt wird, damit diese Routine die STOP-Taste nicht mehr abfragen kann. Anschließend wird der CHRGET -Zeiger auf das BASIC-Programm gestellt und ein CLR ausgeführt. Dieses muß deshalb geschehen, weil in der letzten Zeile in die Interpreterschleife gesprungen wird . Das heißt, es wird ein RUN ausgeführt. Wenn Sie nun ein BASIC-Programm mit diesem Programm und einem Autostart ausrüsten, wird man nicht in der Lage sein, aus dem Programm wieder herauszukommen, es sei denn, man schaltet den Computer aus oder arbeitet mit einem anderen Betriebssystem, in dem man durch gleichzeitiges Drücken einer Tastenkombination und der RESET - Taste diese Markierung (CBM80) übergehen kann.
2.4
Warum eigentlich Compilieren?
Ein Compiler ist dazu gedacht, BASIC-Programme schneller zu machen. Da er aber beim Compi lieren den BASIC-Code in einen
BASIC - Programmschutz
51
fast unleserlichen Code umwandelt, eignet sich ein Compiler sehr gut zum Schützen von Programmen. Es ist ratsam, die LADE-Programme zu compilieren, die einen Hauptteil nachladen, der nachher zum Beispiel noch ein Password abfragt, damit Unbefugte nicht einfach an das Password herankommen oder die Password-Abfrage herausbauen können. Natürlich sollte man Passwords nicht einfach als ASCII-Codes in seinem Programm ablegen, weil man sonst trotz des Compilers das Password hinterher mit einem Maschinensprachemonitor sehen könnte. Dasselbe gilt auch für Disketten-Befehle, die an die Floppy gesandt werden . Passwords oder Floppy-Befehle sollte man mit Hilfe des CHR$Befehls in sein BASIC-Programm einbauen. Dieses hat den Vorteil, daß die Befehle auch in diesem Compiler-Code übersetzt werden und nur mit dem Entschlüsselungsalgorithmus des Compilers erkannt werden können.
2.4.1
Warum der Compilercode keinen absoluten Schutz bildet!
Das, was ein Compiler aus einem BASIC-Programm macht, ist zwar extrem schwer zu entschlüsseln, aber findige Knacker schaffn es, auch solchen Code zu lesen. Das liegt daran, daß der Compilercode ursprünglich gar nicht speziell dafür gedacht war, Programme zu schützen, sondern nur dafür, BASIC-Programme zu beschleunigen. Es gibt prinzipiell zwei Arten von BASIC-Compilern. Die erste Art erzeugt einen sogenannten P-Code, die zweite direkt einem Maschinencode. P-Code ist ein Code, der erst während des Programmablaufs noch von einem Interpreter übersetzt werden muß, ähnlich wie ein BASIC-Programm vom BASIC-Interpreter ausgeführt wird. Im Gegensatz zu BASIC-Befehlen sind die Operationen, die ein P-Code-Befehl bewirkt, weitaus simpler, also der Maschinen-
52
Das g rolle Allli-Cracker-Buch
sprache näher, als der entsprechende BASIC-Befehl. Daher sind solche P-Code-Programme auch schneller als BASIC-Programme. Ein Beispiel: POKE 53280,0
wird vom BASIC-Interpreter folgendermaßen ausgeführt: Zuerst wird der Befehl POKE, der als Token abgelegt ist, erkannt. Der Interpreter weiß nun, daß zwei Zahlen folgen müssen, die im Programmspeicher als ASCII-Codes stehen und durch ein Komma getrennt werden . Die beiden Zahlen werden geholt, vom ASCII-Format ins Fließkommaformat umgewandelt und schließlich vom Fließkommaformat ins Integer-Format. Dabei muß außerdem geprüft werden, ob die Syntax des Befehls richtig ist und ob die Parameter zulässige Werte besitzen. Dann erst kann die eigentliche Operation ausgeführt werden. Wenn ein solcher Befehl in den P-Code übersetzt wurde, weiß der PCode-Interpreter, daß die Syntax des entsprechenden P-CodeBefehls richtig ist. Außerdem werden die Zahlen gleich im richtigen Format hinter dem Befehl abgelegt. Daher wird der Befehl wesentlich schneller ausgeführt als der ursprüngliche BASIC-Befehl. Er ist allerdings nicht so schnell wie die direkte Sequenz in Maschinensprache: LOA #$00 STA $0020
Weiterhin ersetzt ein Compiler Variable und Zeilen nummern durch ihre Speicheradressen . Maschinencode-Compiler un terscheiden sich von P-Code-Compilern meist nur dadurch, daß die Unterprogrammaufrufe, die ein spezieller P-Code bewirken würde, bei ihnen durch direkte Unterprogrammaufrufe mit dem Befehl "JSR" ersetzt werden . Das bringt natürlich nur noch eine leichte Geschwindigkeitssteigerung.
BAS Je - Programmschutz
53
Es gibt auch Compiler. die einen verringerten BASIC-Befehlssatz in nahezu "echte" Maschinensprache übersetzen können. Solche Compiler eignen sich aber meistens nicht zum Schützen, da ihr Code einfach zu verstehen ist. Einige "Freaks" haben sich allerdings die Arbeit gemacht, den PCode einiger BASIC-Compiler aufzuschlüsseln. Es existieren sogar für bestimmte Compiler sogenannte RE-Compiler, die den P-Code wieder in BASIC übersetzen. Sollten Sie Ihr Programm also wirksam mit einem Compiler schützen wollen, so erkundigen Sie sich vorher, ob ein solcher RE-Compiler für Ihren speziellen Compiler auf dem Markt erhältlich ist.
54
Das große Allti-Cracker-Buch
Programmschutz für Profis
~. 3.1
55
Programmschutz fü r Profis Autostart
Sicher wird Ihnen schon einmal aufgefallen sein, daß fast alle professionellen Programme, die es für den C64 zu kaufen gibt, mit einem Autostart versehen sind. Warum sich die Softwarefirmen soviel Mühe geben, einen neuen und komplizierten Autostart zu konzipieren und zu installieren, ist einfach zu beantworten: Das selbständige Einladen des Programms stellt einen Kopierschutz dar, der das Programm vor unerlaubtem Kopieren schützen soll. 3.1.1
Warum eigentlich Autostart?
Bevor man sich über die Technik des Autostarts Gedanken macht, sollte die Frage nach dem Sinn des selbständigen Programmstarts geklärt werden. Soll das Programm mit einem Autostart versehen werden, damit sich der Benutzer nach dem Laden das Eintippen der drei Buchstaben "RUN" ersparen kann, oder soll damit noch ein anderer Zweck erfüllt werden? Ist der erste Grund ausschlaggebend, wird es besser sein, sich auf die einfacheren Methoden zu beschränken, da diese mit wenig Aufwand zu realisieren sind. Außerdem ist der Programmcode relativ kurz und die Ausführung im Vergleich zu den etwas komplizierteren Methoden wesentlich schneller. Als "Alternative" läßt sich der Autostart aber auch zum Schutz eigener Programme einsetzen. Diese zweite, weitaus interessantere Variante erfordert jedoch einige Vorkenntnisse im Bereich der Assemblerprogrammierung und der Speicheraufteilung des C64 . Ist das Prinzip aber einmal erkannt, stehen eine Menge Möglichkeiten zur Verfügung, unbefugten Personen den Einblick in seine Programme zu versperren. Schließlich ist der beste Kopierschutz sinnlos, wenn er sich durch das Löschen weniger BASIC-Zeilen entfernen läßt.
56
Das große Allti-Cracker-Buch
Der Autostart stellt also nicht nur einen "Mini-Kopierschutz" dar, sondern dient vor allem dem Sichern der Kopierschutzabfrage in den einzelnen Programmen. Zusammen mit einer sinnvollen Codierroutine läßt sich dadurch der Zugriff für fast jeden Unbefugten versperren. Hundertprozentig sicher ist aber kein Autostart, da man jeden rückgängig machen kann, wenn das Programm in einen anderen Speicherbereich geladen und so der Autostart unterdrückt wird . Es liegt also vor allem beim Programmierer, hier kreativ zu sein und sich neue Tricks einfallen zu lassen, die zum Beispiel das Abarbeiten eines Programms nur in einem bestimmten Speicherbereich erlauben. Auf gar keinen Fall darf ein Assemblerprogramm mit Autostart relokatibel (an jeder Stelle des Speichers lauffähig) sein. Doch bevor wir tiefer in das Prinzip des Autostarts einsteigen, noch einige Tips zu einfacheren Methoden.
3.1.2 Der einfachste Autostart Der wohl einfachste Autostart, der sozusagen "serienmäßig" im C64 eingebaut ist, läßt sich durch die Tastenkombination "SHIFT -RUNjSTOP" erreichen. Es erscheint "LOAD" und zwei Zeilen tiefer "PRESS PLA Y ON TAPE" auf dem Bildschirm. Für die Besitzer einer Datasette bedeutet diese Tastenkombination, daß das nächste Programm von der Kassette geladen und sofort danach gestartet wird. Ein Autostart erfolgt natürlich nur, wenn das Programm auch sonst mit dem BASIC-Befehl RUN gestartet werden kann. Der Benutzer einer Diskettenstation kann mit diesen Tasten aber nichts anfangen, da der Parameter ",8", der für das Laden von der Floppy unbedingt nötig ist, nicht auftaucht. Aber auch hier kann mit der oben erwähnten Tastenkombination etwas erreicht werden. Tippen Sie zuerst die Befehlszeile zum Laden des Programms ein, die etwa so aussieht: LOAD "PROGRAMMNAME",8
Programmschutz für Profis
57
Doch anstatt die RETURN-Taste zu drücken, schreiben Sie ":" und drücken jetzt "SHIFT -RUN/STOP". Nach dem Laden erscheint "RUN" auf dem Bildschirm, und das Programm wird gestartet.
3.1.3
Autostart im Tastaturpuffer
Die oben besprochene Methode zum automatischen Programmstart bezieht sich nur auf das Starten selbst, indem das Eingeben des Befehls RUN "simuliert" wird; der Programmeode wird dabei allerdings nicht geändert. Diese Methode ist also nicht dauerhaft und hat nichts mit Programmschutz zu tun. Interessanter ist die Möglichkeit, den Autostart mit dem eigentlichen Programm abzuspeichern, so daß dieses nach jedem Laden gestartet wird. Ein einfacher Trick besteht darin, einen Befehl auf den Bildschirm zu schreiben und dann das Drücken der RETURN-Taste zu simulieren. Um das Prinzip zu verstehen, geben Sie folgende Zeile ein: PRINT "(CLR/HOME) PRINT 3
+
4"
Durch die Eingabe dieser Zeile wird der Bildschirm gelöscht, und es erscheint der Text "PRINT 3 + 4" am oberen Bildschirmrand. Bis jetzt hat sich noch nichts ereignet, was an die Automatisierung eines Befehls oder gar eines Programmstarts erinnern würde. Doch gehen Sie jetzt mit dem Cursor einige Zeilen nach unten und geben die folgende Zeile ein: POKE 631,19:POKE 632,13:POKE 198,2
Wenn Sie beim Eintippen der beiden Zeilen keine Fehler gemacht haben, wird die Zeile, die am oberen Bildschirmrand abgebildet ist, ausgeführt, und der Cursor erscheint zusammen mit der READY -Meldung zwei Zeilen tiefer. Es besteht also kein Unterschied zu einem Befehl, den Sie normalerweise im Direktmodus eingeben.
58
Das große Anti-Cracker-Buch
Schauen wir uns den Vorgang einmal genauer an, stellen wir fest, daß der Cursor in die erste Zeile gebracht und der dort stehende Befehl ausgeführt wurde. Wir haben den Computer also dazu veranlaßt zu erkennen, daß die HOME- und daraufhin die RETURN-Taste gedrückt wurde. Die ganze Aktion wurde natürlich durch die drei zuletzt eingegebenen POKEs veranlaßt. Untersuchen wir die angesprochenen Speicherzellen und die Werte, die in diese geschrieben werden, läßt sich das "Phänomen" relativ einfach erklären. Durch POKE 631,19 und POKE 632,13 werden die Werte für gedrückte Tasten in den sogenannten Tastaturpuffer ab $0277 (631) geschrieben. In diesem Tastaturpuffer werden normalerweise die Tastencodes zwischengespeichert, die zwar vom Benutzer schon eingegeben wurden, aber noch nicht verarbeitet werden konnten. Das Betriebssystem liest diese Codes in regelmäßigen Abständen aus und gibt sie an den BASIC-Interpreter weiter. Mit unseren POKEs haben wir die Codes für die CLRund die RETURN-Taste in diesen Puffer geschrieben. Nun muß dem Betriebssystem noch die Anzahl der gedrückten Tasten mitgeteilt werden, was durch POKE 198,2 geschieht. In der Adresse $C6 (198) befindet sich die Anzahl der Zeichencodes, die im Tastaturpuffer gespeichert sind. Da wir zwei Tastencodes in den Puffer geschrieben haben, müssen wir hier den Wert 2 ablegen. Da der Inhalt des Puffers regelmäßig verarbeitet wird, haben wir so indirekt die Ausführung eines Befehls veranlaßt. Um nun unsere eigentliche AufgabensteIlung, den selbständigen Programmstart, zu lösen, muß eine Methode gefunden werden, den Befehl RUN unmittelbar nach dem Laden ausführen zu lassen. Dazu benutzen wir das oben verwandte Prinzip, mit dem Unterschied, daß die oberste Zeile "PRINT 3 + 4" durch "RUN" ersetzt wird. Die POKEs, die die Tastencodes in den Tastaturpuffer schreiben, können wir unverändert übernehmen, da diese unabhängig vom Inhalt der obersten Bildschirmzeile sind. Daraus folgt, daß auch POKE 198,2 übernommen wird.
59
Programmschutz für Profis
.-/
BASIC - RAM
$0800
(2048)
BIldschIrmspeIcher (VIdeo - RAM)
$0<400
(102<4) Bandpuffer
$033C
(0828)
Sprungvektoren
$0300
(0768)
$0200
(0512) Stapel (Stack)
(0256)
$0100 Zeropage
$0000 Abb.3.1.1 :
Unterer Speicherbereich des C64
(0000)
60
Das große Anti-Cracker-Buch
Das Problem besteht nun darin, den Inhalt des Tastaturpuffers und dessen Zähler zusammen mit dem Hauptprogramm abzuspeichern, so daß die Inhalte nach jedem Laden vorhanden sind, um den Autostart auszuführen. Schauen wir uns dazu die Abbildung 3.1.1 an, in der der untere Speicherbereich des C64 abgebildet ist. Wie dort zu sehen ist, beginnt das BASIC-RAM ab $0800 (2048). Da in der Speicherzelle $0800 (2048) immer $00 steht, werden alle BASIC-Programme ab $0801 (2049) geladen und auch abgespeichert. Bestimmt wird die Anfangs- und Endadresse von BASIC-Programmen durch Vektoren in der Zero page. Die Anfangsadresse ist in $2B/$2C (43/44) und die Endadresse in $2D/ $2E (45 / 46) im LOW- und HIGH-ByteFormat abgelegt. LOW- und HIGH-Byte-Format bedeutet hierbei, daß zuerst das LOW-Byte und dann erst das HIGH-Byte abgespeichert wird . Die BASIC-Startadresse ist also folgendermaßen abgelegt: $002B 01 08 ..
Setzt man die BASIC-Startadresse auf $C6 (I98), wird durch SAVE IPROGRAMMNAME",8
der gesamte Speicherbereich von $C6 (I98) bis zum Programmende abgespeichert, inklusive des Tastaturpuffers und dessen Zähler. Wenn wir dazu die oben erwähnten POKEs eingeben und sich das Hauptprogramm zu diesem Zeitpunkt im Speicher befindet, wird nach jedem Laden ein Autostart durchgeführt. Zu beachten ist noch, daß die Zeiger auf das Programmende dabei neu gesetzt werden müssen . Wir lesen diese also zuvor durch PEEK (45) und PEEK (46) aus und schreiben sie mit in die oberste Bildschirmzeile. Der gesamte Ablauf sieht nun also wie folgt aus: Laden Sie das mit einem Autostart zu versehende BASIC-Programm und tippen Sie als erstes PRINT"(CLR/HOME)POKE 45,IPEEK(45)":POKE 46,IPEEK(46)I:RUN"
Programmschutz für Profis
61
ein. Nach dem Drücken der RETURN-Taste sollte der Bildschirm gelöscht werden und POKE45,(Wert1):POKE46,(Wert2):RUN
in der ersten Bildschirmzeile erscheinen. Wertl und Wert2 stehen hier symbolisch für die BASIC-Endadresse, die ja, wie oben schon erwähnt, in den Speicherzellen 45 und 46 im LOW - und HIGH-Byte-Format abgelegt ist. Gehen Sie nun mit den Cursor zwei Zeilen tiefer und geben wieder POKE 631,19:POKE 632,13:POKE 198,2: POKE 43,198:POKE44,O:SAVE INAME",8
ein. Nun wird das im Speicher befindliche Programm auf Diskette abgespeichert, nachdem die Startadresse durch POKE 43,198 und POKE 44,0 auf $C6 (198) heruntergesetzt wurde. Geben Sie jetzt POKE 43, I :POKE44,8 ein, wodurch die Startadresse wieder auf ihren Normalwert zurückgesetzt wird und Sie wie gewohnt weiterarbeiten können. Das Programm befindet sich nun in einer etwas längeren Version auf Diskette und kann mit LOAD INAME",8,1 geladen werden. Geben Sie aber vor dem Laden bitte NEW oder CLR ein, da sich sonst Probleme mit dem (mitabgespeicherten) Stapel ergeben können. Da wir, wie aus Abbildung 3.1.1 ersichtlich, auch den Bildschirmspeicher mit gespeichert haben, wird der Bildschirm beim Laden überschrieben, und es erscheinen die POKEs und der RUN-Befehl. Zusätzlich zu diesen POKEs können Sie auch noch weitere in die erste Zeile schreiben, bevor Sie das Programm abspeichern. Zum Beispiel wird durch POKE 808,225 der STOP- Vektor so verändert, daß die STOP-Taste nicht mehr abgefragt wird und . so ein Anhalten Ihres Programms durch RUN/STOP nicht mehr möglich ist.
62 3.1.4
Das große Anti-Cracker-Buch Autostart über Sprungvektoren
Die oben besprochene Autostartmethode zeichnet sich duch ihre verblüffende Einfachheit aus. Dem BASIC-Programmierer wird damit die Möglichkeit geboten, mit geringem Aufwand und minimalen Vorkenntnissen über die Speicheraufteilung des C64 seine eigenen Programme mit einem Autostart zu versehen. Diese Methode ist jedoch mit einigen Nachteilen verbunden. Beispielsweise ist die Eingabe von NEW vor dem Laden nicht wünschenswert. Weiterhin kann man beim Einladen auf dem Bildschirm sehen, mit welchen Befehlen der Autostart erzeugt wurde. Dazu kommt noch, daß im Vergleich zu anderen Verfahren das bearbeitete Programm relativ viele Blöcke mehr benötigt als ohne den Autostart. Die nun folgenden Autostartmethoden beziehen sich hauptsächlich auf Assemblerprogramme, was nicht bedeutet, daß mit ihnen nicht auch BASIC-Programme gestartet werden können. Einleitend muß erwähnt werden, daß der C64 im Gegensatz zu vielen anderen Computern über ein ROM (Read Only Memory) verfügt, in dem das Betriebssystem des Rechners fest abgelegt ist. Das hat den Vorteil, daß das Betriebssystem nach dem Einschalten nicht erst "gebootet", also eingeladen, werden muß. Andererseits bringt gerade diese Eigenschaft den großen Nachteil für den Anwender mit sich, daß er das ROM nicht abändern und seinen Bedürfnissen anpassen kann. Um dem ein wenig abzuhelfen, bietet der C64 eine Reihe von sogenannten Sprungvektoren an, die zwar vom Betriebssystem benutzt werden, aber nicht im ROM, sondern am Anfang des Arbeitsspeichers abgelegt sind. Diese Vektoren liegen ab der Adresse $0300 (768) und verweisen auf die jeweiligen Routinen innerhalb des Betriebssystems. An dieser Stelle kann der Programmierer eingreifen, indem er die Vektoren auf seine eigenen Routinen stellt. Es lassen sich jedoch auch andere Effekte erreichen, die äußerst nützlich sein können. Hier soll eine schematische Darstellung verdeutlichen, wie die Sprungvektoren vom Betriebssystem benutzt werden.
Programmschutz für Profis
63
Betriebssystem Ind. Sprung j - - - JMP ($ .... ) I Routine +---,
ISprungvektoren I I I I I I---~ Vektor - - - - -
Abb. 3.1.2:
Funktion der Sprungvektoren
Nachdem eine Routine direkt oder aus einer anderen Routine heraus angesprungen wurde, wird durch einen indirekten Sprung über die Vektoren zur eigentlichen Routine verzweigt. Diese Routine befindet sich im allgemeinen direkt hinter dem indirekten Sprungbefehl. Die ersten Vektoren sind die des BASICInterpreters, die die nachfolgenden aufgeführten Funktionen
64
Das große Anti-Cracker-Buch
erfüllen. Zum besseren Verständnis ist es ratsam, die Routinen in einem ROM-Listing, wie es zum Beispiel in dem Buch "64 INTERN" abgedruckt ist, nachzuschlagen.
Adresse $0300/0301 (768/769)
Vektor $E38B
$0302/0303 $A483 (770/771 ) $0304/0305 $A57C (772/773) $0306/0307 $A71A (774/775) $0308/0309 $A7E4 (776/777) $030A/030B $AE86 (778/779) $0311/0312 $B248 (785/786)
Beschreibung Vektor für BASIC-Warmstart; wird nach END sowie beim Auftreten eines Fehlers angesprungen (Fehlernummer im Akku). Vektor für Eingabe einer Zeile; Rechner bleibt in der Eingabewarteschleife, bis RETURN erfolgt. Vektor für Umwandlung in den Interpretercode. LIST-Vektor; wird bei Umwandlung in den Klartext angesprungen. Vektor für BASIC-Befehlsadresse holen; zeigt an die Stelle des Interpreters, die den BASIC-Befehl ausführt. Vektor wird angesprungen, wenn ein Element eines Ausdrucks berechnet werden soll. USR-Vektor; steht normalerweise auf 'ILLEGAL QUANTITY'.
Die Vektoren des Betriebssystems stehen ab $0314/0315 Adresse
Vektor
Beschreibung
$03 14/0315 (788/789) $03 16/0317 (790/791 ) $0318/0319 (792/793) $031A/031B (794/795) $031C/031D
$EA31
IRQ-Vektor; wird jede 1/60 Sekunde angesprungen. BRK-Vektor
$FE66 $FE47 $F34A
NMI·Vektor; wird beim Drücken der RESTORE-Taste benutzt. OPEN-Vektor
$F291
CLOSE-Vektor
Programmschutz für Profis (796/797) $031E/031F (798/799) $0320/0321 (800/801) $0322/0323 (802/803) $0324/0325 (804/805) $0326/0327 (806/807) $0328/0329 (808/809) $032A/032B . (810/811) $032C/032D (812/813) $032E/032F (814/815) $0330/0331 (816/817) $0332/0333 (818/819)
65
$F20E
CHKIN-Vektor
$F250
CKOUT-Vektor
$F333
CLRCH-Vektor
$F157
$F6ED
INPUT-Vektor; zeigt normalerweise auf Routine für Eingabe von der Tastatur. OUTPUT-Vektor; zeigt normalerweise auf Routine für Ausgabe auf den Bildschirm. STOP-Vektor
$F13E
GET-Vektor
$F32F
CLALL'Vektor
$FE66
Warmstart-Vektor
$F4A5
LOAD'Vektor
$F5ED
SAVE'Vektor
$F1CA
Mit diesen Vektoren lassen sich nun einige 'Kunststücke' vollbringen. Man kann zum Beispiel die LIST-Vektoren so umstellen, daß diese auf ein R TS zeigen. Das bewirkt, daß nach einem LIST-Befehl sofort wieder ins BASIC zurückgesprungen wird, ohne daß überhaupt etwas gelistet wird. Der Befehl wird also einfach ignoriert. Wir haben noch einige andere Ideen für Sie aufgeschrieben: POKE 774, 226 68 7
160
POKE775, 252 166 168 240
Adresse
Routine
$FCE2 $A644 $A807 $FOAO
RESET NEU SYNTAX ERROR Absturz des Systems
(64738) (42564) (43015) (61600)
66
Das große Anti-Cracker-Buch
Natürlich lassen sich auch die anderen Vektoren auf diese Routinen 'umbiegen'. Wirkungsvoll wäre es zum Beispiel noch beim SA VE- Vektor. Das Umstellen dieses Vektors läßt zu, daß man mit einem Programm wie üblich arbeiten kann; man kann jedoch danach nicht wieder abspeichern. Dieser Trick stellt also einen kleinen Kopierschutz dar. Für den BASIC-Anwender kann es auch von Nutzen sein, den RESTORE- Vektor abzuändern, wodurch eine Unterbrechung des Programms mittels RUN/STOP-RESTORE unmöglich wird. Auch hier kann wieder zu einer eigenen Routine verzweigt, bzw. die Funktion ganz ausgeschaltet werden. Dies kann zum Beispiel durch POKE 792,193:POKE 793,254 erzielt werden. Durch das Drücken der RESTORE-Taste wird eine bestimmte Leitung auf Masse gelegt, wodurch ein NMI (nicht maskierbarer Interrupt) ausgelöst wird. Bei einem NMI wird automatisch zur Adresse $FFF A verzweigt, was mit einer bestimmten Eigenart des Prozessors zusammenhängt. Von dort aus wird die NMIRoutine angesprungen, die sofort einen indirekten Sprung über den oben genannten Vektor ausführt. Durch die oben genannten POKEs wird die Zieladresse des NMI-Vektors in $0318/0319 (792/793) von $FE47 auf $FECI umgeändert. In der neuen Zieladresse steht aber ein RTI-Befehl (Return from Interrupt), so daß unmittelbar nach dem Aufruf des NMI ein Rücksprung erfolgt. Nun zu einer interessanteren Anwendung dieser Vektoren, dem Autostart. Wie Sie sicherlich wissen, kann man mit LOAD"PROGRAMMNAME",8,1 ein Programm in einen bestimmten Bereich des Speichers laden, wenn es zuvor aus diesem Bereich heraus abgesichert wurde. Dieses kann man am einfachsten mit einem Maschinensprachemonitor, wie zum Beispiel dem PROFI-MON, machen. Werden nun beim Laden eines Programms die Sprungvektoren überschrieben, werden dadurch alle Zeiger umgestellt, und der Rechner stürzt ab. Wurde jedoch vor dem Abspeichern nur ein Zeiger umgeändert und die anderen in ihrem Zustand gelassen, lädt der Computer ordnungsge-
Programmschutz für Profis
67
mäß, und nur eine einzige Routine wird anders ausgeführt. Man kann nun einen Vektor umstellen, der nur manchmal benutzt wird, zum Beispiel den LIST-Vektor, oder einen, über den ständig verzweigt wird. Ein Vektor, über den immer verzweigt wird und der deshalb ideal erscheint, ist der STOP-Vektor in $0328/0329 (790/791). Auch während des Ladens wird über diese Adresse gesprungen , so daß schon vor Beendigung des Ladevorgangs ein Autostart erfolgen kann. Doch die speziellen Eigenarten dieser Routine wollen wir später noch genauer betrachten. Für unseren normalen Autostart bietet sich auch die Eingabewarteschleife ($0302/0303) an , die fast ständig auf eine Eingabe von der Tastatur wartet. Diesen Vektor kann man nun auf den eigenen Programmanfang stellen , so daß nach dem Laden nicht mehr der Cursor erscheint, sondern direkt ein Programmstart erfolgt. Wie aus Abbbildung 3.1.1 ersichtlich, ist es sinnvoll, das Programm in den Kassettenpuffer ab $033C (828) zu legen, da sonst der Bildschirm mit abgespeichert werden muß. Auf diese Weise kann aber nur ein Maschinenprogramm gestartet werden, da zu einer bestimmten Adresse gesprungen wird. Soll ein BASIC-Programm gestartet werden, so muß man eine kleine Routine zwischenschalten , die folgendermaßen aussieht: 033C JSR $A659 ;CHRGET·Zeiger auf Programmstart und CLR 033F JMP $A7AE ;in die Interpreterschleife springen
Eine andere Möglichkeit ist es, über den OUTPUT-Vektor $0326/0327 (806/807) zu verzweigen, da dieser bei jeder Ausgabe auf den Bildschirm angesprungen wird und dementsprechend schwer zu unterdrücken ist. Diese Eigenschaft des OUTpuT - Vektors stellt uns jedoch vor ein Problem. Wie soll ein Programm mit dem abgeänderten Vektor geSA VEd werden, ohne daß es schon bei der Ausgabe der Meldung "SA VING" anläuft? Die einfachste Möglichkeit besteht darin, den Vektor zunächst unverändert zu lassen und das Programm in den gewünschten Bereich, am besten ab $033C (828), zu schreiben. Nach der Fertigstellung des Programms wird dieses mitsamt den Sprungvek-
68
Das gro ße Anti-Cracker- Buch
toren ab $0300 (768) in einen anderen Speicherbereich kopiert. Dieses Kopieren kann durch eine selbstgeschriebene Routine oder mit Hilfe eines Maschinensprachemonitors geschehen. Der Standardbefehl dazu steht in fast jedem Monitor zur Verfügung und hat folgendes Format: T (Anfangsadresse) (Endadresse) (neue Anfangsadresse) Das Transferieren sollte möglichst in Schritten von $1000 (4096) vorgenommen werden, da die Adressenumrechnung dadurch erheblich vereinfacht wird. Ein Programm, das im Kassettenpuffer liegt, sollte also möglichst mit den Sprungvektoren ab $0300 in den Bereich ab $1300 verschoben werden. Danach wird der OUTPUT-Vektor, der sich jetzt in $1326/1327 befindet, auf unseren Programmstart abgeändert. Beginnt das Programm im Orginalbereich ab $033C, so muß diese Adresse in den OUTPUT-Vektor geschrieben werden, was dann so aussieht: $1300 3C 03
Beachten Sie dabei, daß die Sprung vektoren im LOW - und HIGH-Byte-Format vorliegen. Das folgende Programm ermöglicht es Ihnen, ein gewöhnliches Programm, das mit RUN gestartet wird, mit einem Autostart zu versehen.
Programmschutz für Profis COOO C002 CO 05 C008 COOA COOD COOF C012 C014 C016 C019 C01A C01D C020 C022 C024 C027 C029 C02B C02D C02F C031 C033 C036 C038 C03A C03C C03E C041 C043 C046 C047 C04A C04D C04F COS1 COS4 COSS COS7 COS9
LDA STA STA LDA STA LDX LDA CMP BEQ JSR INX JMP JSR LDX LDY JSR LDX LDY STX STY LDA STA JSR LDA CMP BNE LDX LDA BEQ JSR INX JMP JSR LDX LDA STA INX BNE LDX LDY
#$00 Farbwert für Schwarz laden und in Register für Hintergrund und SD020 Vordergrund schreiben SD021 #$05 Farbwert für Grün S0286 in Register für Schriftfarbe #$00 Zeiger auf SOO stellen SC090,X erstes ASCII-Zeichen holen mit SPACE vergleichen #$20 SC01D wenn SPACE, dann Schleife beenden SFFD2 Zeichen auf Bildschirm ausgeben Zeiger um 1 erhöhen SCOOF nächstes Zeichen holen Unterroutine für Eingabe des Pgm-Namens SC082 ~ert für Laufwer k (,8) #$08 #$01 Sekundäradresse = 1 setzen SFFBA File-Parameter setzen #SOO LO~-Byte des Zeigers auf Filenamen #$CF HIGH-Byte des Zeigers auf Filenamen Zeiger auf Filename setzen SBB SBC (Zeropage 187/188) #$00 ~ert für Programmodus in S9D Flag schreiben (Meldung unte rdrücken) Programm laden SFFDS S90 ~ert aus Fehlerstatus laden #$40 und testen SCOOO Fehler, dann zum Programmstart zurück #$00 Zeiger auf SOO setzen SC091,X ASCII-Zeichen holen SC04A Ende, dann Schleife beenden SFFD2 Zeichen ausgeben Zeiger erhöhen nächstes Zeichen holen SC03E Filenamen eigeben SC082 #$00 Zeiger auf SOO setzen #$20 ~ert für SPACE S0400 , X in Bildschirmspeicher schreiben Zeiger erhöhen SCOS1 kleiner 255, nächst e Position löschen #S40 LOW-Byte des neuen OUTPUT-Vektors #S03 HIGH-Byte des Vektors
69
70
Das gro ße Anti -Cracker- Buch
C05B STX S0326 C05E STY S0327 C061 LOX #$00 C063 LOA SCOAA,X C066 STA S0340,X C069 INX C06A CPX #$10 C06C BNE SC063 C06E LOX #$00 C070 LOY #$03 C072 STX SFB C074 STY SFC C076 LOA #$FB C078 LOX SAE C07A LOY SAF C07C JSR SFF08 C07F JMP S0340 C082 LOX #$00 C084 STX SB7 C086 JSR SFFCF C089 STA SCFOO,X C08C INX C080 INC SB7 C08F CMP #$00 C091 BNE SC086 C093 RTS
Werte in OUTPUT-Vektor schreiben Zeiger auf SOO setzen Byte für Startprogramm holen und in Tastaturpuffer schreiben Zeiger erhöhen vergleicht auf Anzahl der Bytes · nicht alle Bytes, dann weiter LOW-Byte und HIGH-Byte der Startadresse des Programms in Zeropage speichern Zeiger auf Startadresse setzen LOW-Byte der Endadresse in X-Register HIGH-Byte der Endadresse in V-Register Programm speichern zu Programmstart (Programm testen) Zeiger und Länge des Namens auf SOO setzen Zeichen von Tastatur holen Zeichen speichern Zeiger erhöhen Länge des Names erhöhen ist Zeichen = RETURN? nein, dann nächstes Zeichen holen Rücksprung
COAA COAC COAE COB1 COB4 COB7
LOW-Byte des OUTPUT-Vektors laden HIGH-Byte des OUTPUT-Vektors laden LOW-Byte in OUTPUT-Zeiger schreiben HIGH-Byte in OUTPUT-Vektor schreiben CHRGET-Zeiger auf Programmstart und CLR in die Int erpreterschleife springen
LOX LOY STX STY JSR JMP
#$CA #$F1 S0326 S0327 SA659 SA7AE
C090 93 00 50 52 4F 47 52 41 C098 40 40 4E 41 40 45 3A 20 COAO 28 4E 45 55 29 00
PROGRA MMNAME: (NEU)
Programmschutz für Profis
71
Hier der BASIC-Loader des oben abgedruckten Autostart-Programms: 5 N=49152 10 READX:IFX=-1THEN30 20 S=S+X:POKE N,X:N=N+1:GOTO 10 30 IFS<>22926 OR N<>49339 THEN PRINT"FEHLER IN DATA S":END 40 SYS49152 101 DATA 169,0,141,32,208,141,33,208,169,5,141,134,2,162,0,189,148,192,2 01 102 DATA 32,240,7,32,210,255,232,76,15,192,32,130,192,162,8,160,1,32,186 ,255 103 DATA 162,0,160,207,134,187,132,188,169,0,133,157,32,213,255,165,144, 201 104 DATA 64,208,196,162,0,189,149,192,240,7,32,210,255,232,76,62,192,32, 130
105 DATA 192,162,0,169,32,157,0,4,232,208,250,162,64,160,3,142,38,3,140, 39 106 DATA 3,162,0,189,170,192,157,64,3,232,224,16,208,245,162,0,160,3,134 ,251 107 DATA 132,252,169,251,166,174,164,175,32,216,255,76,64,3,162,0,134,18 3
108 DATA 32,207,255,157,0,207,232,230,183,201,13,208,243,96,147,13,80,82 ,79 109 DATA 71,82,65,77,77,78,65,77,69,58,32,40,78,69,85,41,0,162,202,160,2 41 110 DATA 142,38,3,140,39,3,32,89,166,76,174,167,32,-1
3.1.5
Stapel-Autostart
Eine weitere Alternative zu den eben besprochenen Autostartmethoden bietet der sogenannte Stapel-Autostart. Der Stapel, auch Stack genannt, befindet sich im Bereich von $0100 (256) bis $OIFF (511) (siehe Abbildung 3.1.1) und enthält unter anderem die Rücksprungadressen des Programms beim Aufrufen von Unterprogrammen. Da auch das Laden von einem Unterprogramm aus geschieht, kann der Stapel dabei mit dem Einsprung unseres Programms überschrieben werden. Dabei muß die Startadresse plus $01 in Low- und High-Byte abgelegt werden.
72
Das große Anti-Cracker-Buch
Beim Rücksprung holt sich der Rechner nun die Adresse aus dem Stapel, die ja mittlerweile abgeändert wurde, und verzweigt dementsprechend. Genau betrachtet, funktioniert dieses Prinzip nur durch eine besondere Eigenschaft des Stapels: der Stapel ist ein sogenanntes LIFO-Register, das 255 Bytes groß ist. LIFO ist eine Abkürzung für den englischen Ausdruck "LAST IN, FIRST OUT", was soviel bedeutet wie: zuletzt hinein, zuerst heraus. Das heißt also, daß das Byte, das zuletzt auf den Stapel geschoben wurde, als erstes wieder herauskommt. Zu vergleichen ist diese Methode mit einem Stapel von Tellern. Der Teller, der als letztes auf den Tellerstapel gelegt wurde, wird auch als erstes wieder heruntergenommen. Damit der Computer weiß, welches Byte nun als letztes abgelegt wurde, existiert ein sogenannter Stapelzeiger. Dieser Stapelzeiger ist ein Ein-Byte-Wert, der die "Höhe" des Stapels anzeigt. Steht dieser Zeiger zum Beispiel auf $F7, wird das nächste Byte aus der Adresse gelesen, die sich aus dem Stapelanfang und dem Zeiger ergibt. In diesem Fall wäre das $0100 + $F7 = $0IF7. Während des Programmablaufs kann der Zeiger mit dem TSXBefehl in das X-Register geschoben und so ausgelesen werden. Umgekehrt kann der momentane Wert des X-Registers mit TXS als Stapelzeiger eingesetzt werden. Bei einem Unterprogrammaufruf durch den JSR-Befehl (Jump to Subroutine) wird die Adresse des zuletzt bearbeiteten Bytes auf den Stapel geschoben. Folgendes Beispiel soll dieses verdeutlichen: 1000 JSR $1080 Aufruf des Unterprogramms 1003 INC $0020 beliebiger nächster Befehl
Der Unterprogrammaufruf liegt hier ab der Adresse $1000. Da der JSR-Befehl drei Bytes lang ist, wird die Adresse $1002 auf den Stapel geschoben. Dieses geschieht in der Reihenfolge LOWund HIGH-Byte. Es wird also zuerst der Wert $02 (LOW-Byte der Adresse) und dann erst der Wert $10 (HIGH-Byte der
Programmschutz für Profis
73
Adresse) auf den Stapel geschoben. Bei der Beendigung der Unterprogrammroutine durch den RTS-Befehl (Return from Subroutine) wird die Absprungadresse wieder zurückgeholt. Da der nächste Befehl aber ab $1003 steht, muß zu dem LüW-Byte der abgelegten Adresse noch $01 addiert werden. Dieses System der Adreßspeicherung im Stapel können wir uns zunutze machen, um nach dem Laden einen Autostart durchführen zu lassen. Wie schon oben erwähnt, werden während des Ladevorgangs im Betriebssystem selbst mehrere Unterprogramme aufgerufen. Zum Beispiel wird die Ausgabe des "SEARCHING FüR" auf dem Bildschirm von einer Unter routine übernommen. Aber auch der gesamte Ladevorgang wird durch ein RTS beendet. Überschreiben wir nun den gesamten Stapel von $0100 bis $OIFF mit bestimmten Werten, wird nicht mehr zu der abgelegten Adresse zurückgesprungen, da diese mittlerweile überschrieben wurde. Es ist auch völlig unerheblich, auf welchen Wert der Stapelzeiger steht, da ja der gesamte Stapel überschrieben wurde. Jetzt müssen wir uns nur noch überlegen, mit welchen Werten der Stapel überschrieben werden soll. Da der Wert des Stapels unbekannt ist, ist es am einfachsten, den gesamten Stapel mit nur einer Zahl zu füllen. Dafür bietet sich der Wert $02 an. Wenn alle Werte $02 sind, muß bei einem RTS zu der Adresse $0202 + $01, also zu $0203 verzweigt werden. Schauen Sie sich die Adressen ab $0203 an, so werden Sie feststellen, daß der gesamte Speicherbereich von $0200 (512) bis $0258 (600) als BASIC-Eingabepuffer fungiert. Da wir den BASIC-Eingabepuffer aber momentan nicht brauchen, kann hier ein Programm abgelegt werden. Die nun zur Verfügung stehenden 88 Bytes ($58) reichen zwar nicht für ein längeres Pro gramm, sie bieten aber genug Platz für ein Ladeprogramm, dem eventuell noch eine Decodierroutine angeschlossen werden kann. Das folgende Programm soll Ihnen als Programmbeispiel dienen, um Ihre eigenen Programme mit einem Stapel-Autostart versehen zu können. Da es wegen des überschriebenen Stapels nicht im ürginalbereich geschrieben werden kann, ist es ratsam, das
Das große Anti-Cracker-Buch
74
Programm, wie oben beschrieben, ab $1100 zu schreiben. Nachher kann die Startadresse mit dem in Kapitel 3.1.4 abgedruckten Programm auf $0100 abgeändert werden. Unser Programm soll ein zweites Programm von Diskette nachladen und es ab $8000 starten. Als erstes muß eine kleine Routine geschrieben werden, die den Bereich von $1100 bis $l1FF mit dem Wert $02 füllt. Als Startadresse haben wir $COOO (49152) gewählt. COOO C002 C004 C007 C008 COOA
LOA LDX STA INX BNE RTS
#$02 Füllwert laden #$00 Zeiger auf SOO setzen S1100,X Füllwert speichern Zeiger erhöhen SC004 wenn kleiner SOO (SFF+1), nächster Wert Rücksprung
Nun folgt das eigentliche Programm: 1200 1201 1202 1203 1205 1207 120A 120C 120E 1210 1213 1215 1218
BRK BRK BRK LDX LDY JSR LDX LDY LDA JSR LDA JSR JMP
#$08 #$01 SFFBA #$1B #$02 #$04 SFFBD #$00 SFFD5 $8000
drei Nullbytes (Bytes sind unerheblich) Gerätenummer für Floppy Sekundäradresse File'Parameter setzen LOW'Byte der Filenamen'Adresse HIGH'Byte der Filenamen·Adresse Länge des Filenamens Namensparameter setzen Flagwert für LOAD (sonst VERIFY) File laden zur Startadresse springen
121B 4E 41 4D 45 00 00 00 00
Um das Beispiel zu vervollständigen, haben wir ein kleines Programm ab $8000 geschrieben, das die Rahmenfarbe in mehreren Schleifend urchläufen ändert.
Programmschutz für Profis 8000 8002 8004 8007 8008 800A 800B 8000
3.1.6
LOX LOY INC INX BNE INY BNE RTS
75
#$00 ersten Zeiger setzen #$00 zweiten Zeiger setzen $0020 Rahmenfarbe erhöhen ersten Zeiger erhöhen $8004 kleiner 255, dann weiter zweiten Zeiger erhöhen $8004 kleiner 255, dann weiter Rücksprung
Autostart während des Ladens
Wie schon oben erwähnt, besitzt einer der Sprungvektoren eine besondere Eigenschaft, die sich hervorragend für eine spezielle Art des Autostarts eignet. Es handelt sich hierbei um den STOPVektor in $0328/$0329 (808/809), der zu einer Routine in $F6ED verzweigt. An dieser Stelle wird das STOP- Flag getestet, um den jeweiligen Programmablauf zu beenden, wenn die RUN/STOP- Taste gedrückt wurde. Wie uns schon von den anderen Autostart-Systemen her bekannt ist, werden die einzelnen Funktionen indirekt über die Sprungvektoren angesprochen. Diese Eigenschaft haben wir uns bei dem OUTPUT-Vektor zunutze gemacht, um nach dem Laden nicht zur READY-Meldung, sondern zu unserem Programmstart zu verzweigen. Hier ist der wesentlichste Unterschied zwischen dem STOP-Vektor und den anderen Sprungvektoren festzustellen: Über den STOP- Vektor wird nicht erst nach, sondern schon während des Ladens verzweigt. Wir können also schon vorzeitig einen Programmstart durchführen. Sicher entspricht dies nicht dem üblichen Ladevorgang, und vielleicht werden Sie sich auch fragen, welchen Sinn es hat, ein Programm zu starten, bevor es sich überhaupt vollständig im Speicher befindet, aber wir können daraus einige Vorteile ziehen. Wenn wir bisher einen Sprungvektor umgestellt haben, um ihn auf unsere Routine zu stellen, haben wir das LOW- und HIGHByte verändert. Dadurch war es uns möglich, jede beliebige
76
Das große Anti-Cracker-Buch
Adresse anzusprechen. Da aber nach jedem Byte, das eingelesen wird, über den STOP-Vektor verzweigt wird, ist es nicht möglich, die gesamte Adresse zu ändern. Ändern wir nur das LOWByte, müßte zu einen Programm in $F6 .. verzweigt werden, da das HIGH-Byte des Vektors $F6ED erhalten bleibt. In diesem Bereich liegt aber das Kernal-ROM, so daß dort kein eigenes Programm stehen kann. Die einzige Möglichkeit besteht also darin, das LOW-Byte mit dem Originalwert zu überschreiben und nur das HIGH-Byte zu ändern. Zu dem Zeitpunkt, an dem der Vektor geändert wird, muß sich aber ein Teilstück des Programms bereits im Speicher befinden, zu dem wir verzweigen können. Es bleiben also nur die Adressen $OOED, $OlED oder $02ED zur Auswahl. Hier bietet sich der Bereich ab $02ED an, da die ersten beiden mit Vektoren und Zeigern belegt sind. Außerdem stehen im Bereich von $02A8 bis $02FF keine wichtigen Informationen, und es muß auch kein unbenutzter Speicherbereich zwischen unserem Programm und dem Sprungvektoren mit abgespeichert werden. Nachdem unsere Routine gestartet wurde, kann die Originalladeroutine durch eine eigene ersetzt werden. Aus Platzmangel ist es jedoch ratsam, den Ladevorgang erst zu unterbrechen, wenn ein weiteres Programm in den Kassettenpuffer von $033C bis $03FF geladen wurde. Außerdem müssen die Sprungvektoren nach dem STOP- Vektor bis zum Ende eingeladen werden. Danach können wir weitere Programme in jeden beliebigen Speicherbereich laden. Ein Programm, das eigentlich ein einziges langes File ist, kann sich aus dem Loader, einem BASIC-Programm ab $0801, einem Maschinensprachemonitor ab $8000 und einem Diskmonitor ab $COOO zusammensetzen. Auch können Programme unter die ROM-Bereiche geladen und zwischenzeitlich benutzt werden. Hier nun ein Beispielprogramm, das ein Programm mit BASICKopf nach $0801 lädt und dort startet. Die Sprungvektoren ab $0300 müssen natürlich unverändert mitabgespeichert werden,
Programmschutz für Profis
77
damit der Rechner nicht abstürzt, wie es zum Beispiel bei der Ausgabe auf den Bildschirm oder einem Interrupt der Fall sein kann.
0300 0308 0310 0318 0320 0328 0330
8B E4 4C 47 50 EO A5
E3 A7 48 FE F2 02 F4
83 86 B2 4A 33 3E EO
A4 AE 00 F3 F3 F1 F5
7C XX 31 91 57 2F
A5 XX EA F2 F1 F3
1A A7
xx XX 66 OE CA 66
FE F2 F1 FE
Programm wird ab $080 I geladen: 0334 OB 08 CO 07 0338 9E 32 30 36 31 00 00 00
02A8 02AB 02AO 02AF 02BO 02B1 02B3 02B5 02B6 02B8 02BA 02BC 02BO 02BF 02C1 02C3 02C6 02C7 02C9 02CB ,02CO
JSR LOY BNE SEI TAX LOA STA TXA STA LOA STA CU INC BNE INC JHP TYA ANO BNE STA BEQ
I
SEE13 S90 S02C6
#$34 S01 (SAE),Y #$37 S01 SAE S02A8 SAF S02A8 #$FO S02CF S90 S02A8
Byte vom IEC-Bus holen Status abfragen verzweigt, wenn Fehler (Oatenende) Interrupt verhindern Byte in X-Register retten Wert für Speicherbelegung RAH unter ROM einblenden Byte wiederholen und abspeichern (SAE/SAF Zieladresse) Wert für Speicherbelegung ROM wieder einblenden Interrupt freigeben Zieladresse (LOW-Byte) erhöhen kein übertrag, dann nächstes Byte Zieladresse (HIGH·Byte) erhöhen nächstes Byte holen Statuswert in Akku schieben Bit 1 löschen (Fehler beim Lesen) Oatenende, dann S02CF Statuswert abspeichern unbedingter Sprung zum Anfang
78
Das große Anti-Cracker-Buch
Datenende erreicht 02CF 0202 0204 0206 0208 020A 020C 020F 02E1 02E4 02E6 02E9 02EC
JSR LOA STA LOA STA LOA STA LOA STA LOA STA JMP NOP
SF528 SAE S20 SAF S2E #$EO S0328 #$F6 $0329 #$00 $0020 $0800
Fi le schl ießen Zeiger (LOW) der Endadresse in Zeiger für BASIC-Programmende Zeiger (HIGH) in BASIC-Programmende LOW-Byte STOP-Vektor abspeichern HIGH-Byte STOP-Vektor abspeichern Code-Wert für Schwarz Rahmenfarbe setzen ProgralTlllstart No Operation
Einsprung über STOP- Vektor ($0328/$0329) Der Ladevorgang wird erst unterbrochen, wenn alle Sprungvektoren eingelesen sind. 02EO 02EF 02F1 02F3 02F4 02F6 02F8 02FA 02FC
LOA CMP BEQ RTS LOA STA LOA STA BNE
$AE #$34 $02F4
0340 0342 0344 0347 0348 034A
(0800) (080F) (0812) (0815) (0816) (0818)
#$01 $AE #$08 SAF $02A8
LOX LOY INC INX BNE INY
Zieladresse (LOW-Byte) laden mit S0334 vergleichen Adresse erreicht, dann weiter sonst weiterladen neue Zieladresse (LOW-Byte) abspeichern neue Zieladresse (HIGH-Byte) abspe ichern unbedingter Sprung zu $02A8
#$00 #$00 $0020 S0344
1. Zeiger setzen 2. Zeiger setzen Rahmenfarbe erhöhen 1. Zeiger vermindern kleiner 255, dann weiter 2. Zeiger vermindern
Programmschutz für Profis 034B (0819) BNE S0344 0340 (081B) BRK
79
kleiner 255, dann weiter Rücksprung
Ab der Adresse $0340 liegt ein kleines Programm, das nach dem Autostart ab $080D liegen wird. Die BASIC-Startzeile mit dem SYS-Befehl ist oben mit den Sprung vektoren ab $0334 abgedruckt. Anstelle unseres kleinen Beispielprogramms können Sie natürlich ein beliebiges anderes Programm einsetzen, das ab $080D gestartet wird. Dazu steht Ihnen der gesamte Speicher zur Verfügung. Da Sie jetzt in der Lage sind, die Programme in beliebige Speicherbereiche zu laden, könnten Sie eventuell wie folgt fortfahren: Setzen Sie unmittelbar hinter das Programm, das ab $080D beginnt, ein weiteres, das Sie zum Beispiel ab $FOOO laden. Nach dem Laden können Sie dann mit lOA #$34 STA S01
den gesamten ROM-Bereich auf das darunterliegende RAM umblenden. Hier sind Ihrer Kreativität keine Grenzen gesetzt. 3.1.7
Autostart über Interrupt
Nach dem unter 3.1.6 beschriebenen Verfahren kann ein Autostart während des Ladevorgangs auch über den Interrupt-Vektor in $0314/$0315 (788/789) durchgeführt werden. Der InterruptVektor wird alle 60stel Sekunde benutzt, um verschiedene Funktionen durchzuführen. Dazu gehören unter anderem die Cursorfunktion, die Abfrage einzelner Tasten und die Datasettensteuerung. Wenn wir den IRQ-Vektor benutzen, kann das Prinzip des STOP-Vektors beibehalten werden. Allerdings stellt sich hier das Problem, den Interrupt genau zu berechnen. Im Gegensatz zum STOP- Vektor können wir hier nicht genau sagen, nach welchem eingeladenen Byte der Vektor wieder benutzt wird. Die Routine ab $02ED, die solange in die Originalladeroutine zurückspringt,
80
Das große Anti-Cracker-Buch
bis ein bestimmtes Byte geladen wurde, kann hier nicht mehr verwendet werden. Stattdessen müssen wir sofort unsere eigene Laderoutine verwenden und mit dieser bis zu einem bestimmten Byte laden. Achten Sie darauf, daß zu Beginn Ihrer Routine ein SEI-Befehl steht, jeder weitere Interrupt also unterdrückt wird. Ohne diesen Befehl würde das Programm beim nächsten Interrupt erneut einspringen, was zu einem totalen Chaos und einem sicheren Absturz des Rechners führen würde. 3.1.8 Autostart über die CIAs Neben den diversen Möglichkeiten, einen Autostart über den Stapel oder die Sprungvektoren durchzuführen, kann man zu diesem Zweck auch die CIAs verwenden. Dabei wird zu einem bestimmten Zeitpunkt ein NMI, also ein nicht maskierbarer Interrupt, von einem Timer der CIAs ausgelöst. An dieser Stelle taucht der erste Unterschied zu den bisher besprochen Autostartmethoden auf, der uns gleichzeitig auch vor ein Problem stellt. Wenn von den Timern ein NMI ausgelöst wird, so wird über den NMI- Vektor in $0318/$0319 (792/793) zur Adresse $FE47 verzweigt. Zwar kann dieser Vektor in gewohnter Manier auf unsere Routine gestellt werden, dieses muß jedoch vor dem Ladevorgang erfolgen. Daraus folgt, daß ein automatischer Programmstart nur von einem Ladeprogramm durchgeführt werden kann, was die ganze Sache ziemlich absurd und sinnlos erscheinen läßt. Doch auch wenn die CIAs nicht für einen richtigen Autostart zu gebrauchen sind, kann hier ein wirksamer Programmschutz installiert werden. Das nun folgende Beispiel soll verdeutlichen, wie die Timer gestellt werden müssen, um während des Ladens einen NMI an einer bestimmten Stelle auszulösen. Dazu muß der NMI-Vektor in $0318/$0319 (792/793) zuerst auf $2000 gestellt werden. Das kann mittels POKE, eines kleinen Maschinenspracheprogramms oder durch Überschreiben des Vektors während des Ladens geschehen. Das eigentliche Programm muß ab $DD06 geladen werden, damit die Werte direkt
Programmschutz für Profis
81
in den Speicherzellen für den Timer stehen und die anderen Funktionen der CIAs nicht beeinträchtigt werden. In den Speicherzellen $DD06 und $DD07 steht im LOW - und HIGH-Byte-Format der Wert, von dem aus der Timer B auf Null herunterzählt. Dieser Wert steht hier auf $0001, so daß nach dem Starten des Timers B durch das Verändern des Wertes in $DDOF ein NMI ausgelöst wird. Die nächsten vier Bytes sind für die Echtzeituhr zuständig und interessieren an dieser Stelle nicht. Das folgende Byte stellt das "Serial Data"-Register dar und ist für uns auch uninteressant. In $DDOD, dem "Interrupt Control"-Register, müssen die Bits 1 und 7 gesetzt werden. Bit 1 = 1 zeigt an, daß ein Unterlauf von Timer B stattgefunden hat. Wenn ein Bit aus diesem Register gesetzt ist, so muß Bit 7 auch gesetzt sein, um das anzuzeigen. Der Wert $80 in $DDOE heißt, daß Bit 7 = 1 ist, was die Taktfrequenz des Echtzeituhr-Triggers auf 50 Hz stellt. Aber auch dieses Register beeinflußt den NMI von Timer B nicht. Interessant wird es wieder in Register 15, also in $DÜOF. Hier muß Bit o gesetzt werden, um den Start des Timers B anzuzeigen. Bit 5 und 6 müssen gelöscht sein, damit Timer B die System takte zählt. Die Inhalte der Speicherzellen $DD06 bis $DDOF könnten also folgendermaßen aussehen: 0000 xx xx xx xx xx xx 01 00 0008 00 00 00 91 00 82 80 19
Diese Bytes, die ein beliebiges Programm darstellen sollen, stehen nach dem Laden ab $3000. 0010 01 02 03 04 05 06 07 08 0018 09 10 11 12 13 14 15 16 0020 17 18 19 20 21 22 23 24
Wenn Sie diese Datenfolge auf einer Diskette ablegen wollen, so speichern Sie bitte die Bytes zuerst aus einem anderen Speicher-
Das große Anti-Cracker-Buch
82
bereich ab, zum Beispiel $1000, und ändern nachträglich die Startadresse mit dem Programm aus 3.1.4 auf $0000 um. Das hier abgedruckte Programm wird nach dem NMI gestartet und setzt die Zieladresse der nächsten Bytes, die geladen werden, auf $3000. Das Programm kann mit einem RTI (Return from Interrupt) beendet werden, da es durch einen Interrupt aufgerufen wurde. 2000 2001 2003 2005 2007 2009 200A
SEI LDX LDY STX STY CLI RTI
#$00 #$30 $AE $AF
maskierbaren Interrupt verhindern LOW-Byte der neuen Zieladresse HIGH-Byte der neuen Zieladresse LOW-Byte abspeichern HIGH-Byte abspeichern Interrupt wieder freigeben Rücksprung vom Interrupt (NMI)
Wärend des Ladens sind keine Veränderungen sichtbar. Trotzdem stehen die Bytes jetzt ab der Adresse $3000. 3000 01 02 03 04 05 06 07 08 3008 09 10 11 12 13 14 15 16 3010 17 18 19 20 21 22 23 24
3.1.9
Autostart mit Adreßverschiebung
Wie in Kapitel 3.1.6 beschrieben, ist es möglich, einen Autostart während des Ladens durchzuführen. Mit diesem Autostart konnten wir durch das Verändern der Speicherzellen $AE/$AF (174/175) die Zieladresse eines Programmes umstellen. Da die Adresse des jeweils zu ladenden Bytes in diesen Speicherzellen (im LOW- und HIGH-Byte-Format) abgelegt ist, kann sie auf eine beliebige Endadresse abgeändert werden, ab der das Programm dann weitergeladen wird_ Diese Zieladresse muß aber nicht unbedingt vom Programm aus verändert werden. Die Speicherzellen $AE/$AF können während des Ladens auch direkt überschrieben werden, wenn das Origi-
Programmschutz für Profis
83
nalprogramm vor dieser Adresse zu laden beginnt. Wird nun das LOW-Byte der Zieladresse verändert, ergibt das nicht viel Sinn, da das HIGH-Byte immer noch auf $00 steht (Zieladresse $OOAE) und so nur innerhalb der Zeropage weitergeladen werden kann. Es ist also sinnvoll, das LOW-Byte mit dem Originalwert zu überschreiben und nur das HIGH-Byte zu ändern. Da wir am Ende des Ladeprogrammes wieder einen Autostart durchführen wollen, also in den Bereich zwischen $0 I 00 und $033C weitere Bytes schreiben müssen, und da trotzdem ein Programm teil zwischen $0800 und $FFFF liegen soll, müssen wir uns hier eines kleinen Tricks bedienen. Wir stellen den Zeiger in $AE/$AF so um, daß zum Beispiel in den Bereich ab $FEOO plus dem LOW-Byte geladen wird. In diesem Bereich liegt zwar momentan ROM-Bereich, was uns aber nicht weiter stören soll, da beim Laden die Bytes in den dar unterliegenden RAMBereich geschrieben werden. Es wird nun also ein beliebiges Programm in den Bereich ab $FEOO geschrieben, das später dort gestartet werden kann. Das funktioniert natürlich nur, wenn der Bereich zuvor mit LDA #$35
STA $01
auf den darunterliegenden RAM-Bereich umgeschaltet wurde. Nun muß das Programm bis $FFFF weitergeladen werden. Wenn Ihr Programm nicht so lang ist, kann der restliche Bereich auch mit Nullbytes aufgefüllt werden. Für den Fall, daß Ihnen für das Programm nicht genug Platz zur Verfügung steht, kann das HIGH-Byte des Zeigers in $AE/$AF auf eine beliebige andere Zieladresse gestellt werden. Nachdem ein Byte in $FFFF abgelegt wurde, die letzte Adresse des Speichers also erreicht wurde, wird das nächste Byte am Anfang des Speichers abgelegt. Es wird also ab $0000 weitergeladen. Von hier an . kann nun normal weitergeladen werden, wenn die Zeropage-Adressen mit Originalwerten überschrieben werden. Der Inhalt der Zero page kann zuvor ausgelesen und
84
Das große Anti-Cracker-Buch
später mit diesen Werten wieder überschrieben werden. Auf die Adressen $AE/$AF ist hier wieder besonders zu achten. Am Ende kann ein beliebiger Autostart, zum Beispiel mit Hilfe des Stapels, durchgeführt werden. Danach muß der ROMBereich, in dem unser Programm liegt, auf den darunterliegenden RAM-Bereich umgeschaltet werden, und unser Programm kann gestartet werden. Doch auch hier können Sie noch weitere, eigene Ideen mit einbauen, um Ihr Programm für andere so "unverständlich" wie möglich zu gestalten. 3.2
Prüfsummen und Selbstzerstörung
Das Bilden einer Prüfsumme von einem Maschinenprogramm eignet sich hervorragend als Änderungsschutz. Falls man sein Maschinenprogramm vor einem Fremdeingriff schützen möchte, bildet man einfach eine Prüfsumme von seinem Maschinenprogramm und fragt diese irgendwann mitten im Programmablauf ab. Es gibt einige Methoden, Prüfsummen zu bilden. Wir haben hier ein Prüfsummen-Programm für Sie vorbereitet:
2000 2002 2004 2006 2008 200A 200C 200E 200F 2011 2013 2014 2016 2018 2019
LDY LDX LDA STA LDA STA LDA CLC ADC STA INY BNE INC DEX BNE
#$00 ersten Zähler setzen #$10 zweiten Zähler setzen #$00 LOW-Byte der Anfangsadresse laden $26 und speichern #$10 HIGH-Byte der Anfangsadresse laden $27 und speichern ($26),Y Wert laden und mit dem $02 Wert aus Speicherzelle $02 addieren $02 und wieder in $02 speichern Zähler erhöhen $200C noch keine 255 Bytes, dann nächstes Byte $27 HIGH -Byte erhöhen Zähler vermindern $200C falls Zähler null, dann weiter
Programmschutz für Profis 201B 2010 201F 2021 2023 2026 2027 2029
LOA CMP BEQ LOX INC INX BNE RTS
$02 #$36 $2029 #$00 $0020 $2023
85
berechneten Prüfsummenwert laden und mit vorgegeben Wert vergleichen falls Werte gleich, dann Rücksprung Zähler auf Null setzen Rahmenfarbe erhöhen Zähler erhöhen falls 255 mal erhöht, dann Rücksprung Rücksprung
Das Programm bildet eine Prüfsumme von dem Speicherbereich $1000 - $lFFF. Die Bereiche lassen sich begrenzen, indem man den Zähler in Zeile $2002 entsprechend setzt. Auch der zu prüfende Speicherbereich läßt sich angeben, indem man das LOWund HIGH-Byte in Zeile $2004 und $2008 entsprechend umstellt. Anschließend wird ein Byte aus dem vorgegebenen Bereich geladen und zu dem Wert aus der Speicherzelle $02 addiert. Dieser Wert ist beim ersten Durchlauf der Schleife gleich Null. Beim zweiten Durchlauf steht dort der Wert der ersten geladenen Speicherzelle. Beim dritten Durchlauf wird der geholte Wert mit dem in $02 addiert. Bei einem Überlauf, also wenn die Summe 255 überschreitet, wird wieder von Null an weiteraddiert. Nach der Prüfsummenbildung wird der berechnete Wert aus der Speicherzelle $02 geladen und mit einem vorgegebenen Wert verglichen. Hier muß natürlich auch die richtige Prüfsumme von dem eigenen Programm stehen. Diese läßt sich auch mit dem gleichen Programm berechnen. Nach der Prüfsummenberechnung braucht man nur den Wert aus dem Akku zu lesen und in die Zeile $201 Deinzusetzen. Falls nun eine Änderung an dem geprüften Programm vorliegt, und die Prüfsumme nicht stimmt, wird bei unserem Beispielprogramm nur die Rahmenfarbe erhöht. Anschließend folgt der Rücksprung. Falls die Prüfsumme stimmt, also wenn keine Änderungen am Programm vorliegen, wird direkt zum Rücksprung verzweigt . .
86
Das große Anti-Cracker-Buch
In der Praxis sollte an dieser Stelle statt einer Rahmenfarberhöhung eine Selbstzerstörung stattfinden. Das heißt, daß einfach wichtige Programmteile zerstört werden und folglich das Programm nicht mehr lauffähig ist. Ein Löschoder Zerstörungsprogramm könnte folgendermaßen aussehen:
033C 0330 033F 0341 0343 0345 0347 0349 034B 0340 034F 0351 0352 0354 0356 0357 0359 035B 0350 035E
SEI LOA STA LOX LOY LOA STA LOA STA LOA STA INY BNE INC OEX BNE LOA STA CLI JMP
#$30 S01 #$FB #$00 #$00 S26 #$04 S27 #$00 (S26),Y S034F S27 S034F #$37 S01 SFCE2
Interrupt verhindern I/O·Bereich und Betriebssystem ausblenden ersten Zähler setzen zweiten Zähler setzen L~·Byte der Anfangsadresse laden und spei chern HIGH-Byte der Anfangsadresse laden und speichern Wert laden und speichern Zähler erhöhen falls 256 Bytes, dann HIGH-Byte der Anfangsadresse erhöhen Zähler erniedrigen falls Zähler Null dann Betriebssystem wieder einblenden Interrupt verhindern zum RESET springen
Dieses Löschprogramm füllt den Speicherbereich von $0400 bis $FFFF mit Nullen und springt anschließend zum RESET, wodurch das Löschprogramm wiederum zerstört wird. Das Betriebssystem und der l/O-Bereich müssen ausgeblendet werden, weil beim Überschreiben des 1/0- Bereichs der Computer abstürzen würde. Nach dem Auffüllen wird noch die RESETRoutine des Betriebssystems angesprungen, um die Löschroutine zu zerstören. Auf diese Weise kann man auch vor einem Programmstart unbenutzte Bereiche füllen, um irgendwelche Fremdprogramme zu zerstören.
Programmschutz für Profis
3.3
87
Codierung von Programmen
Neben den Illegal-Opcodes, die wir in Kapitel 3.4 beschrieben haben, gibt es noch eine andere Methode, eigene Programme und speziell Kopierschutzabfragen vor Fremdeingriffen zu schützen. In dem nun folgenden Kapitel soll die Codierung von Programmen erklärt werden.
3.3.1
Verwendung der EXOR- Verknüpfung
Zuerst sollte der Begriff der Codierung allgemein erklärt werden, um eventuellen Mißverständnissen vorzubeugen. Codieren bedeutet, z.B. einen Text nach einen bestimmten System zu verschlüsseln. Der codierte Text kann nun nicht mehr gelesen werden, ohne daß man ihn zuvor mit demselben System wieder zu decodiert. Da ein Programmlisting, ähnlich wie ein Text auch, gelesen werden kann, müssen wir ein System finden, das die einzelnen Bytes eines Programms nach einer bestimmten Logik verändert, so wie es auch bei den einzelnen Buchstaben eines Textes der Fall ist. Dann muß das Programm in codierter Form mit einer zusätzlichen Decodierroutine ab gespeichert werden. Diese Routine muß vor dem Programmablauf gestartet werden, um die Bytes wieder in ihren Originalzustand zu versetzen. Systeme, mit denen eine Byte-Folge verändert werden kann, gibt es sicherlich recht viele, und einige davon werden wir Ihnen in diesen Kapitel vorstellen. Am einfachsten ist dieses Ziel jedoch mit der EXOR-Verknüpfung zu erreichen. EXOR ist eine logische Verknüpfung aus der Booleschen Algebra, die auf andere Verknüpfungen aufbaut. Es würde zu weit führen, an dieser Stelle die gesamte Grundstruktur dieser Algebra zu erklären, und deshalb beschränken wir uns hier auf die EXOR- Verknüpfung und den entsprechenden Assemblerbefehl EOR. Wie Sie sicherlich wissen, bestehen ein Assemblerbefehl und die dazugehörigen Parameter aus einem bzw. mehreren Bytes, die
Das große Anti-Cracker-Buch
88
sich jeweils aus acht Bits zusammensetzen. Verknüpft man ein Programm-Byte mit einem beliebigen anderen Byte, werden die einzelnen Bits der Bytes miteinander verknüpft. Man betrachtet also zuerst die beiden ersten Bits, dann die zweiten beiden Bits und so weiter. Sind alle acht Bits miteinander verknüpft worden, ergibt sich daraus eine völlig neue Bitfolge, also ein neues Byte. Bei der EXOR-Verknüpfung ist das Ergebnisbit immer dann gleich 1, wenn beide zu verknüpfenden Bits gleich lang sind. Sind die Bits gleich, so ist das Ergebnisbit gleich O. Hier die dazugehörige Tabelle: EXOR 00= 0
o 1 =1 10= 1 1 1
=0
Anhand eines Beispiels wollen einander verknüpft werden:
WIr
zeigen, wie zwei Bytes mit-
Byte 1 = 10000101 = $85 = 133 EXOR Byte 2 = 00101010 = S2A = 42 Ergebnis
: 10101111 = SAF = 175
Eine spezielle Eigenart dieser Verknüpfung, die vor allem bei der Codierung weiterhilft, besteht darin, daß ein Byte A, wird es zweimal mit einem Byte B EXOR-verknüpft, wieder das ursprüngliche Byte A ergibt. Das können wir an dem obigen Beispiel recht einfach nachvollziehen: Byte A = 10000101 = $85 = 133 EXOR Byte B = 00101010 = S2A = 42 Ergebnis : 10101111 = SAF = 175 EXOR Byte B = 00101010 = S2A = 42 Ergebnis
: 10000101
= S85 = 133
Programmschutz für Profis
89
Mit dieser Verknüpfung besitzen wir ein fast optimales System zur Programmcodierung. Eine Routine, die das Hauptprogramm codiert und auch als Decodierroutine mit abgespeichert werden kann, könnte so aussehen: 1000 1002 1005 1007 100A 100B 1000
LOX LOA EOR STA INX BNE RTS
#$00 Zeiger auf SOO setzen S2000,X Byte holen und #$F4 (de)codieren $2000,X Byte zurückschreiben Zeiger erhöhen $1002 zum Schleifenanfang, wenn kleiner 256 Rücksprung
Nach dem Abarbeiten steht das Hauptprogramm codiert im Speicherbereich von $2000 bis $20FF und kann so abgespeichert werden. Um die Codierroutine auch zum Decodieren benutzen zu können, muß der RTS-Befehl in $IOOD durch einen JMPBefehl zum Programmstart ersetzt werden. Außerdem sollte die Routine unmittelbar vor oder hinter dem Hauptprogramm stehen, um den dazwischenliegenden Speicherbereich nicht mit absichern zu müssen. Um Programme zu codieren, die länger als 256 Bytes sind, muß in der Routine noch ein zweiter Zähler benutzt werden, der die Anzahl der 256-Byte-Blöcke zählt. Außerdem muß nach jedem Block das HIGH-Byte der Zieladressen erhöht werden.
2027 2029 202B 202E 2030 2033 2034 2036 2039 203C
LOX LOY LOA EOR STA INX BNE INC INC INY
#$00 #$00 S5000,X #$F4 $5000,X $202B $2020 S2032
ersten Zeiger setzen zweiten Zeiger setzen Programm-Byte holen Byte verknüpfen Programm-Byte wieder abspeichern ersten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 HIGH-Byte des Programmzeigers erhöhen HIGH-Byte der Zieladresse erhöhen zweiten Zeiger erhöhen
90
Das große Anti-Cracker-Buch
2030 Cpy #$10 203F BNE $202B 2042 RTS
3.3.2
schon $10 Blöcke (de)codiert? wenn nicht, dann zum Schleifenanfang Rücksprung
Codierung mit verstecktem Einsprung
Das in Kapitel 3.3.1 beschriebene System zeichnet sich durch seine Einfachheit aus und ist aus diesem Grunde besonders für Leute geeignet, die noch keine große Erfahrung haben. Der Codierschutz ist aber recht einfach zu entfernen, indem man den Programmeinsprung am Ende der Decodierroutine durch ein BRK oder ein RESET (JMP $FCE2) ersetzt. Dann kann die Decodierroutine gestartet werden, ohne daß das Hauptprogramm abläuft. Da der Einsprung durch den JMP-Befehl auch bekannt ist, kann das nun decodierte Programm leicht nachverfolgt und ein eventuell vorhandener Kopierschutz entfernt werden. Wenn der Einsprung aber unbekannt ist, läßt sich das Programm nicht mehr so leicht nachverfolgen. Als Beispiel haben wir eine Routine erstellt, die das Programm "von hinten her" decodiert. Betrachtet man die Decodierroutine, sieht man nur eine Schleife, an deren Ende ein BRK-Befehl steht, welcher den Programmfluß nach der Routine stoppen müßte. In Wirklichkeit verzeigt der BNE-Befehl in $100B aber nur solange zu $1002, bis die Speicherzelle $lOOD decodiert und damit verändert wird. Der BNE-Befehl verzweigt aber intern gar nicht zu der Adresse $1002, sondern nur eine bestimmte Anzahl von Bytes nach vorne oder hinten. Im Speicher steht also nur der Operationscode für BNE und danach ein Byte, das den Einsprung bestimmt. Ist dises Byte zum Beispiel 10, so wird 10 Bytes nach vorne verzweigt. Alle Werte, die größer als 127 sind, veranlassen einen Sprung in die andere Richtung. Das heißt, daß die Bits 0 bis 7 die Schrittweite angeben, die also maximal 127 Byte betragen kann, und daß Bit 7 die Richtung bestimmt. Auf unser Programmbeispiel bezogen bedeutet das, daß wir den Wert, mit dem codiert wird, so wählen müssen, daß aus dem BNE $1002 nachher ein BNE $100E wird. In diesem Fall wäre das der Wert $F4.
Programmschutz für Profis
91
Unser Beispiel haben wir bewußt einfach gestaltet, so daß es leicht nachzuvollziehen ist. In der Praxis können Sie das Programm anders gestalten, indem Sie zum Beispiel dem BRKBefehl durch einen völlig unsinnigen JMP-Befehl ersetzen oder eine ganze Routine schreiben, die jeden, der das Prinzip nicht kennt, völlig verwirrt. Hier nun das Programm beispiel mit einem kleinen Hauptprogramm und der dazugehörigen Codierroutine: 1000 1002 1005 1007 100A 100B
LOX LOA EOR STA OEX BNE
#$FF S1000,X #$F4 S1000,X S1002
1000 BRK
Zeiger auf SFF setzen codiertes Byte holen mit Wert SF4 decodieren Byte zurückschreiben Zeiger um 1 vermindern unbedingter Sprung zum Schleifenanfang, da SOO nie erreicht wird scheinbares Programmende
Einsprung nach Abändern des BNE-Wert durch EOR #$F4: 100E 1010 1012 1015 1016 1018 1019 101B
LOX LOY INC INX BNE INY BNE BRK
#$00 #$00 $0020 S1012 S1012
ersten Zeiger setzen zweiten Zeiger setzen Rahmenfarbe erhöhen ersten Zeiger vermindern zum Schleifenanfang zweiten Zeiger erhöhen zum Schleifenanfang Programmende
Um das oben abgedruckte Programm zu codieren, brauchen wir ein Programm, das folgendermaßen aussehen könnte. Der Zeiger wird dabei auf $OE gesetzt, weil das erste zu codierende Byte, der LDX-Befehl, in $1000 + $OE = $lOOE steht.
92 2000 2002 2005 2007 200A 200B 2000
Das große Anti-Cracker-Buch LOX LOA EOR STA INX BNE RTS
#SOE S1000,X #SF4 S1000,X S2002
Zeiger auf SOE setzen erstes Byte holen Byte codieren Byte zurückschreiben Zeiger erhöhen zum Schleifenanfang Rücksprung
Das Programm besteht nun aus einem codierten und einem uncodierten Teil und sieht wie folgt aus: 1000 1002 1005 1007 100A 100B 1000 100E 1010 1011 1012 1013 1014 1016 1018 1019 101B 101C 101F
LOX LOA EOR STA OEX BNE BRK LSR 11? ?11 11? ??? BIT BIT ?17 BIT ?17 ASL ASL
#SFF S1000,X #SF4 S1000,X S1002 SF4,X
Zeiger setzen Byte holen und decodieren Byte zurückschreiben Zeiger vermindern zum Schleifenanfang scheinbares Programmende codiertes Programm
S1C SOE S03 S1E1E,X S1E1E,X
Ende des codierten Programms
3.3.3 Codierung mit Timer An den Schluß dieses Kapitels haben WIr einen Unterpunkt gesetzt, der sich mit einem komplexeren Codierungssystem beschäftigt. In den bislang beschriebenen Beispielen wurde jedes Byte eines Programmes immer mit dem gleichen Code verschlüsselt. Hier haben wir nun ein System gewählt, bei dem sich
Programmschutz für Profis
93
der Code wert andauernd ändert. Zu diesem Zweck lassen wir einen Timer der CIAs die Taktzyklen zählen und benutzen den Zählerwert als als Codewert. Bei dieser Codiermethode muß man darauf achten, daß sowohl die Codier- als auch die Decodierroutine sich zeitlich nicht voneinander unterscheiden. Das nun folgende Programm soll demonstrieren, welche Schwierigkeiten dabei entstehen. Es läßt dazu den Timer B eine bestimmte Zeit lang laufen und legt dann den ausgelesenen Wert im LOW-/HIGH-Byte-Format im Speicher ab. 2000 2001 2003 2006 2008 200B 2000 2010 2012 2015 2017 2018 201A 2010 2020 2023 2026 2028 202B 202C
SEI LOA STA LOA STA LOA STA LOA STA LOX INX BNE LOA STA LOA STA LOA STA CLI RTS
#$00 SOOOF #$FF SO006 #$FF SO007 #$99 SOOOF #$00 S2017 SO006 $4000 SO007 $4001 #$00 SOOOF
Interrupt verhindern Wert laden und Tirner stoppen LOW-Byte des Wertes in den Tirner schreiben HIGH-Byte des Wertes in Tirner schreiben Wert für "Timer im Continue-Modus starten" in Register schreiben Zeiger auf SOO setzen (Warteschleife) Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 LOW-Byte des Tirners laden und Wert in $4000 abspeichern HIGH-Byte laden und Wert in 4001 abspeichern Wert laden und damit Tirner stoppen Interrupt freigeben Rücksprung
Das BASIC-Programm soll die oben abgedruckte Routine starten und den Timerwert im LOW- und HIGH-Byte-Format ausgeben. In Zeile 30 wird das Programm wiederholt.
Das große Anti-Cracker-Buch
94
10 SYS2*4096 20 PRINT PEEK(4*4096),PEEK(4*4096+1) 30 GOT010
Wie Sie beim Programmablauf bestimmt festgestellt haben, ändern sich die Werte jedesmal. Das läßt sich dadurch erklären, daß der Timer, im Gegensatz zum Programm, kontinuierlich läuft. Zwar haben wir im Programm durch den SEI-Befehl alle maskierbaren Interrupts ausgeschaltet, doch der Video-Controller unterbricht das Programm immer noch. Um dieses zu verhindern, müssen wir vorher den Bildschirm ausschalten. Da der Bildschirm nicht an jeder beliebigen Stelle, sondern erst am Ende des Rasterstrahldurchlaufs ausgeschaltet werden kann, muß eine Warteschleife eingefügt werden. Diese liegt in dem folgenden Programm von $2009 bis $2011. 2000 SEI 2001 LOA $D011 2004 ANO #$EF 2006 STA $0011 2009 LOX #$00 200B LOY #$FO 2000 INX 200E BNE $2000 2010 INY 2011 BNE $2000 2013 LOA #$00 2015 STA $OOOF 2018 LOA #$FF 201A STA $0006 2010 LOA #$FF 201F STA $0007 2022 LOA #$99 2024 STA $OOOF 2027 LOX #$00 2029 INX 202A BNE $2029 202C LOA #$00 202E STA $OOOF
Interrupt verhindern Wert aus Steuerregister 1 laden Bit 4 löschen (Bildschirm ausschalten) Wert zurückschreiben erster Zeiger für Warteschleife zweiter Zeiger für Warteschleife ersten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 zweiten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 Wert laden und Timer stoppen LOW-Byte für Timer setzen HIGH-Byte für Timer setzen Timer im Continue-Modus starten Zeiger setzen (Warteschleife) Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 Timer stoppen
Programmschutz für Profis 2031 2034 2036 2039 203C 203F 2042 2045 2046
LOA ORA STA LOA STA LOA STA CLI RTS
$0011 #$10 $0011 $0006 $4000 $0007 $4001
95
Bildschirm wieder einschalten (Bit 4 setzen) LOW-Byte des Timerwertes in $4000 speichern HIGH-Byte in $4001 speichern Interrupt wieder freigeben Rücksprung
Auch dieses Programm kann wieder mit dem BASIC-Programm gestartet werden_ Nun wird der Bildschirm kurzzeitig ausgeschaltet, was sich durch ein Flimmern bemerkbar macht. Die ausgelesenen Timerwerte sind jetzt immer identisch. Nun kommen wir zu der eigentlichen Codierroutine, die die einzelnen Bytes des Programmes mit den Werten aus dem Timer codiert. Da der Timer kontinuierlich abläuft, entsteht für jedes codierte Byte ein anderer Wert. Die einzelnen Programm-Bytes werden dadurch EXOR-verknüpft. Daraus ergibt sich, daß das folgende Programm auch als Decodierprogramin benutzt werden kann, wenn die Anfangswerte für den Timer unverändert bleiben. 2000 2001 2004 2006 2009 200B 2000 200E 2010 2011 2013 2015 2018 201A 2010
SEI LOA ANO STA LOX LOY INX BNE INY BNE LOA STA LOA STA LOA
$0011 #$EF $0011 #$00 #$FO $2000 $2000 #$00 $OOOF #$FF $0006 #$FF
Interrupt verhindern Bildschirm ausschalten (Bit 4 löschen) ersten Zeiger laden zweiten Zeiger laden ersten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 zweiten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 Timer stoppen LOW-Byte des Timers setzen HIGH-Byte des
Das große Anti-Cracker-Buch
96 201F STA S0007 2022 LOA #$99 2024 STA $DOOF 2027 LOX #$00 2029 LOY #$FO 202B LOA S5000,X 202E EOR $D006 2031 STA S5000,X 2034 INX 2035 BNE S202B 2037 INC S2020 203A INC S2033 2030 INY 203E BNE S202B 2040 LOA #$50 2042 STA S2020 2045 STA S2033 2048 LOA #$00 204A STA $DOOF 2040 LOA $D011 2050 ORA #$10 2052 STA $D011 2055 CU 2056 RTS
Timers setzen Timer im Continue-Modus starten ersten Zeiger setzen zweiten Zeiger setzen Programm-Byte holen mit LOW -Byte des Timers verknüpfen Programm-Byte wieder abspeichern ersten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 HIGH-Byte des Programmzeigers erhöhen HIGH-Byte der Zieladresse erhöhen zweiten Zeiger erhöhen zum Schleifenanfang, wenn kleiner 256 ursprüngliches HIGH-Byte der Programm- und Zieladresse wieder herstellen Timer stoppen Bildschirm wieder einschalten (Bit 4 setzen) Interrupt wieder freigeben Rücksprung
Die folgenden Bytes sollen ein beliebiges Programm darstellen, das codiert werden soll: 5000 5008 5010 5018 5020 5028 5030
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
55 55 55 55 55 55 55
Programmschutz für Profis
97
Nach dem Ablauf der Codierroutine sieht der Speicherbereich folgendermaßen aus: 5008 5010 5018 5020 5028 5030
33 83 13 E3 73 C3
01 91 61 F1 41 01
17 E7 77 C7 57 27
65 F5 45 05 A5 35
4B OB AB 3B 8B 1B
59 29 B9 09 99 69
AF 3F 8F 1F EF 7F
BO 00 9D 6D
FO 40
Nach erneutem Durchlauf des Programmes sieht der Speicherbereich wieder wie oben aus. Das Decodierprogramm haben wir hier als BASIC-Loader abgedruckt, für den Fall, daß Sie keinen Assembler besitzen oder daß Ihnen das Abtippen des Assemblercodes zu mühselig erscheint. 10 FOR X=O TO 86 20 READ A:POKE2*4096+X,A 30 S=S+A 40 NEXT X 50 I~ S<>11452 THEN PRINT"FEHLER IN OATAS":STOP 60 REM SYS2*4096 100 OATA120,173,17,208,41,239,141,17,208,162,0,160,240,232,208,253,200,2 08 110 OATA250,169,0,141,15,221,169,255,141,6,221,169,255,141,7,221,169,153 120 OATA141,15,221,162,0,160,240,189,0,80,77,6,221,157,0,80,232,208,244, 238 130 OATA45,32,238,51,32,200,208,235,169,80,141,45,32,141,51,32,169,0,141 1400ATA15,221,173,17,208,9,16,141,17,208,88,96
3.3.4
Einzelschritt- Decodierung
Bei vielen Programmschutz-Anwendungen stellt sich die Frage: Wie verhindere ich, daß ein "Knacker" mitten im Programmlauf seinen RESET -Schalter betätigt und sich dann in aller Ruhe das Schutzprogramm ansieht? Ein 'CBM80' (siehe Kapitel 2.3) im
98
Das große Anti-Cracker-Buch
Speicher ist nicht zuverlässig genug, und nicht immer hat man für Programme Speicherbereiche frei, die bei einem RESET gelöscht werden (siehe Kapitel 7.1). Es gibt allerdings noch eine andere Methode, sich gegen unerwünschte Blicke zu wehren, nämlich indem man sein Programm codiert. Ein Programm muß natürlich vor seinem Ablauf erst wieder decodiert werden, aber es reicht ja, immer nur den nächsten Befehl, der ausgeführt werden soll, zu entschlüsseln. Der vorhergehende Befehl wird nach seinem Ablauf wieder codiert. Dadurch bleibt immer nur ein kleiner Teil des Programmes sichtbar. Die beiden am Ende dieses Unterkapitels abgedruckten Programme leisten das Gewünschte. Das erste Programm codiert ein beliebiges Maschinenprogramm. Dieses muß als ersten Befehl 'JSR $COOO' enthalten, um die Decodierroutine zu aktivieren. Die ersten sechs Bytes bleiben uncodiert, damit das Programm überhaupt starten kann. Die verwendete Codierung sieht so aus, daß zu dem aktuellen Byte das LOW-Byte seiner Adresse hinzuaddiert und das Ergebnis mit 'EOR #$55' verknüpft wird. Sie können selbstverständlich eine wesentlich komplexere Codierung verwenden. Das zweite Programm ist ein sogenannter Einzelschritt-Decodierer. Es verwendet den IRQ-Timer A, um immer dann eine Unterbrechung zu erzeugen, wenn gerade ein Befehl abgearbeitet worden ist. Dann kann der letzte Befehl codiert und der nächste Befehl decodiert werden. Bei dem zu codierenden Programm gibt es einiges zu beachten. Wie schon gesagt muß es mit 'JSR $COOO' beginnen. Es darf kein selbstmodifizierender Code verwendet werden, außer wenn man dabei die Codierung beachtet. Datentabellen können nicht mitcodiert werden. Man sollte keine allzu langen Schleifen verwenden, da die Ablaufgeschwindigkeit drastisch gesenkt wird. Die Tastaturabfrage bleibt während des Programms abgeschaltet. Betriebssystemroutinen können aufgerufen werden, da das ROM
Programmschutz für Profis
99
weder codiert noch decodiert wird. Allerdings werden dabei Daten, die sich im RAM-Bereich unter dem ROM befinden, teilweise zerstört. Der Befehl 'SEI' führt zum Abbruch des CodierensjDecodierens, wodurch man zeitkritische Teile uncodiert in den Programmtext einfügen kann. Dabei müssen drei Bytes nach dem 'SEI' mitcodiert werden. Mit CLI nimmt der Einzelschritt-Decodierer seine Arbeit wieder auf. Wenn Sie die Einzelschritt-Ebene verlassen wollen, so müssen Sie Ihr Programm mit folgenden drei Befehlen abbrechen: SEI
JSR SFF84 JSR SFF8A
Vergessen Sie bitte nicht, daß die Bytes des Befehls 'JSR $FF84' mitcodiert werden sollten. Wenn die Codierroutine Sie nach der "Endadresse+l" fragt, geben Sie die Adresse des Befehls 'FF8A' an. Noch sicherer wird das Programm, wenn man 'illegale Opcodes' (Kapitel 3.4) verwendet, die sich problemlos verarbeiten lassen. Zur Bedienung ist folgendes zu sagen: geben Sie zuerst den BASIC-Loader der Codierroutine ein und starten Sie ihn mit 'RUN'. Laden Sie nun Ihr zu codierendes (Maschinen-) Programm. Die Codierroutine wird mit 'SYS 49152' gestartet. Nachdem Sie die Fragen nach der Start- und Endadresse beantwortet haben, wird Ihr Programm verschlüsselt. Bevor Sie es laufenlassen können, muß sich zuerst der Einzelschritt-Decodierer im Speicher befinden. Wenn Sie den entsprechenden BASIC-Loader benutzt haben und Ihr Programm kurz dahinter steht, zum Beispiel ab $CIOO, dann können Sie gleich beides als ein Programm abspeichern.
100
Das große Anti-Cracker-Buch
Codierroutine: BASIC-Loader: 100 FORI=1T0156STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(BS)-48:IFB>9THENB=B -7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:"iPEEK(63)+PEEK(64)*256:STOP 300 DATAA9,74,AO,CO,20,1E,AB,20,3E,CO,18,69,06,85,FB, 139 301 DATA8A,69,00,85,FC,A9,85,AO,CO,20,1E,AB,20,3E,CO, 9 302 DATA85,FD,86,FE,AO,00,B1,FB,20,96,CO,91,FB,E6,FB, 53 303 DATADO,02,E6,FC,A5,FB,C5,FD,DO,ED,A5,FC,C5,FE,DO, 7 304 DATAE7,60,20,42,CO , AA,20,52,CO,OA,OA,OA,OA,8D,50, 74 305 DATACO,20,52,CO,09,00,60,20,CF,FF,38,E9,30,90,OF, 57 306 DATAC9,OA,90,OA,E9,07,C9,OA,90,05,C9,10,BO,01,60, 175 307 DATAA2,F8,9A,A9,3F,20,D2,FF,4C,74,A4,93,53,54,41, 236 308 DATA52,54,41,44,52,45,53,53,45,3A,20,24,00,OD,45, 125 309 DATA4E,44,41,44,52,45,53,53,45,2B,31,3A,20,24,00, 115 310 DATA18,65,FB,49,55,60,00,00,00,00,00,00,00,00,00, 118
Einzelschritt-Decodierer: BASIC-Loader 100 FORI=1T0132STEP15:FORJ=OT014:READA$:B$=RIGHTS(AS,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATA78,A9,37,8D,14,03,A9,CO,8D,15,03,68,AA,18,69, 157 301 DATA01,85,AC,68,A8,69,00,85,AD,98,48,8A,48,A9,16, 78 302 DATA8D,04,DC,A9,00,8D,05,DC,A9,19,8D,OE,DC,AD,OD, 119 303 DATADC,A2,02,CA,DO,FD,24,FF,58,60,AO,00,B1,AC,20, 15 304 DATA6C,CO,91,AC,C8,CO,03,DO,F4,BA,BD,05,01,85,AC, 102 305 ·DATABD ,06,01,85 ,AD ,AO, 00,B1 ,AC, 20, 78, co, 91 ,AC,C8, 80 306 DATACO,03,DO,F4,A9,11,8D,OE,DC,AD,OD,DC,68,A8,68, 198 307 DATAAA,68,40,18,65,AC,8C,74,CO,18,69,FF,49,55,60, 185 308 DATA49,55,8C,7F,CO,38,E9,FF,38,E5,AC,60,00,00,00, 178
Programmschutz für Profis
Codierroutine: Assemblerlisting benutzte Register: $FB/$FC: Zeiger auf zu codierendes Programm $FD/$FE: Zeiger auf Endadresse + 1 COOO LDA #$74 C002 LDY #$CO C004 JSR $AB1E C007 JSR $C03E COOA CLC COOB ADC #$06 COOD STA SFB COOF TXA C010 ADC #$00 C012 STA $FC C014 LDA #$85 C016 LDY #$CO C018 JSR $AB1E C01B JSR $C03E C01E STA $FD C020 STX $FE C022 LDY #$00 C024 LDA ($FB),Y C026 JSR $C096 C029 STA ($FB),Y C02B INC $FB C02D BNE SC031 C02F INC $FC C031 LDA $FB C033 CMP $FD C035 BNE $C024 C037 LDA $FC C039 CMP $FE C03B BNE $C024 C03D RTS
Text 1: LOW-Byte von SC074 Text 1: HIGH-Byte von $C074 Text ausgeben Eingabe einer Hexadezimalzahl nach A/X Carry für Addition löschen Sechs addieren in Zeiger auf Programm LOW speichern HIGH-Byte der Eingabe nach Akku Ubertrag addieren in Zeiger auf Programm HIGH speichern Text 2: LOW-Byte von $C085 Text 2: HIGH-Byte von $C085 Text ausgeben Eingabe einer Hexadezimalzahl nach A/X LOW-Byte speichern HIGH-Byte speichern Index auf Null setzen Programm-Byte holen Byte codieren und wieder wegspeichern Zeiger LOW-Byte erhöhen verzweige, wenn kein Ubertrag Zeiger HIGH-Byte erhöhen Zeiger LOW-Byte holen mit Endadresse LOW vergleichen verzweige, wenn ungleich Zeiger HIGH-Byte holen mit Endadresse HIGH vergleichen verzweige, wenn ungleich Rücksprung
101
102
Das große Anti-Cracker-Buch
Eingabe einer vierstelligen Hexadezimalzahl: C03E C041 C042 C04S C046 C047 C048 C049 C04C C04F COS1
JSR TAX JSR ASL ASL ASL ASL STA JSR ORA RTS
SC042 SCOS2
SCOSO SCOS2 #$00
zweistellige Hexzahl holen und als HIGH-Byte nach X schieben eine Hexziffer holen durch viermaliges Links-SHIFTen ins obere Halbbyte bringen und für OR-Verknüpfung speichern eine Hexziffer holen mit oberem Halbbyte verknüpfen Rücksprung
Eine Hexziffer holen und umwandeln: COS2 COSS COS6 COS8 COSA COSC COSE C060 C062 C064 C066 C068 C069 C06B C06C C06E C071
JSR SEC SBC BCC CMP BCC SBC CMP BCC CMP BCS RTS LDX TXS LDA JSR JMP
SFFCF #$30 SC069 #$OA SC068 #$07 #$OA SC069 #$10 SC069 #$F8 #$3F SFFD2 SA474
BASIN: eingegebenes Zeichen holen Carry für Subtraktion setzen S30 (=48) abziehen verzweige, wenn zu klein mit SOA (=10) vergleichen verzweige, wenn kleiner Sieben subtrahieren mit SOA (=10) vergleichen verzweige, wenn zu klein mit S10 (=16) vergleichen verzweige, wenn zu groß Rücksprung X-Register mit SF8 (=248) laden Stapelzeiger initialisieren ASCII "1" Fragezeichen ausgeben zurück zum BASIC-Interpreter
Text 1: C074: "STARTADRESSE: $" Text 2: COBS: "ENDADRESSE+1: $"
Programmschutz für Profis C074 C07C C084 C08C C094
93 52 00 45 24
53 45 00 53 00
54 53 45 53 18
41 53 4E 45 65
52 45 44 2B FB
54 3A 41 31 49
41 20 44 3A 55
44 24 52 20 60
.STARTAD RESSE: $ .. ENDADR ESSE+1: $ ...... .
Unterroutine zum Codieren: C096 C097 C099 C09B
CLC ADC $FB EOR #55 RTS
Carry für Addition löschen Programmzeiger LOW-Byte addieren mit EXOR #$55 (=85) verknüpfen Rücksprung
Einzelschritt - Decodierer: Assemblerlisting benutzte Register: $AC/ $AD: Zwischenspeicher für den Programmzeiger COOO C001 C003 C006 C008 COOB COOC COOD COOE C010 C012 C013 C014 C016 C018 C019 C01A C01B C01C C01E
SEI LDA STA LDA STA PLA TAX CLC ADC STA PLA TAY ADC STA TYA PHA TXA PHA LDA STA
#$37 S0314 #$CO $0315
#$01 SAC
#$00 SAD
#$16 SDC04
Interrupts sperren LOW-Byte von $C037 in IRQ-Vektor LOW speichern HIGH-Byte von SC037 in IRQ-Vektor HIGH speichern Rücksprungadresse LOW vom Stapel holen zwischenspeichern Carry für Addition löschen Eins addieren und in Programmzeiger LOW speichern Rücksprungadresse HIGH vom Stapel holen zwischenspeichern übertrag addieren und in Programmzeiger speichern Rücksprungadresse HIGH-Byte wieder auf Stapel legen Rücksprungadresse LOW-Byte wieder auf Stapel legen LOW-Byte von S0016 (=22) in IRQ-Tirner LOW speichern
103
104 C021 C023 C026 C028 C02B C02E C030 C031 C033 C035 C036
Das große Anti-Cracker-Buch LDA STA LDA STA LDA LDX DEX BNE BIT CLI RTS
#$00 $DC05 #$19 $DCOE SDCOD #$02 SC030 SFF
HIGH-Byte von S0016 (=22) in IRQ-Timer HIGH speichern Akku mit S19 (=25) laden und Timer mit neuem Zählwert starten Interrupt-Kontrollregister löschen Zähler für Zeitschleife setzen Zähler verringern verzweige, wenn ungleich Null drei Taktzyklen warten Interrupts erlauben Rücksprung
In terruptrou tine des Einzelschritt - Decodierers: C037 C039 C03B C03E C040 C041 C043 C045 C046 C049 C04B C04E C050 C052 C054 COS? C059 COSA COSC COSE C060 C063 C066 C067 C068
LDY LDA JSR STA INY CPY BNE TSX LDA STA LDA STA LDY LDA JSR STA INY CPY BNE LDA STA LDA PLA TAY PLA
#$00 (SAC),Y SC06C (SAC),Y #$03 SC039 S0105,X SAC S0106,X SAD #$00 (SAC),Y SC078 (SAC),Y #$03 SCOS2 #$11 SDCOE $DCOD
Index auf Null setzen Programm-Byte holen Byte codieren und wieder speichern Programmzeiger erhöhen schon drei Bytes codiert? verzweige, wenn nein Stapelzeiger ins X-Register Programmzeiger LOW-Byte holen und zwischenspeichern Programmzeiger HIGH-Byte holen und zwischenspeichern Index auf Null setzen Programm-Byte holen Byte decodieren und wieder speichern Programmzeiger erhöhen schon drei Bytes codiert? verzweige, wenn nein Akku mit S11 (=17) laden und Timer wieder starten Interrupt-Kontrollregister löschen V-Register vom Stapel holen und übernehmen X-Register vom Stapel holen
Programmschutz für Profis C069 TAX C06A PLA C06B RTI
105
und übernehmen Akku vom Stapel holen Rückkehr vom Interrupt
Unterroutine zum Codieren: C06C C060 C06F c072 C073 C075
con
CLC ADC STY CLC ADC EOR RTS
SAC SC074 #$FF #$55
Carry für Addition löschen Programmzeiger LOW-Byte addieren V-Register für Addition speichern Carry für Addition löschen V-Register addieren mit EXOR #$55 (=85) verknüpfen Rücksprung
Unterroutine zum Decodieren: C078 C07A C07D C07E C080 C081 C08l
EOR STY SEC SBC SEC SBC RTS
#$55 SC07F #$FF SAC
mit EXOR #$55 (=85) verknüpfen V-Register für Subtraktion speichern Carry für Subtraktion setzen V-Register subtrahieren Carry für Subtraktion setzen Programmzeiger LOW-Byte subtrahieren Rücksprung
3.3.5 Codierung über ASCII-Code Eine weitere sehr raffinierte Methode zum Codieren von Daten wollen wir Ihnen jetzt vorstellen. Sie zeichnet sich nicht nur durch ihren komplexen Code, sondern auch durch das verblüffende Entcodierungssystem aus. Der Vorteil bei diesem System ist, daß der Knacker die Entcodierungsroutine kaum als solche erkennt und nur schwer herausfinden kann, wo die entcodierten Daten abgelegt werden. Doch kommen wir jetzt zur Erlärung des Systems.
Das große Anti-Cracker-Buch
106
Die Grundüberlegung beruht auf der Tatsache, daß ASCII-Zeichen, die mit der BSOUT -Routine ($FFD2) auf den Bildschirm geschrieben werden, zuvor in den Bildschirmcode gewandelt werden. Wäre es nicht möglich, entsprechend codierte Programme mit Hilfe der BSOUT -Routine in die Bildschirm zu schicken, und dort zu starten? Durch Setzen des Cursers könnte man die Adresse, zu der die Daten geschoben werden, genau angeben. Kaum ein Knacker würde vermuten, daß durch eine harmlose Ausgabe von Zeichen auf dem Bildschirm ein Programm geschrieben wird. Das hört sich alles fantastisch an und ist mit ein paar Tricks in die Praxis umzusetzen. Das ganze hat leider noch einen kleinen Haken, denn es ist nicht möglich, jedes Byte in einen dazugehörigen ASCII-Code zu wandeln, weil es außer den normalen Zeichen auch Steuer-Codes gibt, die nicht in den Bildschirm-Speicher geschrieben, sondern ausgeführt werden. Außerdem stellt sich das Problem, daß Bildschirm-Codes, die größer als $7F sind, durch dieselben ASCIICodes wie die Bildschirm-Codes, die kleiner als $80 sind, erzeugt werden, nur mit dem Unterschied, daß BildschirmCodes größer $7F mit gesetztem REVERSE-Flag erreicht werden. Zur Verdeutlichung zeigen wir Ihnen dies noch an einem Beispiel.
ASCII·Code $41 $41
S93
REVERSE
Bildschirm·Code
aus ein xxx
S01
$81
Steuer·Code (Bildschirm löschen)
Um diese Probleme zu lösen, haben wir uns den Zusammenhang zwischen ASCII-Code und Bildschirm-Code näher angesehen und daraufhin unseren eigenen ASCII-Code entwickelt. Bei der Umwandlung von ASCII-Code in Bildschirm-Code ändert sich nur das HIGH-Nibbel, wie man aus folgender Tabelle entnehmen kann:
Programmschutz für Profis ASCII-Code $40 $50 S20 S30 SCO $00 SAO SBO
107
Bi ldschi rm-Code $00 $10 $20 S30 $40 $50 $60 $70
Die Bildschirm-Codes größer als $7F werden mit unserer Routine anders gewandelt, als es das Betriebssystem machen würde: ASCII-Code SEO SFO $60 S70 S80 S90 SOO $10
Bi ldschirm-Code $80 590 SAO SBO
sco $00 SEO $FO
Eine entsprechende Ausgabe-Routine wandelt unsere speziellen ASCII-Codes in die normalen Codes um und bringt diese mit gesetztem REVERSE-Flag auf den Bildschirm_ Mit diesem Trick kann man alle möglichen Bildschirm-Codes in ASCII-Codes und umgekehrt wandeln. Durch Änderung des HIGH-Bytes der Bildschirm-Anfangsadresse und durch Positionierung des Cursers kann man Daten in jeden beliebigen Speicherbereich schreiben. Um die zu codierenden Daten noch stärker zu "verstümmeln" haben wir jedes Byte zusätzlich mit $2F addiert. Unser nun folgendes Codierungsprogramm wandelt das sich im Akku befindliche Zeichen in den entsprechenden Code um. X und Y - Register werden gerettet.
108 1000 STX $FE 1002 STA $FD 1004 AND #$FO 1006 LDX #$OF 1008 CMP $101D,X 100B BEQ $1010 100D DEX 100E BPL $1008 1010 LDA $FD 1012 AND #$OF 1014 ORA $102D,X 1017 CLC 1018 ADC #$2F 101A LDX $FE 101C RTS 101D BRK
Das große Anti-Cracker-Buch X-Register retten Akku zwischenspeichern HIGH-Nibbel isolieren Zeiger auf Tabelle setzen Zeichen mit Tabelle vergleichen verzweige, wenn Zeichen gefunden Zeiger verringern unbedingter Sprung Akku wieder holen HIGH-Nibbel löschen und mit neuem HIGH-Nibbel verknüpfen Carry für Addition löschen Byte mit $2F addieren X-Register wieder holen Rücksprung
101E 10 20 30 40 50 60 70 80 Daten, mit denen das 102690 AO BO co DO EO FO 40 Byte verglichen wird 102E 50 20 30 CO DO AO BO EO entsprechende HIGH-Nibbel 1036 FO 60 70 80 90 00 10 00 die eingesetzt werden
Die Umrechnung der Startadresse in unserer EntcodierungsRoutine verfügt zusätzlich über eine Umrechnung der gewünschten Adresse in ~ie dazugehörige Curser-Position. Die Adresse gibt an, wohin das nächste Zeichen durch die BSOUTRoutine geschrieben werden soll. Somit kann man Daten an jeden beliebigen Platz des Speichers schreiben und ist nicht auf den momentan vom sichtbaren Bildschirm beanspruchten Speicherbereich angewiesen. Sie kann auch Daten nach $COOO schreiben, obwohl das Bildschirm-RAM immer noch ab $0400 liegt. Die Adresse, ab der die Daten geschrieben werden sollen, muß im Akku und X-Register stehen, wobei im Akku das LOW- und im X-Register das HIGH-Byte stehen muß. Die Routine liegt ab $112F. Das Byte, das entcodiert und geschrieben werden soll, muß sich im Akku befinden, wenn Sie die Ausgabe-Routine ab $1100 anspringen.
Programmschutz für Pro fis
Das folgende Programm erfüllt alle diese Aufgaben: 1100 1102 1103 1105 1107 1109 110B 110E 1110 1111 1113 1115 1116
STX SEC SBC STA AND LDX CMP BEQ DEX BPL LDA CLC BCC
SFE
X-Register retten Carry für Subtraktion setzen #$2F S2F vom Byte abziehen SFD und Wert zwischenspeichern #$FO HIGH-Nibbel isolieren #$07 Zeiger auf Tabelle setzen S1145,X Wert mit Byte aus Tabelle vergleichen S1118 verzweige, wenn Wert gefunden Zeiger verringern S110B verzweige, wenn nicht ganz verglichen SFD Zeichen laden Carry löschen S1123 unbedingter Sprung
1118 111A 111C 111E 1120 1123 1126 1128 112A 112C 112D 112E
LDA STA LDA AND ORA JSR LDA STA LDX RTS NOP NOP
#$01 SC7 SFD #$OF S114D,X SFFD2 #$00 SC7 SFE
REVERSE - Flag setzen Zeichen laden LOW-Nibbel isolieren und mit neuem Nibbel verknüpfen Zeichen ausgeben REVERSE-Flag wieder löschen X-Register wieder herstellen Rücksprung
Routine zum Errechnen der Curser-Position: 112F 1132 1134 1136 1138 1139 113B
STX LDX CMP BCC SEC SBC INX
S0288 #$00 #$28 S113E #$28
HIGH-Byte der Adresse speichern X-Register für Division löschen LOW-Byte durch 40 teilen im X-Register steht das Ergebnis der
109
Das große Anti-Cracker-Buch
110 113C 113E 1140 1142 1145
BCS STX STA JMP BRK
$1134 $06 $03 $E56C
Division Ergebnis als Curser-Zeile nehmen Rest ist Curser-Spalte Curser positionieren
1146 10 60 70 80 90 EO FO AO Tabelle unserer eigenen ASCII-Codes 114E BO 20 30
3.4
co
00 40 50 00 Tabelle zur Umrechnung in normalen ASCII-Code
Die Illegal-Codes
Im nun folgenden Kapitel wollen wir eine spezielle Art der Operations-Codes (kurz Opcodes genannt) besprechen_ Es handelt sich hierbei um die illegalen Opcodes, die bei der Programmierung des C64 normalerweise nicht berücksichtigt werden, da sie nicht zu den offiziellen Assemblerbefehlen gehören_
3.4.1
Erklärung der IIIegal-Opcodes
Wenn Sie schon öfter in Maschinensprache programmiert haben, wird Ihnen beim Disassemblieren emlger Speicher bereiche bestimmt schon aufgefallen sein, daß dabei nicht nur Assemblerbefehle auf den Bildschirm gebracht werden_ Ab und zu mogeln sich auch drei Fragezeichen zwischen die Befehle, was dann folgendermaßen aussehen kann: 12FO LDA $0830 12F3 11? 12F4 INX
Dieses 'Fragezeichen-Phänomen' kann beim ersten Betrachten Verwirrung stiften, läßt sich aber relativ einfach erklären: Ein Assemblerbefehl kann zwar aus mehreren Bytes bestehen, so benötigt zum Beispiel LDA #$40 zwei Bytes; der eigentliche Befehl, also LDA, besteht aber immer nur aus einem Ein-Byte-
Programmschutz für Pro fis
111
Codewert. Beim Disassemblieren wird dieser Codewert, hier A9, gelesen und der entsprechende Befehl auf den Bildschirm gebracht. Mit einem Byte ließen sich also theoretisch 256 verschiedene Befehle definieren. Da die tatsächliche Anzahl der Befehle aber wesentlich kleiner ist, bleiben einige Codewerte unbenutzt, also auch undefiniert. Man spricht hier von undefinierten Operationscodes oder von illegalen Opcodes. Beim Versuch, diese Opcodes zu disassemblieren, wird kein entsprechender Befehl gefunden und stattdessen nur drei Fragezeichen auf dem Bildschirm ausgegeben. Daß die illegalen Opcodes in der offiziellen Programmierung der 6510-Maschinensprache keine Verwendung finden, heißt aber nicht, daß sie keine Funktionen erfüllen. Vielmehr ist hier das Gegenteil der Fall, was Tab. 2.4.1 zeigt. Hier ist die erste der drei Gruppen, in die sich die illegalen Opcodes aufteilen lassen, abgebildet. Trifft der Prozessor auf diese Opcodes, stürzt der Rechner ab, oder sie werden einfach überlesen, ohne eine Funktion auszuführen, wie es von dem Opcode $EA (NOP) her bekannt ist.
Das große Anti-Cracker-Buch
112
Illegal Codes Ab-
NOP
3
02 12 22 32 42 52 62 72 92 B2 02 F2
Abb. 3.4.1:
OC 1C 3C 5C 7C OC 80- FC 82 89 C2 04 E2 F4 04 14 34 44 54 64 74
Die erste Gruppe der Illegal-Codes
Die zweite Gruppe verbindet jeweils zwei bekannte Opcodes miteinander. Hierbei können alle Adressierungsarten verwendet werden, die von dem Befehl ST A her bekannt sind.
Programmschutz für Pro fis
Befehl Adru alerung
113
ROR: DEC: INC: ADC CMP SBC 63 C3 E3
LDA: TAX
73
D3
F3
B3
4F
6F
CF
EF
AF
3F
5F
7F
DF
FF
I
1B
3B
5B
7B
DB
FB
BF
ZP
07
27
47
67
C7
E7
A7
ZP,X
17
37
57
77
D7
F7
I
ZP,Y
I
I
I
I
I
I
B7
ROL: AND
( ,X)
ASL: ORA 03
23
LSR: EOR 43
O,Y
13
33
53
ABS
OF
2F
ABS,X
1F
ABS,Y
Abb. 3.4 .2:
A3
Die zweite Gruppe der Illegal-Codes
Die Befehle der dritten Gruppe lassen sich durch bekannte Opcodes nur sehr schwer ersetzen. Sie führen, wie die Befehle der zweiten Gruppe auch, zwei oder mehr Befehle auf einmal aus. Da es sich dabei zumeist um recht komplexe Operationen handelt, ist der Einsatzbereich ziemlich begrenzt. Es sei hier noch erwähnt, daß die illegalen Opcodes nur unbeabsichtigte Nebenprodukte des eigentlichen Befehlssatzes sind und aus diesem Grund auch nicht bei jedem Computer die gleiche Wirkung erzielen.
114
Das große Anti-Cracker-Buch
ILlEGAl - CODES OBMM 2BMM 4BMM 6BMM 83MM 87MM 8BMM 8FLO HI 93MM 97MM 9B LO HI 9C LO HI 9ELO HI 9FLO HI BB LO HI CBMM EBMM
Abb.3.4 .3;
AND MM und bringt Negativflag ins Carry wie OB MM AND#MM: LSR wenn Dezimal = 0: AND #MM : ROR: ausserdem kommt Bit 0 des Akkus ins Carry und Bit 5 EOR mit Bit 6 ins Overflow Akku AND mit X-Register kommt nach (MM,X) Akku AND mit X - Register kommt nach MM TXA:AND#MM Akku AND mit X-Register kommt nach LO HI AND zwischen Akku, X- Register und die Summe aus 1 und MM+1 kommt nach (MM),V Akku AND X-Register kommt nach MM,X Akku AND mit X- Register in Stapelzeiger, dann Stapelzeiger AND #HI+1 nach LO HI,V V - Register AND mit #HI + 1 nach HI LO,X X-RegisterAND mit#NN+1 nach HI LO,V AND zwischen Akku, X-Register, #HI+1 nach HI LO,V HI LO, V AND mit Stapelzeiger Ins X - Register, dann TXS:TXA Akku AND mit X-Register ins X-Register, dann X-Register minus #MM (ohne Carry) SBC#MM
Dritte Gruppe der Illegal - Codes
3.4.2 Anwendung der IIIegal-Opcodes
Die Verwendung von undefinierten Opcodes in einem Programm bringt für den Programmierer drei Vorteile mit sich. Als erstes wäre zu erwähnen, daß sich bestimmte Befehlsfolgen durch Illegal-Opcodes kürzer, das heißt mit weniger Bytes darstellen lassen. Zu diesem Zweck sind diese Opcodes aber denkbar ungeeignet, da das Programm nur unwesentlich kürzer wird, der Programmieraufwand aber erheblich ansteigt, da die Codes direkt als Hexwerte eingegeben werden müssen. Der zweite Vorteil liegt darin, daß für emlge Befehle bzw. Befehlskombinationen neue Adressierungsarten verwendet wer-
Programmschutz für Profis
115
den können, die normalerweise nicht zur Verfügung stehen. Aber auch hier ist der Aufwand so hoch, daß man besser auf die Standardbefehle zurückgreifen sollte. Der dritte und einzig wesentliche Vorteil besteht darin, daß die Illegal-Opcodes von einen normalen Disassembler nicht gelesen und in Befehlsworte "übersetzt" werden können. Dadurch wird das Lesen der Programme für Unbefugte erheblich erschwert. Verfügt man nicht über die oben abgebildeten Tabellen, wird dieses sogar zu einer unüberwindlichen Hürde. Wollen Sie also wichtige Teile Ihres Programms vor Fremdzugriffen schützen, bieten die Illegal-Opcodes eine große Hilfe. Es ist zum Beispiel äußerst sinnvoll, die Abfrage des Kopierschutzes auf Diskette durch Illegal-Opcodes zu verstecken. Ein weiteres Beispiel bezieht sich auf die Decodierung von Programmen. Hier könnte man die Decodierroutine für das Hauptprogramm nochmals codieren. Dieses erfordert dann eine weitere, jedoch sehr kurze Decodierroutine, die mit Hilfe der Illegal-Opcodes geschrieben werden kann. Ist der Einsprung des Programms nicht bekannt, so ist es nahezu unmöglich, überhaupt eine Codier- bzw. Decodierroutine zu entdecken. Es ist also auch nicht möglich, das Programm zu decodieren und dann zu verändern. Die einfachste Methode, ein Programm durch Illegal-Opcodes unleserlich, also auch unverständlich, zu gestalten, besteht darin, zwischen einzelne Befehle einfach Illegal-Opcodes zu setzen, die ein NOP ausführen. Diese Opcodes finden Sie in Abbildung 3.4.1. Wesentlich besser ist es jedoch, das Programm selbst mit Illegal-Opcodes zu schreiben. Wie es möglich ist, ein ganz simples Programm mit Hilfe der Illegal-Opcodes so zu verschlüsseln, daß es für jeden Unbefugten unlesbar wird, wollen wir an dem nun folgenden Beispiel demonstrieren. Wir haben ein kleines Programm geschrieben, das durch zwei ineinander verschachtelte Schleifen die Rahmenfarbe verändert. Hierbei dient die Speicherzelle $FB (251) als Zähler für die innere Schleife und das X-Register als Zähler für die äußere Schleife.
Das große Anti-Cracker-Buch
116 1000 1002 1004 1006 1009 100B 1000 100F 1011 1012 1014
LOX LOA STA OEC OEC LOA CMP BNE INX BNE BRK
#$00 #$00 SFB $0020 SFB SFB #$00 S1006 S1006
Zeiger für äußere Schleife Wert für innere Schleife Zähler setzen Rahmenfarbe vermindern Zeiger vermindern Zeiger laden innere Schleife beendet? wenn nein, dann weiter Zeiger für äußere Schleife erhöhen wenn kleiner 256, dann weiter Progranmende
Bei der ersten Modifikation werden die drei Befehle in $1009, $lOOB und $100D, die den inneren Schleifenwert vermindern und auf Null überprüfen, durch den Illegal Opcode $C7 ersetzt. Diesen Befehl finden Sie in der zweiten Tabelle (Abb. 3.4.2). Er bewirkt ein DEC $ZEROPAGE-ADRESSE; CMP $ZEROPAGE-ADRESSE.
1000 1002 1004 1006 1009 100A 100B 1000 100E 1010
LOX LOA STA OEC ??? 11? BNE INX BNE BRK
#$00 #$00 SFB $0020
$1006 S1006
Zeiger der äußeren Schleife Zeiger der inneren Schleife setzen Farbe vermindern Illegal SC? (OEC) und Adresse SFB (+CMP) zum Schleifenanfang Zeiger erhöhen zum Schleifenanfang Progranmende
1000 A2 00 A9 00 85 FB CE 20 1008 00 C? FB 00 F9 E8 00 F6 1010 00 00 00 00 00 00 00 00
Programmschutz für Profis
117
Wie Sie sehen, wird schon durch diese eine Programmänderung das gesamte Konzept recht unübersichtlich. Bei der zweiten Änderung wird das Laden des ersten Zeigers und des Wertes für den zweiten Zeiger durch den Opcode $AF ersetzt. Dadurch wird der Wert aus der Adresse in den Akku geladen, die sich aus den beiden folgenden Bytes ergibt. Danach wird der Inhalt des Akkus in das X-Register geschoben. Da wir die beiden Zeiger auf $00 setzen wollen, müssen wir aus einer Adresse laden, in der immer ein Null-Byte steht. Wir haben dafür die Adresse $E379 gewählt, da diese sich im ROMBereich befindet.
1000 1001 1004 1005 1008 1009 100A 100C 1000 100F
11? AOC 11? OEC ?11 ?11 BNE INX BNE BRK
S85E3,Y $0020
S1005 S1005
Illegal-Opcode führt LOA SE379 und TAX aus Farbe vermindern Illegal-Opcode führt OEC SFB:CMP SFB aus. zum Schleifenanfang Zeiger erhöhen zum Schleifenanfang Programmende
1000 AF 79 E3 85 FB CE 20 00 1008 C7 FB 00 F9 E8 00 F6 00 1010 00 00 00 00 00 00 00 00
Wenn Sie sich das disassemblierte Programm anschauen, stellen Sie fest, daß nicht nur der LDA- und der LDX-Befehl, sondern auch der ST A - Befehl nicht mehr zu lesen ist. Das ist dadurch zu erklären, daß beim Disassemblieren der Inhalt der Speicherzelle $1000 nicht verarbeitet werden kann und so drei Fragezeichen ausgegeben werden. Danach wird aus $1001 der Hexwert $79 gelesen, der eigentlich die Zieladresse für den Illegal-Opcode darstellt, und als ADe $ .... ,Y verarbeitet. Die nächsten beiden Bytes werden als Adresse für den ADe-Befehl betrachtet.
Das große Anti-Cracker-Buch
118
So kommt es, daß der STA-Befehl nicht mehr disassembliert wird. Während des Programmablaufs wird dieser Befehl natürlich ordnungsgemäß verarbeitet, da der Prozessor im Gegensatz zum Disassembler mit dem Illegal-Opcode etwas anzufangen weiß.
1000 1001 1004 1005 1006 1009 100A 100C 1000 100F
???
AOC S8SE3,Y ???
11? JSR $C7DO ???
BNE $1005 INX BNE $1005 BRK
1000 AF 79 E3 85 FB CF 20 00 1008 C7 FB 00 F9 E8 00 F6 00 1010 00 00 00 00 00 00 00 00
Bei der dritten Modifikation unseres Programms wurde der Befehl DEC $D020 durch den Illegal-Opcode $CF ersetzt. Dadurch wird wieder ein DEC $Adresse:CMP $Adresse ausgeführt. Der CMP-Befehl soll uns hier nicht stören, da unmittelbar danach durch den Opcode $C? noch ein DEC ausgeführt wird, der Zustand der Flags zu diesem Zeitpunkt also unberücksichtigt bleibt. 3.4.3 Taktzyklen der Illegal-Codes Zum Schluß haben wir für Sie die Taktzyklen der Illegal-Opcodes in Abbildung 3.4.4 zusammengestellt.
119
Programm schutz für Profis
Hexeode TaklzyklU$
~~
07 OB OC OF 13 14 17 1A 1B 1C 1F 23 27 2B 2F 33 34 37 3A 3B 3C 3F 43 44 47
~
5 2 4 8
8 4 6 2 7 5 7 8 5 2 6 8
4 6 2 7 5
7 8 3 5
A bb.8.4.4:
3.5
Hexeode TaklzyklU$ Hexeode Taklzyklua
:~
53 54 57 5A 5B 6C 5F 63 64 67 6B 6F 73 74 77 7A 7B 7C 7F 80 82 83 87 89 8B
~
8 4 8
2 7 6 7 8 3 5 2 6
8 4 8
2 7 5
7 2 2 6 3 2 2
:~
97 9B 9C 9E 9F A3 A7 AF B3 B7 BB BF C2 C3 C7 CB CF 03 04 07 DA OB OC OF E2
2 8
4 5 5 5 5 6 3 4 6
4 5
Hexeode Taklzyklua
~~
EB EF F3 F4 F7 FA FB FC FF
~
2 8
8 4 8
2 7 5 7
5
2 8 5
2 6 8
4 6 2 7 5
7 2
Taktzyklen der Illegal-Codes
Dongle als Programmschutz
Bestimmt haben Sie in letzter Zeit schon mal etwas über Dongles gehört oder gelesen. Es handelt sich dabei um einen HardwareZusatz, der zu der jeweiligen Programmdiskette mitgeliefert wird. Programme, die ein Dongle als Kopierschutz verwenden, sind zur Zeit noch recht selten zu finden. Aber schon bald könnte sich dieses Verfahren parallel zum bisher gebräuchlichen Kopierschutz auf Diskette etablieren. Der Vorteil besteht darin, daß auch eine zum Original absolut identische Kopie ohne das Dongle nicht lauffähig ist. Zu einem einfachen Kopierschutz existiert zumeist schon ein Programm, mit dem man eine Sicherheitskopie der Diskette anfertigen kann. Ist das nicht der Fall, so erscheint kurze Zeit nach dem Auftauchen des Kopierschutzes ein Kopierprogramm, das diese Aufgabe sogar mit "Garantie"
Das große Anti-Cracker-Buch
120
übernimmt. Dongles jedoch lassen sich nicht einfach mit einem Kopierprogramm vervielfältigen. Der Aufwand, der für den Nachbau eines Dongles erforderlich ist, überschreitet meistens die Arbeitsbereitschaft und vor allem das Fachwissen der Raubkopierer. So kann das Dongle unter Umständen einen idealen Kopierschutz darstellen. 3.5.1
Wie arbeitet ein Dongle?
Die zur Zeit übliche Form des Dongles ist ein kleiner Stecker, der in den Control-Port des Rechners eingesteckt wird. Neben der normalen Kopierschutzabfrage der Diskette wird während des Programmablaufs das Dongle kontrolliert. Ist das Dongle nicht eingesteckt oder handelt es sich um ein falsches Dongle, so wird das Programm abgebrochen. Die Abfrage kann einmalig, an einer bestimmten Stelle des Programms oder in einem immer wieder aufgerufenen Unterprogramm geschehen. Eine recht einfache Art, ein Dongle zu bauen und auch abzufragen, besteht darin, verschiedene Leitungen des Control-Ports auf Masse zu legen. Dazu einige Erläuterungen zum Control-Port und zur Joystick-Abfrage. Die nachfolgend aufgeführten Eigenschaften beziehen sich auf beide Control-Ports. Die Adressen und das unten abgedruckte Beispielprogramm beziehen sich auf Control-Port 2. Normalerweise sind die Pins 0 bis 4 des Control-Ports auf HIGH gesetzt, das heißt, sie führen Strom. Direkt abhängig von diesen Zuständen sind die Bits 0 bis 4 in $DCOO (56320). Diese Bits stehen normalerweise also auch auf HIGH, das heißt, sie enthalten eine 1. Bei einer Bewegung des Joysticks werden die jeweiligen Leitungen mit der Leitung 7 des Control-Ports verbunden und dadurch auf Masse gelegt. Das dementsprechende Bit in $DCOO (56320) wird dadurch gelöscht. Die Bits 0 bis 3 entsprechen üblicherweise den vier möglichen Richtungen. Das Bit 4 ist für den Feuerknopf zuständig.
Programmschutz für Profis
121
Durch ein Dongle können nun verschiedene Leitungen des Control-Ports gleichzeitig auf Masse gelegt und abgefragt werden. Bei einem eingegossenen Dongle sind die Verbindungen der einzelnen Leitungen nur noch durch Ausmessen herauszufinden.
3.5.2
Eine einfache Dongle-Abfrage
Im unserem nun folgenden Beispiel fragen wir ein Dongle ab, das die Leitungen 0 und 1 auf Masse legt, also mit Leitung 7 verbindet. Normalerweise sind diese Leitungen beim Joystick für die Richtungen OBEN und UNTEN zuständig, so daß das Dongle mit einem Joystick nicht imitiert werden kann. Um das Dongle nachzubauen, benötigen Sie nur einen Control-PortStecker und drei kleine Kabelstücke. Diese Teile erhalten Sie für einige Groschen in jedem Elektronik-Fachgeschäft.
10 20 30 40 50 60 70
At = "(CLR)O.K." PRINT"(CLR)DONGLE EINSTECKEN UND TASTE DRUECKEN" POKE 198,0:WAIT 198,1 A= PEEK(56320) AND 3 IF A <>0 GOTO 70 PRINT A$:GOTO 40 PRINT "ENDE",A
Da die Tastaturabfrage des Rechners ebenfalls über die Leitungen des Control-Ports geschieht, kann die Tastatur hier nicht mehr vollständig abgefragt werden. Stecken Sie das Dongle also erst ein, nachdem Sie das Programm gestartet haben. In Zeile 20 und 30 werden Sie aufgefordert, das Dongle einzustecken. Das Programm wartet nun, bis Sie eine beliebige Taste gedrückt haben. In Zeile 40 wird das Port-Register A aus $DCOO gelesen und die Bits 0 und I isoliert. (2" 0+2" 1 = 1+2 = 3, durch die AND- Verknüpfung mit diesem Wert werden alle anderen Bits gelöscht.)
Das große Anti-Cracker-Buch
122
Wenn die Bits nicht beide auf LOW stehen, also 0 sind, wird das Programm in 70 beendet. In Zeile 60 wird "O.K." auf den Bildschirm gebracht und wieder zum Schleifenanfang verzweigt. Dieses kleine Programm sollte Ihnen die Arbeitsweise eines Dongles verdeutlichen. Es ist so natürlich noch nicht als Kopierschutz geeignet, da die Abfrage für jedermann einsehbar als BASIC-Programm vorliegt. Wie Sie das Programm gegen fremde Augen schützen, haben wir allerdings in den vorhergehenden Kapiteln schon ausgiebig beschrieben.
3.5.3 Dongle mit
Ie
Das unter Kapitel 3.5.2 beschriebene Dongle, in dem verschiedene Leitungen gleichzeitig auf Masse gelegt wurden, kann mit einiger Fachkenntnis und den entsprechenden Meßgeräten relativ einfach ausgemessen und nachgebaut werden. Auch wenn die meisten C64-Besitzer nicht über diese Grundvoraussetzungen verfügen und ihnen der Arbeitsaufwand zu groß ist, offenbart sich hier doch eine Schwäche des Systems. Abhilfe würde hier ein Dongle schaffen, dessen Leitungen nicht fest, sondern variabel auf Masse gelegt werden können. Um das zu erreichen, haben wir in das Dongle ein IC eingesetzt, das eine ganz einfache Schaltung enthält. Prinzipiell kann man dafür fast jede Schaltung benutzen, die aus einfachen oder miteinander kombinierten Gattern besteht. Wie oben erwähnt, wird die Tastatur aber auch über die Port-Leitungen abgefragt, so daß die einzelnen Leitungen möglichst auf HIGH liegen sollten. Daraus ergibt sich, daß zum Beispiel eine Schaltung mit einem N AND-Gatter, also einem NOT - AND-Gatter, ungeeignet wäre, weil hierbei mindestens eine Leitung immer LOW ist. Wir haben für das Dongle ein IC mit vier voneinander unabhängigen AND-Gattern gewählt, wovon allerdings nur ein Gatter benutzt wird. Das hier verwendete Gatter hat zwei Eingänge und einen Ausgang, der im "Normalzustand" auf HIGH liegt. Die genaue Schaltung können Sie Abbildung 3.5.1 entnehmen.
Programmschutz für Profis
8
123
10
9
11
12
13
14
+ 5V
GND
1
2
3
4
5
6
1
2
3
4
5
o
0
o 6
Abb.8.6.1:
7
0 7
8
9
Dongle mit AND-Gatter
Hier nun ein Programm, mit dem das Dongle abgefragt werden kann. Dabei wird das Dongle bei jedem Interrupt umgeschaltet und überprüft, so daß jedes andere Programm unabhängig davon laufen kann. Wird ein falsches Dongle erkannt, verzweigt das Programm zum unbedingten RESET. An dieser Stelle können Sie aber auch zu einem eigenen Programm verzweigen, das zu Beispiel den gesamten Speicher löscht und dann einen Absturz verursacht. Hierzu können weitere Informationen zum Beispiel aus Kapitel 3.4 über die Illegal-Opcodes entnommen werden.
124
Das große Anti-Cracker-Buch
Interrupt-Vektor auf Abfrageroutine stellen: COOO C001 C003 C006 C008 COOB COOC
SEI LOA STA LOA STA CLI RTS
Interrupt verhindern #$00 LOW-Byte des neuen Interrupt-Vektors S0314 abspeichern #$CO HIGH-Byte des neuen Interrupt-Vektors S0315 abspeichern Interrupt wieder freigeben Rücksprung
Abfrageprogramm für Dongle: COOO COOE C011 C012 C014 C017 C01A C01C C01E C01F C022 C025 C027 C02A C020 C02F C031 C033 C034
PHA LOA PHA ANO STA LOA ANO BNE PLA STA LOA ORA STA LOA ANO EOR BNE PLA JMP
SOCOO #$FE $DCOO SOCOO #$05 SC037 $DCOO $DCOO #$01 $DCOO $DCOO #$05 #$05 SC037 SEA31
Akku-Inhalt auf Stapel retten Wert für Control -Port 2 holen und auf Stapel retten Bit 0 löschen Wert zurückschreiben neuen Wert laden Bit 0 und Bit 2 isolieren wenn Bit 0 und 2 gesetzt, dann RESET Originalwert für Control-Port wiederholen und zurückschreiben neuen Wert holen Bit 0 setzen Wert zurückschreiben und neuen Wert holen Bit 0 und Bit 2 isolieren Bits invertieren wenn Bit 0 und 2 nicht gesetzt, RE SET Akkuinhalt vom Stapel holen zur Betr iebssystem-Interruptroutine
RESET ausführen, wenn falsches Dongle erkannt wird: C037 INC $0020 Rahmenfarbe erhöhen C03A SEI Interrupt verhindern C03B JMP SFCEF RESET ohne CBM80- bzw_ Modulabfrage
Programmschutz für Profis
125
Hier nun das Programm als BASIC- Loader: 5 S=O:A=O:X=O 10 READ A:S=S+A:IF A=-1 THEN GOTO 25 12 POKE49152+X, A:X=X+1 15 GOTO 10 25 IF S <> 6626 THEN PRINT "FEHLER IN DATA S", S:STOP 30 SYS 49152 100 DATA 120, 169, 13, 141, 20, 3, 169, 192, 141, 21, 3, 88, 96, 72, 173 , 0, 220, 72, 41, 254 110 DA TA 141, 0, 220, 173 , 0, 220, 41, 5, 208, 25, 104, 141, 0, 220, 173 , 0, 220, 9, 1, 141, 0 120 DATA 220, 173, 0, 220, 41, 5, 73, 5, 208, 4, 104, 76, 49, 234, 238, 32, 208, 120, 76, 239 130 DATA 252, 0, -1
Natürlich ist die hier beschriebene Anwendung des Dongles nur ein Beispiel unter vielen. Weitere Abfragen gleicher oder ähnlicher Art können zum Beispiel auch über den User-Port geschehen. Alle möglichen Varianten zu beschreiben, würde allerdings den Rahmen dieses Buches überschreiten, so daß hier Ihre Fantasie angesprochen wird, um weitere Methoden selbst auszuklügeln. 3.6
Password-Abfrage
Eine Password-Abfrage ist eigentlich dafür gedacht, daß ein Unbefugter nicht ohne weiteres bestimmte Programme oder Dateien benutzen kann. Das Password kann entweder direkt am Anfang eines Programms abgefragt werden oder an ganz bestimmten Stellen eines Programms, um diese zu sichern. Man kann Password-Abfragen in BASIC oder in Maschinensprache schreiben. Die erste Möglichkeit eignet sich nur, wenn
126
Das große Anti-Cracker-Buch
man diese ausreichend schützt, damit man sich das Password nicht einfach über den LIST -Befehl aneignen kann. Wenn man nun eine Password-Abfrage in BASIC schreibt, sollte man diese nachher mit einem LIST -RUNjSTOP-RESTORE- und einem Änderungsschutz versehen. Eine Password-Abfrage kann folgendermaßen aussehen: 10 20 30 40 50 60 70 80
POKE 808,225:PRINT CHR$(147):A=0 POKE 19,1 PRINT CHR$(19):INPUT "PASSIJORD :";A$ POKE 19,0:PRINT CHR$(13) IF A$="" THEN 20 IF A$="HARALD" THEN PRINT CHR$( 17) ; "PASSIJORD AKZEPTIERTI" :END A=A+1:IF A=3 THEN SYS 64738 GOTO 20
Das Programm holt sich per INPUT den Text in den Speicher und vergleicht diesen mit dem vorgegebenen Password. Die POKEs vor und hinter dem INPUT unterdrücken das Fragezeichen, das normalerweise erscheinen würde. Außerdem ist noch ein Zähler eingebaut, der nach dem dritten falschem Wort einen Systemreset durchführt. Falls einfach nur RETURN gedrückt wird, verzweigt das Programm sofort zum INPUT, ohne vorher den Zähler zu erhöhen. Falls HARALD eingegeben wird, gibt das Programm die Meldung PASSWORD AKZEPTIERT aus und schließt das Programm mit END ab. Beim Einbau in ein eigenes BASIC-Programm kann an diese Stelle ein GOTO eingesetzt werden, um zu einer eigenen Routine zu verzweigen. Da aber eine Password-Abfrage in BASIC trotzdem zu unsicher ist, weil man sich den BASIC-Code einfach im Maschinensprachemonitor ansehen könnte, obwohl das BASIC 'listgeschützt' ist, sollte man daher das Password in Maschinensprache abfragen.
Programmschutz für Profis
127
Wir haben Ihnen eine Password-Abfrage in Maschinensprache geschrieben. Das Programm liegt ab der Adresse $COOO und wird einfach mit SYS 49152 gestartet.
COOO JSR SE544 C003 LDX #$00 C005 LDA SC063,X C008 JSR $FFD2 COOB INX COOC CPX #$09 COOE BNE $C005 C010 LDX #$00 C012 JSR $FFCF C015 STA $C080,X C018 INX C01+9 CMP #$00 C01B BNE $C012 C01D DEX C01E STX S02 C020 LDX #$00 C022 LDA SC06C,X C025 EOR #$24 C027 STA SC06C,X C02A INX C02B CPX #$06 C02D BNE SC022 C02F LDX #$00 C031 LDA SC080,X C034 CMP SC06C,X C037 BEQ SC03C C039 JMP SC051 C03C INX C03D CPX S02 C03F BNE SC031 C041 LDX #$00 C043 LDA SC06C,X C046 EOR #$24 C048 STA SC06C,X C04B INX
Bildschirm löschen Zähler auf Null PASSWORD: ausgeben Zähler erhöhen wenn alle Buchstaben geholt, dann weiter Zähler auf Null zur INPUT-Routine verzweigen geholtes Byte speichern Zähler erhöhen auf RETURN warten falls gedrückt, dann weiter RETURN-Code aus Text löschen Zähler speichern Zähler auf Null Password-Text decodieren und Spei chern Zähler erhöhen wenn alles decodiert, dann weiter Zähler auf Null Eingegebenes Password mit vorhandenem vergleichen wenn richtig, dann normal weiter ansonsten Password decodieren und RESET Zähler erhöhen Länge vergleichen wenn alle getestet, dann weiter Zähler auf Null Password wieder decodieren und spei chern Zähler erhöhen
Das große Anti-Cracker-Buch
128 C04C C04E C050 C051 C053 C056 C058 C05B C05C C05E C060
CPX BNE RTS LDX LDA EOR STA INX CPX BNE JMP
#$06· $C043 #$00 $C06C,X #$24 $C06C,X #$06 $C053 $FCE2
wenn alles decodiert, dann weiter Rücksprung Zähler auf Null Password decodieren und speichern Zähler erhöhen wenn alles decodiert dann weiter Systemreset
C063 50 41 53 53 57 4F 52 54 PASSUORD C06B 3A 00 00 00 00 00 00 00 : .••••••
Nachfolgend der BASIC-Loader: 100 FORI=1T0120STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)·48:IFA>9THENA=A·7 110 B=ASC(B$)·48:IFB>9THENB=B·7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A:NEXT:READA: IFC=ATHENC=O:NEXT :END 130 PRINT"FEHLER IN ZEILE:"iPEEK(63)+PEEK(64)*256:STOP 300 DATA20,44,E5,A2,00,BD,63,CO,20,D2,FF,E8,EO,09,DO, 93 301 DATAF5,A2,00,20,CF,FF,9D,80,CO,E8,C9,OD,DO,F5,CA, 175 302 DATA86,02,A2,00,BD,6C,CO,49,24,9D,6C,CO,E8,EO,06, 23 303 DATADO,F3,A2,00,BD,80,CO,DD,6C,CO,FO,03,4C,51,CO, 187 304 DATAE8,E4,02,DO,FO,A2,00,BD,6C,CO,49,24,9D,6C,CO, 79 305 DATAE8,EO,06,DO,F3,60,A2,00,BD,6C,CO,49,24,9D,6C, 242 306 DATACO,E8,EO,06,DO,F3,4C,E2,FC,50,41,53,53,57,4F, 88 307 DATA52,54,3A,6C,65,76,65,68,60,00,00,68,60,00,00, 28 308 DATAOO,00,00,00,00,00,00,00,44,4B,44,4B,4A,44,4B, 247
Diese Maschinenroutine kann jederzeit aus einem BASIC-Programm heraus angesprungen werden, um das Password abzufragen. Es ist nur eine Eingabe möglich. Sobald etwas falsch eingegeben wird, wird sofort ein Systemreset durchgeführt.
Programmschutz für Profis
129
Nun ein wenig zur Funktionsweise des Programms: Das Maschinenprogramm macht eigentlich nichts anderes, als das vorherige in BASIC. Es gibt den Text 'PASSWORD :' auf dem Bildschirm aus und verzweigt anschließend zur INPUT -Routine des Betriebssystems. Die geholten Zeichen werden in einem Speicherbereich hinter dem Maschinenprogramm abgelegt. Die Länge der Zeichenkette wird in der Speicherzelle $02 zwischengespeichert. Sie ist für die spätere Abfrage des Passwords notwendig. Gleich darauf wird das vorgegebene Password, in unserem Fall 'HARALD', decodiert, damit ein Vergleich der beiden Passwords möglich wird. Das vorgegebene Password befindet sich ab der Adresse $C06C. Dort kann man sein eigenes Password einsetzen. Es darf nicht länger als 16 Zeichen sein, weil es sonst von einem anderen Teil des Programms überschrieben wird. Beim Ändern des Passwords müssen noch zwei Dinge berücksichtigt werden. Nach dem Ändern des Passwords muß dieses wieder mit EOR #$24 codiert werden, weil das Programm das Password codieren würde, statt es zu decodieren. Das würde unweigerlich zu einer falschen Abfrage führen. Falls eine Änderung des Passwords notwendig ist, verfahren Sie wie folgt: Tragen Sie das neue Password ab der Adresse $C06C ein, indem Sie die ASCII-Zeichen einfach mit dem Maschinensprachemonitor eintragen oder aus dem BASIC einpoken. Merken Sie sich die Länge des Passwords. Passen Sie dann die Vergleichsbefehle in den Adressen $C02B, $C04C und $C05C an die Länge des neuen Passwords an, und springen Sie in die Adresse $C051 des Password-Programms ein. Dort wird das neue Password codiert. Nachdem dieses geschehen ist, wird ein Systemreset durchgeführt. Damit haben Sie das Password des Programms geändert ünd können es somit wieder auf Diskette oder Kassette von $COOO bis $C074 mit dem Maschinensprachemonitor abspeichern. Es gibt noch eine weitere Möglichkeit, eine Art Password abzufragen, und zwar durch Drücken mehrerer Tasten gleichzeitig.
130
Das große Anti-Cracker-Buch
Das Programm springt nur aus der Schleife, falls die Tasten 'M', '8' und 'D' gleichzeitig gedrückt sind. So eine Abfrage muß natürlich in Maschinensprache erfolgen, da die Tasten direkt vom Port geholt werden. Die CIAs verfügen über zwei 8-Bit-Datenregister (PRA und PRB), bei denen jede der Leitungen sowohl als Eingang als auch als Ausgang gewählt werden kann. Um dieses festzulegen, verfügen die CIAs über jeweils zwei 8-Bit-Datenrichtungsregister (DDRA und DDRB). Wenn ein Bit im DDR gesetzt ist, also gleich I ist, arbeitet das korrespondierende Bit des PRs als Ausgang. Ist das Bit im DDR gelöscht, also gleich Null, so ist das entsprechende Bit des PRs als Eingang definiert. Durch das Auslesen der DDRs erfährt man die Eingangsspannungsbelegungen der PRs. Die Portleitungen PAO bis PA? und PBO bis PB? des CIAs I fragen unter anderem die Tastatur ab. Sie bilden eine 8ma18Matrix, die es ermöglicht, 64 Tasten abzufragen. Falls Sie die Tasten Ihres C64 einmal gezählt haben, werden Sie festgestellt haben, daß dieser 66 Tasten hat. Das ist dadurch zu erklären, daß die RESTORE-Taste über eine eigene Leitung verfügt, die bei einem Tastendruck die RESTORE-Leitung auf Masse legt und dadurch einen NMI, also einen nicht maskierbaren Interrupt, auslöst. Die zweite fehlende Taste ist SHIFTLOCK. Sie ist parallel zur linken SHIFT - Taste geschaltet. Eine Erklärung vorweg: Wenn ein Daten-Port im Datenrichtungsregister auf Eingang geschaltet und dieser nicht belegt ist, sind diese Bits im Datenregister auf HIGH geschaltet, also gesetzt. Zum besseren Verständnis arbeiten Sie bitte mit der Tabelle am Ende des Kapitels. Beim CIA 1 sind die Leitungen PAO bis PA? als Ausgang geschaltet, die Leitungen PBO bis PB? als Eingang. Fragt die Interrupt-Routine des Betriebssystems die Tastatur ab, so legt sie die Anschlüsse des Ports A kurzzeitig LOW, die Bits der Adresse $DCOO (56320) werden gelöscht.
Programmschutz für Profis
131
Wird eine Taste gedrückt, so wird das LOW -Signal des Ports A über die Leiterbahn auf das entsprechende Bit des Ports B übertragen. Die anderen Bits des Ports B sind in diesem Fall nicht angeschlossen und deshalb, wie bereits erklärt, gesetzt. Erkennt das Betriebssystem einen vollständig gesetzten Port B, so wurde keine Taste gedrückt, und es wird zum Ende der Tastenabfrage gesprungen. Wird beispielsweise H gedrückt, so wird Bit 5 des Port B gelöscht. Daran kann der Rechner erkennen, daß eine der Tasten 'F3', 'S', 'F', 'H', 'K', ':', '=' oder die Commodore-Taste gedrückt sein muß. Um festzustellen, welche dieser Tasten gedrückt ist, setzt der Rechner alle Bits des Ports A bis auf das erste auf HIGH und schiebt die Bits des Ports B nacheinander in das Carry-Flag, um zu überprüfen, ob sie gelöscht sind. Sobald er ein gelöschtes Bit festgestellt hat, holt er sich den entsprechenden Tastencode aus einer der Tabellen ab $EB81. Aus welcher Tabelle der Tastencode geholt wird, hängt davon ab, ob die SHIFT - oder die COMMODORE-Taste gleichzeitig betätigt wurden. Waren alle Bits des Ports B gesetzt, wird das nächste Bit des Ports A gelöscht und alle übrigen gesetzt. Daraufhin wiederholt sich die Kontrolle des Ports B. Sind mehrere Tasten gedrückt, so werden entsprechend mehr Bits des Ports B gelöscht. Daraus wird ersichtlich, daß es möglich ist, mehrere Tasten gleichzeitig abzufragen. Das folgende Programm fragt mehrere Tasten auf einmal ab. Es springt nur aus der Schleife, falls 'M', '8' und 'D' gleichzeitig . gedrückt sind:
COOO C001 C003 C006 C009 COOB COOD COOF
SEI LDA STA LDA CMP BEQ LDX LDA
#$00
socoo SOC01 #$FF SC001 #$00 #$FE
Interrupt verhindern Port A auf LOW setzen Port B laden Taste gedrückt? wenn nein, dann wieder vom Anfang Zähler auf Null setzen Alle Bits bis auf das erste gesetzt
Das große Anti-Cracker-Buch
132 C011 C012 C015 C018 C01B C01D C01E C01F C021 C022 C023 C025 C027 C028 C029 C02B C02C
PHA STA LDA CHP BEQ PLA CLC BCC PLA INX CPX BEQ SEC ROL BNE CLI RTS
SDCOO SOC01 SC02D,X SC021
SC001
#$08 SC02B
SC011
Wert speichern an Port A übergeben Wert von Port B holen mit Wert aus Tabelle vergleichen wenn ja, dann weiter ansonsten Stapel rücksetzen und zun Anfang springen gespeicherten Wert holen Zähler erhöhen Hit Endwert vergleichen wenn ja, dann Rücksprung Carry setzen gelöschtes Bit un eins verschieben unbedingter Sprung Interrupt freigeben Rücksprung
Tastencodes: C02D FF FF FB F7 EF FF FF FF
Hier der dazugehörige BASIC-Loader: 100 105 110 120 END 130 300 301 302 303
FORI=1T053STEP15:FORJ=OT014:READAS:BS=RIGHTS(AS,1) A=ASC(AS)-48:IFA>9THENA=A-7 B=ASC(BS)-48:IFB>9THENB=B-7 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A:NEXT:READA:IFC=ATHENC=O:NEXT: PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP DATA78,A9,00,8D,00,DC,AD,01,DC,C9,FF,FO,F4,A2,00, 98 DATAA9,FE,48,8D,00,DC,AD,01,DC,DD,2D,CO,FO,04,68, 8 DATA18,90,EO,68,E8,EO,08,FO,04,38,2A,DO,E6,58,60, 132 DATAFF,FF,FB,F7,EF,FF,FF,FF,00,00,00,00,00,00,00, 220
Programmschutz für Profis
133
Die Bits des Ports A werden einzeln und der Reihe nach gelöscht und die dazugehörige Bitkombination d~s Ports B überprüft. Daraus ergeben sich acht Werte für Port B, die ab Adresse $C02D abgelegt sind. Wie schon erwähnt, holt sich das Betriebssystem den ASCII-Wert der gedrückten Taste aus einer Tabelle. Zuvor wird jedoch die soeben gedrückte Taste in einem anderen Code in der Adresse $CB abgelegt. Dieser Code entspricht der Stellung des entsprechenden ASCII-Wertes in der eben erwähnten Tabelle ($EB81). Die entsprechende Tabelle zur Identifizierung der Tasten finden Sie am Ende dieses Kapitels. Noch ein kleiner Tip: verwenden Sie bei der Tastenabfrage keine dicht aneinanderliegenden Tasten, weil die Vernetzung der Tastaturplatine schlecht ist. Es könnte passieren, daß beim Drücken nicht verzeichneter Tasten das Programm trotzdem aus der Schleife springt. Die eigenen Tastencodes können Sie ab der Adresse $C02D eintragen. Es empfiehlt sich, nicht mehr als drei Tasten zu verwenden, weil wie schon gesagt das Programm aus der Schleife springen kann, ohne die richtigen Tasten betätigt zu haben. Beim Eintragen brauchen Sie nur die Bits der jeweiligen Taste zu löschen und einzutragen.
Das große Anti-Cracker-Buch
134
PORT A SOCOO
PORT B BIT 7
BIT 6
BIT 5
BIT 0
BIT 1
BIT 2
BIT 3
BIT ..
BIT 5
BIT 6
BIT 7
Abb.3.6:
Tastatur-Matrix
$OC01 BIT ..
BIT 3
BIT 2
BIT 1
BIT 0
Directory-Schutz
135
4.
Directory-Schutz
4.1
Versteckter Filename
Durch Directory-Manipulation ist es möglich, einen Fremdzugriff auf Programm-Files zu verhindern. Wir möchten Ihnen hier ein paar Beispiele demonstrieren, die das Laden oder den Einblick in das Directory erschweren. Fangen wir am besten direkt mit einem Beispiel an, mit dem Sie einen Programmnamen "verstecken" können, der dann nicht ohne weiteres geladen werden kann. Geben Sie ein: SAVE" [SHIFT-SPACElhallo",8
Das SHIFT -SPACE bedeutet, daß Sie zuerst die SHIFT - und dann die SPACE-Taste drücken müssen. Beide Tasten müssen also einen Augenblick gleichzeitig gedrückt sein. Anschließend können Sie den Filenamen eingeben. Es ist aber auch möglich, direkt nach dem SHIFT -SPACE ein Hochkomma zu setzen. Wenn man das Directory listet, sieht dies dann folgendermaßen aus: ODISKETTENNAME,ID 1 ""HALLO PRG "" PRG 662BLOCKS FREE.
(Beispiel 1) (Beispiel 2)
READY_
Wenn Sie nun versuchen, einen der beiden Programme zu laden, werden Sie immer die Fehlermeldung "MISSING FILENAME ERROR" oder "FILE NOT FOUND ERROR" erhalten. Das hat folgenden Grund: der Computer erkennt die Hochkommata als Anfangs- und Endmarkierung und ist dadurch nicht in der Lage, den dahinterliegenden Filenamen zu erfassen. Das zweite Hochkomma wird durch das ab gespeicherte SHIFT -SPACE erzeugt, das den Wert $AO hat. In dem Directory dient das $AO
Das große Anti-Cracker-Buch
136
als Markierung. Dem Prozessor wird nun das Ende des Filenamens "vorgegaukelt", sodaß er das zweite Hochkomma setzt. Sie werden es bestimmt als widersinnig empfinden, wenn Sie Ihre eigenen Programme nicht wieder laden können. Aber die beiden Programme sind mit demselben Trick zu laden, mit dem sie auch gespeichert wurden: LOAD"[SHIFT-SPACElHALLO",8
oder LOAD" [SH 1FT - SPACEl", 8
Mann kann dadurch zum Beispiel auch ein "verstecktes" Programm über ein Ladeprogramm aufrufen, um so das Hauptprogramm zu schützen. 4.2
Verstecken der Filenamen durch Steuerzeichen
Auch die Steuerzeichen eignen sich durchaus für den DirectorySchutz. Unter Steuerzeichen versteht man die CURSOR-Tasten, die Farbtasten, die Delete- und die Insert-Taste. Die Steuerzeichen sind nur im Hochkomma-Modus sichtbar und können daher nicht gelistet werden, sondern werden während eines LIST - Vorgangs ausgeführt. Auf diese kleine Schwäche der LIST -Funktion stützt sich auch unser Schutzverfahren. Geben Sie dazu folgende Zeile ein: SAVE"" [OELl HAL [INSl [DEl] LO",8
Das ganze nochmal in Worten: Nach dem ersten Hochkomma setzen Sie noch ein zweites, um den Hochkomma-Modus abzuschalten. Anschließend drücken Sie die DEL-Taste, um das zweite Hochkomma zu löschen, und geben die ersten drei Buchstaben des Filenamens ein. Danach drücken Sie zuerst die INSund anschließend die DEL-Taste, um ein reverses "T" zu erzeugen. Jetzt kann man den Rest des Filenamens eingeben und abspeichern. Dies sieht im Directory folgendermaßen aus:
Directory-Schutz
137
ODISKETTENNAME,ID 1
HALO
PRG
663BLOCKS FREE. READY.
Es wird Ihnen bestimmt aufgefallen sein, daß ein Buchstabe des Filenamens verschwunden ist, und zwar ein "L", obwohl der Filename mit zwei "LL" abgespeichert wurde. Daran ist, wie schon erwähnt, unser Steuerzeichen schuld. Wenn man nun versucht, das Programm auf normale Art und Weise zu laden, meIdet der Computer wieder "FILE NOT FOUND ERROR", weil ihm das zweite "L" fehlt, um das Programm im Directory zu finden. Das Laden des Programms erfolgt nach dem gleichen Prinzip, wie es abgespeichert wurde: LOAD"" [DELl HAL [I NSl [OEl] LO", 8
Mit den Farbtasten kann man auch einen bunten Filenamen erzeugen, wenn man das Programm mit den jeweiligen FarbCodes abspeichert. Zum Beispiel so: SAVE"H [CTRL 1lA[CTRL 2] l[CTRL 3]l [CTRL 4]0",8
Mit CTRL 1 bis CTRL 4 sind die Farb-Codes auf der Tastatur gemeint. Auch diese Codes können nicht gelistet werden, sie verändern nur die Farbe der Buchstaben. Auch diesmal muß das Programm genau so geladen werden, wie es gespeichert wurde. Noch ein wichtiger Hinweis: Dieser LIST -oder DIRECTORYSchutz funktioniert nur auf dem originalen Betriebssystem. Wenn Sie zufällig Speed-DOS oder ein anderes Betriebssystem verwenden, ist es durchaus möglich, daß dieser Schutz nicht funktioniert, weil eine andere LIST -Routine verwendet wird. 4.3
Der "blinde" Block
Vor der Praxis möchten wir Ihnen den Aufbau des Directorys verdeutlichen. Der Directory-Block beginnt, falls keine Mani-
138
Das große Anti-Cracker-Buch
pulationen vorgenommen wurden, immer ab Track $12 und Sektor $01. Wenn wir uns diesen Block einmal mit dem Diskmonitor ansehen, sehen wir folgendes: 00: 08: 10: 18: 20: 28: 30: 38:
12 4C AO 00 00 4C AO 00
04 4F AO 00 00 4F AO 00
82 31 AO 00 83 32 AO 00
11 AO AO 00 11 AO AO 00
004841 AO AO AO AO 00 00 00 00 01 01 4841 AO AO AO AO 00 00 00 00 01
4C AO 00 00 4C AO 00 00
..... HAL L01 .... . ...••••. ....... . ..... HAL L02 ••••• .•....•• ....... .
Die ersten beiden Bytes in Zeile $00 stellen den Block-Linker dar. Dieser hat die Aufgabe, gegebenenfalls auf den nächsten Directory-Block zu zeigen. Die erste Zahl zeigt auf den Track und die andere auf den Sektor, meistens $12 $04(18 01). Das dritte Byte bestimmt den Filetyp ($81=SEQ, $82=PRG, $83=USR, 84=REL). Die nächsten beiden Bytes zeigen auf den Track und Sektor des Programms mit dem Namen "HALLO 1". Anschließend folgt der Filename, für den genau 16 Bytes reserviert sind. Nach den ASCII-Codes folgen noch einige $AO, die sowohl als Platzhalter als auch als Endmarkierung bestimmt sind, damit der Prozessor weiß, wann er das zweite Hochkomma setzen muß. Er setzt es automatisch, wenn er nach den ASCII-Codes auf ein $AO trifft. Die letzten beiden Bytes in Zeile $18 stellen in LOW- und HIGH-Byte die Block-Anzahl des Programms dar. In unserem Fall ist das Programm einen Block lang. Nach dem Sie jetzt über den Aufbau des Directorys Bescheid wissen, können wir mit der Praxis beginnen. Im Prinzip ist dieser Schutz einfach aufgebaut, jedoch äußerst wirkungsvoll. Er macht das Listen des Directorys schier unmöglich. Man muß nur in den ersten beiden Bytes in Zeile $00 "FF" hineinschreiben. Die Floppy · wird bei einem Leseversuch des
Directorv-Schutz
139
Directorys mit der Fehlermeldung "ILLEGAL TRACK OR SECTOR" antworten, weil der Block-Linker auf einen nicht existenten Block zeigt. Jedoch können die Programme weiterhin normal geladen werden, es sei denn, das Directory geht über $12 $01 hinaus. Dann muß der Block-Linker nicht in $12 $01, sondern in $12 $04 usw. geändert werden , weil sonst die untersten Programme im Directory von der Floppy nicht gefunden werden können. Falls Sie jedoch keinen Maschinensprachemonitor zur Hand haben, können Sie auch das folgende BASIC-Programm eingeben, das für Sie die Manipulation vornimmt: 10 OPEN 1,8,2,CHR$(0)+"P,R":CLOSE 1 20 OPEN 1,8,15:0PEN 2,8,2,"#" 30 PRINT#1,"M'R"CHR$(24)CHR$(0)CHR$(2) 40 GET#1,A$,B$ 50 T$=STR$(ASC(A$+CHR$(O») 60 S$=STR$(ASC(B$+CHR$(O») 70 PRINT#1,"U1 2 O"+T$+S$ 80 PRINT#1,"B-P 2 0" 90 PRINT#2,CHR$(255)i 100 PRINT#1,"U2 2 O"+T$+S$ 110 CLOSE 1:CLOSE 2
Die Zeilen 10 bis 60 haben die Aufgabe, den letzten DirectoryBlock zu finden. Das geht folgendermaßen vor sich: In Zeile 10 wird ein File eröffnet, das nicht auf Diskette vorhanden ist, damit das Betriebssystem der Floppy das ganze Directory nach diesem File durchsucht. Das Betriebssystem wird das File natürlich nicht finden, was auch der Zweck dieser Operation ist. Dadurch haben wir aber direkt den letzten Directory-Block gefunden. Man muß ihn anschließend nur aus der Floppy auslesen. Diese Operation übernehmen die Programmzeilen 20 bis 60. Nachdem dieses geschehen ist, braucht man nur diesen Block in den Puffer der Floppy einzulesen und das entsprechende Byte zu
140
Das große Al1li-Cracker-Buch
ändern. Dies passiert in den Zeilen 70 bis 90. Die letzten beiden Zeilen haben dann nur noch die Aufgabe, den Block wieder auf Diskette zu speichern und die Kanäle zu schließen. Falls Sie einmal die Absicht haben, den Schutz wieder zu entfernen, müssen Sie nur in der Zeile 90 statt CHR$(255); ein CHR$(O); eintragen. Nach dem Programmstart ist der Schutz deaktiviert, und das Directory kann dann wieder normal gelistet werden. Dazu muß wieder gesagt werden, daß dieser Schutz nur auf dem originalen Betriebssystem einwandfrei funktioniert, weil das Directory nicht ins BASIC geladen, sondern sofort auf den Bildschirm gebracht wird. 4.4
Der Trick mit den Nullen
Bei diesem Schutz manipulieren wir nicht das eigentliche Directory, sondern den Diskettennamen. Der Diskettenname ist in der BAM, die immer auf Track $12 und Sektor $00 liegt, abgelegt. Mit dem Diskettenmonitor sieht dies dann folgendermaßen aus: 00: 12 01 41 00 15 FF FF 1F OB: 15 FF FF 1F 15 FF FF 1F
90: 44 AO AO: AO AB: AO BO: 00
9B:
49 AO AO AO 00
53 AO 30 AO 00
4B AO 3B 00 00
AO AO AO 00 00
AO AO 32 00 00
AO AO 41 00 00
.. A.....
AO DISK .... AO ...•..•. AO •. OB.2A. 00 00
Der Diskettenname in Zeile $90 ist einfach in ASCII-Codes abgelegt. Er ist ähnlich wie ein Filename im Directory aufgebaut. Hinter den ASCII-Codes folgen wieder die 16 Platzhalter
Directory-Schutz
141
$AO. Sofort dahinter folgen die ID und das Formatkennzeichen. Die restlichen Bytes sind unbenutzt. Auch dieser Schutz ist nicht schwer zu installieren. Wir überschreiben einfach in Zeile $90 die ersten 6 Bytes des Diskettennamens mit den Werten "14 14 14" und "00 00 00". Die Zeile $90 sieht dann so aus: 90: 14 14 14 00 00 00 AO AO
Speichern Sie den Block wieder auf $12 $00 und gehen Sie mit "X" wieder in BASIC. Dort initialisieren Sie das Laufwerk mit OPEN 1,8,15,"1", damit sich die Floppy die neue BAM in den Puffer holt. Normalerweise geschieht dieser Vorgang bei jedem Diskettenwechsel automatisch, vorausgesetzt, die IDs sind verschieden. Falls Sie gerade keinen Maschinensprachemonitor zur Hand haben, nimmt Ihnen dieses kleine BASIC-Programm die Arbeit ab: 10 20 30 40 50 60 70 80
OPEN 3,8,3,"#" OPEN 15,8,15 PRINT#15,"U1 3 0 18 0" PRINT#15,"B·P 3 144" PRINT#3,CHR$(20)CHR$(20)CHR$(20)CHR$(O)CHR$(0)CHR$(O)CHR$(160); PRINT#15,"U2 3 0 18 0" PRINT#15,"I" ClOSE 3:ClOSE 15
Wenn Sie jetzt versuchen, das Directory Il).it LOAD"$",8 zu laden, erscheint sofort nach dem LIST die READY -Meldung. Die Programme lassen sich aber immer noch ganz normal laden. Das Directory ist ganz normal in den BASIC-Speicher geladen worden, aber es läßt sich nicht listen. Schuld daran sind die drei Nullen. Der BASIC-Interpreter setzt diese Nullen als End-Markierung ans Ende eines BASIC-Programms. Stößt nun der
142
Das große Anti- Cracker-Buch
BASIC-Interpreter während des LIST-Vorgangs auf drei Nullen, erkennt er sie als End-Markierung und beendet den LIST - Vorgang mit einer READY-Meldung. Unser Directory-Schutz beruht auf dem gleichen Prinzip. Das Directory wird samt Diskettenname in den BASIC-Speicher gelesen. Da aber in dem Diskettennamen nur" 14 14 14 00 00 00" steht, kann das Directory nicht gelistet werden. Der Wert $14 ist nichts weiter als das Steuerzeichen DEL. Dadurch werden die ersten drei Bytes (0 "), die der Floppy-Prozessor automatisch setzt, sofort wieder gelöscht. Die danach folgenden Nullen setzen die BASIC-Endmarkierung, wodurch der weitere LIST-Vorgang automatisch beendet wird. Auf dem Bildschirm bleibt nur noch die READY-Meldung sichtbar. Auch dieser Schutz funktioniert nur auf dem originalen Betriebssystem, weil das Directory bei Speed-DOS oder anderen Betriebssystemen nicht ins BASIC geladen, sondern direkt in den Bildschirmspeicher geschrieben wird. Dies hat den Vorteil, daß das bereits im Speicher vorhandene BASIC-Programm nicht überschrieben werden kann. 4.5
Das geteilte Directory
Mit dem gleichen Verfahren ist es möglich, ein Directory zu "halbieren". Das heißt, der obere Teil ist sichtbar und der Rest ist unsichtbar, also nicht zu listen. Das sieht so aus: O"DISKETTENNAME",ID 1 "LOADER" PRG 200BLOCKS FREE. READY.
Folglich bleiben die Programme, die nach dem LOADER folgen, unsichtbar.
143
Directory-Schutz
Die "BLOCKS FREE"-Meldung ist nichts weiter als ein manipulierter Filename, hinter dem noch drei Nullen stehen, also die BASIC-Endmarkierung. Sehen wir uns das doch einmal im Diskmonitor an: 00: 08: 10: 18: 20: 28: 30: 30:
12 44 AO 00 00 4C 45 00
04 45 AO 00 00 4F 45 00
82 52 AO 00 82 43 00 00
11 AO AO 00 11 4B 00 00
00 AO AO 00 01 53 00 00
4C AO 00 00 14 20 00 00
4F AO 00 01 14 46 00 FF
41 AO 00 00 42 52 00 00
..... LOA DER .....
........ ........ ....... B LOCKS FR EE ......
........
Man sieht in Zeile $20 eindeutig, daß die "BLOCKS FREE"Meldung ein ganz normales File ist. Aber vor den eigentlichen ASCII-Zeichen sind noch zwei Bytes mit dem Wert $14 eingefügt. Es sind die Steuerzeichen für DEL (Backspace), die dafür Sorge tragen, daß das Hochkomma gelöscht wird und daß die ASCII-Zeichen näher an den Rand gerückt werden, damit das Ganze realistischer aussieht. Nach den ASCII-Zeichen folgen noch die drei Nullen, die jegliches Listen verhindern. So bleiben die dahinterliegenden Programme unsichtbar. Das Ganze läßt sich auch vorn BASIC aus realisieren. Als Voraussetzung muß die Diskette, auf der der Schutz aufgetragen werden soll, leer sein. Jetzt kann man den LOADER auf Diskette abspeichern. Wenn man keinen LOADER haben möchte, kann man auch direkt den manipulierten Filenamen auf Diskette aufbringen. Dieses funktioniert dann so: SAVE CHR$(20)+CHR$(20)+"BLOCKS FREE"+CHR$(O)+CHR$(O)+CHR$(O)
Die weiteren Programme, die nun auf diese Diskette gespeichert werden, werden hinter diesem Filenamen gespeichert und bleiben deshalb unsichtbar. Auch hierzu ist zu sagen, daß dieser Schutz nur im Original-Betriebssystem funktioniert. Mit dem Speed-DOS-Betriebssystem ist auch dieser List- oder DirectorySchutz unwirksam.
144
4.6
Das große Anti-Cracker-Buch
Die Tarnung des Filetyps
Eine äußerst wirkungsvolle Methode zum Schutz eines Programms vor Fremdzugriffen ist auch das Ändern eines Programms in eine sequentielle Datei. Das läßt sich mit einem Diskmonitor ohne weiteres realisieren. Man braucht nur ein Byte zu ändern: 00: 08: 10: 18:
00 4E AO 00
FF AO AO 00
81 AO AO 00
11 AO 00 00
00 AO 00 00
4D AO 00 00
41 AO 00 20
49 AO 00 00
.•••• MAI N...... .
Man muß nur beim dritten Byte in Zeile $00 eine $81 hineinschreiben. Nach dem Abspeichern des Blocks sieht das Directory dann wie folgt aus: O"DI SKETTENNAME", ID 32 "MAIN" SEQ 255 BLOCKS FREE
Falls es sich um ein BASIC-Programm handelt, kann man es auch mit SA VE MAIN,S,W",8 abspeichern. Das hat dann die gleiche Wirkung. Mit einem normalem LOAD MAIN",8 läßt sich das Programm nicht laden, weil das Betriebssystem mit Hilfe dieses Wertes den Filetyp feststellt und einen Lade-Vorgang nicht zuläßt. Aber mit einem kleinem Trick läßt sich auch dieses Programm laden. I
I
LOAD "MAIN,S,R",8
Auf diese Weise teilt man dem Betriebssystem mit, daß man ein sequentielles File öffnen möchte. Anschließend läßt sich das Programm ohne Schwierigkeiten in den Speicher laden. Noch ein kleiner Trick: man kann auf diese Weise ein LadeProgramm in BASIC schreiben und dieses LOADER nennen. Das Hauptprogramm "MAIN", das im Directory als sequentielles File erscheint, wird vom LOADER nachgeladen, wenn ein bestimm-
D i reclory- Schul Z
145
tes Password eingegeben wurde. Auf diese Weise läßt sich auch ein Programm vor Fremdbenutzung schützen. Diese Methode läßt sich bei allen Programm-Files durchführen, so daß Sie auf diese Weise alle wichtigen Programme sichern können. 4.7
Gelöschte Files
Als nächstes wollen wir einzelne Files kurzzeitig "wiederbeleben". Dazu brauchen wir die Blocklese- und Schreibbefehle. Außerdem benötigen wir eine leere Diskette. Dann speichern wir das zu sichernde Programm und anschließend folgendes Ladeprogramm ab:
10 OPEN 3,8,3,"#" 20 OPEN 15,8,15 30 PRINT#15,"U1 3 0 18 1" 40 PRINT#15,"B-P 3 2" 50 PRINT#3,CHR$(130); 60 PRINT#15,"U2 3 0 18 1" 70 PRINT#15,"I" 80 CLOSE 15 90 CLOSE 3 100 LOAD"PROGRAMMNAME",8
Wichtig ist, daß diese Reihenfolge streng eingehalten wird. Und nun benutzen wir den Scratch-Befehl der Floppy, um das erste File, also das Hauptprogramm, wieder zu löschen. Achtung! Ab sofort dürfen keine Schreibzugriffe auf diese Diskette mehr ausgeführt werden. Im Directory ist jetzt nur das zweite File sichtbar. Das erste File steht immer noch im Directory, allerdings mit dem Filetyp *DEL. Der Stern vor dem DEL bedeutet, daß ein nicht ordnungsgemäß geschlossenes File vorliegt. Solche Sternchen findet man, wenn das Abspeichern eines Programms auf Diskette wegen Platzmangels nicht korrekt abgeschlossen werden kann.
146
Das große Anti-Cracker-Buch
Da sich die Floppy als "intelligent" bezeichnet, unterschlägt sie die *DEL-Einträge beim Laden von Directories in den Computer-Speicher, um die gelöschten Files nicht zu listen. Natürlich ist das File auch noch auf der Diskette vorhanden, es ist für uns nur nicht mehr sichtbar. Um es wieder zu regenerieren, muß das für den Filetyp zuständige Byte des Eintrags auf den Wert $82 (130) gesetzt werden. Beim ersten Eintrag ist das Byte Nummer 2, also das dritte (Zählungen beginnen bei Computern grundsätzlich bei Null) im Block Track 18 Sektor 1. Wichtig ist, daß die Diskette vor dem Laden des Hauptprogramms initialisiert wird. Die Floppy merkt sich nämlich immer einige Teile des Directories und liest diese erst gar nicht von der Diskette, wenn ein Directory abgerufen wird. Die Folge wäre, daß wir immer noch mit dem alten Directory arbeiten, obwohl das neue auf der Diskette steht. Beim Initialisieren werden dann allerdings die internen Zwischenspeicher des Laufwerks aktualisiert. Die ersten Befehle im Hauptprogramm sollten dieses wieder "löschen", indem die obere Sequenz verwendet wird, mit dem Unterschied, daß anstelle des CHR$(l30) ein CHR$(O) steht. Dieses ist die Kennzeichnung für ein *DEL-File. Das geht schneller und weniger auffällig als ein Scratch-Befehl. Dieses Prinzip läßt sich fast beliebig ausweiten, so zum Beispiel, wenn man die Filenamen verändert oder den falschen Programm-Linker wieder richtig stellt. Die beste Methode: der Compiler-Schutz
Zum Abschluß dieses Kapitels noch einige allgemeine Tips: Schutz-Programme, die in BASIC geschrieben wurden, sollte man unbedingt compilieren. Jeder Compiler erstellt einen derartig verwirrten, fast unlesbaren Code, daß selbst die versiertesten Knack-Profis resignieren müssen.
Directorv-Schutz
147
Wichtig ist aber, daß Befehle, die an die Floppy gesendet werden, im Programm text verschlüsselt stehen sollten. Zum Beispiel, indem sie mit dem CHR$-Befehl arbeiten und die einzusendenden Zahlenwerte über einen Entschlüsselungsalgorithmus errechnet werden. Dann kann man dem Compilat nicht einmal über einen Monitor entnehmen, welche Befehle zur Floppy gesendet werden.
148
Das große Anti-Cracker-Buch
Kopierschutz mit der Datasette
5.
149
Kopierschutz mit der Datasette
Da viele C64-Besitzer immer noch mit einer Datasette arbeiten und es für etliche Firmen billiger ist, ihre Programme auf Kassetten anstatt auf Disketten zu vertreiben, darf in diesem Buch ein Kapitel über Kassettenkopierschutz nicht fehlen.
5.1
Sinn und Unsinn eines Kassettenkopierschutzes
Ganz am Anfang dieses Kapitels muß die Frage stehen, ob es überhaupt sinnvoll ist, ein auf Band aufgenommenes Programm vor dem Kopieren schützen zu wollen. Für einen Kopierer ist es nämlich ein leichtes, zwei Kassettenrecorder mit Hilfe eines Überspielkabels miteinander zu verbinden, um sich dann ein Duplikat des Programms anzufertigen. Dieses Verfahren ist jedoch zu unrationell, als daß ein auf diese Weise kopiertes Programm auf dem schwarzen Markt weite Verbreitung finden könnte. Schließlich wird jede weitere Kopie, die man von einer vorhandenen Kopie anlegt, in der Qualität etwas schlechter sein als der Vorgänger, was dazu führt, daß sich ab einer gewissen Stufe das Programm nicht mehr einladen läßt. Außerdem müssen zur Anfertigung eines Duplikats sowohl Aussteuerung als auch Tonkopfeinstellung des Kassettenrecorders exakt eingestellt sein. Ein weiteres Argument, das gegen das Erstellen einer 1: I-Kopie spricht, ist der Platzbedarf. Man kann unter Verwendung von Turbo-Tape auf einer 60-Minuten-Kassette etwa 40 bis 50 raubkopierte Programme mittlerer Länge unterbringen, bei denen der Kopierschutz entfernt wurde. Wenn jedoch der Kopierschutz noch vorhanden ist, also die Kopie mit Hilfe zweier Recorder angefertigt wurde, reicht dasselbe Band maximal für ca. acht bis neun Programme. Die meisten Raubkopierer sind sowieso an einer wesentlich schneller lad- und kopierbaren Diskettenversion eines Programms interes·siert. Wer Wert darauf legt, daß ein von ihm entworfenes Programm auch nicht ein einziges Mal kopiert wird, ist mit einer Bandaufnahme seines Programms völlig hilflos. Wer aber sein
150
Das große Antj-Cracker-Buch
Programm vor einer schnellen und ausgedehnten Verbreitung auf dem schwarzen Markt schützen will, für den bietet die Datasette mehr Kopierschutzmöglichkeiten als eine Diskettenstation. Denn für das Diskettenlaufwerk existieren im Gegensatz zur Datasette unzählige von Kopierprogrammen, die von etwa 90% der geschützten Software funktionsfähige Kopien erstellen können. Jede Kopie steht dann dem Original um nichts in der Qualität nach und läßt sich auch ebenso schnell weitervervielfältigen. Solche Kopierprogramme für die Datasette zu erstellen, ist aus Gründen, auf die wir später noch genauer eingehen werden, nicht möglich. Existierende Kassettenkopierprogramme erlauben es höchstens, das vom Betriebssystem verwendete Aufzeichnungsformat oder das in Deutschland nahezu zum Standard gewordene Turbo- Tape-Format zu kopieren. Da aber die meisten Softwarefirmen, die ihre Programme auf Kassetten vertreiben, ein eigenes Aufzeichnungsformat verwenden, sind solche Kopierprogramme zum Kopieren von Originalen untauglich. Einige Raubkopierer sind soweit gegangen, speziell für einige von Firmen wiederholt verwendete Formate Kopierprogramme zu schreiben. Prinzipiell braucht man aber für jedes neue Aufzeichnungsverfahren ein neues "Copy". Ein generelles Kopierprogramm existiert bis zum jetzigen Zeitpunkt noch nicht, und es hat auch den Anschein, daß es unmöglich ist, jemals ein solches Programm zu entwerfen. 5.2
Laden und Abspeichern von Programmen und Daten
5.2.1
Was man von BASIC aus machen kann
Wer einen Kopierschutz entwerfen will, sollte zuerst einmal wissen, auf welche verschiedenen Arten man überhaupt etwas auf einer Kassette abspeichern und wieder einladen kann. Das Betriebssystem des C64 stellt uns dazu prinzipiell zwei Möglichkeiten zur Verfügung.
Kopierschutz mit der Datasette
151
Die erste und einfachste Möglichkeit ist die, mit den Befehlen LOAD und SA VE zu arbeiten. SAVE "Prograrrmname" oder SAVE IProgralT1T1name",1
speichert das im Computer befindliche BASIC-Programm auf Band, LOAD IProgralT1T1name" oder LOAD IProgralT1T1name",1
lädt ein Programm vom Band wieder zurück in den Rechner. als Anwender brauchen sich bei dieser Prozedur nicht geringsten darum zu kümmern, wie und wo Ihr Programm Rechnerspeicher untergebracht wird. Diese Arbeit erledigt Sie der BASIC-Interpreter.
Sie im im für
Bei dieser Aufzeichnungsmethode werden auf dem Band zwei unterschiedliche Teile abgelegt. Der erste Teil ist der sogenannte 'Header'. Er enthält neben dem Programmnamen auch noch Daten über die Lage des Programms im Speicher und seine Länge. Der zweite Teil nun enthält das eigentliche Programm. Diese Unterteilung wird auch beim Einladen des Programms sichtbar, wenn der Computer nach Einlesen des Headers sich erst noch einmal zur Anzeige des Programmnamens meldet. Danach wird erst das eigentliche Programm vom Band geholt. Das zweite Verfahren, mit der Datasette Daten aufzuzeichnen, liefert der OPEN-Befehl. Damit ist es möglich, vom laufenden BASIC-Programm aus Zahlen, Buchstaben oder Variablenwerte auf Band zu sichern und von dort auch wieder zurückzuholen. Die Befehlsfolgen dazu sehen folgendermaßen aus: OPEN 1,1,1,IFilename"
eröffnet ein sogenanntes 'Datenfile' auf der Kassette, das heißt, man kann jetzt Daten beliebig hintereinander auf dem Band ablegen. Dies geschieht mit Hilfe des Befehls PRINT# 1, ••• (Variablen,Daten wie bei PRINT).
152
Das große Anti-Cracker-Buch
Dieser Befehl funktioniert genau wie der PRINT -Befehl, nur daß die Werte nicht auf dem Bildschirm, sondern zur Datasette hin ausgegeben werden. Der dritte Befehl, den man noch benötigt, um seine Daten abzuspeichern, teilt dem Computer mit, daß die Aufzeichnung beendet werden soll. Er lautet: CLOSE 1
Wenn man diesen Befehl vergißt, kann es dazu kommen, daß einem hinterher Daten auf dem Band fehlen. Was bedeuten nun die Zahlen, die den Befehlen mitgegeben werden müssen? Die erste Eins hinter dem Befehl OPEN ist die 'logische Filenummer' . Sie kann einen beliebigen Wert von 0 bis 255 annehmen. Es ist dieselbe Zahl wie hinter dem PRINT#und CLOSE-Befehl, denn ihr Zweck ist es, diesen beiden Befehlen mitzuteilen, auf welchen OPEN-Befehl sie sich beziehen. Die zweite Zahl heißt 'Geräteadresse' , da sie angibt, welches Gerät (hier die Datasette) der OPEN-Befehl ansprechen soll. Die Eins hinter SA VE und LOAD hat genau dieselbe Funktion. Die dritte Zahl, die sogenannte 'Sekundäradresse' , ist die interessanteste, da sie je nach angesprochenem Gerät unterschiedliche Funktionen erfüllt. Bei der Datasette hat eine Eins die Bedeutung 'Datenfile zum Schreiben eröffnen'. Um nämlich die geschriebenen Daten wieder lesen zu können, braucht man bloß ein Datenfile mit der Sekundäradresse Null zu eröffnen: OPEN 1,1,O,"Filename"
Das Einlesen der Daten erfolgt dann mit den Befehlen GET#1, ... (Folge von Variablen)
oder INPUT#1, ... (Folge von Variablen).
Kopierschutz mit der Datasette
153
Diese bei den Anweisungen funktionieren genauso wie ihre Gegenstücke GET und INPUT, nur werden die Zeichen nicht über die Tastatur eingetippt, sondern vom Band eingelesen. Auch hier sollte man nach Abschluß des Einlesens mit CLOSE 1 den Datenkanal wieder schließen. Nun zur Praxis. Folgendes Beispielprogramm liest eine vorher bekannte Anzahl von Zahlen von der Tastatur und schreibt sie auf Band:
10 20 30 40 50 60 70 80
OPEN 1,1,1,"DATEI":REM Datei zum Schreiben eröffnen PRINT"\JIEVIELE ZAHLEN \JOLLEN SIE ABSPEICHERN" INPUT A:PRINT#1,A:REM Anzahl der Daten auf Band FOR 1=1 TO A PRINT I;". ZAHL";:INPUT B PRINT#1,B:REM eine Zahl abspeichern NEXT I CLOSE 1:REM File schließen
Das dazugehörige Programm, das die Daten wieder einliest und anzeigt, sieht so aus:
10 20 30 40 50 60 70
OPEN 1,1,0,"DATEI":REM Datei zum Lesen öffnen INPUT#1,A:REM Datenanzahl einlesen FOR 1=1 TO A INPUT#1,B:REM eine Zahl einlesen PRINT I;". ZAHL:";B NEXT 1 CLOSE 1
Mit diesem Handwerkszeug können wir nun endlich zum ersten Kopierschutz übergehen. Er besteht einfach darin, daß man hinter sein Programm ein Datenfile auf das Band schreibt, die Daten dann einliest und vergleicht, ob sie stimmen. Wenn nicht, so läuft auch das Programm nicht.
154
Das große Anti-Cracker-Buch
Angenommen, man hat mit dem oben angegebenen Beispielprogramm die vier Zahlen 3, 4.1, -10 und I auf Band abgelegt, dann könnte das Abfrageprogramm wie folgt aussehen: 10 OPEN 1,1,0,"OATEI" 20 INPUT#1,A,B,C,D,E:REM 5 Werte (mit Oatenanzahl) lesen 30 CLOSE1 40 IF A<>4 OR B<>3 OR C<>4.1 OR 0<>-10 OR E<>1 THEN PRINT"LAUEFT NICHT": STOP 50 PRINT"KOPIERSCHUTZ ERKANNT"
Wer so etwas kopieren will, kommt mit LOAD und SA VE nicht weiter. Er muß sich ein Programm dafür schreiben. Zugegeben, dies ist nicht besonders kompliziert. Man kann aber hier ein Handikap einbauen, das es schwer macht zu erkennen, wie viele Daten überhaupt kopiert werden müssen. Ein Kopierprogramm für Datenfiles müßte nämlich normalerweise so aussehen, daß es jedes einzelne Daten-Byte mit GET# in ein Array einliest und dann an hand der Status-Variablen ST nachprüft, ob schon alle Daten gelesen wurden. Diese Variable erhält nämlich den Wert 64, sobald man das letzte Daten-Byte geholt hat. Das Betriebssystem erkennt das Ende des Datenfiles daran, daß es hinter das File das Zeichen 'CHR$(O)' schreibt. Nichts hindert uns aber, dieses Zeichen selbst auf das Band zu schreiben. Dadurch erhält ST den Wert 64 schon, bevor alle Daten eingelesen wurden. Wenn also das Kopierprogramm im Prinzip so aussieht: 10 OlM D$(1000):REM maximal 1000 Bytes 20 INPUT"NAME OES OATENFILES";N$ 30 OPEN 1,1,0 , N$ 40 1=0 50 GET#1,0$(I):REM Ein Byte lesen 60 IF ST=O THEN 1=1+1:GOTO 50:REM alle Daten gelesen? 70 CLOSE 1 80 INPUT"KASSETTE WECHSELN UND RETURN DRUECKEN";A$ 90 OPEN 1,1,1,N$ 100 FOR J=O TO I 110 PRINT#1,0$(J);:REM Daten wieder schreiben 120 NEXT J 130 CLOSE 1
Kopierschutz mit der Datasette
155
dann kann man sich durch folgende Bandaufzeichnung davor schützen: 10 20 30 40 50
OPEN 1,1,1,"FILENAME" PRINT#1,"ERSTER TEIL" PRINT#1,CHR$(0);:REM Datenende vortäuschen PR I NT#1 , "Z\.IE ITER TE I L" CLOSE 1
Die Abfrage dazu sieht dann folgendermaßen aus:
10 20 30 40 50 60 70
OPEN 1,1,0,"FILENAME" INPUT#1,A$ GET#1,B$:REM CHR$(O) überspringen INPUT#1,B$ CLOSE 1 IF A$<>"ERSTER TEIL" OR B$<>"Z\.IEITER TEIL" THEN STOP PRINT"KOPIERSCHUTZ ERKANNT"
Jemand, der ein solches Datenfile duplizieren will, muß genau wissen, wieviele Daten-Bytes das File enthält. Den einzigen Hinweis, den er dazu erhält, ist, daß beim letzten Byte ST den Wert 64 annimmt, was aber nicht notwendigerweise bedeuten muß, daß das File wirklich zu Ende ist. Sie werden sich fragen, ob es möglich ist, Daten zu schreiben, ohne daß sie mit CHR$(O) beendet werden. In der Tat geht auch das. Wir werden aber erst später darauf zurückkommen. Bei der gerade besprochenen Aufzeichnungsmethode werden die Daten in mehreren Teilen abgelegt. Als erstes kommt wie bei mit SA VE abgespeicherten Programmen ein Header mit dem Filenamen und der Information, daß es sich um ein Datenfile handelt. Danach folgen in kurzen Blöcken die Daten. Jeder Block wird zuerst in einen speziell dafür reservierten Speicherbereich, dem sogenannten Kassettenpuffer, gelesen und von da aus an das BASIC-Programm weitergeleitet. Immer wenn die Daten aus dem Kassettenpuffer vollständig übergeben wurden, wird der nächste Datenblock gesucht und eingeladen. Das macht
Das große Anti-Cracker-Buch
156
es einem Kopierer natürlich wieder einfacher, da er die Datenanzahl nicht aufs Byte genau festzustellen braucht, sondern nur wissen muß, wieviele Datenblöcke zu dem File gehören. Wenn er das einmal herausgefunden hat, braucht er nur alle diese Blöcke vollständig zu kopieren, ohne sich um CHR$(O)-Bytes zu kümmern. Damit er das aber nicht herausfindet, sollte man die Kopierschutzabfrage nicht für jedermann offen einsehbar im Programm unterbringen. Anregungen zu diesem Thema finden Sie im Kapitel "Programmschutz". Sekundäradressen können noch andere Zwecke erfüllen als die oben beschriebenen. In Verbindung mit dem OPEN-Befehl ist noch die Sekundäradresse Zwei von Bedeutung. Mit OPEN 1, 1,2,IFilename"
wird ein Datenfile genauso zum Schreiben eröffnet wie unter Verwendung der Sekundäradresse Eins. Hinter das Datenfile wird dann eine EOT (= End of Tape = Bandende)-Markierung geschrieben. Diese Markierung bewirkt, daß jeder Versuch, über sie hinweg nach einem Programm oder Datenfile zu suchen, mit qer Fehlermeldung 'DEVICE NOT PRESENT' abgebrochen wird. Sie dient dazu, den Punkt auf dem Band anzuzeigen, hinter dem sich nichts mehr an Aufzeichnungen befindet. Von BASIC aus läßt sich allerdings die Existenz dieser Markierung nicht ohne weiteres abfragen, da sie zum Abbruch des laufenden Programmes führt. Man kann sie höchstens überspringen, indem man über die File-Ende-Markierung hinweg mehr Daten einliest, als man aufgezeichnet hat. Es müssen nur soviele Daten angefordert werden, daß der nächste Block nach dem Datenfile, also der EOT -Block, nachgeladen wird. Als Kopierschutz ist diese Markierung allerdings kaum zu verwenden, da sie zu leicht erkannt und noch leichter wieder erzeugt werden kann. Auch bei den Befehlen LOAD und SA VE können Sekundäradressen angegeben werden. Um den Sinn der Funktionen der Sekundäradressen zu verstehen, sollte man wissen, daß sich der Speicherbereich, in dem ein BASIC-Programm im Rechner untergebracht wird, verlegen läßt. Die Anfangsadresse des BASIC-Speichers befindet sich in den Speicherstellen 43 und 44
Kopierschutz mit der
Data.~ell(!
157
im LOW-HIGH-Byte Format und hat normalerweise den Wert 2049 (=$0801). Will man beispielsweise den BASIC-Start nach 3000 (=$OBB8=11*256+184) verschieben, so braucht man bloß diese beiden Adressen zu verändern. Außerdem muß nach 2999 eine Null stehen. Danach fehlt nur noch die Eingabe von NEW, und schon beginnt der BASIC-Speicher bei 3000. Die komplette Anweisung lautet also: POKE 43,184:POKE 44,11:POKE 2999,O:NEW
Hat man ein BASIC-Programm, das an der Speicherstelle 2049 begann, mit SA VE "Programmname" abgespeichert, und lädt man dieses Programm nach Ändern des BASIC-Starts wieder in den Rechner, so wird es automatisch an die neue Adresse geladen und vom BASIC-Interpreter angepaßt, damit es probemlos auch im neuen Bereich läuft. Gibt man jedoch beim Laden die Sekundäradresse Eins an, also LOAD "PrograJllll'lame",1,1
so wird das Programm an die Adresse zurückgeladen, an der es beim Abspeichern stand, in diesem Falle also 2049. Dort ist es natürlich jetzt nicht mehr lauffähig. Das Laden an eine andere Adresse als den momentanen BASIC-Start wird dann von Bedeutung, wenn man Maschinenprogramme lädt und speichert, die ja normalerweise nicht im BASIC-Speicherbereich liegen. Man kann auch schon beim Abspeichern festlegen, daß das Programm an die Originaladresse zurückgeladen wird, auch ohne beim LOAD-Befehl die Sekundäradresse Eins zu verwenden. Dies geschieht ganz einfach dadurch, daß man diese Sekundäradresse beim SA VE-Befehl verwendet. Beispiel: Ein mit SAVE IPrograJllll'lame",1,1
abgespeichertes Programm wird in jedem Falle an die Adresse zurückgeladen, an der es vorher im Speicher stand, egal ob man es mit
158
Das große Allti-Cracker-Buch LOAD, LOAD "ProgralTf11name",1
oder LOAD "Programmname" , 1,1
einlädt. Damit läßt sich ein einfacher Schutz seiner Programme gegen Fremdbenutzung erstellen. Man verschiebt den Anfang des BASIC-Speichers an eine nur einem selbst bekannte Adresse und speichert das zu schützende Programm ab dieser Adresse unter Verwendung der Sekundäradresse Eins ab. Wer dieses Programm wieder zum Laufen bringen will, muß wissen, wo der BASIC-Start gelegen hat, da er es nicht ohne größeren Aufwand an den normalen BASIC-Start 2049 laden kann. Zur Vollständigkeit sei hinzugefügt, daß auch beim SA VEBefehl die Möglichkeit besteht, eine EOT -Markierung hinter das Programm zu schreiben. Dies geschieht entweder durch Angabe der Sekundäradresse 2, was dem Abspeichern ohne Sekundäradresse entspricht, oder durch Angabe der Sekundäradresse 3, entsprechend zur Sekundäradresse 1. Hier nun nochmal eine Zusammenfassung der verschiedenen Funktionen von Sekundäradressen: OPEN 1,1,0," ••. " OPEN 1,1,1," ... " OPEN 1,1,2," ... "
Datenfi le zum Lesen eröffnen Datenfile zum Schreiben eröffnen Datenfile zum Schreiben eröffnen mit EOT
SAVE n ••• 11,1 SAVE " ••• ",1,1 SAVE " ••• ",1,2 SAVE " ..• ",1,3
Programm Programm Programm Programm mit EOT
LOAD " ... ",1
versch i ebbares Programm an neuen BASIC-Start laden Programm an Origina ladresse laden
LOAD " ... ",1,1
verschiebbar abspeichern unverschiebbar abspeichern verschiebbar abspeichern mit EOT unverschiebbar abspeichern
Kopierschutz mit der Datasette 5.2.2
159
Laden und Speichern von Maschinensprache aus
Prinzipiell läßt sich alles das, was bisher in diesem Kapitel beschrieben wurde, auch mit Maschinensprache erreichen. Durch Maschinenprogramme lassen sich aber einige wesentlich bessere Kopierschutzmethoden verwirklichen als durch BASIC-Programme. Dazu muß man wissen, wie die Entsprechungen der Befehle LOAD, SAVE, OPEN, CLOSE, GET# und PRINT# in Maschinensprache aussehen, wobei man im wesentlichen auf Unterroutinen des Betriebssystems zurückgreifen wird. Die Befehle LOAD, SA VE und OPEN haben gemeinsam, daß sie einen Filenamen, eine Geräteadresse und eine Sekundäradresse benötigen. Intern brauchen auch LOAD und SA VE, genau wie OPEN, eine Filenummer. Um diese Parameter festzulegen, existieren zwei Betriebssystemroutinen, genannt FILPAR und FILNAM. FILPAR hat die Adresse $FFBA (=65466). Vor dem Aufruf dieser Routine lädt man die Filenummer in den Akkumulator, die Geräteadresse ins X-Register und die Sekun- · däradresse ins Y -Register. FILNAM beginnt bei $FFBD (=65469) und braucht im Akku die Länge des Filenamens, im X-Register das LOW-Byte der Adresse des Filenamens und im Y -Register das entsprechende HIGH-Byte. Die vollständige Sequenz, um alle Parameter zu setzen, sieht dann so aus: Filenamenadresse: $C 100 Programmstartadresse: $COOO Filename: "PROGRAMM" (muß im Speicher ab $CIOO stehen) COOO C002 C004 C006 C009 COOB CDDD CDOF
LDA LDX LDY JSR LDA LDX LDY JSR
#$01 #$01 #$01 $FFBA #$08 #$00 #$C1 $FFBD
Filenurrmer Geräteadresse Sekundäradresse FILPAR Länge des Filenamens LOW-Byte von $C100 HIGH-Byte von $C100 FILNAM
160
Das große Anti-Cracker-Buch
Nachdem nun das Betriebssystem alle Werte "kennt", kann man den eigentlichen Befehl aufrufen. Beginnen wir mit dem SA VEBefehl. Die zugehörige Betriebssystemroutine beginnt bei $FFD8 (=65496). Man muß ihr allerdings den Speicherbereich mitteilen, der abgespeichert werden soll. Die Startadresse dieses Bereichs muß in zwei Zeropage-Adressen untergebracht werden, die Endadresse plus 1 im X-und Y- Register. Der Akkumulator enthält dabei die Adresse der ersten Zeropage-Speicherstelle, die man benutzt. Beispiel: Sie wollen den Speicherbereich $1000 (=4096) bis $15FF (=5631) auf Band abspeichern. Die FileParameter sind bereits gesetzt, es fehlt nur noch das eigentliche Abspeichern. Benutzt werden sollen die Zeropage-Adressen $FB (=251) und $FC (=252). Der Aufruf könnte dann beispielsweise so erfolgen: C012 C014 C016 C018 C01A C01C C01E C020
LDA STA LDA STA LDA LDX LDY JSR
#$00 $FB #$10 $FC #$FB #$00 #$16 $FFD8
LOW-Byte von $1000 nach $FB HIGH-Byte von $1000 nach $FC Benutzte Zeropage-Adresse: $FB LOW-Byte von $15FF + 1 HIGH-Byte von $15FF + 1 SAVE
Das so abgespeicherte Programm läßt sich einerseits von BASIC aus mit dem Befehl LOAD wieder einladen. Man kann diesen Befehl aber auch von Maschinensprache aus benutzen. Die Adresse der Routine LOAD des Betriebssystems ist $FFD5 (=65493). Im Akku muß dabei eine Null stehen, da sonst statt der Laderoutine der Befehl VERIFY aufgerufen wird. Vorher sollte man aber wieder die Routinen FILPAR und FILNAM benutzt haben. Hat man sein Programm mit der Sekundäradresse Null abgespeichert und als Sekundäradresse für den LOADBefehl ebenfalls eine Null angegeben, so werden die Werte des x- und Y-Registers als LOW- und HIGH-Byte der Ladeanfangsadresse interpretiert. Beispiel: Falls beim Laden oder Speichern die Sekundäradresse Eins verwendet wurde, reichen schon folgende zwei Zeilen zum Aufruf der LOAD-Routine:
Kopierschutz mit der Datasette C012 LDA #$00 C014 JSR $FFD5
161
Auswahl zwischen LOAD und VERIFY LOAD
Wenn dagegen beidesmal die Sekundäradresse Null gebraucht wurde, muß der Aufruf so aussehen: Startadresse (Beispiel) : $1234 C012 C014 C016 C018
LDA LDX LDY JSR
#$00 #$34 #$12 $FFD5
Auswahl LOAD/VERIFY LOW-Byte von $1234 HIGH-Byte von $1234 LOAD
Falls während des Ladens eine Unterbrechung aufgetreten ist, so wird nach Abschluß der LOAD-Routine das Carry-Flag gesetzt. Die Unterbrechung kann beispielsweise darin bestehen, daß man während des Ladevorgangs die RUNjSTOP- Taste betätigt hat, oder auch darin, daß beim Suchen nach dem nächsten Programm die Laderoutine auf eine EOT - Markierung gestoßen ist. Im letzteren Fall steht zusätzlich im Akku eine Fünf. Von der Ebene der Maschinensprache aus ist es also probemlos möglich, die EOT -Markierung abzufragen. Trotzdem sollte man diese Markierung nicht als Kopierschutz verwenden, da sie viel zu leicht zu erkennen ist. Sollten die Daten auf dem Band fehlerhaft sein, so ist das nicht am Carry-Bit zu erkennen. Man muß in der Status-Adresse $90 (=144) nachsehen, ob sie einen Wert ungleich null enthält. Diese Adresse entspricht der BASIC-Status- Variablen ST. Nun fehlt noch die Erklärung, wie man Datenfiles von Maschinensprache aus erzeugt. Der OPEN-Befehl benötigt, wie schon gesagt, ebenfalls einen vorhergehenden Aufruf der Routinen FILPAR und FILNAM. Danach kann man sofort die Betriebssystemroutine OPEN aufrufen, die bei $FFCO (=65472) beginnt. Hat man ein Datenfile zum Schreiben eröffnet, so muß man dem Computer als nächstes mitteilen, daß Bytes statt auf den Bildschirm aufs Band gebracht werden sollen. Dazu lädt man die
Das große Anti-Cracker-Buch
162
Filenummer ins X-Register und springt zur Routine CKOUT, $FFC9 (=65481). Jetzt lassen sich die Daten einfach dadurch auf die Kassette schreiben, daß man jeweils ein Datum in den Akku lädt und dann die Routine BASOUT bei $FFD2 (=65490) aufruft. Dies entspricht dem Schreiben von einem Zeichen mit dem Befehl PRINT# in BASIC. Beim Einlesen des Datenfiles verfährt man entsprechend. Erst kommt wieder die Filenummer ins X-Register, worauf man das Gegenstück zu CKOUT, nämlich CHKIN, aufruft. Diese Routine beginnt bei $FFC6 (=65478) . Geholt werden die Daten dann mit BASIN $FFCF (=65487). Das Datenbyte steht danach selbstverständlich im Akku. In BASIC wäre das der Befehl GET#. Achtung: Der Inhalt des Y-Registers wird beim Aufruf von BASIN zerstört. Nach Beendigung des Lese - oder Schreibvorgangs wird der Datenkanal mit CLOSE $FFC3 (=65475) geschlossen. Die Filenummer muß dabei im Akku stehen. Am Schluß sollte man noch die Routine CLRCH $FFCC (=65484) benutzen, damit Ein- und Ausgabe wieder über Tastatur und Bildschirm erfolgen. Nach so vielen Erklärungen nun wieder ein Beispiel: Es soll das im Speicher befindliche BASIC-Programm als Datenfile auf Band geschrieben werden. Die Anfangsadresse des Programms steht in den Speicherstellen $2B (=43) und $2C (=44), die Endadresse in $2D (=45) und $2E (=46). Als Zähler verwenden wir die Adressen $FB (=251) und $FC (=252). COOO LDA C002 LDX C004 LDY C006 JSR C009 LDA COOB JSR COOE JSR C011 LDX C013 JSR C016 SEC C017 LDA
#$01 #$01 #$01 $FFBA #$00 $FFBD $FFCO #$01 $FFC9 $2D
Filenummer Gerätenummer Sekundäradresse: Schreiben auf Band FILPAR kein Filename FILNAM OPEN Filenummer CKOUT: Ausgabegerät Band Subtraktion vorbereiten LOW'Byte der Endadresse
Kopierschutz mit der Dataselle C019 C01B C01C C01F C020 C022 C024 C027 C029 C02B C02D C02F C031 C033 C03S C037 C039 C03B C03D C03F C042 C044 C046 C048 C04A C04C C04F
SBC PHP JSR PLP LDA SBC JSR LDA STA LDA STA LDY LDA CMP BNE LDA CMP BEQ LDA JSR INC BNE INC BNE LDA JSR JMP
163
minus LOW-Byte der Anfangsadresse Carry retten BASOUT: LOW-Byte Programmlänge $FFD2 Carry holen HIGH-Byte der Endadresse $2E minus HIGH-Byte Anfangsadresse $2C BASOUT: HIGH-Byte Programmlänge $FFD2 LOW-Byte der Anfangsadresse $2B $FB in Zähler HIGH-Byte der Anfangsadresse $2C in Zähler $FC indirektes Laden vorbereiten #$00 schon Endadresse erreicht? $FB Test auf LOW-Byte $2D verzweige, wenn nein $C03D Endadresse erreicht? $FC Test auf HIGH-Byte $2E verzweige, wenn ja $C04A ($FB),Y Byte aus BASIC-Programm holen BASOUT: Byte schreiben $FFD2 Zähler LOW-Byte erhöhen $FB verzweige, wenn kein Übertrag $C031 Zähler HIGH-Byte erhöhen $FC unbedingter Sprung $C031 Filenummer #$01 CLOSE $FFC3 CLRCH, Abschluß $FFCC $2B
Das Gegenstück zu dieser Routine, welches das Prog.ramm wieder einliest, sieht so aus: COOO C002 C004 C006 C009 COOB CO OE C011
LDA LDX LDY JSR LDA JSR JSR LDX
#$01 #$01 #$00 $FFBA #$00 $FFBD $FFCO #$01
Filenummer Gerätenummer Sekundäradresse: Lesen vom Band FILPAR kein Filename FILNAM OPEN Filenummer
164 C013 C016 C019 C01B C01E C020 C022 C024 C026 C028 C02B C020 C02F C031 C033 C035 C037 C039 C03B C030 C03F C041 C043 C045 C047 C049 C04C C04F C052 C055
Das große Anti-Cracker-Buch JSR JSR STA JSR STA LOA STA LOA STA JSR lOY STA INC BNE INC OEC lOA CMP BNE OEC CMP BNE lOA BNE lOA JSR JSR JSR JSR JMP
$FFC6 $FFCF $FB $FFCF $FC $2B $20 $2C $2E $FFCF #$00 ($20), Y $20 $C035 $2E $FB $FB #$FF $C03F $FC #$00 $C028 $FC $C028 #$01 $FFC3 $FFCC $A659 $A533 $A474
CHKIN: Eingabegerät Band BASIN: lO~-Byte Programmlänge nach Zeiger BASIN: HIGH -Byte Programmlänge nach Zeiger lO~-Byte Anfangsadresse als Startwert für Endadresse HIGH-Byte Anfangsadresse als Startwert für Endadresse BASIN: Zeichen holen indirekte Adressierung vorbereiten Zeichen in Programmspeicher schreiben lO~-Byte Endadresse erhöhen verzweige, wenn kein Übertrag HIGH-Byte Endadresse erhöhen lO~-Byte Zähler dekrementieren l~-Byte Zähler ho len Übertrag? verzweige, wenn nein HIGH-Byte Zähler dekrementieren LOW-Byte gleich Null? verzweige, wenn nein HIGH-Byte gleich Null? verzweige, wenn nein Fi lemmner ClOSE CLRCH CLR Programmzeilen binden Sprung zurück ins BASIC
Ein auf diese Weise abgespeichertes Programm ist auf einfache Weise gegen das Kopieren gesichert. Insbesondere stehen aufgrund des Aufbaus eines BASIC-Programmtextes im Datenfile Null-Bytes, also CHR$(O)-Zeichen, wodurch es, wie schon beschrieben, schwer wird, die tatsächliche Länge des Datenfiles festzustellen .
Kopierschutz mit der Datasette
165
Theoretisch könnte man nach obigem Prinzip ein Programm schreiben, das das BASIC-Programm im Speicher als Datenfile abspeichert, zusätzlich aber noch eine große Anzahl unnützer Daten. Die Laderoutine weiß, welche der Daten zum BASICProgramm gehören und welche sie überspringen muß. Ein Kopierprogramm kann das jedoch nicht unterscheiden und muß daher zwangsläufig alles mitkopieren. Wenn man nun so viele Daten auf das Band schreibt, daß es nicht mehr möglich ist, alle diese Bytes auf einmal in den Speicher zu lesen, läßt sich das File nicht mehr kopieren. Diese Methode hat aber einen unübersehbaren Nachteil, nämlich den, daß das Datenfile so lang wird, daß es fraglich ist, ob es überhaupt noch auf eine Kassettenseite paßt. Hinzu kommt noch, daß sich die Ladezeit extrem erhöht, was sicherlich auch nicht im Sinne des Anwenders liegt. Noch ein Letztes. Es ist von BASIC aus nicht ohne weiteres möglich (und für BASIC-Programme auch nur selten sinnvoll), die RAM-Bereiche $AOOO bis $BFFF und $DOOO bis $FFFF abzuspeichern. Der Bereich $0000 bis $FFFF bereitet auch von Maschinensprache aus Probleme , der Bereich $AOOO bis $BFFF ist allerdings unproblematisch. Wir erwähnen das hier deshalb, weil man in die Bereiche $AOOO bis $BFFF und $EOOO bis $FFFF problemlos Daten hineinladen kann, was heißt, daß Programme in diesen Speicherbereichen in gewisser Weise kopiergeschützt sind. Falls Sie also ein Programm besitzen, daß länger als 38 KByte ist, also den Bereich ab $AOOO belegt, so ändern Sie vor Aufruf der Routine SA VE die Speicherkonfiguration mit: LDA #$36 STA $01
Hierdurch wird der normalerweise ab $AOOO befindliche BASICInterpreter durch das darunter befindliche RAM ersetzt. Bevor Sie wieder wieder ins BASIC zurückkehren, ändern Sie den Wert der Speicherstelle Eins wieder auf $37 (=55): LDA #$37 STA $01
166
Das große Allti-Cracker-Buch
Wie Sie auch den Bereich von $EOOO bis $FFFF so abspeichern, daß er beim Einladen auch genau an diese Stelle wieder zurückgelangt, erfahren Sie im nächsten Unterkapitel.
5.3
Autostart als Kopierschutz
5.3.1
Eingriffe in die LOADjSAVE-Routine
Für einen Kopierschutz muß man nicht unbedingt soweit gehen, daß man eine eigene Lade- und Schreibroutine entwickelt. Durch Änderungen an der bestehenden Laderoutine lassen sich auch schon effektive Schutzmechanismen erstellen. Für die folgenden Ausführungen sollten Sie allerdings einen Maschinensprachemonitor besitzen . Es gibt prinzipiell zwei Ansätze zum Ändern der im ROM befindlichen Routinen. Man kann einerseits die LOAD- und SA VE- Vektoren, also die Speicherstellen, die die Startadressen der LOAD- und SA VE-Routinen enthalten, auf eine eigene Routine zeigen lassen. Wir wollten aber gerade keine völlig neue Routine, weshalb wir diese Methode vorerst außer acht lassen. Der zweite Ansatz beruht darauf, daß sich im Speicherbereich des Betriebssystems umschaltbar auch noch RAM-Speicher befindet. Man kann das Betriebssystem also vom ROM ins RAM kopieren und dort dann die Änderungen vornehmen. Mit den meisten Monitoren geschieht das mit dem Befehl: .T EOOO FFFF EOOO
Sollte Ihr Monitor einen anderen Befehl zum Verschieben von Speicherbereichen besitzen, so verwenden Sie bitte diesen. Einige Monitore benötigen auch noch den BASIC-Interpreter im RAM, da das Betriebssystem-ROM alleine nicht abgeschaltet werden kann. In diesem Falle geben Sie auch noch folgendes ein: .T AOOO BFFF AOOO
167
Kopjerschutz mit der Datasetle
Jetzt muß nur noch auf das RAM umgeschaltet werden. Geben Sie zuerst ein: .M 0001
Es sollten jetzt acht Hexadezimalzahlen, angezeigt werden, von denen die erste den Wert $37 hat. Ändern Sie bitte diesen Wert auf $35 und vergessen Sie bitte nicht, RETURN zu drücken. Jetzt kann das Betriebssystem nach Belieben abgeändert werden. Als erstes wollen wir das Problem lösen, Programme so abzuspeichern, daß sie beim Einladen nach $EOOO gelangen. Bringen Sie dazu das Programm in einen freien Teil des Speichers, beispielsweise $1000. Angenommen dieses Programm sei $1234 Bytes lang, dann liegt es normalerweise im Bereich $EOOO bis $F234. Modifizieren Sie nun mit dem Assembler Ihres Monitors die Abspeicherroutine wie folgt: .A .A .A .A
F78B F790 F795 F79A
LDA LDA LDA LDA
#$00 #$EO #$35 #$F2
LOW-Byte von $EOOO HIGH-Byte von $EOOO LOW-Byte von $F234 + 1 HIGH-Byte von $F235
= $F235
Wenn Sie jetzt das Programm von $1000 bis $2234 abspeichern, kommt es beim Einladen nach $EOOO. Bevor Sie weiterarbeiten, sollten Sie aber die SA VE-Routine wieder restaurieren, indem Sie das ROM wieder einschalten, also $37 in die Speicherstelle I schreiben. Falls Sie direkt im Anschluß daran andere Veränderungen am Betriebssystem vornehmen wollen, so kopieren Sie besser erst wieder das ROM ins RAM. Theoretisch könnte man durch Modifikationen am Betriebssystem das Aufzeichnungsformat völlig ändern oder die Aufzeichnungsgeschwindigkeit erhöhen. Die Lade- und Speicher-Routinen des Systems sind aber dermaßen kompliziert aufgebaut, daß es wesentlich (!) einfacher ist, für solche Zwecke eine komplett neue Routine zu entwerfen. Wir wollen hier nicht weiter auf das weite Feld der Betriebssystemänderungen eingehen, da wir uns sonst vom Hauptthema
168
Das große Anti-Cracker-Buch
diese Kapitels, dem Kopierschutz, zu weit entfernen. Falls Sie selbst eigene Verbesserungen am Betriebssystem vornehmen wollen, empfehlen wir Ihnen, sich ein dokumentiertes ROMListing zu besorgen, wie es beispielsweise im '64 Intern' abgedruckt ist.
5.3.2 Selbststartende Programme Nun aber zu einer echten Kopier- /Programmschutzanwendung, dem Autostart. Im Gegensatz zur Diskettenstation stellt ein einfacher Autostart bei der Datasette schon einen Kopierschutz dar, da es kaum ein Kopierprogramm gibt, das ein so ausgestattetes Programm kopieren kann. Es gibt noch einen anderen Unterschied zum Diskettenlaufwerk: Die Möglichkeit für einen Autostart sind nicht so vielfältig. Ein Autostart wird grundsätzlich folgendermaßen erzeugt: Man ändert zuerst den Vektor einer Betriebssystem- oder BASICInterpreter-Routine, die sofort nach dem Ladevorgang aufgerufen wird, so ab, daß er auf das eigene Programm zeigt. Dann speichert man das Programm mit dem veränderten Vektor ab. Dabei ist es wichtig, die Sekundäradresse Eins zu verwenden, damit beim Einladen die Daten nicht an den BASIC-Anfang, also nach $0801, kommen. Am Schluß sollte man die Vektoren wieder zurücksetzen. Nach dem Einladen eines so aufgenommenen Programms springt der Prozessor über den umgestellten Vektor ohne weiteres Dazutun in das Programm, welches als erstes den benutzten Zeiger auf seinen ursprünglichen Wert setzen sollte. Wer will, kann den Vektor für die Abfrage der STOP- Taste zusammen mit dem Einsprungvektor umändern und abspeichern, wodurch das Programm gegen Eingriffe durch STOP oder STOP /RESTORE gesichert wird. Wo liegen die benötigten Vektoren im Speicher? Für einen Autostart kommen nur drei Zeiger in Frage:
Kopierschutz mit d er Datasette Vektor $0302/$0303 $0324/$0325 $0326/$0327
norm. Wert $A483 $F157 $F1CA
169
Funktion Eingabe einer BASIC'Zeile BASIN, Eingabe eines Zeichens BASOUT, Ausgabe eines Zeichens
Außerdem ist noch der STOP- Vektor von Bedeutung: Vektor norm. Wert $0328/$0329 $F6ED
Wert für gesperrte STOP-Taste $F6E1
Der Vektor $0326/ $0327 ist eigentlich für einen Autostart nach der oben beschriebenen Methode nicht zu gebrauchen, da das Programm schon anläuft, sobald nur die Meldung 'PRESS RECORD AND PLA Y ON TAPE' ausgegeben werden soll. Wir werden aber trotzdem noch auf ihn zurückkommen. Wenn man auf die oben beschriebene Weise einen Autostart erzeugt, muß man beachten, daß man nicht den Vektor $0314/$0315 (IRQ-Vektor) mitabspeichert, da dieser Vektor sowohl von der LOAD- als auch von der SA VE-Routine verstellt wird. Das bedeutet, daß man für Programme im Bereich $02A 7 bis $02FF den Vektor $0302/ $0303 benutzen muß, dagegen für Programme ab $080 I, insbesondere BASIC-Programme, auf den Vektor $0324/ $0325 zurückgreifen muß. Es läßt sich dabei nicht umgehen, daß der von $0400 bis $07E7 befindliche Bildschirmspeicher mitabgespeichert wird. Wie schon erwähnt, steht die Endadresse plus I des aktuellen BASIC-Programms in den Speicherstellen $2D (=45) und $2E (=46). Außerdem existieren zwei Betriebssystemroutinen zum Zurücksetzen der benutzten Vektoren: $E453 initialisiert Vektoren $0300 bis $030B $FF8A initialisiert Vektoren $0314 bis $0333
170
Das große Anti-Cracker-Buch
Das Abspeichern sieht dann prinzipiell so aus: LDA STA LDA STA LDA STA LDA TAX TAY JSR LDA LDX LDY JSR LDA STA LDA STA LDA LDX LDY JSR JSR RTS
#$Startadresse $0324 #$Startadresse $0325 #$E1 $0328 #$01
LOW-Byte BASIN-Vektor LOW HIGH-Byte BASIN -Vektor HIGH STOP-Vektor: $F6ED in $F6E1 umändern und dadurch STOP sperren_ Filenurrmer 1 Geräteadresse 1 Sekundäradresse $FFBA FILPAR #$Filenamenlänge #$Filenamenadresse LOW-Byte #$Filenamenadresse HIGH-Byte $FFBD FILNAM #$26 LOW-Byte $0326 Zeiger auf Anfangsadresse #$03 HIGH-Byte $0326 Zeiger auf Anfangsadresse + 1 #$Zeiger auf Anfangsadresse $2D Endadresse LOW-Byte $2E Endadresse HIGH-Byte $FFD8 SAVE $FF8A Vektoren initialisieren
Das so abgespeicherte Programm sollte nicht die Routine $FF8A zum Zurücksetzen des Autostart- Vektors nehmen, da damit auch der STOP-Vektor wieder auf seinen ursprünglichen Wert zurückgesetzt würde. Mit dieser Methode kann man allerdings nur Maschinenprogramme automatisch starten. Wollen Sie jedoch ein BASIC-Programm selbsttätig anlaufen lassen, so müssen Sie eine Maschinenroutine mitabspeichern, die den RUN-Befehl des BASIC-Interpreters aufruft. Dieses Programm sollte am besten im freien Bereich $07E8 bis $07FF liegen. Die Sequenz für RUN sieht so aus:
Kopierschutz mit der Datasette
171
LDA #$00 JSR $AB71 JMP $A7AE
Es gibt noch eine andere Möglichkeit, das Abspeichern zu organisieren. Man kann sein Programm inklusive den (unveränderten) Vektoren in einen freien Speicherbereich verschieben, am besten von $0324 beginnend nach $1324. Dann verändert man das Betriebssystem in der oben beschriebenen Weise so ab, daß ein abgespeichertes Programm in jedem Fall nach $0324 geladen wird. Man kann nun ohne weiteres mit einern Monitor die Vektoren ab $1324 nach Belieben umstellen, beispielsweise jetzt auch den Vektor $0326/$0327. Es ist auch möglich, Programme in den Bereich $l33C bis $17E7 einzubringen, die nach dem Einladen in $033C bis $07E7 stehen, also im Bandpuffer oder im Bildschirmspeicher. Andererseits besteht kein Zwang, bei $0324 zu beginnen. Genausogut kann man durchgehend den Bereich von $02A 7 bis zum Programmende nach $12A 7 bringen und von dort aus abspeichern. Dabei muß allerdings der IRQVektor bei $l314/$l315 auf den Wert $F92C gesetzt werden. Ein Nachteil dieser Schutzmethoden soll hier nicht verschwiegen werden. Mit den gleichen Tricks, mit denen man einen Autostart erzeugt, kann er auch wieder entfernt werden. Es ist für einen Knacker sicherlich ein leichtes, eine Routine zu schreiben, die ein selbsstartendes Programm einlädt und danach sofort alle Vektoren initialisiert. Insofern ist es auch sicherer, die Vektoren $0324/$0325 und $0326/$0327 dem Vektor $0302/$0303 vorzuziehen, da letzterer nicht funktioniert, wenn das geschützte Programm mit einern Monitor geladen wird. Zum Schluß noch ein Programm, das ein beliebiges mit RUN startbares Programm mit einem Autostart versieht. Das Programm benutzt die BASIC-Interpreterroutine $EID4, welche die Parameter für LOAD und SA VE, also Filename, Geräteadresse und Sekundäradresse, aus der eingegebenen BASIC-Zeile holt. Dadurch läßt es sich folgendermaßen bedienen: Tippen Sie zuerst den abgedruckten BASIC-Lader ein und starten Sie ihn
172
Das große Anti-Cracker-Buch
mit RUN. Danach laden Sie das zu schützende Programm. Geben Sie dann bitte 'SYS 49 J 52 "FiJename'" ein . Ihr Programm wird nun zusammen mit einem Autostart auf Band gebracht.
BASIC-Lader: 100 FORI=1T085STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATAA2,12,BD,43,cO,9D,E8,07,CA,10,F7,20,D4,E1,A9, 79 301 DATA01,AA,A8,20,BA,FF,A9,93,20,D2,FF,A9,E8,8D,24, 155 302 DATA03,A9,07,8D,25,03,A9,E1,8D,28,03,A9,36,85,01, 15 303 DATAA9,24,85,FB,A9,03,85,FC,A9,FB,A6,2D,A4,2E,20, 227 304 DATAD8,FF,E6,01,4C,8A,FF,A9,57,8D,24,03,A9,F1,8D, 110 305 DATA25,03,A9,00,20,71,A8,4C,AE,A7,A4,A4,A4,CC,C4, 39
Assemblerlisting: benutzte Adressen: $FB/$FC: Zeiger auf Startadresse für SAVE COOO C002 coos C008 C009 COOB COOE C010 C011 C012 C015 COH C01A C01C
LDX LDA STA DEX BPL JSR LDA TAX TAY JSR LDA JSR LDA STA
#$12 $C043,X $07E8,X SC002 SE1D4 #$01
$FFBA #$93 SFFD2 #$E8 S0324
ZähLer für SchLeife auf $12 setzen Start-Programm, weLches RUN- BefehL ausführt, nach $07E8 verschieben schon aLLe Bytes verschoben? verzweige, wenn nein Filenamen holen FiLenUlllller = 1 Geräteadresse = 1 Sekundäradresse = FILPAR $93 ist ASCII-Wert für 'CLR/HOME' BASOUT, hier: Bildschirm löschen S07E8 in den
Kopierschutz mit der Dalaselle C01F C021 C024 C026 C029 C02B C02D C02F C031 C033 C035 C037 C039 C03B C03E C040
LOA STA LDA STA LDA STA LDA STA LDA STA LDA LDX LDY JSR INC JMP
#$07 $0325 #$E1 $0328 #$36 $01 #$24 $FB #$03 $FC #$FB $2D $2E $FFD8 $01 $FF8A
173
BASIN-Vektor schreiben STOP -Vektor auf $F6E1 setzen im Bereich $AOOO bis $BFFF das RAM einschalten $0324 als Startadresse für SAVE nach $FB/$FC $FB ist der benutzte Zeiger Programmendadresse LOW -Byte Programmendadresse HIGH-Byte SAVE ROM wieder einschalten ($37 nach $01) Vektoren initialisieren
Die folgende Routine wird nach $07E8 verschoben, mitabgespeichert und vom Autostart angesprungen_ C043 C045 C048 C04A C04D C04F C052
5.4
LDA STA LDA STA LDA JSR JMP
#$57 $0324 #$F1 $0325 #$00 $A871 $A7AE
BASIN-Vektor auf seinen alten Wert $F157 zurücksetzen Zero-Flag setzen RUN-Befehl aufrufen zurück zum BASIC-Interpreter
Der Kassettenpuffer
Der Kassetten- oder Bandpuffer ist ein Speicherbereich, der zwei Bestimmungen hat. Zum einen enthält er beim Laden und Abspeichern den schon angesprochenen "Header" (eng!. Head = Kopf), also die Daten über Start- und Endadresse und den Filenamen des Programms. Zum anderen wird er bei Datenfiles dazu benutzt, den jeweils nächsten Datenblock aufzunehmen. Er
Das große Anti-Cracker-Buch
174
liegt normalerweise im Bereich $033C (=828) bis $03FF (=1023). In seiner Funktion als Header-Speicher ist seine Aufteilung folgende: Hexadez. 033C 033D/033E 033F/0340 0341·03FF
Dezimal 828 829/ 830 831/ 832 833·1023
Funktion Headertyp Startadresse des Programmes Endadresse des Programmes Filename
Der Puffer muß nicht bei 828 beginnen, er kann auch verschoben werden. Der Zeiger $B2/$B3 (178/179) weist auf seinen Beginn. Durch Ändern dieses Zeigers läßt sich aber kein wirkungsvoller Kopierschutz aufbauen, weshalb wir darauf auch nicht eingehen werden. Als erstes Byte des Kassettenpuffers ist der Header-Typ vermerkt. Folgende Werte haben dabei die folgende Bedeutung: 1:
verschiebbar abgespeichertes Programm; bei Verwendung der Sekundäradresse 0 wird das Programm nicht ·an die im Header angegebene Startadresse geladen, sondern an die vom Anwender übergebene. Beim BASIC-Befehl LOAD ist das der Anfang des BASIC-Speichers $0801 (=2049).
2:
dieser Block ist kein Header, sondern ein Datenblock. Er enthält keine Adressen und keinen Filenamen, sondern nur Datenbytes.
3:
nicht verschiebbar abgespeichertes Programm; das Programm wird in jedem Fall an die im Header angegebene Adresse geladen.
4:
Header eines Datenf iles
5:
Header einer End-of - Tape-Markierung.
Die nächsten vier Bytes beinhalten die Start- und Endadressen. Probieren Sie doch beispielsweise folgendes aus: Laden Sie ein
Kopierschutz mit der Dalaselle
175
auf einer Kassette abgespeichertes Programm bis zur Anzeige der 'FOUND'-Meldung und betätigen Sie dann die STOP-Taste. Durch 'PEEK(829) + PEEK(830) * 256' erhalten Sie dann die Startadresse, durch 'PEEK(83 I) + PEEK(832) * 256' die Endadresse des Programms. Alle weiteren Bytes sind für den Filenamen zuständig. Sie werden sich vielleicht darüber wundern, daß hier 191 Bytes frei sind, obwohl der Filename ja normalerweise nur 16 Zeichen lang sein darf. In der Tat werden beim Laden auch nur maximal 16 Zeichen als Name angezeigt. In Wirklichkeit kann der Filename jedoch wesentlich länger sein . Geben Sie zum Beispiel folgendes ein: SAVE "LANGER NAME
Z"
Achten Sie bitte darauf, daß das einzelne "Z" den 17. Buchstaben bildet. Wenn Sie das so abgespeicherte Programm wieder einladen, erscheint als Meldung: FOUND LANGER NAME
Das "Z" ist also nicht mehr zu sehen. Trotzdem erhalten Sie mit 'PRINT CHR$(PEEK(849)' ein "Z" zurück, das heißt, der Buchstabe steht an der richtigen Stelle im Bandpuffer. Dieser Trick stellt schon einen eleganten Kopierschutz dar, sofern man in seinem Programm einen Test auf die Korrektheit des Filenamens vornimmt. Schließlich kann ein Kopierer ohne genauere Überprüfung nicht feststellen , welchen Namen er dem Programm geben muß, damit es läuft. Man kann das Spiel sogar noch weiter treiben, indem man auf diese Weise ganze Programme im Kassettenpuffer unterbringt. Es ist sogar möglich, dort eine komplette eigene Laderoutine unterzubringen, wodurch der gesamte restliche Speicher für das eigentliche Programm fre i bleibt. Dazu gehen Sie folgendermaßen vor: Schreiben Sie zuerst das Programm für den Puffer beginnend bei $0351 (=849). Hier fängt nämlich der nicht sichtbare Teil des Filenamens an. Schieben Sie dann dieses Programm direkt hinter den 16 Zeichen langen Filenamen, den Sie vorh er
Das große Anti-Cracker-Buch
176
in einem freien Teil des Speichers abgelegt haben. Sollte der Filename weniger als 16 Zeichen besitzen, so füllen Sie die fehlenden Bytes mit Leerzeichen auf. Beim Abspeichern rufen Sie wie gewohnt die Routinen FILPAR, FILNAM und SA VE auf (s. Unterkapitel 2.2). Die Filenamenlänge geben Sie dabei groß genug an, damit das gesamte Programm erfaßt wird . Damit bei der Meldung 'SA VING .. .', die sich im Gegensatz zur FOUNDMeldung nicht auf 16 Buchstaben beschränkt, keine unnötigen Zeichen auf dem Bildschirm erscheinen, können Sie vorher noch die Speicherstelle $90 (=157) auf Null setzen. Damit verhindern Sie die Ausgabe der Betriebssystemmeldungen. Das im Unterkapitel 5.3 beschriebene Kassetten- Kopierschutz-System liefert ein Beispiel für dieses Vorgehen. In Verbindung mit der Erstellung von Datenfiles war davon d ie Rede, daß man Daten ohne File-Endemarkierung schreiben kann. Dazu muß man wissen, daß für diese Form der Datenspeicherung der Bandpuffer von $033C (=828) bis $03FB (=1019) benutzt wird. Wenn man nun nur einige Daten mit dem Befehl 'PRINT#' in das File und damit an den Anfang des Puffers schreibt, wird bei Aufruf des 'CLOSE'-Befehls trotzdem immer der gesamte Pufferinhalt aufs Band geschrieben. Daher lassen sich Werte in den Kassettenpuffer "poken", die zwar nicht mit einer Null abgeschlossen sind, aber trotzdem abgespeichert werden. Das ganze sieht dann so aus: OPEN 1,1,1 PRINT#1,IDATEN" POKE 1010,123 CLOSE 1
Nach der 123 steht keine Null als File-Endekennzeichen. Das Einlesen geschieht analog dazu: OPEN 1,1,0 A=PEEK(1010)
A enthält jetzt den Wert 123, sofern niemand den gescheiterten Versuch unternommen hat, das Datenfile zu kopieren.
Kopierschutz mit der Datasette 5.5
Entwicklung eines eigenen Aufzeichnungsformats
5.5.1
Direkte Ansteuerung der Datasetten-Funktionen
177
Die wohl interessanteste und sicherste Schutzmethode ist die Entwicklung einer eigenen Aufzeichnungs- und Laderoutine. Neben der Tatsache, daß dieses Verfahren Kopierversuche ohne Zuhilfenahme zweier Rekorder völlig sinnlos macht, spricht noch ein anderer Grund für diesen aufwendigen Schutz. Er erlaubt nämlich, die Ladegeschwindigkeit beträchtlich zu erhöhen, was sich bei längeren Programmen äußerst angenehm bemerkbar macht. Weiterhin bietet eine eigene Laderoutine meist wesentlich mehr Möglichkeiten für einen guten Programmschutz als die Originalroutine. Es ist zum Beispiel möglich, Programmteile in die gerade ablaufende Laderoutine hineinzuladen, ohne die man die nachfolgenden Daten nicht lesen kann. Man kann damit ein solches Verwirrspiel treiben, daß auch hartnäckigste Knacker nach einiger Zeit den Mut verlieren. Wir kommen allerdings nicht darum herum, einige Grundlagen zu beschreiben, damit Sie die weiteren Erklärungen verstehen. Wie sieht prinzipiell das Schreiben und Lesen von Daten auf Band aus? Man braucht eigentlich nur eine Technik, die es einem erlaubt, einzelne Bits auf dem Band abzulegen. Schließlich kann man sein Programm komplett in Bits 'zerlegen', die dann beim Einladen wieder zu Programm-Bytes zusammengefügt werden. Tatsächlich existiert auch eine Leitung, über die ein LOWoder HIGH-Signal aufs Band geschrieben werden kann. Man kann aber jetzt nicht hingehen und einfach jedes Bit, das man schreiben will, auf diese Leitung legen. Wenn man nämlich mehrere gleichartige Bits hintereinander schreiben würde, ließe sich aufgrund der relativ hohen Gleichlaufschwankungen der Datasette nicht mehr exakt unterscheiden, wieviele Bits sich auf dem Band befinden. Außerdem existiert keine Leitung, an der man direkt das LOW- oder HIGH-Signal auf dem Band ablesen könnte. Ein lesbares Signal stellt nur der Wechsel von HIGH nach LOW dar, was als fallende beziehungsweise negative Flanke bezeichnet wird. Um nun lesbare Daten auf eine Kassette zu
178
Das große Anti-Cracker-Buch
bringen, verfährt man wie folgt: Man untersucht fallende Flanken nach unterschiedlichen Zeitabständen, wobei beispielsweise eine kleine Zeitspanne ein Null-Bit und eine große Zeitspanne ein Eins-Bit bedeutet. Beim Ei nlesen mißt man diese Zeitspanne und kann dann daraus erkennen, ob ein Null- oder Eins-Bit auf dem Band steht.
Timing beim Lesen und Schreiben von Bits ayf Band HIGH - + _. .. __
LOW _
t
t
t
Signal
signal
Signal
t Signal: Wechsel von HIGH nach LOW
groue Zeitspanne: 1 -Bit
Abb.5.5.1:
grolle Zeltlpanne: 1 - Bit
Timing beim Lesen und Schreiben von Bits auf Band
Da sich zum Messen von Zeiten ein Chip im C64 besonders eignet, der sogar gleich zweimal vorhanden ist, soll dieser Chip als allererstes beschrieben werden. Die Für Die uns
Rede ist vom 'Complex Interface Adapter 6526', kurz CIA. unsere Zwecke reicht es, nur den ersten CIA zu betrachten. Basisadresse dieses Chips liegt bei $DCOO (=56320). Die f ür interessanten Register sind die folgenden: $OC04 $OC05 $OC06 $OC07
(=56324): (=56325): (=56326): (=56327):
Timer Timer Timer Timer
A A B B
LOW -Byte HIGH-Byte LOW -Byte HIGH-Byte
Kopierschutz mit der Dalase[[e
179
$DCOO (=56333): Interrupt-Kontroll-Register (ICR) $OCOE (=56334): Kontroll-Register Timer A (CRA) $DCOF (=56335): Kontroll-Register Timer B (CRB)
Der CIA enthält zwei 16-Bit-Timer, die von einem voreingestellten Wert auf Null zurückzählen und dann einen Interrupt auslösen können. Timer A wird vom Betriebssystem verwendet, um in regelmäßigen Abständen die Tastatur abzufragen. Man sollte daher für eigene Experimente Timer B verwenden. Jeder der beiden Timer besteht aus einem Speicher für den Startwert und einem Zähler. Schreibt man in eines der TimerRegister einen Wert, so gelangt dieser in den Startwertspeicher. Liest man das Timer-Register aus, so erhält man den aktuellen Stand des Zählers. Ob der Zähler gestartet, angehalten oder auf den Startwert gesetzt werden soll, bestimmt man über das zugehörige Kontrollregister. Ebenfalls über das Kontrollregister wird die Timer-Triggerung festgelegt, das heißt, das Signal, welches das Herabzählen um Eins auslöst. Üblicherweise geschieht dies durch den Sytemtakt oder auch durch externe Signalquellen_ Es besteht aber auch die Möglichkeit, Timer B zählen zu lassen, wie oft Timer A abgelaufen ist . Dadurch kann man die zwei 16Bit-Timer zu einem 32-Bit-Timer zusammenschalten. Hier nun die genaue Beschreibung der Kontrollregister:
Kontrollregister A: $OCOE Bit 0: 1=Timer AStart, O=Timer A Stop Bit 1-2: nur für externe Signale wichtig Bit 3: 1=Timer zählt nur einmal vom Startwert auf Null, dann wird der Startwert wieder in den Zähler geladen und der Timer gestoppt. O=Timer zählt fortlaufend vom Startwert auf Null und beginnt dann wieder von vorn. Bit 4: 1=Startwert wird in Zähler übertragen, egal, ob der Timer gerade läuft oder ni cht. Bit 5: O=Timer zählt Systemtaktpulse. 1=Timer wird durch eine externe Signalquelle getriggert. Bit 6 -7: für unser Vorhaben uninteressant.
180
Das große Al1ti-Cracker-Buch
Kontrollregister B: $DCOF Bit 0-4: Diese Bits haben die gleiche Bedeutung wie die Bits 0 bis 4 von Timer A, nur jetzt bezogen auf Timer B_ Bit 5-6: OO=Timer zählt Systemtaktpulse_ 01=Timer wird durch eine externe Signalquelle getriggerL 10=Timer B zählt, wie oft Timer A abgelaufen ist_ 11=Timer B zählt, wie oft Timer A abgelaufen ist, aber nur, wenn ein externes Signal anliegt_ Bit 7: für unser Vorhaben uninteressant_
Über das Interrupt-Kontroll-Register läßt sich festlegen, ob ein bestimmtes Ereignis, wie zum Beispiel das Ablaufen eines Timers, zu einem Interrupt (IRQ) am Prozessor führen solL Außerdem kann man über dieses Register feststellen, ob eines dieser Ereignisse schon stattgefunden hat. Hier nun die Zuordnung der Bits dieses Registers zu den möglichen Ereignissen:
Interrupt-Kontroll-Register: $DCOD Bit 0: 1=Timer A abgelaufen_ Bit 1: 1=Timer B abgelaufen_ Bit 2-3: für unser Vorhaben uninteressant. Bit 4: 1=negative Flanke am Pin-FLAG aufgetreten. Da der Pin-FLAG dieses Chips mit der Ausgangsleitung des Kassettenrekorders verbunden ist, läßt sich anhand dieses Bits feststellen, ob ein Signal auf dem Band aufgezeichnet wurde. Bit 5-6: immer Null. Bit 7: 1=lnterrupt wurde durch eines der oben aufgeführten Ereignisse ausgelöst.
Wenn man in dieses Register einen Wert schreibt, beeinflußt man den sogenannten Interrupt-Masken-Speicher, welcher bestimmt, welche(s) Ereignis(se) zu einem IRQ führen soll(en).
Kopierschutz mit der Dataselle
181
Wenn Bit 7 des geschriebenen Wertes gesetzt ist, werden alle Bits des Masken-Speichers gesetzt, die in diesem Wert auf Eins standen. Entsprechend kann man die Bits des Masken-Speichers löschen, indem man Bit 7 auf Null setzt und alle zu löschenden Bits auf Eins. Dazu ein Beispiel: LDA #$81
STA $DCOD
setzt Bit 0 im Maskenspeicher, da $81=%10000001 ist. Nur Bit 0 wird gesetzt, alle anderen Bits bleiben unbeeinflußt. LDA #$02
STA $DCOD
löscht Bit 1, da $02=%00000010 ist. Ein gesetztes Bit im Masken-Speicher läßt das zugehörige Ereignis einen Interrupt auslösen. Ein gesetztes Bit im Interrupt-Kontroll-Register dagegen zeigt an, ob ein Ereignis stattgefunden hat. Diese Bits werden automatisch gelöscht, sobald man dieses Register ausliest. Es ist allerdings auch notwendig, die Bits zu löschen, wenn ein Interrupt durch das entsprechende Ereignis stattfinden soll. Das bedeutet insbesondere, daß eine regelmäßig aufgerufene Interrupt-Routine jedesmal durch einen Lesezugriff die Bits auf Null setzen muß. Theoretisch könnten wir jetzt schon Daten vom Band lesen. Was uns aber dazu noch fehlt, ist die Methode, wie wir überhaupt Daten aufs Band bekommen. Wie gesagt, existiert eine Leitung, deren LOW- oder HIGH-Signal direkt zur Datasette geschickt wird. Diese Leitung ist mit dem Ein-/ Ausgabe-Port des Prozessors verbunden, das heißt, sie läßt sich über die Speicherstelle Eins ansprechen. Über diese Speicherstelle läßt sich auch der Motor der Datasette einschalten und testen, ob eine Taste des Recorders gedrückt ist. Hier nun die Funktionen der einzelnen Bits:
182
Das große Allti-Cracker-Buch
Professor-Port: $01 Bit 3: Schreib-Leitung zur Oatasette Bit 4: O=Oatasetten-Taste gedrückt, 1=nicht gedrückt Bit 5: O=Motor ein, 1=Motor aus
Noch ein Hinweis, bevor wir zu Beispielen übergehen: Wie bei allen zeitkritischen Operationen muß man vor Beginn unserer Schreib- und Lesevorgänge dafür sorgen, daß der Prozessor nicht von außen unterbrochen oder angehalten wird_ Falls Sie also keine eigene Interrupt-Routine für den Bandbetrieb verwenden, benutzen Sie am Beginn Ihres Programms den Befehl SEI. Außerdem stoppt der Videocontroller (VIC) bei eingeschaltetem Bildschirm den Prozessor von Zeit zu Zeit für 42 Takzyklen_ Um den Bildschirm abzuschalten, löscht man Bit 4 im Register $DOII (=53265) beispielsweise so: LOA $0011 ANO#$EF STA $0011
Das Wiedereinschalten des Bildschirms sieht dann entsprechend so aus: LOA $0011 ORA#$10 STA $0011
5.5 .2 Signalerzeugung auf dem Band als Kopierschutz Jetzt wollen wir endlich dazu übergehen, das theoretisch Besprochene in die Praxis umzusetzen. Wir werden ein Programm entwerfen, das eine Reihe von Signalen in gleichen Zeitabständen aufs Band schreibt. Das dazu passende Leseprogramm soll erkennen, ob die Abstände zwischen den Signalen stimmen oder nicht. Diese Aufzeichnung kann demnach schon als Kopierschutz fungieren, indem man sie einfach hinter sein abgespeichertes Programm setzt und abfragt. Das eigentliche Programm läßt sich dann zwar kopieren, jedoch der 'von Hand' erzeugte Teil nicht. Hier also das Programm:
Kopierschutz mit der Dalaselle benutzte Speicherstellen: $FBj$FC : Zähler für die Anzahl geschriebener Signale
COOO C001 C004 C006 C009 COOB COOO COOF C011 C013 C015 C017 C019 C01A C01C C01D C01F C020 C022 C024 C026 C028 C02A C02C e02E C030 C032 e033 C035 C037 e039 C03B C03C C03E C040
SEI lOA ANO STA lDA BIT BNE lOA ANO STA lDA lDX DEY BNE DEX BNE SEC SBC BNE STX lDA STA lDA AND STA lOX DEX BNE ORA STA lDX OEX BNE DEC BNE
$0011 #$EF $0011 #$10 $01 $COOB S01 #$DF $01 #$04 #$00 SC019 SC019 #$01 SC019 SFB #$20 SFC S01 #$F7 S01 #$14 SC032 #$08 S01 #$3e $C03B SFB SC02A
Interrupts verhindern Unterbrechungen durch den Videocontroller verhindern Bit 4 von Speicherstelle testen Oatasetten-Taste gedrückt? verzweige, wenn nein Speicherstelle 1 auslesen Bit 5 löschen (Motor an) Wert zurückschreiben Zähler für Zeitschleife Zähler für Zeitschleife abwarten, bis der Motor seine Arbeitsgeschwindigkeit erreicht hat dabei gleichzeitig Abstand für die leseroutine halten
Zähler $FB/$FC auf $2000 (=8192) setzen Bit 3 von Speicherstelle löschen (lOW-Signal auf dem Band erzeugen) Zeitschleifenzähler auf $14 (=20) 20*5=100 Taktzyklen warten Bit 3 von Speicherstelle 1 setzen (HIGH-Signal auf dem Band erzeugen) Zeitschleifenzähler auf $3C (=60) 60*5=300 Taktzyklen warten Zähler lOW -Byte verringern verzweige, wenn noch nicht Null
183
Das große Anti-Cracker-Buch
184 C042 C044 C046 C049 C04B C04E COSO COS2 COS4 C056 C05?
OEC BNE LOA ORA STA LDA ORA STA STA CLI RTS
SFC SC02A S0011 #$10 S0011 S01 #$20 S01 SCO
Zähler HIGH-Byte verringern verzweige, wenn noch nicht Null Bildschirm durch Setzen von Bit 4 in $0011 wieder einschalten Motor der Datasette durch Setzen von Bit 5 in Adresse 1 abschalten Motorsteuerflag ungleich Null Interrupts wieder zulassen Rücksprung
Zuerst sperrt dieses Programm jegliche Unterbrechungsmöglichkeiten. Dann fragt es Bit 4 von Speicherstelle Eins ab, um zu sehen, ob schon eine Taste an der Datasette gedrückt wurde. Sobald man RECORD und PLA Y betätigt hat, wird durch Löschen von Bit 5 in Adresse Eins der Datasettenmotor eingeschaltet. Man muß dem Motor etwas Zeit geben, bis er seine volle Geschwindigkeit erreicht. Dazu reicht normalerweise die oben verwendete Zeitschleife ab $C017 aus: C01? C019 C01A C01C C01D
LDX #$00 OEY BNE SC019 DEX BNE SC019
Hier geben wir aber dem Motor sogar die vierfache Zeit, damit später beim Lesen der Daten ein größerer Spielraum für die Hochlaufzeit vorhanden ist und der Anfang der Signale nicht verpaßt wird. Als nächstes setzt das Programm den Zähler für die Anzahl der zu schreibenden Signale auf $2000 (=8192). Dies scheint ziemlich groß, aber da der zeitliche Abstand zwischen zwei Signalen etwa 400 Taktzyklen beträgt, benötigt die Aufzeichnung nur knapp dreieinhalb Sekunden. Um ein Signal zu schreiben, wird für etwa 100 Taktzyklen die Schreibleitung der Datasette auf LOW gelegt. Danach erhält diese Leitung für ungefähr 300 Taktzyklen
Kopierschutz mit der Dalaselle
185
den Pegel HIGH. Der Abstand zwischen den Signalen beträgt demnach 400 Zyklen. Man sollte einen LOW- oder HIGH-Pegel für mindestens 30 bis 40 Taktzyklen beibehalten, da aufgrund der Gleichlaufschwankungen des Recorders sonst die Gefahr besteht, daß ein Signal beim Lesen nicht mehr erkannt wird. Um die richtige Anzahl von Taktzyklen zwischen den Signalen abzuwarten, verwenden wir folgende Form der Zeitschleife:
LOOP
LOX #$Wert OEX BNE LOOP
Wie Sie aus der Taktzyklentabelle dieses Buches entnehmen können, benötigt der Befehl 'DEX' zwei, der Befehl 'BNE' (wenn die Verzweigung ausgeführt wird) drei Zyklen. Die obige Befehlsfolge benötigt demnach ziemlich genau 'Wert * 5' Taktzyklen (eigentlich: Wert * 5 + I). Nachdem alle Signale geschrieben wurden, wird der Motor der Datasette abgeschaltet, der Bildschirm wieder eingeschaltet und der Wert der Speicherstelle $CO (= 192) ungleich Null gesetzt. Diese Speicherstelle wird vom Betriebssystem dazu verwendet, um zu bestimmen, ob bei gedrückter Rekorder-Taste der Motor gestartet werden soll oder nicht. Falls man diese Speicherstelle auf dem Wert Null ließe, so würde nach dem Befehl 'CL!' die Datasette, trotz vorherigem Setzen des Bits 5 der Speicherstelle Eins, weiterlaufen. Zu diesem Programm benötigt man ein zweites, welches die Daten wiedererkennt: benutzte Speicherstellen: siehe oben
COOO C001 C004 C006 C009 COOB
SEI LOA ANO STA LOA STA
$0011 #$EF $0011 #$FF $OC06
Interrupts verhindern Unterbrechungen durch den Videocontroller verhindern Startwert für Tirner $FFFF Timer LOW -Byte setzen
186 COOE STA C011 LOA C013 BIT C01S BNE C01? LOA C019 ANO C01B STA C010 LOX C01F OEY C020 BNE C022 OEX C023 BNE C02S LOA C02? BIT C02A BEQ C02C OEX C020 BNE C02F STX C031 LOA C033 STA C03S LOA C03? STA C03A LOA C03C BIT C03F BEC C041 LOA C043 STA C046 LOA C049 LOX C04C LOY C04E STY COS 1 EOR COS3 TAY COS4 TXA COSS EOR COS? TAX C058 CPY C05A SBC COSC BCC COSE CPY
Das große Anti-CrQcker-Buch $OCO? #$10 $01 $COH S01 #$OF $01 #$00 $COH $COH #$10 $OCOO $C02? $C02? $FB #$18 $FC #$19 $OCOF #$10 $OCOO $C03C #$08 $OCOF $OW6 $OCO? #$19 SOCOF #$FF
#$FF #$C8 #$00 $C06S #$58
Timer HIGH -Byte setzen Bit 4 von Speicherstelle 1 testen verzweige, wenn Bit gesetzt Speicherstelle 1 auslesen Bit 5 löschen (Motor einschalten) und zurückschreiben Zähler für Zeitschleife Motor Zeit bis zum Erreichen der Arbeitsgeschwindigkeit geben Bit 4 des ICR testen Signal vom Band (HIGH/LO~-~echsel)? verzweige, wenn nein schon 256 Signa le empfangen? verzweige, wenn nein Zeiger für Anzahl zu lesender Signale auf $1800 (=6144) setzen Timer auf Startwert setzen und starten Bit 4 des ICR testen Signal vom Band? verzweige, wenn nein Timer anhalten Timer LO~ - Byte lesen Timer HIGH-Byte lesen Timer wieder starten Timer-Bits invertieren, um Anzahl der Taktzyklen seit letztem Signal zu erhal t en Timer-~ert mit 200 (=$CB) vergleichen verzweige, wenn kleiner Timer - ~ert
Kopierschutz mit der Datasette C060 C061 C063 C065 C068 C06A C06C C06E C070 C072 C075 C078 C07A C07D C07F C081 C083 C085 C086
TXA SBC BCC JMP OEC BNE OEC BNE LOA STA LOA ORA STA STA LOA ORA STA CL! RTS
#$02 SC068 SFCE2 SFB SC03A SFC SC03A #$08 SOCOF S0011 #$10 S0011 SCO S01 #$20 S01
187
mit 600 (=$0258) vergleichen verzweige, wenn kleiner falls größer, Sprung nach RE SET Zähler für Anzahl zu lesender Signale vermindern verzweige, wenn ungleich Null Timer anhalten Bildschirm wieder einschalten Motorsteuer-Flag ungleich Null Speicherstelle Eins lesen Bit 5 setzen (Motor anhalten) Wert zurückschreiben Interrupts wieder zulassen Rücksprung
Dieses Programm demonstriert zum ersten mal die Benutzung der Timer_ Der Programmbeginn ist der gleiche wie beim ersten Programm; alle Unterbrechungen werden ausgeschaltet, und der Motor wird eingeschaltet, sobald eine Recordertaste gedrückt wird (hier sollte es die PLA Y - Taste sein). Außerdem läßt man dem Motor wieder Zeit zum Anlaufen. Vorher ist aber schon der Startwert für den Timer auf $FFFF (=65535), also auf den Maximalwert, gesetzt worden. Um Zeiten zu messen, braucht man bloß den Zähler des Timers auszulesen und alle Bits dieses Wertes zu invertieren. Man erhält dann die abgelaufene Zeit plus Eins. Die 'plus Eins' können wir aber getrost vergessen, da es aufgrund der Ungenauigkeit der Datasette auf einen Taktzyklus mehr oder weniger nicht ankommt. Sobald das Band läuft, werden erst einmal 256 Signale abgewartet, damit nicht die Gefahr besteht, daß zufälligerweise einige Signale, die gar nicht zur geschriebenen Signalfolge gehören, auf
188
Das große Anti-Cracker-Buch
ihren zeitlichen Abstand getestet werden. Solche Signale können beim Einschaltvorgang der Datasette unbeabsichtigt auftreten. Das Erkennen eines Signals erfolgt folgendermaßen: Wir testen Bit 4 des Interruptkontrollregisters (/CR, $DCOD). Sobald dieses Bit den Wert Eins annimmt, lag ein Signal (eine negative Flanke) auf dem Band vor. Durch das Auslesen dieses Register wird automatisch das entsprechende Bit im ICR wieder gelöscht und man kann direkt auf das nächste Signal warten. Jetzt kann die eigentliche Kopierschutzabfrage beginnen. Der Zähler $FB/$FC für die Anzahl der Signale wird auf $1800 (=6144) gesetzt. Wir haben zwar anfangs $2000 (=8192) Daten aufs Band gebracht, falls aber aus irgend welchen Gründen der exakte Beginn der Aufnahme verpaßt werden sollte, ist hierdurch ein ausreichender Spielraum gegeben.
Als nächstes wird der Timer gestartet: C035 LDA #$19 C037 STA $DCOF
=%00011001 ins Kontrollregister schreiben
Folgende Bits im Timer-Kontrollregister werden so gesetzt: Bit 0: Timer wird gestartet. Bit 3: Timer hält nach Ablauf an. Bit 4: Startwert wird in den Zähler geladen.
Außerdem sind die Bits 5 und 6 auf Null, das bedeutet, der Timer zählt Systemtakte. Sobald der Timer läuft, wird auf ein Signal vom Band gewartet. Kommt das Signal, halten wir den Timer an, lesen ihn aus und starten ihn neu. Dann invertieren wir den gelesenen Timer- Wert, um die vergangene Zeit in Taktzyklen zu erhalten. Diese Zeit betrug beim Schreiben 400 Zyklen und soHte demnach beim Lesen auch etwa diesen Wert betragen. In unserem Beispiel räu-
Kopierschutz mit der Datasetle
189
men wir dem Signal einen Spielraum von 200 Zyklen nach oben und unten ein. Sollten also zwei Signale in kürzerem Abstand als 200 Taktzyklen oder in längerem Abstand als 600 Taktzyklen aufeinander folgen, so gilt der Kopierschutz als nicht erkannt. In diesem Falle springt das Programm zur RESET -Routine, die den Rechner in den Einschaltzustand versetzt. Sobald das Programm alle Signale richtig erkannt hat, macht es das gleiche wie die Schreibroutine bei ihrem Abschluß: Der Motor wird aus-, der Bildschirm eingeschaltet und Interrupts werden wieder zugelassen. Diese Abfrage wird also nur dann richtig beendet, falls der Kopierschutz erkannt wurde . Anderenfalls wird entweder ein RESET ausgelöst, oder, falls überhaupt keine Signale auf dem Band sind, die Warteschleife nicht verlassen. Falls Sie dieses Programm selber als Kopierschutz verwenden wollen, so sollten Sie daran denken, daß jeder, der ebenfalls das Programm aus diesem Buch besitzt, ohne weiteres die Schutzaufzeichnung reproduzieren kann, wenn Sie das Programm unverändert übernehmen. Sie können allerdings auch Änderungen vornehmen, die dieses Problem ausschließen. Es bieten sich dabei zum Beispiel die Anzahl und der Abstand zwischen den Signalen an. Wie weit Sie bei solchen Änderungen gehen wollen, bleibt Ihnen selbst überlassen. Nun zur Handhabung des Programms. Bringen Sie die Erkennungsroutine in dem zu schützenden Programm unter. Falls Sie ein BASIC-Programm schützen wollen, so können Sie das nachfolgende DA T A-Listing dort einbauen . Zum Starten des Programms dient dann der Befehl 'SYS 49152'. Die Erkennungsroutine läßt sich aber auch leicht in ein Maschinenprogramm packen, da sie völlig relokatibel ist. Egal, mit welchem der beiden Fälle Sie es zu tun haben, Sie sollten Ihr Programm codieren und mit einem Autostart versehen, eventuell sogar gegen einen RESET schützen und die Erkennungsroutine nach ihrem Ablauf zerstören. Genügend Anregungen dazu finden Sie in den Kapiteln zum Programmschutz.
190
Das große Al1ti-Cracker-Buch
Nachdem sich Ihr Programm nun auf dem Band befindet, laden Sie (am besten von einer anderen Kassette) die Schreibroutine. Wenn diese Routine als DATA-Listing vorliegt, so starten Sie sie noch mit 'RUN'. Legen Sie nun wieder die Kassette mit Ihrem Programm ein, starten Sie die Schreibroutine mit 'SYS 49152' und betätigen Sie RECORD/PLA Y am Recorder. Drei bis vier Sekunden später erscheint der Bildschirm wieder, und Sie sind fertig. Spulen Sie nun die Kassette bis zum Anfang Ihres Programms zurück und laden Sie es. Falls Ihr Programm einen Autostart besitzt, geschieht alles weitere automatisch. Falls nicht, starten Sie es, aber lassen Sie am besten die PLA Y - Taste gedrückt. Die Leseroutine sollte nun den Kopierschutz erkennen. Hier nun die DAT A-Listings der beiden Schutzprogramme: Leseroutine: Bereich $COOO bis $C086 100 FORI=lT0135STEP15:FORJ=OT014:READA$:B$=RIGHT$CA$,1) 105 A=ASCCA$)'48:IFA>9THENA=A-7 110 B=ASCCB$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=CC+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATA78,AD,11,DO,29,EF,8D,11,DO,A9,FF,8D,06,DC,8D, 48 301 DATA07,DC,A9,10,24,01,DO,FC,A5,01,29,DF,85,01,A2, 99 302 DATAOO,88,DO,FD,CA,DO,FA,A9,10,2C,OD,DC,FO,FB,CA, 108 303 DATADO,F8,86,FB,A9,18,85,FC,A9, 19,8D,OF,DC,A9,10, 126 304 DATA2C,OD,DC,FO,FB,A9,08,8D,OF,DC,AD,06,DC,AE,07, 109 305 DATADC,AO,19,8C,OF,DC,49,FF,A8,8A,49,FF,AA,CO,C8, 0 306 DATAE9,00,90,07,CO,58,8A,E9,02,90,03,4C,E2,FC,C6, 144 307 DATAFB,DO,CE,C6,FC,DO,CA,A9,08,8D,OF,DC,AD,11,DO, 172 308 DATA09,10,8D,11,DO,85,CO,A5,01,09,20,85,01,58,60, 217
Kopierschutz mit der Dataselle
191
Schreibroutine: Bereich $COOO bis $C057 100 FORI=1T088STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASCCA$)-48:IFA>9THENA=A-7 110 B=ASCCB$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=CC+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATA78,AD, 11,DO,29,EF,8D,11,DO,A9, 10,24,01,DO,FC, 54 301 DATAA5,01,29,DF,85,01,A9,04,A2,OO,88,DO,FD,CA,DO, 114 302 DATAFA,38,E9,01,DO,F5,86,FB,A9,20,85,FC,A5,01,29, 123 303 DATAF7,85,01,A2,14,CA,DO,FD,09,08,85,01,A2,3C,CA, 9 304 DATADO,FD,C6,FB,DO,E8,C6,FC,DO,E4,AD,11,DO,09,10, 99 305 DATA8D,11,DO,A5,01,09,20,85,01,85,CO,58,60,A4,A4, 8
Diese Art des Schutzes ist zwar an sich schon sehr wirkungsvoll, birgt aber noch den Nachteil in sich, daß sie die geringe Ladegeschwindigkeit beibehält. Unser nächstes Ziel wird demnach sein, ein eigenes, schnelleres Aufzeichnungsformat zu entwickeln. Ganz zu Anfang haben wir die Behauptung aufgestellt, es lasse sich kein allgemeines Kopierprogramm schreiben. Jetzt wird es Zeit, diese Behauptung zu rechtfertigen. Aufgezeichnete Daten bestehen aus Signalen, die sich in unterschiedlichen Zeitabständen auf dem Band befinden. Da ein Kopierprogramm nicht "weiß" (und aufgrund der Gleichlaufschwankungen auch nicht feststellen kann), ob dieses Aufzeichnungsformat zwei oder mehr unterschiedliche Signalabstände verwendet, kann es prinzipiell nur eines machen, nämlich jedesmal die Zeit von einem Signal bis zum nächsten zu messen und abzuspeichern. Diese Methode benötigt zwangsläufig wesentlich mehr Speicherplatz, als das Programm nach dem normalen Einladen wirklich belegt. Ein Beispiel: Ein Aufzeichnungsformat arbeitet mit zwei verschieden Zeiten für ein Null- und ein Eins-Bit. Zusätzlich wird noch nach jedem achten Bit ein weiteres Signal (mit anderen möglichen Zeiten) geschrieben. Für eine gute Zeitmessung benötigt man pro Signal zwei Bytes. Wenn das zu kopierende Programm vier Kilobyte belegt, würden demnach 4096 * (8+1) = 36864 Signale auf dem Band sein . Das Kopierprogramm würde dem-
192
Das große Anti-Cracker-Buch
nach 2 * 36864 = 73728 Byte an Speicherplatz benötigen, also wesentlich mehr, als der C64 zur Verfügung hat. Man kann die Aufnahme auch nicht in mehrere Teile aufsplitten, da eine zusammenhängende Datenfolge auch zusammenhängend geschrieben werden muß. Man könnte annehmen, daß es eventuell sinnvoll wäre, ein Kopierprogramm für sehr kurze Programme zu schreiben oder eine Speichererweiterung zu verwenden. Sicherlich könnte man so tatsächlich Kopien anfertigen, die allerdings das gleiche Problem beinhalten würden, als wenn man mit einem Überspielkabel arbeiten würde. Schließlich sind die gelesenen Zeitabstände wegen der vielzitierten Gleichlaufschwankungen nicht exakt die gleichen wie die geschriebenen. Daher wird jede weitere Kopie ein wenig schlechter sein wie ihr Vorgänger. Um also sinnvoll Kopien anfertigen zu können, muß das Kopierprogramm die Lese- und Schreibzeiten des jeweiligen Formates kennen und kann demnach nicht mehr für alle möglichen Formate verwendbar sein.
5.5.3
Ein neues Aufzeichnungsformat am Beispiel des "KKS"
Wir haben uns zum Ziel gesetzt, in diesem Kapitel ein eigenes Aufzeichnungsformat für Bandaufnahmen zu entwickeln. Dieses Programm soll den Namen "KKS" (Kassetten-KopierschutzSystem) tragen. Das KKS muß folgende Eigenschaften besitzen:
1.
nicht mit diversen Kopierprogrammen zu kopieren
2.
höhere Aufzeichnungsgeschwindigkeit
3.
automatisches Starten der schnellen Laderoutine und automatisches Starten des nachgeladenen Programms.
4.
einfache Handhabung für Leser, die sich nicht im Detail mit dem Programm auseinandersetzen wollen.
Kopierschutz mit der Dalaselte
193
5.
. speichern aus dem RAM-Bereich unter dem BASIC-Interpreter
6.
Option zum Laden in andere Speicher bereiche, als die, aus denen abgespeichert wurde.
Prinzipiell sieht das Programm so aus: Zuerst wird der Vektor der SA VE-Routine des Betriebssystems auf die eigene Routine umgestellt. Gibt man jetzt den Befehl 'SA VE "Programmname'" ein, wird zuerst ein Teil der Laderoutine nach $02A8 (=680) geschoben. Der andere Teil befindet sich im Filenamen und gelangt demnach beim Einladen in den Kassettenpuffer. Die Laderoutine wird dann ganz normal, "langsam" und inklusive Autostart abgespeichert. Danach wird das eigentliche Programm unter dem neuen Format abgelegt. Beim Einladen gibt man wie gewohnt 'LOAD' ein und betätigt die PLA Y - Taste. Das Betriebssystem lädt und startet die neue Laderoutine automatisch . Diese lädt und startet nun das Hauptprogramm. Wie sieht das neue Aufzeichnungsformat aus? Zuerst wird eine sogenannte "Synchronisations-Markierung" geschrieben, die zum Erkennen des Programmanfangs dient. Genaueres dazu erfahren Sie im übernächsten Absatz. Als nächstes folgen die Start- und Endadresse des zu ladenden Programms. Danach steht dann das Hauptprogramm . Ganz am Ende kommt noch ein Byte als Prüfsumme, welches das Erkennen von Ladefehlern ermöglicht. Die Prüfsumme wird gebildet, indem alle Bytes mit dem Befehl 'EOR' verknüpft werden. Wenn die ab gespeicherte Prüfsumme mit der beim Laden gebildeten nicht übereinstimmt, können die geladenen Daten nicht die gleichen sein wie die ursprünglich abgespeicherten. Der oben beschriebene Vorgang setzt voraus, daß die Möglichkeit besteht, Bits und Bytes aufs Band zu bringen. Wir gehen dabei so vor, daß ein Eins-Bit als langer und ein Null-Bit als kurzer Abstand des vorhergehenden Signals vom nächsten codiert wird. Die hier verwendeten Zeiten sind 388 Taktzyklen
194
Das große Al1li-Cracker-Buch
für die Null und 643 Zyklen für die Eins. Dementsprechend werden dann je acht gelesene Bits zu einem Byte zusammengefaßt. Die Laderoutine steht zunächst einmal vor dem Problem, erkennen zu müssen, welches das erste Bit ist, das zu den aufgezeichneten Daten gehört. Dieses Problem wird mit Hilfe der schon angesprochenen Synchronisationsmarkierung gelöst. Diese Markierung besteht aus einer Folge gleicher Bytes, deren Bitkombination so beschaffen ist, daß es eindeutig ist, welches Bit an welcher Stelle eines Bytes steht. Beispielsweise ist eine Folge von $55-Bytes ($55=%0 I 0 I 0 101) dafür nicht geeignet, da die Bitkombination mehrerer hintereinander liegender Bytes so aussieht: ••• 010101010101010101010101010101 ...
Hier ist nicht unbedingt klar, daß das erste Bit der Folge tatsächlich das erste Bit in einem Byte der Sync-Markierung ist. Es könnte genauso gut das dritte oder das fünfte Bit sein. Dagegen schließt zum Beispiel eine $03-Byte-Folge ($03=%00000011) alle Mehrdeutigkeiten aus: ••• 011000000110000001100000011000 •..
Diese Folge kann nur auf eine Weise wieder in $03-Bytes zerlegt werden: ••• 011 00000011 00000011 000000'1 000 ..•
Die Synchronisationsmarkierung wird meistens noch durch ein oder mehrere Bytes ergänzt, welche die Wahrscheinlichkeit verringern, eine solche Markierung mit einer Bytefolge einer anderen Bandaufzeichnung zu verwechseln. Bei unserem Beispiel verwenden wir ein Byte mit dem Wert $48. Wie erreicht man nun das genaue Timing beim Schreiben und Lesen? Am einfachsten geht es mit einer Interruptroutine. Beim Schreiben wird der CIA - Timer als InterruptqueIIe verwendet. Dieser Timer bleibt auf einem konstanten Wert. Beim Auslösen eines Interrupts wird getestet, ob das nächste zu schreibende Bit
195
Kopierschutz mit der Dalaselle
ein Null-Bit ist. Falls ja, so wird sofort ein Signal auf dem Band erzeugt, falls nein, so wird vorher noch eine Zeitschleife ausgeführt. Danach startet man den Timer neu. Beim Lesen wird ebenfalls der Timer benutzt. Diesmal aber erzeugt nicht er einen IRQ, sondern das Signal von der Kassette. Das Bit, welches den Ablauf des Timer-Zählers anzeigt, wird damit gleichzeitig zum gelesenen Bit, da es genau dann eins wird, wenn der Abstand zweier Signale einen bestimmten Wert überschreitet. Der Zeitablauf beim Lesen und Schreiben errechnet sich beim KKS wie folgt: Bei Verwendung des IRQ-Vektors $0314/$0315 liegt zwischen dem Auslösen des Interrupts und dem Erreichen der eigenen Interruptroutine eine Zeitspanne von durchschnittlich 39 Taktzyklen. Bei der Schreibroutine vergehen bei einem Null-Bit noch 16 Zyklen, bis das Signal auf die Leitung gegeben wird, und noch einmal sechs Zyklen, bis der Timer neu gestartet wird. Wenn man den Timer auf einen Wert von 327 (=$0147) Zyklen setzt, so ergibt sich die Zeitdauer für ein Null-Signal folgendermaßen:
Starten des Timers: 6 Zeit bis zum IRQ : 327 Zeit bis zum Erreichen der eigenen IRQ -Routine: 39 Zeit bis zum Ändern der Signal-Leitung: 16 Gesamtzeit:
Zyklen Zyklen Zyklen Zyklen
388 Zyklen
Für ein Eins-Bit kommen zusätzlich durch die Zeitschleife noch einmal 255 Taktzyklen hinzu, wodurch sich der Abstand zum vorhergehenden Signal auf 255 + 388 = 643 Taktzyklen erhöht. Egal, ob ein Null- oder Eins-Bit geschrieben wird, die Schreibleitung bleibt für ca. 80 Taktzyklen auf LOW, was aber für die eigentliche Zeitberechnung unwichtig ist.
196
Das große Allli-Cracker-Buch
Beim Einlesen der Daten muß der Timer so eingestellt werden, daß er bei einem kurzen Bandsignal in der Zeit bis zum Auslesen des Interrupt-Kontroll-Registers mit Sicherheit noch nicht abgelaufen ist, dagegen bei einem langen Bandsignal das TimerBit im ICR mit Sicherheit auf Eins steht. Für ein Null-Bit läßt sich folgende Zeitberechnng anstellen:
Beginn: negative Flanke löst Interrupt aus Zeit bis zum Erreichen der Interrupt'Routine: Zeit zum Lesen des ICR: Zeit bis zum Start des Timers:
39 Zyklen 4 Zyklen 6 Zyklen
Gesamtzeit:
49 Zyklen
Zeit bis zum nächsten Signal: Zeit bis zum Lesen des ICR: Gesamtzeit:
388 . 49 = 339 Zyklen 39 + 4 = 43 Zyklen 382 Zyklen
Die gleiche Rechnung für ein Eins-Bit liefert als Ergebnis 637 Zyklen. Der Startwert für den Timer sollte demnach möglichst genau zwischen diesen beiden Werten liegen, also bei 510 (=$OlFE) Zyklen. Wie gelangen jetzt die Bytes aus dem Speicher auf das Band? Die Interruptroutine für das Abspeichern schreibt immer genau ein Byte. Dazu benutzt sie vier Adressen: - ein Schiebe register , aus welchem immer das ntichste zu schreibende Bit geschoben wird - ein Bitzähler, der festhält, wieviele Bits schon aus dem Schieberegister herausgeschoben wurden - ein Zwischenspeicher für das nächste Byte, welches ins Schieberegister übernommen wird - ein Speicher, der anzeigt, ob der Zwischenspeicher schon von der Interruptroutine ausgelesen wurde
Kopierschutz mit der Da(aselle
197
Das neben der Interruptroutine laufende Hauptprogramm wartet, bis die Interruptroutine das vorherige Byte aus dem Zwischenspeicher geholt hat, und schreibt dann das nächste Datenbyte dorthin. Der Speicher, an dem das Hauptprogramm erkennt, daß der Zwischenspeicher frei ist, wird zunächst von der Interruptroutine auf $80 (=128) gesetzt. Wenn das Hauptprogramm den Zwischenspeicher wieder belegt hat, ersetzt es die 128 durch eine Null. Sobald durch die Interruptroutine die letzten acht Bits aus dem Schieberegister auf das Band gebracht wurden, holt sie das Byte aus dem Zwischenspeicher ins Schieberegister, wodurch sich der Kreis schließt. Die Lese-Interruptroutine arbeitet genau umgekehrt, wobei wieder die gleichen vier Register verwendet werden. Jedes eingelesene Bit gelangt zunächst ins Schieberegister. Jeweils nach acht Bits wird der Inhalt des Schieberegisters in den Zwischenspeicher übertragen. Die Übergabe der Bytes an das Hauptprogramm vollzieht sich genauso wie die Übergabe von Bytes beim Schreiben. Der einzige noch zu betrachtende wichtige Fall ist die Synchronisation, also das Erkennen , bei welchem Signal die gespeicherten Daten anfangen. Hierzu läßt das Hauptprogramm die Interruptroutine arbeiten, ohne Daten aus dem Zwischenspeicher zu übernehmen. Es fragt stattdessen das Schieberegister ab, ob es den Wert $03 (von der Synchronisationsmarkierung) annimmt. Sobald dieser Fall eintritt, wird noch vor der nächsten IRQ-Anforderung der Zähler für die Anzahl der gelesenen Bits auf den Maximalwert sieben gesetzt. (Der Zähler wird immer von sieben auf null heruntergezählt.) Jetzt können regulär Bytes über den Zwischenspeicher eingelesen werden. Zu Beginn dieses Unterkapitels haben wir sechs Bedingungen an unser KKS gestellt, auf deren Erfüllung wir nun zu sprechen kommen. Der erste Punkt, die schwere Kopierbarkeit, wird allein dadurch erreicht, daß wir ein neues Aufzeichnungsformat benutzen. Der zweite Punkt, die höhere Aufzeichnungsgeschwindigkeit, ergibt sich durch die gewählten Zeiten zwischen den Signalen. Die durchschnittliche Zeitspanne beträgt hier 516 Taktzyklen.
198
Das große Anti-Cracker-Buch
Bei einer Taktfrequenz von 0.98 Megahertz ergibt sich eine Aufzeichnungsrate von etwa 1900 Bit/ Sekunde. Das ist mehr als sechsmal so schnell wie bei dem Aufzeichnungsformat des Betriebssystems. Wenn Sie wollen, können Sie die Geschwindigkeit noch erhöhen. Die nötigen Änderungen dazu sind im nachfolgend abgedruckten Assemblerlisting an drei Stellen durchzuführen: $COC9 Startwert des Timers beim Schreiben $C148 Zeitschleife für Differenz zwischen null und eins $037D Startwert des Timers beim Lesen Bedenken Sie aber, daß eine höhere Geschwindigkeit immer eine Verringerung der Datensicherheit zur Folge hat und daß sich die Aufzeichnungsrate wegen der Gleichlaufschwankungen der Datasette nicht beliebig steigern läßt. Sollten Sie beispielsweise keine Datasette, sondern einen Hi-Fi - Rekorder mit einem Interface zum Anschluß an den C64 besitzen, so können Sie die Geschwindigkeit wesentlich höher ansetzen. Das automatische Starten der neuen Laderoutine wird durch Ändern des BASIC-Eingabe- Vektors $0302/$0303 (=770/ 771) erreicht. Das automatische Starten des Hauptprogramms läßt sich durch Aufruf der BASIC-Interpreterroutine RUN realisieren, wie es schon in Teil 3 beschrieben wurde: LDA #$00
JSR $A871 JMP $A7AE
Soll Ihr Programm nicht durch den Befehl RUN, sondern durch den Befehl SYS gestartet werden, so ersetzen Sie einfach diesen Aufruf durch einen direkten Einsprung mit dem Befehl 'JMP'. Das Umstellen des SA VE- Vektors macht die Handhabung des KKS relativ einfach. Nicht nur der BASIC-Befehl 'SA VE' arbeitet mit dem Programm zusammen, sondern auch diverse Maschinensprachemon i tore.
Kopierschutz mit der DalclSelle
199
In diesem Zusammenhang können wir jetzt auch auf die Bedienung des KKS zu sprechen kommen. Tippen Sie einfach den nachfolgend abgedruckten BASIC-Lader ab und geben Sie 'RUN' ein. Nach Ablauf dieses Programms muß das KKS mit 'SYS 49152' gestartet werden. Möchten Sie eines Ihrer eigenen Programme schützen, laden Sie es ein und speichern Sie es ganz normal mit 'SA VE "Programm name'" ab. Wenn Sie nicht jedesmal den BASIC-Lader einladen und starten wollen, um das Kopierschutz-System benutzen zu können, so können Sie das Maschinenprogramm auch nach Ablauf des Laders direkt abspeichern. Entweder verwenden Sie dazu einen Maschinensprache-Monitor oder folgende Befehlszeilen: POKE 43,0:POKE 44,192:POKE 45,106:POKE 46,194 :POKE 56,208 :CLR SAVEIKKS",1,1
Jetzt können Sie das KKS mit 'LOAD "KKS'" laden und sofort mit dem obigen SYS-Befehl installieren. Es empfiehlt sich, danach 'NEW' einzugeben, damit alle BASIC-Zeiger auf einen sinnvollen Wert gesetzt werden. (Das Maschinenprogramm wird dadurch selbstverständlich nicht gelöscht.) Achtung: Das Programm verstellt den SA VE- Vektor, der durch Betätigen von STOPjRESTORE wieder auf seinen alten Wert zurückgesetzt wird. Sollten Sie also diese Tastenkombination benutzt haben, müssen Sie erst noch einmal 'SYS 49152' eingeben, um das KKS wieder verwenden zu können . Um Programme aus dem RAM-Bereich unter dem BASIC-Interpreter abzuspeichern, schreibt das KKS vor Beginn des Speichervorgangs den Wert $06 in die Speicherstelle 1. Hinterher erhält diese Speicherstelle wieder ihren ursprünglichen Wert. Haben Sie ein Programm in den Speicher geladen, dessen Länge 38 KBytes (154 Blöcke auf einer Diskette) überschreitet, und wollen Sie es nun mit dem KKS abspeichern, so klappt das nur, wenn Sie keinen Filenamen angeben. Anderenfalls erhalten Sie die Fehlermeldung 'OUT OF MEMORY ERROR'. Hier kann man sich mit einem Trick helfen, indem man nämlich 'POKE 56,208:CLR' eingibt. Dadurch kann der BASIC-Interpreter seine Zeichenketten in einem freien Speicherbereich ablegen. Wollen
200
Das große Allti-Cracker-Buch
Sie nach dem Abspeichervorgang ein BASIC-Programm ablaufen lassen, dann bringen Sie die Speicherstelle 56 wieder auf ihren alten Wert: 'POKE 56,160:CLR'. Der sechste der genannten Punkte, das Laden in andere Speicherbereiche als die, aus denen die Daten abgespeichert wurden, läßt sich durch eine einfache Änderung am KKS verwirklichen. Da das KKS einen Unterschied zwischen der Ladeanfangsadresse und der Anfangsadresse beim Abspeichern macht, kann man diesen Adressen auch unterschiedliche Werte geben. Dazu haben wir im KKS an zwei Stellen durch 'NOP'Befehle Platz gehalten. Um also eine andere Ladeadresse zu erhalten, ersetzen Sie mit einem Maschinensprachemonitor die INOP's" durch folgende Befehle: C07E LDA #$HIGH-Byte der Ladeadresse C08S LDA #$LOW-Byte der Ladeadresse
Diese Änderung ist in zweierlei Hinsicht nützlich. Einmal lassen sich damit Programmteile, die in Bereiche geladen werden sollen, aus denen man nichts abspeichern kann, trotzdem abspeiehern. Zu diesen Speicher bereichen zählen der Bereich $COOO bis $C269 (weil dort das KKS selbst liegt) und der Bereich $EOOO bis $FFFF (weil dort das Betriebssystem liegt). Da die Laderoutine komplett im Kassettenpuffer liegt und oberhalb von $0400 (=1024) keinen Speicherplatz beansprucht, kann man problemlos in diese Bereiche Programme laden. Haben Sie beispielsweise ein Programm, das im Bereich von $COOO bis $CFFF liegt, so verschieben Sie dieses Programm zuerst in einen anderen Bereich, zum Beispiel $2000 bis $2FFF (am besten mit einem Monitor). Laden Sie dann das KKS und nehmen folgende Änderung vor: C07E LDA #$CO C08S LDA #$00
HIGH-Byte von $COOO LOW-Byte von $COOO
Als nächstes starten Sie das K KS mit 'SYS 49152'. Jetzt können Sie das Progra~m von $2000 bis $2FFF abspeichern. Es wird dann so eingeladen, als hätten Sie es aus dem Bereich $COOO abgespeichert.
KODierschutz mit der Dalaselle
201
Es war davon die Rede, daß die gerade besprochene Änderungsoption noch zu einem anderen Zweck genutzt werden kann. Man kann damit nämlich einen interessanten Programmschutz aufbauen, der sich normalerweise nur bei einem Diskettenprogramm realisieren läßt. Die Speicherstellen $AC/$AD werden beim Einladen als Zeiger auf das jeweils nächste Byte verwendet (siehe Assemblerlisting). Wenn man dort Werte hineinlädt, gelangen die nachfolgenden Bytes an eine völlig andere Stelle. Vorsicht ist dabei geboten, wenn man durch die Speicherstelle $9B hindurchlädt. Sie enthält nämlich die Prüfsumme über die geladenen Daten. Falls diese bei Beendigung des Ladevorgangs nicht stimmt, so nimmt das KKS an, die Daten seien fehlerhaft, und löst einen RESET aus. Legen Sie beispielsweise folgende Bytefolge im Speicher ab: 109B 11 00 00 00 00 00 00 00 10A3 00 00 00 00 00 00 00 00 10AB 00 AC 7F 00 00 00 00 00 1100 74 A4 74 A4 C3 C2 CD 38 1108 30 00 00 00 00 00 00 00
Speichern Sie dann den Bereich von $109B bis $1109 so ab, daß er nach $009B geladen wird. Beim Einladen wird zuerst die Prüfsumme zerstört, was hier aber durchaus beabsichtigt ist. Die folgenden Null-Bytes bewirken gar nichts. Erst das Byte $AC, welches nach $OOAC kommt, ist von Bedeutung. Das KKS schreibt also den Wert $AC nach $OOAC und inkrementiert dann diesen Zeiger. Das nächste Byte, $7F, kommt dann nach $OOAD, wodurch der Zeiger nach dem nächsten Inkrementieren auf $7FAE steht. Dorthin werden dann alle folgenden Bytes geladen. In $8000 steht dann die bekannte 'CBM80'-Erkennung. (Genaueres dazu finden Sie unter dem Kapitel "Programmschutz".) Sobald der Ladevorgang abgeschlossen ist, prüft das KKS die Korrektheit der Prüfsumme. Da diese aber nicht mehr stimmt, kommt es zu einem RESET. Durch das 'CBM80' springt der
202
Das große Anti-Cracker-Buch
Prozessor aber nach $A474, also in den BASIC-Interpreter. Sie können diese Einsprungadresse selbstverständlich so umändern, daß dadurch Ihr eigenes Programm gestartet wird. Wir wollen hier nicht auf weitere Details dieses Programmschutzes eingehen, da es sich an dieser Stelle nur um eine Anregung handeln soll. Sie finden allerdings eine ähnliche Version in dem Programmschutzteil dieses Buches, die sich dort aber nur auf die Diskettenstation bezieht. Damit wären wir schon fast am Ende dieses Kapitels angelangt. Es folgen nur noch die beiden Programmlistings und eine Auflistung der in diesem Zusammenhang interessanten Systemadressen und -routinen. Wir hoffen, Ihnen hiermit ein Werkzeug an die Hand gegeben zu haben, mit dessen Hilfe Sie sich selbst einen Kopierschutz sozusagen "nach Maß" zusammenstellen können. Vielleicht schafft es ja eines Tages doch jemand, den "unkopierbaren" Kopierschutz zu entwickeln. Das Kassetten-Kopierschutz-System KKS (Assemblerlisting) verwendete Adressen:
$9B $9C $AC/$AD: $AE/$AF: $B4 $BE $BF $C1/$C2:
Prüfsumme des geladenen/gespeicherten Programms wenn negativ: Byte wurde geschrieben/gelesen Zeiger auf aktuelles Byte Zeiger auf Endadresse+1 Zähler für Anzahl gelesener/geschriebener Bits aktuelles Byte Schieberegister für zu schreibendes/lesendes Byte geschriebene Ladeanfangsadresse
Kopierschutz mit der Dalaselle
Assemblerlisting: SA VE-Routine: COOO C002 C005 C007 COOA COOB COOD COOF C011 C014 C016 C019 C01C C01D C01F C021 C023 C025 C027 C029 C02C C02D C02F C031 C033 C035 C037 C03A C03B C03E C040 C041 C042 C045 C047 C049
LDA STA LDA STA RTS LDA CMP BEQ JMP LDX LDA STA INX CPX BNE LDY CPY BEQ LDA STA INY CPY BNE CPY BEQ LDA STA INY JMP LDA TAX TAY JSR LDA LDX LDY
#$OB $0332 #$CO $0333 $BA #$01 $C014 $F5ED #$00 $C23E,X $02A8,X #$48 $C016 #$00 $B7 $C031 ($BB),Y $C183,Y #$10 $C023 #$10 $C03E #$20 $C183,Y $C031 #$01
$FFBA #$BC #$83 #$C1
SAVE-Vektor ändern: LOW-Byte von $COOB nach $0332: LOW-Byte des SAVE-Vektors HIGH-Byte von $COOB nach $0333 : HIGH -Byte des SAVE-Vektors Rücksprung Geräteadresse =1 (Kassette)? wenn ja, weiter wenn nein, normale SAVE-Routine Verschiebeschleife vorbereiten Teil der Laderoutine nach $02A8 verschieben Zähler erhöhen schon alle Bytes verschoben? verzweige, wenn nein Zeiger auf Filenamen vorbereiten schon kompletten Filenamen verschoben? verzweige, wenn ja Filenamensbyte holen und vor Ladeprogramm setzen Zeiger erhöhen schon 16 Zeichen verschoben verzweige, wenn nein schon 16 Zeichen verschoben? verzweige, wenn ja wenn nein, mit Leerzeichen auffüllen Leerzeichen hinter Filenamen setzen Zeiger erhöhen unbedingter Sprung Filenummer Geräteadresse=1 Sekundäradresse=1 (nicht verschieblieh) FILPAR Filenamenlänge incl. Laderoutine Filenamenbeginn bei $C183
203
204 C04B C04E COSO COS1 COS3 COS4 COS6 COS7 COS9 COSA COSC COSE C060 C062 C064 C066 C068 C06A C06C C06F C071 C074 C076 C078 C07B C07C C07E C07F C080 C082 C083 C08S C086 C087 C089 C08A C08C C080 C08F C092
Das große Al1ti-Cracker-Buch JSR LOA PHA LOA PHA LOA PHA LOA PHA LOA STA LOA STA LOA STA LOA STA LOA STA LOA STA LOA STA JSR PLA STA NOP NOP STA PLA STA NOP NOP STA PLA STA PLA STA JSR SEI
SFFBO SAE SAF SC1 SC2 #$A8 SC1 #$02 SC2 #$04 SAE #$03 SAF #$03 S0302 #$02 S0303 #$00 S90 SFSEO SAO
SC2 SAC
SC1 SAF SAE SE4S3
FILNAM LOY-Byte Endadresse auf Stapel retten HIGH-Byte Endadresse retten LOY-Byte Anfangsadresse retten HIGH-Byte Anfangsadresse retten S02A8 als neue Anfangsadresse nach $C1/$C2 $0304 als neue Endadresse nach $AE/$AF Startadresse für Autostart nach $0302/$0303 bringen (BASIC-Vektor für Eingabe einer Zeile) o nach $90 bringen (verhindert Anzeige des verlängerten Filenamens) normale SAVE-Routine aufrufen HIGH-Byte Anfangsadresse zurückholen (Platz für Änderungen) (Platz für Änderungen) gleich HIGH-Byte für Ladeanfangsadresse LOY-Byte Anfangsadresse zurückholen (Platz für Änderungen) (Platz für Änderungen) gleich LOY-Byte für Ladeanfangsadresse HIGH-Byte Endadresse zurückholen LOY-Byte Endadresse zurückholen BASIC -Vektoren wieder zurücksetzen Interrupt verhindern
Kopierschutz mit der Dalasclle C093 C096 C098 C09B C09D COAO COA2 COA5 COA7 COA8 COAA COAC COAE COBO COB2 COB4 COB5 COB7 COB8 COBA COBB COBD COBF COC1 COC4 COC6 COC9 COCB COCE COCF COD2 COD4 COD6 COD8 CODB CODE CODF COE1 COE4 COE5
LOA AND STA LDA STA LDA STA LDA PHA LOA STA STA LDA LDY STY DEX BNE OEY BNE SEC SBC BNE LOA STA LDA STA LDA STA INX STX LDA STA LDA STA LOA CLI LDA JSR DEY BNE
$0011 #$EF $D011 #$44 $0314 #$C1 $0315 $01 #$06 $01 $CO #$03 #$00 $9B $COB4 $COB4 #$01 $COB4 #$7F $OCOD #$82 $DCOD #$47 $DC06 $DC07 #$07 $B4 #$19 $DCOF $OCOD #$03 $C17A $CODF
Unterbrechungen vom VIC durch Abschalten des Bildschirms unterbinden Interruptvektor auf $C144 für SAVE-Routine verbiegen Speicherkonfiguration auf Stapel retten eigene Konfiguration einstellen dabei gleichzeitig Datasettenmotor an Motorsteuerflag ungleich Null Zeitschleife vorbereiten Prüfsumme auf 0 Zeitschleife für Hoch laufzeit des Motors (dreimal so lang wie beim Laden) alle Interruptmöglichkeiten der CIA 1 abschalten lnterrupt durch Timer B zulassen Timer B auf Wert $0147 stellen Bitzähler auf Anfangswert sieben stellen Tirner starten Interruptregister löschen Interrupts zulassen Wert Drei zur Synchronisation auf Band schreiben schon 256mal geschrieben? verzweige, wenn nein
205
206 COE7 COE9 COEC COEE COF1 COF3 COF6 COF8 COF9 COFB COFC COFE C100 C101 C102 C103 C105 C108 C109 C10B C10E C110 C111 C113 C115 C116 C119 C11C C11F C121 C123 C126 C129 C12C C12D C12E C130 C132 C135 C138
Das grnße Al1ti-Cracker-Buch LOA JSR LDA JSR LDA JSR LDA SEC SBC PHA LDA SBC TAX PLA CLC ADC JSR TXA ADC JSR LDA TAX EOR STA TXA JSR JSR JSR BNE LDA JSR JSR JSR SEI PLA ORA STA JSR JSR JSR
#$48 $C17A $C1 $C17A $C2 $C17A $AE
$48 als Prgrammbeginnkennzeichnung auf Band schreiben LOW-Byte Anfangsadresse auf Band schreiben HIGH-Byte Anfangsadresse auf Band schreiben LOW-Byte Endadresse im Speicher
$AC
minus LOW-Byte Anfangsadr_ im Speicher retten HIGH-Byte Endadresse im Speicher minus HIGH-Byte Endadresse im Speicher retten LOW-Byte Programmlänge
$AF $AD
$C1 $C17A $C2 $C17A ($AC),Y $9B $9B $C17A $FCDB $FCD1 $C10E $9B $C17A $C17A $C17A
#$20 $01 $FD15 $FDA3 $FC93
plus LOW-Byte Anfangsadr. für Ladeprg. auf Band schreiben HIGH-Byte Programmlänge plus HIGH-Byte Anfangsadr. für Ladeprg. auf Band schreiben Programm-Byte holen (Y=O) zwischenspeichern Prüfsumme bilden und wegspeichern Programm-Byte zurückholen auf Band schreiben laufender Prg.-zeiger $AC/$AD erhöhen schon Endadresse $AE/$AF erreicht? verzweige, wenn nein Prüfsumme holen auf Band schreiben warten, bis letztes Byte vollständig geschrieben worden ist Interrupt sperren Speicherkonfigurationswert holen Motor aus alte Konfiguration wieder herstellen Interruptvektor zurücksetzen CIAs wieder auf alten lnterruptbetrieb Motor aus, Bildschirm an
Kopierschut::: mit der Dataselle e13B LOA #$OE e130 STA S0020 C140 cu e141 JMP SA474
Randfarbe des Bildschirms wieder auf Standartwert Interrupts wieder zulassen zurück zum BASIC-Interpreter
Interruptroutine für Band schreiben: C144 C146 C148 C14A C14B C14D C14F C151 C153 e155 C158 C15A C15B C15D C15F C161 C164 C166 C168 C16A C16C C16E C170 C172 C174 C177
ASL BCC LOX OEX BNE LDA AND STA LDX STX LOX DEX BNE ORA STA INC DEC BPL LDA STA LDA STA LDA STA LDA JMP
SBF $C140 #$33 SC14A S01 #SF7 S01 #$19 SOCOF #$10 SC15A #$08 S01 SD020 SB4 SC174 #$07 SB4 SBE SBF #$80 S9C SDCOO $FEBC
Bit aus Schieberegister holen verzweige, wenn Bit gleich Null Zeitschleife: ca. S33*5 (=255) Taktzyklen warten verzweige, wenn noch nicht fertig Portbyte holen Bit 3 auf Null Signal auf Band schreiben Timer neu starten Zei tsch leite: ca. S10*5 (=80) Taktzyklen warten verzweige, wenn noch nicht fertig Bit 3 auf Eins Signal wieder zurücksetzen Kontrolle durch Ändern de r Randfarbe schon komplettes Byte geschrieben? verzweige, wenn nein Bitzähler wieder auf 7 setzen nächstes zu schreibendes Byte ins Schieberegister Flag für Byte geholt setzen Interrupt-Port freimachen Interrupt-Ende
Ein Byte schreiben C17A BIT S9C C17C BPL SC17A
letztes Byte schon geholt? verzweige, wenn nein
207
208
Das große Al1ti-Cracker-Buch
C17E STA $BE C180 STY $9C C182 RTS
nächstes Byte übergeben 'Byte geholt'-Signa l löschen Rücksprung
C183 bis C192: Platz für Filenamen
Die nun folgende Laderoutine liegt im Speicher ab $C193, wird aber beim Einladen nach $0351 verschoben _ 0351 0352 0354 0357 0359 035C 035E 0361 0363 0365 0367 0369 036B 0360 036E 0370 0371 0373 0375 0378 037A 0370 037F 0382 0384 0387 0389 038C 038F 0390
SEI lOA STA lDA STA lDA STA lDA AND STA STA lDY STY DEX BNE DEY BNE lDA STA lOA STA lDA STA lDA STA lDA STA lOA CU LOA
#$OB $D011 #$A8 $0314 #$02 $0315 $01 #$1F $01 SCO #$00 $9B $0360 $0360 #$7F $DCOD #$90 $DCOO #$FE SDC06 #$01 $OC07 #S19 $OCOF SOCOO SBF
Interrupts verhindern Unterbrechungen vom VIC durch Abschalten des Bildschirms verhindern Interruptvektor auf S02A8 für laderoutine verbiegen Port-Byte holen Bit für Datasettenmotor auf Nnull Motor starten Motorsteuerflag ungleich Null Zeitschleife vorbereiten Prüfsumme auf Null Zeitschleife, um Motor Zeit zum Anlaufen zu geben Interrupts durch CIA 1 verhindern Interrupt durch Signal am Pin FLAG zulassen Timer B von CIA 1 auf $01FE setzen Timer starten Interrupt -Bits löschen Interrupts wieder zulassen Schieberegister auslesen
Kopierschutz mit der Dataselle 0392 0394 0396 0398 039A 039C 039E 03A1 03A3 03A5 03A6 03A8 03AB 03AO 03AF 03B1 03B3 03B6 03B8 03B9 03BB 03BO 03CO 03C2 03C4 03C6 03C9 03CC 03CE 0301 03D3 0305 0308 0309 030C 030F 03E2 03E4 03E7 03E9
CMP BNE LOA STA LOX STX JSR CMP BNE OEX BNE JSR CMP BEQ CMP BNE JSR STA INX CPX BNE JSR STA EOR STA JSR JSR BNE JSR CMP BEQ JMP SEI JSR JSR JSR LOA STA LDX LDY
#$03 S0390 #S07 SB4 #$10 S9C S02CA #$03 S0390 S039E S02CA #$03 S03A8 #$48 S0390 S02CA SAC,X #S04 S03B3 S02CA (SAC),Y S9B S9B SFCOB SFC01 S03BO S02CA S9B S0308 SFCE2 SF015 SFOA3 SFC93 #$OE S0020 SAE SAF
Drei gefunden? verzweige, wenn nein Bit-Zähler auf Sieben setzen Zähler für Synchronisationsmarkierung Signal für 'Byte gelesen' rücksetzen Byte lesen gleich Drei? verzweige, wenn nein schon S10 mal das Byte Drei gefunden? verzweige, wenn nein Byte lesen gleich Drei? verzweige, wenn ja gleich S48? verzweige, wenn nein Schleife: Anfangs-, Endadreß-Bytes lesen nach SAC bis $AF bringen Zeiger erhöhen schon vier Bytes geholt? verzweige, wenn nein Programm-Byte lesen in Speicher schreiben Prüfsumme bilden und speichern SAC/MD erhöhen schon Endadresse $AE/AF erreicht? verzweige, wenn nein Prüfsumme lesen mit gebildeter Prüfsumme vergleichen verzweige, wenn gleich sonst RESET Interrupts verhindern Interruptvektor zurücksetzen CIAs wieder auf ,alten Interruptbetrieb Motor aus, Bildschirm an Randfarbe des Bildschirms wieder auf Standardwert LOW-Byte der Endadresse HIGH-Byte der Endadresse
209
210 03EB 03EC 03EF 03F1 03F4 03F6 03F9
Das gro ße AI/I i-Cracker-Buch CLI JMP LOA STA LOA JSR JMP
$0209 #$E1 $0328 #$00 $A871 $A7AE
Interrupts zulassen Endadresse in BASIC-Programmendezeiger $E1 nach LOW-Byte des STOP-Vektors und so STOP sperren RUN -Befehl für Autostart zurück zur Interpreterschleife
Der nächste Teil der Laderoutine beginnt im Speicher bei $C23F, wird aber zum Ablauf nach $02A8 geladen_ Interruptroutine für Laden: 02A8 02AB 02AO 02BO 02B1 02B2 02B4 02B7 02B9 02BB 02BO 02BF 02C1 02C3 02CS 02C7
LOA LOX STX LSR LSR ROL INC OEC BPL LOA STA LOA STA LOA STA JMP
$OCOO #$19 $OCOF
$BF $0020 $B4 $02C7 #$07 $B4 $BF $BE #$80 $9C $FEBC
Interruptregister lesen Timer neu starten Bit für Timer B ins Carry -Bit sch ieben als gelesenes Bit ins Schieberegister optische Kontrolle schon komplettes Byte gelesen? verzweige, wenn nein Bit-Zähler auf Sieben setzen Sch i eberegister nach Zwischenspeicher für gelesenes Byte Signal 'Byte gelesen' setzen Interrupt-Ende
gelesenes Byte holen: 02CA 02CC 02CE 0200 0202
BIT BPL STY LOA RTS
$9C $02CA $9C $BE
Byte schon gelesen? verzweige, wenn nein 'Byte gelesen'-Signal löschen Byte holen Rücksprung
Kopierschutz mit der Dalaselle
Einsprung für Autostart: 0203 02D6 0209 020B 02DE 02E1 02E4
JSR JMP STX STY JSR JSR JMP
$E453 $0351 $2D $2E $A569 $A533 $03EF
BASIC-Vektoren rücksetzen Sprung zur Laderoutine BASIC-Ende LO~-Byte setzen BASIC-Ende HIGH-Byte setzen CLR Programmzeilen binden unbedingter Sprung
Das Kassetten-Kopierschutz-System KKS (BASIC-Lader): 100 FORI=1T0636STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,l) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A :NEXT:READA:IFC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 OATAA9,OB,80,32,03,A9,CO,8D,33,03,60,A5,BA,C9,Ol, 43 301 DATAFO,03,4C,ED,F5,A2,OO,BD,3E,C2,9D,A8,02,E8,EO, 143 302 OATA48,OO,F5,AO,OO,C4,B7,FO,OA,Bl,BB,99,83,C1,C8, 51 303 DATACO,10,OO,F2,CO,10,FO,09,A9,20,99,83,C1,C8,4C, 21 304 DATA31,CO,A9,01,AA,A8,20,BA,FF,A9,BC,A2,83,AO,Cl, 177 305 OATA20,BO,FF,A5,AE,48,A5,AF,48,A5,Cl,48,A5,C2,48, 112 306 OATAA9,A8,85,C1,A9,02,85,C2,A9,04,85,AE,A9,03,85, 154 307 OATAAF,A9,D3,8D,02,03,A9,02,8D,03,03,A9,OO,85,9D, 198 308 OATA20,ED,F5,68,85,AD,EA,EA,85,C2,68,85,AC,EA,EA, 36 309 OATA85,C1,68,85,AF,68,85,AE,20,53,E4,78,AD,11,DO, 218 310 DATA29,EF,8D,11,DO,A9,44,8D,14,03,A9,C1,8D,15,03, 38 311 DATAA5,01,48,A9,06,85,01,85,CO,A9,03,AO,OO,84,9B, 211 312 OATACA,OO,FO,88,DO,FA,38,E9,Ol,DO,F5,A9,7F,8D,OD, 146 313 OATAOC,A9,82,8D,OD,OC,A9,47,8D,06,DC,E8,8E,07,DC, 53 314 DATAA9,07,85,B4,A9,19,8D,OF,DC,AD,OD,DC,58,A9,03, 189 315 OATA20,7A,C1,88,OO,F8,A9,48,20,7A,C1,A5,C1,20,7A, 247 316 DATAC1,A5,C2,20,7A,C1,A5,AE,38,E5,AC,48,A5,AF,E5, 32 317 DATAAD,AA,68,18,65,C1,20,7A,Cl,8A,65,C2,20,7A,Cl, 100 318 DATAB1,AC,AA,45,9B,85,9B,8A,20,7A,Cl,20,DB,FC,20, 3 319 OATAD1,FC,DO,ED,A5,9B,20,7A,Cl,20,7A,Cl,20,7A,C1, 219
211
212 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342
Das große Al1ti-Cracker-Buch
DATA78,68,09,20,85,01,20,15,FD,20,A3,FD,20,93,FC, DATAA9,OE,8D,20,DO,58,4C,74,A4,06,BF,90,05,A2 ,33, DATACA,DO,FD,A5,01,29,F7,85,01,A2,19,8E,OF,DC,A2, DATA10 ,CA, DO,FD,09,08,85,01,EE,20,DO,C6 , B4,10,OC, DATAA9,07,85,B4,A5,BE,85,BF,A9,80,85,9C,AD,OD,DC, DATA4C,BC,FE,24,9C,10,FC,85,BE,84,9C,60,20,20,20, DATA20 ,20,20,20,20,20,20,20,20,20,20,20,20,78,A9, DATAOB,8D, 11,DO,A9,A8,8D, 14,03,A9,02,8D, 15,03,A5, DATA01,29,1F,85,01,85,CO,AO,00,84,9B,CA,DO,FD,88, DATADO,FA,A9,7F,8D,OD,DC,A9,90,8D,OD,DC,A9,FE,8D, DATA06,DC,A9,01,8D,07,DC,A9, 19,8D,OF,DC,AD,OD,DC, DATA58,A5,BF,C9,03,DO,FA,A9,07,85,B4,A2,10,86,9C, DATA20,CA,02,C9,03,DO,EB,CA,DO,F6,20,CA,02,C9,03, DATAFO,F9,C9,48,DO,DD,20,CA,02,95,AC,E8,EO,04,DO, DATAF6,20,CA,02,91,AC,45,9B,85,9B,20,DB,FC,20,D1, DATAFC,DO,EF,20,CA,02,C5,9B,FO,03,4C,E2,FC,78,20, DATA15,FD,20,A3,FD,20,93,FC,A9,OE,8D,20,DO,A6,AE, DATAA4,AF,58,4C,D9,02,A9,E1,8D,28,03,A9,00,20,71, DATAA8,4C,AE,A7,AD,OD,DC,A2,19,8E,OF,DC,4A,4A,26, DATABF,EE ,20,DO,C6,B4,10,OC,A9,07,85,B4,A5,BF,85, DATABE,A9,80,85,9C,4C,BC,FE,24,9C, 10,FC,84 , 9C,A5, DATABE,60,20,53,E4,4C,51,03,86,2D,84,2E,20,59,A6, DATA20,33,A5,4C,EF,03,A4,A4,A4,A4,A4,A4,A4,A4 ,A4,
5.5.4
48 31 185 178 112 245 193 99 242 75 204 15 187 112 7 188 9 78 205 5 159 153 250
Wichtige Betriebssystemadressen und -routinen
Im KKS werden einige Adressen und Unterroutinen des Betriebssystems verwendet. Diese und noch einige andere können beim Erstellen von Kopierschutzverfahren und ähnlichen Programmen für die Datasette äußerst hilfreich sein. Aus diesem Grunde sind die wichtigsten im folgenden aufgelistet:
Zeropage- Adressen: Hexadez. Dezimal Funktion $2B/$2C 43/ 44 Anfang des BASIC·Speichers $2D/$2E 45/ 46 Ende des BASIC·Speichers 144 Status (entspricht Variable ST in BASIC) $90
Kopierschutz mit der Dataselte S93 S90
147 157
SA6
166
SAC/SAD SAE/SAF SB2/SB3 SB7 SB8 SB9 SBA SBB/SBC SCO
172/173 174/175 178/179 183 184 185 186 187/188 192
SC1/SC2 SC3/SC4
193/194 195/196
Flag für LOAD ($00) oder VERIFY (S01) Flag für Direktmodus $80 oder Programm $00 ($CO läßt Ausgabe aller Fehlermeldungen, also auch die des Betriebssystems, zu; S80 nur die des BASIC-Interpreters; $00 überhaupt keine) Anzahl der schon aus dem Bandpuffer gelesenen oder in den Puffer hineingeschriebenen Bytes laufende Adresse für LOAD/SAVE Zeiger auf Endadresse + 1 für LOAD/SAVE Zeiger auf Beginn des Bandpuffers Länge des Filenamens logische Filenummer Sekundäradresse Geräteadresse Zeiger auf Filenamen Wenn bei gedrückter Datasetten-Taste der Inhalt dieser Speicherstelle ungleich null ist, so wird der Motor nicht eingeschaltet Startadresse für Ein-/Ausgabe Endadresse für Ein-/Ausgabe
Betriebssystem- und BASIC-In terpreter- Rou tinen: SA474 SA533 SA659 SA7AE SA871 SE17A SE1D4 SE453 SF68F SF750 SF800 SF817 SF838 SFC93
Einsprung in Eingabeschleife des BASIC-Interpreters Programmzeilen neu binden CLR-Befehl Befehlsausführungsschleife des BASIC -Interprete rs RUN-Befehl Ladefehler testen und ggf_ anzeigen, dann wie $E1A1 Filename, Sekundär-, Geräteadr_ aus BASIC-Text holen Zurücksetzen aller BASIC-Interpreter-Vektoren 'SAVING "Filename'" ausgeben Ausgabe 'FOUND "Filename'" , wartet auf 'C=' Bandpufferzeiger SA6 erhöhen Wartet auf Bandtaste, Ausgabe "PRESS PLAY ON TAPE" Wartet auf Bandtaste, "PRESS RE CORD AND PLAY ON TAPE" Rekorderbetrieb beenden
213
214 $FCD1 $FCDB $FCE2 $FDA3 $FEBC $FF8A $FFBA $FFBD $FFCO $FFC3 $FFC6 $FFC9 $FFCC $FFCF $FFD2 $FFDS $FFD8 $FFE7
Das grolle AIlI;-Cracker-Buch Vergleich von $AE/$AF mit $AC/AD $AC/$AD inkrementieren RESET, Rechner in Einschaltzustand versetzen CIAs initialisiern Abschluß der IRQ-Routine Zurücksetzen aller Betriebssystem-Vektoren FILPAR, setzt Geräte-, Sekundäradresse, Filenummer FILNAM, .setzt Filenamen-Parameter OPEN CLOSE CHKIN, Eingabegerät setzen CHKOUT, Ausgabegerät setzen CLRCH, Ein-, Ausgabe zurücksetzen BASIN, Eingabe eines Zeichens BASOUT, Ausgabe eines Zeichens LOAD (bzw_ VERIFY) SAVE CLALL, alle offenen Kanäle schließen
Diskettenkopierschutz
6.
215
Diskettenkopierschutz
Wie arbeitet ein solcher Diskettenkopierschutz? Wie und wofür kann ich ihn selber benutzen? Das sind die Fragen, die in dem folgenden Kapitel beantwortet werden sollen. Wofür Sie einen Kopierschutz brauchen, ist einfach zu beantworten. Sie können mit Hilfe eines solchen Schutzes sicherstellen, daß Ihr mühsam geschriebenes Programm nicht ohne Ihr Wissen weitergegeben werden kann. Diese in diesem Buch erläuterten Schutzverfahren sind auch ohne weiteres dazu geeignet, professionelle Software zu schützen. Sie entsprechen nicht den in einigen Zeitschriften oder ähnlichem beschriebenen Verfahren, die mit jedem drittklassigen Kopierprogramm kopiert werden können. Selbst wenn Sie kein Interesse haben, Ihre Software zu schützen, ist dieses Kapitel auch im Hinblick auf die interessante FloppyProgrammierung sehr zu empfehlen. Auf die Frage, wie ein solcher Kopierschutz arbeitet, ist allgemein zu sagen, daß es sich hierbei um eine Manipulation auf der geschützten Diskette handelt, die nicht ohne weiteres von einem Kopierprogramm kopiert werden kann. Wie dieses im einzelnen aussieht, wird im Laufe dieses Buches noch besprochen. Leider müssen wir zum Verständnis der folgenden Programme ein recht gutes Wissen über das Arbeiten der Floppy sowie Kenntnisse der Programmierung in Maschinensprache voraussetzen, da derart weitreichende Einführungen den Rahmen dieses Buches sprengen würden. Für diejenigen, die diese Kenntnisse nicht haben, ist dieses Kapitel dennoch interessant, weil alle Schutzsysteme auch in Form eines BASIC-Loaders vorliegen und somit für jeden nutzbar sind. 6.1
Unser "Handwerkszeug"
Bevor wir uns intensiver mit der Programmierung eines Kopierschutzes befassen, wollen wir zuvor näher auf das "In-
216
Das große Anti-Cracker-Buch
nenleben" der Floppy eingehen, um Ihnen das Verständnis der zu besprechenden Kopierschutzsysteme zu ermöglichen. Wenn Sie über dieses Wissen bereits verfügen, können Sie sich sofort dem Auftragen eines Kopierschutzes widmen und die Kapitel bis 6.2 überspringen. An dieser Stelle nun die Übersicht über die Speicheraufteilung der Floppy und eine Übersicht über die Nutzung der Puffer der Floppy. VIA I ist der Baustein, der den Datenaustausch zwichen Computer und Rechner herstellt. Er verwaltet den Serielle-Bus. VIA 2 wird ausschließlich für den Datenaustausch zwischen Floppy und Diskette verwendet. Es ist noch wissenswert, daß das RAM der Floppy ab $8000 gespiegelt ist, was bedeutet, das die Speicherstellen ab $8000 die gleichen Werte und auch die gleiche Bedeutung wie die Speichestellen ab $0000 haben.
217
DiskettenkoDierschutz SPEICH ERAUFTEILUNG
65535
$FFFF
DOS
$C100
49408
RAMBELEGUNG
$07FF BAM
7183
$0700
$1COF DIRECTORY
VIA 2 $1COO
7168
USER USER USER
6159
$180F
VIA 1 $1800
2047
$07FF
RAM o
$0000
$0500 $0400 $0300
PAGE 2
$0200 STACK
6144
$0600
ZEROPAGE
$0100 $0000
218 6.1.1
Das große Anti-Cracker-Buch
Die Arbeitsweise des DOS
Die VCl541 ist ein intelligentes Diskettenlaufwerk mit eigenem Mikroprozessor und Betriebssystem (Disk Operating System, DOS). Dadurch wird kein Speicherplatz und keine Rechenzeit des angeschlossenen Rechners benötigt. Der Rechner braucht der Floppy lediglich Befehle zu übermitteln, die diese dann selbsttätig ausführt. Die Floppy hat damit drei Aufgaben gleichzeitig zu erledigen: Zum einen muß sie den Datenverkehr vom und zum Rechner durchführen. Die zweite Aufgabe ist die Interpretation der Befehle, die Verwaltung von Dateien, der zugeordneten Übertragungskanäle und der Blockpuffer. Die dritte Aufgabe ist die hardwaremäßige Bedienung der Diskette; dazu gehört das Schreiben und Lesen einzelner Blocks auf der Diskette sowie das Formatieren von Disketten. Diese Aufgaben muß bei der VCl541 ein 6502-Mikroprozessor gleichzeitig durchführen. Dies ist nur mit Hilfe der InterruptTechnik möglich. Nur so können drei Programme quasi gleichzeitig ablaufen. Nachdem wir schon etwas näher auf den Aufbau der Diskette eingegangen sind, wollen wir uns einmal ansehen, wie das DOS alle seine Aufgaben realisiert. Dies ist besonders dann wichtig, wenn man selbst Programme in der Floppy schreiben will, wie beispielsweise seinen eigenen Kopierschutz. Beim Schreiben von Programmen im Floppypuffer kann man selbstverständlich auch viel vom Betriebssystem der Floppy Gebrauch machen, das für die meisten Aufgaben schon Routinen parat hat. Doch in der Floppy ist dies nicht so problemlos wie im C64. Das Betriebssystem der Floppy kann man in zwei Teile unterteilen. Der erste Teil ist das Hauptprogramm, das in einer Endlosschleife läuft. Von diesem Programm aus wird hauptsächlich der serielle Bus verwaltet. Fast alle Unterprogrammaufrufe werden mit absoluten Sprüngen realisiert und müssen somit auch mit einem JMP-Befehl zurück ins Hauptprogramm abgeschlossen werden. Diese Routinen können deswegen
219
Diskettenkopierschutz
kaum für die eigene Arbeit verwendet werden, da sie nach einmaligem Aufruf sofort ins Hauptprogramm springen. Sie müssen solche Routinen also selbst schreiben und können nicht auf die vorhandenen Routinen des Betriebsystems zurückgreifen. Der zweite Teil des Betriebsystems ist dafür um so besser für eigene Programme zu verwenden. Es handelt sich hierbei um ein Interrupt-Programm, die sogenannte 'Jobschleife'. Dieses Programm übermimmt die Lese- und Schreiboperationen auf und von Diskette. Bei jedem Interrupt werden die Speicherstellen $00 bis $05 der Zeropage, die die 'Schnittstelle' zwischen dem Hauptprogramm und Interruptprogramm herstellen, auf ihre Werte überprüft. Werte, die größer oder gleich $80 (128) sind, werden als Befehle (Jobs) erkannt und daraufhin ausgeführt. Jede dieser Speicherstellen (Job-Speicher) bezieht sich auf einen bestimmten Speicherbereich. Zusätzlich gehören zu jedem JobSpeicher noch zwei Speicherstellen, die den Track und den Sektor angeben, auf die sich der Job (Befehl) bezieht. Nachfolgend eine Tabelle, welche den Job-Speichern ihre Track und Sektorangabe sowie ihren Speicherbereich zuordnet: JOB SOO S01 S02 S03 S04 SOS
TRACK SEKTOR SPEICHERBEREICH S06 S08 SOA SOC SOE S10
S07 S09 SOB SOD SOF S11
S0300-S03FF S0400-S04FF SOSOO-SOSFF S0600-S06FF S0700-S07FF kein RAH
Anschließend folgt eine Tabelle, die die Job-Codes und ihre Bedeutung zeigt.
220
Das große Anti-Cracker- Buch JOB· CODE
BEDEUTUNG
$80 S90 SAO SBO SCO $00 SEO
Sektor lesen Sektor schreiben Sektor verifizieren Sektor suchen Bump, Kopfanschlagen Programm im Puffer ausführen Programm im Puffer ausführen, vorher Laufwerksmotor einschalten und Kopf positionieren
128 144 160 176 192 208 224
Machen wir uns die Verarbeitung der Job-Codes anhand eines Beispiels besser verständlich: Wenn vom BASIC aus durch einen 'UI' Befehl ein Block gelesen werden soll, so nimmt das Hauptprogramm diesen Befehl entgegen, erkennt ihn und veranlaßt, daß der entsprechende Block gelesen wird. Diese Veranlassung sieht jetzt so aus, daß die Track und Sektornummer in die Speicherspellen, die im Zusammenhang mit dem angegebenen Puffer stehen, geschrieben werden. Daraufhin wird der Job-Code $80 (Sektor lesen) in den entsprechenden Job-Speicher geschrieben. Nehmen wir an, zum Lesen dieses Blocks wäre Puffer 2 reserviert worden; dann wäre die Track- und Sektornummer in die Speicherstellen $OA und $OB geschrieben worden. Der Job-Code $80 wäre danach in den Job-Speicher $02 geschrieben worden. Nach diesen Arbeitsgängen beginnt die Aufgabe der Jobschleife. Diese überprüft jetzt die Job-Speicher, findet im Job-Speicher die $80 und holt sich aus $OA und $OB die Track- und Sektornummer des zu lesenden Sektors. Daraufhin wird der Kopf positioniert und der Sektor gelesen. Die gelesenen Daten werden im Puffer 2 ($0500-$05FF) abgelegt. Das Hauptprogramm befindet sich während dieser Zeit in einer Wartestellung. Es wartet auf die READY-Meldung des Jobs. Diese Rückmeldung wird in den selben Job-Speicher geschrieben, in den auch der Befehl geschrieben wurde. Die Rückmel-
Diskettenkopierschutz
221
dungen unterscheiden sich von den Job-Codes dadurch, daß bei den Job-Codes das höchstwertige Bit gesetzt und bei den Rückmeldungen gelöscht ist. Das Hauptprogramm überprüft immer das höchstwertigste Bit und beginnt, sobald die Rückmeldung kommt, diese auszuwerten. Bevor wir auf die Rückmeldungen eingehen, noch ein Hinweis . zu den Job-Codes, die ein Programm im Puffer ausführen. Der Job-Code $DO startet ein Programm bei der Anfangsadresse des jeweiligen Puffers. Der Befehl $EO fährt den Laufwerkmotor zuvor noch hoch, positioniert den Kopf und führt dann wie $DO das Programm im angegebenen Puffer aus. Wollen wir also den Inhalt von Track 24, Sektor 8 in den Puffer #0 ($300 - $3FF) lesen, kann dies durch folgende Routine geschehen:
100 110 120 130 140 150 160 170 180
OPEN 1,8,15 TRACK = 24: SECTOR = 8: lESEN = 128 PRINT#1, "M-W"CHR$(6)CHR$(O)CHR$(2)CHR$(TRACK)CHR$(SECTOR) PRINT#1, "M-W"CHR$(0)CHR$(0)CHR$(1)CHR$(lESEN) PRINT#1, "M-R"CHR$(O)CHR$(O) GET#1, A$:IF A$="" THEN A$=CHR$(O) IF ASC(A$»127 THEN 140 PRINT "FEHlERCOOE ="; ASC(A$) ClOSE 1
In Zeile 120 werden Track- und Sektornummer für Puffer #0 übergeben, in Zeile 130 der Befehl zum Lesen. Die Programmschleife in Zeile 140 bis 160 wartet das Ende des Jobs ab. In Zeile 170 wird der Fehlercode ausgeben. Diese Codes haben folgende Bedeutung:
222
Das große Anti-Cracker-Buch
Code
BEDEUTUNG
DOS-Fehlermeldung
$01 $02 $03 $04 $05
alles ok Headerblock nicht gefunden SYNC nicht gefunden Datenblock nicht gefunden Checksummenfehler im Datenblock Verify-Fehler Diskette schreibgeschützt Checksummenfehler im Headerblock Falsche ID gelesen Diskette nicht eingelegt
00, 20, 21, 22, 23,
$07 $08 $09 $OB $OF
OK READ READ READ READ
ERROR ERROR ERROR ERROR
25, WRITE ERROR 26, WRITE PROTECT ON 27, READ ERROR 29, ID MISMATCH 74, DRIVE NOT READY
Die im Floppyhandbuch aufgeführten Fehlermeldungen '24, READ ERROR' (GCR-Kodierung wird nicht erkannt) und '28, WRITE ERROR' treten bei der VCl541 nicht auf. Zur Bedeutung der Fehlermeldungen sehen Sie bitte im Anhang nach_ Zur Benutzung der Disk-Controller-Routinen sei noch folgendes vermerkt: Wenn Sie einen Block mit dem 'VI'-Befehl lesen, so wird der Befehl 'Lese Block' an die Disk-Controller übergeben. Wird von diesem nun ein Fehler gemeldet, so veranlassen die 'logischen DOS-Routinen' (das Hauptprogramm) weitere Leseversuche jeweils eine halbe Spur links und rechts vom Track. Hat auch dies keinen Erfolg, wird ein 'Bump' durchgeführt (der Kopf geht nach Track I und gibt die typischen Geräusche bei einem Lesefehler von sich) und das ganze wird nochmal versucht. Normalerweise werden vom DOS 5 Leseversuche gemacht, ehe endgültig ein Fehler gemeldet wird. Diesen Mechanismus umgehen wir, wenn wir direkt die Disk -Controller-Routinen ansprechen. Für normale Schreib- und Leseoperationen sollten daher weiterhin die 'VI' und 'V2' -Befehle benutzt werden.
Diskettenkopierschutz 6.1.2
223
Das Disketten-Aufzeichnungsverfahren
In diesem Kapitel versuchen wir, Ihnen näher zu bringen, wie die Bits auf die Diskette kommen und von dort wieder gelesen werden können. Wie Sie bereits wissen, ist die Diskette in 35 Spuren oder Tracks unterteilt, die als konzentrische Ringe auf der Diskette angeordnet sind. Die äußerste Spur erhält die Nummer I, und Track 35 ist die innerste Spur. Um die einzelnen Spuren anzusteuern, hat das Laufwerk einen Schritt- oder Steppermotor, mit dem der Schreib/Lesekopf über jeden Track positioniert werden kann. Die Aufzeichnung der Daten geschieht nun bitweise. Dabei wird jedes '1'-Bit durch einen Wechsel der Magnetisierungsrichtung gekennzeichnet, während bei einem '0'Bit nichts passiert. Die Bits werden in einem festen Takt geschrieben, der ca. 250000 Bits/s beträgt. Wenn wir nun Daten nach diesem Verfahren auf eine Spur schreiben, sind wir anschließend nicht mehr in der Lage, die Daten korrekt wieder zu lesen. Da sich die Diskette fortlaufend dreht, die Spuren also keinen Anfang und kein Ende haben, können wir den Beginn unserer Aufzeichnung nicht ermitteln. Doch selbst wenn wir den Anfang der Spur feststellen könnten, würden wir Probleme haben, den Beginn jedes Bytes festzustellen. Theoretisch ist durch den Beginn der Aufzeichnung auch der Beginn jedes einzelnen nachfolgenden Bytes bestimmt. Dies setzt jedoch voraus, daß die Umdrehungsgeschwindigkeit der Diskette immer absolut gleichbleibend ist, auch von einem Drive zum anderen. Diese Forderung ist jedoch illusorisch, und man hat sich eine andere Möglichkeit überlegt, wie man den Beginn der einzelnen Bytes sicher erkennen kann. Zuerst unterteilt man einen Track in mehrere Sektoren. Da auf einer Spur bei der oben genannten Bitrate mehr als 6000 Bytes untergebracht werden können, braucht man zum einen einen Pufferspeicher dieser Größe in der Floppy. Ein weiterer Nachteil wäre es, daß nur maximal 35 Dateien abzuspeichern wären. Wenn wir ein Programm von I KByte Länge abspeichern würden, so wären 5 KByte verschenkt, die Ausnutzung der Diskette also sehr unökonomisch. Da die Tracks von außen nach innen schmaler werden, werden die äußeren Tracks in mehr Sektoren
224
Das große Anti-Cracker-Buch
unterteilt als die inneren. Dazu muß die Bitrate nach außen hin gesteigert werden. Die folgende Tabelle enthält die entsprechenden Daten. TRACK
BITRATE
BYTES PRO TRACK
1-17 18-24 25 -30 31-35
307692 285714 266667 250000
7692 7143 6667 6250
Doch damit hat sich unserer Problem nur vergrößert: Wie erkennen wir den Beginn der Sektoren auf dem Track? Wir müssen also bestimmte Bitkombinationen bevorzugt erkennen können, die als Daten-Bytes nicht vorkommen können. Mit acht Bit lassen sich 256 Kombinationen darstellen, die es auch alle als Daten-Bytes kann. Der Schlüssel zur Lösung liegt darin, ein Byte nicht durch 8, sondern für die Diskettenaufzeichnung durch mehr Bits darzustellen. Damit sind wir schon beim Group Code Recording, wie es von Commodore verwendet wird. Wir benutzen als sogenanntes Synchron- oder SYNC-Zeichen die Bitkombination %11111111 oder $FF. Die Daten-Bytes müssen nun so verschlüsselt werden, daß niemals 8 'l'-Bits hintereinander vorkommen. Das Group Code Recording, kurz GCR genannt, bildet nun jeweils 4 Bits auf 5 Bits ab. Die 5-BitKombination ist so gewählt, daß die obige Forderung erfüllt ist. Eine zusätzliche Forderung besteht noch darin, dafür zu sorgen, daß auch niemals mehr als zwei 'O'-Bit hintereinander folgen, da dabei ja kein Wechsel der Magnetisierungsrichtung erfolgt und es dadurch Probleme beim Lesen der Daten geben kann. Die Tabelle gibt nun an, wie jeweils 4 Bits verschlüsselt werden.
Diskettenkooierschutz ORIGINALDATEN SO S1 S2 S3 $4
S5 $6
S7 S8
S9 SA SB
sc $0
SE SF
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
225
GCR·CODE 01010 01011 10010 10011 01110 01111 10110 10111 01001 11001 11010 11011 01101 11101 11110 10101
Durch beliebiges Aneinandereihen des GCR-Codes kann also niemals ein Bitmuster entstehen, das mehr als 8 aufeinanderfolgende '1'-Bits oder mehr als zwei '0'-Bits enthält. Wir können also jetzt den Beginn eines Sektors durch SYNC-Bytes markieren, die durch eine Digitalschaltung erkannt und gemeldet werden. Doch wie wissen wir, welcher Sektor nach einem SYNCByte folgt? Dazu steht vor jedem Sektor ein sogenanntes Blockheaderfeld (Sektorkopf), das Track- und Sektornummer sowie zusätzlich noch die Identifikation der Diskette enthält (das sind die zwei Zeichen, die Sie beim Formatieren der Diskette mit angeben). Der nachfolgende eigentliche Sektorinhalt wird wieder durch vorausgehende SYNC-Bytes eingeleitet. Damit man Blockheader und Datenblock unterscheiden kann, folgt nach dem SYNC-Zeichen ein Kennbyte. Ein Headerblock wird durch eine 8 identifiziert, ein Datenblock durch eine 7. Damit Lesefehler, die z.B. durch eine verschmutzte oder zerkratzte Diskette entste hen können, erkannt werden, enthält jeder Block noch eine Checksumme über alle Bytes. Ein Blockheader hat damit folgenden Aufbau:
226
Das große Anti-Cracker-Buch 5
2 8
SYNC-ZEICHEN HEADERBLOCKKENNZEICHEN ($08) CHECKSUMME ÜBER ALLE BYTES SEKTOR-NUMMER TRACK-NUMMER ID2 ID1 ABSCHLUSSBYTES ($OF) FÜllBYTES ($55)
Bis auf die SYNC-Zeichen ($FF, %11111111) und die 8 Füllbytes ($55 %01010101) werden alle anderen Bytes in GCR-Code umgewandelt. Die Füllbytes sind erforderlich, da immer nur eine durch 4 teilbare Anzahl Bytes in GCR-Code umgewandelt werden kann_ Aus einem Byte (8 Bits) werden nach der Umwandlung 10 Bits_ 4 Bytes (32 Bits) werden in 40 Bits umgewandelt, was gerade 5 Bytes entspricht. Das folgende Schema soll deutlich machen, wie die Umwandlung vonstatten geht.
ORIGINALDATEN GCR-DATEN
11112222 33334444 55556666 77778888 11111222 22333334 44445555 56666677 7778888
Eine Ziffern gruppe soll dabei ein Nibble (4 Bits) darstellen. Die Umwandlung geschieht von einer DOS-Routine mit Hilfe der GCR-Code-Tabelle, die ab Adresse $F77F im ROM steht. Doch jetzt zum Aufbau des Datenblocks.
5 256
MIN.
2 4
SYNC-ZEICHEN DATENBLOCKKENNZEICHEN ($07) DATEN-BYTES CHECKSUMME ABSCHLUSSBYTES (SOO) FÜllBYTES (S55)
Diskettenkopierschutz
227
Der Block wird wieder mit 5 SYNC-Zeichen eingeleitet, dann folgen im GCR-Code das Kennzeichen für den Datenblock, eine 7, dann die 256 Daten-Bytes. Zur Fehlererkennung folgt eine aller Ein-Byte-Checksumme (Exklusiv-Oder- Verknüpfung Bytes) und zwei Nullbytes als Abschlußbytes. Die Anzahl der Füllbytes bis zum Beginn des nächsten Headerblocks ist variabel und wird bei der Formatierung aus der Gesamtkapazität des jeweiligen Tracks ermittelt. Ein kompletter Sektor auf Diskette enhält also 8 (Headerblock) + 260 (Datenblock) Bytes, die in GCR-Code gewandelt 335 Bytes ergeben, sowie 10 SYNC-Zeichen und mindestens 12 Füllbytes. Damit besteht ein Sektor aus mindestens 357 Bytes im Verhältnis zu 256 Nutzbytes. Es gehen also ca. 28% der gesamten Diskettenkapazität für die Datenorganisation verloren. Das Beschreiben der Diskette geschieht bereits komplett beim Formatieren. Sehen wir uns jetzt einmal an, wie die Floppy einen Sektor liest. 1. 2. 3. 4. 5. 6. 7. 8.
Laufwerksmotor einschalten. SchreibjLesekopf über Track bringen. Headerblock für gewünschten Track und Sektor erzeugen und in GCR-Code umwandeln. SYNC-Zeichen abwarten. Die nächsten 8 Bytes lesen und mit dem oben erzeugten GCR-Code vergleichen. Falls keine Übereinstimmung: zurück zu Schritt 4. Der richtige Sektorheader wurde gefunden, jetzt SYNC-Zeichen des Datenblocks abwarten. Datenblock lesen und GCR-Code decodieren.
Dies sind, vereinfacht dargestellt, die Schritte, die beim Lesen eines Sektors durchlaufen werden. Nicht berücksichtigt wurde dabei, was im Fehlerfall passiert, z.B. falls ein Checksummenfehler festgestellt wurde oder falls schon der Headerblock nicht korrekt gelesen werden konnte.
228
Das gro ße Anti -Cracker- Buch
Das Schreiben eines Blocks verläuft ähnlich. Auch hier muß zuerst der korrekte Headerblock gefunden werden, ehe man den alten Datenblock mit den neuen Daten überschreiben kann. I. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
Laufwerksmotor einschalten. SchreibjLesekopf über Track bringen. Datenblock in GCR-Code umwandeln. Headerblock für gewünschten Track und Sektor erzeugen und in GCR-Code umwandeln. SYNC-Zeichen abwarten. Die nächsten 8 Bytes lesen und mit dem oben erzeugten GCR-Code vergleichen. Falls keine Übereinstimmung zurück zu Schritt 5. 8 GAP-Bytes überlesen. 5 SYNC-Zeichen schreiben. GCR-Daten schreiben. Geschriebenen Sektor lesen und mit den Daten im RAM vergleichen (Verify).
Beim Schreiben eines Blocks wird also nur der Datenblock mit den zugehörigen SYNC-Bytes geschrieben. Der Blockheader bleibt unverändert, er wird nur einmal bei der Formatierung erzeugt.
6.1.3 Einführung in die Lese- und Schreibtechnik Bevor wir dazu übergehen, die einzelnen Kopierschutzverfahren zu erläutern, sollte erst grundlegend die Methodik des Lesens und Speicherns von Daten auf Diskette besprochen werden. Die zur Floppy-Steuerung interessantesten Register sind Register 1 und 2 des VIA 2. Es sind die Adressen $ICOO $lCOI, wobei $ICOO hauptSächlich zur Ansteuerung Laufwerk-LED und der Motoren und $ICOI zum Lesen Schreiben der Daten benötigt werden.
die und der und
229
Diskettenkooierschutz Erläuterung der Bitfunktionen der Adresse $ICOO Bit
o Steppermotorsteuerung 1 2 3 4 5
Steppermotorsteuerung Laufwerksmotor; Laufwerk-LED; Schreibschutz; Zur Einstellung der Bitrate zum Tonkopf (Speedflag) 6 Bedeutung wie Bit 5 7 SYNC-Signal beim Lesen gefunden;
Bit=O: Motor aus Bit=O: LED aus Bit=O: Lichtschranke unterbrochen
Bit=O: SYNC gefunden
Zum Lesen und Schreiben von Daten wird, wie schon gesagt, $ICOI benutzt. Schreibt man einen Wert in dieses Register, so wird dieser, sofern zuvor auf Schreiben umgestellt wurde, dieses Byte auf die Diskette geschrieben. Doch wie kann der Programmierer feststellen, wann das Byte vollständig geschrieben oder gelesen wurde? Um dies feststellen zu können, existiert eine BYTE-READY-Leitung, die mit dem OVERFLOW-Flag des Prozessorstatusregisters verbunden ist. Sobald ein Byte vollständig übertragen wurde, wird aufgrund dieser Leitung das OVERFLOW-Flag gesetzt. Dieses Flag wird von dieser Leitung zwar gesetzt, jedoch nicht wieder gelöscht. Dies muß der Programmierer mit Hilfe des CL V -Befehls 'von Hand' erledigen, damit er nicht ständig die Ready-Meldung für ein verarbeitetes Byte erhält. Die Zeit, in der ein Byte von Diskette gelesen oder auf Diskette geschrieben wird, liegt zwischen 26 und 32 Mikrosekunden, je nach Einstellung der Speed-Flags. Zum Starten von Programmen in der Floppy gibt es grundsätzlich zwei Möglichkeiten. Zum einen können Sie Ihre Programme über die lobcodes $EO und $DO als Interrupt-Programm starten, zum anderen ist es auch möglich, die Programme direkt zu starten. Wir werden uns erst das Starten von Programmen mit Hilfe der lobeodes ansehen.
Das große Anti-Cracker-Buch
230
Beispiel für das Laden eines Bytes: 0500 BVC S0500 0502 ClV 0503 lDA S1C01
In der ersten Zeile wird so lange gewartet, bis das V - Flag (BYTE-READY) auf HIGH gesetzt wird als Zeichen, daß ein Byte anliegt. Das V -Flag wird daraufhin 'von Hand' wieder auf LOW gesetzt. Anschließend wird das geladene Byte in den Akku geholt. Beispiel für das Speichern eines Bytes: 0500 STA S1C01 0503 BVC $0503 0505 ClV
Das Speichern eines Bytes läuft, wie Sie sehen, analog dazu ab . Das Byte wird gespeichert, worauf auf BYTE-READY gewartet wird. Abschließend wird das V -Flag wieder gelöscht. Vor dem Speichern muß man der Floppy noch mitteilen, daß die Daten, die in die Adresse $ICOI geschrieben werden, auch wirklich gespeichert werden. Dies erledigt das folgende kleine Programm. lDA AND ORA STA lDA STA
S1COC #S1F #sco S1COC #SFF S1C03
PCR auf Schreibbetrieb umschalten
Port für Schreib·/lesekopf auf Ausgang
Diskettenkopierschutz
231
Natürlich gibt es auch eine Routine zum Umschalten auf Lesen: LDA ORA STA LDA STA
S1COC #$EO S1COC #$00 S1C03
PCR auf Lesebetrieb umschalten Port für Schreib·/Lesekopf auf Eingang
Diese Routine existiert auch in dieser Form im Betriebssystem der Floppy und läßt sich aufrufen mit: JSR SFEOO
Um Daten gezielt auf Diskette schreiben und auch wieder von dort lesen zu können, wird es häufig nötig sein, auf eine SYNCMarkierung zu warten. Auch für diesen Fall stellt das Betriebssystem der Floppy eine Routine zur Verfügung. Sie läßt sich aufrufen mit: JSR $F556
Die Routine wartet 20 ms auf ein SYNC-SignaL Wird dieses nicht gefunden, wird eine entsprechende Fehlermeldung ausgegeben. Hier liegt auch der Nachteil im Benutzen der Routine. Der Programmierer hat keine Möglichkeit, selber auf ein eventuelles Nichtauffinden der SYNC-Markierung zu reagieren, da kein Rücksprung auf den Unterprogrammaufruf folgt. Die Lösung ist einfach. Sie übernehmen die Routine in Ihr Programm und können somit bei negativer Abfrage zu Ihrer eigenen Routine verzweigen. Wenn Sie sicher sind, daß eine SYNC-Markierung gefunden wird, können Sie das entsprechende Bit auch ohne Fehlerabfrage direkt abfragen. 0500 BIT S1COO Port abfragen 0502 BMI S0500 verzweige, wenn SYNC nicht gefunden
Das folgende Programm arbeitet selber mit Jobcodes und wird daher mit einem M-E-Befehl aufgerufen. Es erzeugt einen 22 READ-ERROR auf dem eingestellten Track und Sektor.
232 0600 0602 0604 0606 0608 060A 060C 060E 0610 0612 0614 0616 0618 061A 061C 061E 0620
Das gro ße Anti -Cracker- Buch LDA STA LDA STA LDA STA LDA BMI LOA STA LOA STA LOA BMI LOA STA RTS
#$01 SOA #$00 SOB #$80 S02 S02 S060C #$09 S47 #$90 S02 S02 S0618 #$07 $47
Nummer des zu ladenden Tracks in Trackspeicher für Puffer 2 schreiben Nummer des zu ladenden Sektors in Sektorspeicher für Puffer 2 schreiben Jobcode für Track/Sektor lesen in Jobspeicher des Puffer 2 schreiben Jobspeicher lesen warten, bis Job abgearbeitet ist eigenes Erkennungszeichen für Datenblock in die entsprechende Adresse schreiben Jobcode für Block speichern in Jobspeicher für Puffer 2 schreiben Jobspeicher lesen warten, bis Job abgearbeitet ist normales Oatenblock-Erkennungszeichen laden und speichern (rücksetzen) Rücksprung
In unserem Fall wird die Erkennungsziffer 7 durch die Ziffer 9 ersetzt Der Daten-Header kann somit nicht mehr gefunden werden_ Bei diesem ERROR handelt es sich um einen SOFTERROR, da die Daten des zerstörten Blocks sich trotz einer Fehlermeldung im Speicher der Floppy befinden_ Vom BASIC oder von einem gewöhnlichen Diskmonitor aus ist es nicht möglich, diesen Block, ohne Umstellung der Speicherzelle $47 (71) auf 9, zu lesen_ Falls Sie diesen Block wieder reparieren wollen, reicht es aus, 0610 LDA #$09 durch 0610 LDA #$07 zu ersetzen und das Programm erneut zu starten. Sie speichern somit die Orginalkennzahl 7 mitsamt dem Block wieder ab. Als nächstes stellen wir eine Routine vor, die den Tonkopf um beliebig viele Halbspuren nach innen oder außen bewegt. In diesem Beispiel wird der Tonkopf 16 Spuren nach außen und danach wieder nach innen gefahren. Es ist ratsam, die Diskette vor dem Ausprobieren dieser Routine zu initialisieren, damit der
Diskettenkopierschutz
233
Tonkopf nicht anstoßen kann. Für die Beobachtung des Tonkopfes bei diesem Beispiel und bei eigenen Experimenten ist es von Vorteil, die vier Schrauben an der Unterseite der Floppy zu lösen und den Deckel vorsichtig abzunehmem. Unserer Meinung nach überwiegen die Vorteile beim Arbeiten mit einer geöffneten Floppy im Gegensatz zu einer geschlossenen. Jetzt aber endlich das erwähnte Programm.
0500 0501 0504 0506 0509 050B 0500 050F 0511 0512 0514 0516 0519 051A 051C 051E 0521 0522 0524 0527 0529 052C 0520 052E 052F
SEI LOA ORA STA LOX STX OEC BNE OEX BNE LOY JSR OEY BNE LOY JSR OEY BNE LOA ANO STA CLI RTS NOP NOP
S1COO #$04 S1COO #$30 S4B S4B S0500 $1500 #$10 S0530 $0516 #$10 $0537 $051E S1COO #$F8 $1COO
Interrupt sperren Control-Register laden Bit für Laufwerksmotor setzen und wieder schreiben Warteschleife lJTI
dem
Motor Zeit ZlJTl Hochfahren zu geben verzweige, wenn Zeit nicht abgelaufen Zähler für Stepanzahl auf $10 setzen Kopf lJTI eine Halbspur nach außen schieben Zähler verringern verzweige, wenn noch nicht abgezählt Zähler erneut setzen Kopf lJTI eine Halbspur nach innen schieben Zähler verringern verzweige wenn noch nicht abgezählt Control-Register laden Bits 0 bis 2 löschen, lJTI Motor zu stoppen und wieder schreiben Interrupt wieder ermöglichen Rücksprung
Das große Anti-Cracker-Buch
234
Routine zur Kopfsteuerung 0530 0533 0534 0535
LDX S1COO Control-Register laden DEX verringern, um Kopf nach außen zu schieben CLC Carry-Flag setzen BCC S053B unbedingter Sprung
0537 053A 053B 053C 053E 0540 0543 0545 0547 054A 054C 054E 0550 0552 0553 0555
LDA INX TXA AND STA LDA AND ORA STA LDX STX DEC BNE DEX BNE RTS
S1COO Control-Register laden erhöhen für Kopfbewegung nach außen Wert in Akku schieben #$03 ersten zwei Bits isolieren S4B und zwischenspeichern S1COO Control-Register erneut laden #$FC Bits 0 und 1 löschen $4B und mit den errechneten Bits verknüpfen S1COO und speichern #$09 S09 ist Zähler für $4B die Warteschleife, $4B um dem Tonkopf S054E genügend Zeit zu geben, S054E positioniert zu werden Rücksprung
Sie werden sich sicher fragen, wie es möglich ist, daß sich der Tonkopf mittels dieser Routine bewegt Für diese Bewegung sind die Bits 0 und I des Control-Registers zuständig_ Der Kopf bewegt sich durch systematische Veränderung dieser Bits, wenn zusätzlich der Laufwerksmotor (Bit 3) eingeschaltet ist Durch eine Veränderung der beiden Bits in der Folge 00/01/10/11/00 bewegt sich der Kopf nach innen, durch die Folge 00/11/10/01/00 bewegt er sich nach außen_ Hier ein Beispiel zum besseren Verständnis_ Sind die Bits 0 und 1 wie in unserem Beispiel gelöscht und wird dann das erste Bit gesetzt, was der Bitfolge 01 entspricht, so bewegt sich der Kopf, wenn man ihm genug Zeit läßt, um eine Halbspur nach innen. Durch das Setzen beider Bits bewegt er sich nach außen. Wie Sie
Diskettenkopierschutz
235
sicher festgestellt haben, wird dieses Programm nicht über die Jobcodes, sondern direkt gestartet und läuft folglich auch nicht als Interrupt-Programm. Zum Schluß noch ein Beispiel für das Starten eines Programms mittels des Jobcode $EO. Das folgende Programm fährt den Tonkopf auf den eingestellten Track und liest dort den ersten Blockheader, den es findet. Die gelesenen Daten werden aus dem GCR-Format in die normalen Bitwerte gewandelt und in den Adressen $16 bis $IA gespeichert. Die Speicherung geschieht in der Reihenfolge: S16 - Erste ID auf Diskette S17 - Zweite ID auf Diskette S18 gelesener Track S19 gelesener Sektor S1A - Prüfsumme über den Blockheader
Die GCR-Daten des Headers stehen ab $24 0500 0503 0505 0508 050A 050C 050E 0510 0512 0514 0516 0518
JHP LDA STA LDA STA LDA STA LDA STA LDA BHI RTS
S0503 #$19 S0501 #$01 SOA #$00 SOB #$EO S02 S02 S0514
Sprung für den ersten Durchlauf unbedeutend LOY-Byte der Einsprungadresse in den JHP-Befehl schreiben (JHP S0519) Track, auf dem ausgeführt werden soll in Speicher für Puffer zwei speichern Sektornummer (unerheblich) speichern Jobcode SEO (Programm im Puffer ausführen) in Jobspeicher für Puffer zwei schreiben Jobspeicher lesen verzweige wenn nicht beendet Rücksprung
236
Das große Anti-Cracker-Buch
Das auszuführende Interruptprogramm 0519 051B 051E 0520 0522 0524 0526 0528 052B 052D 052E 0531 0533 0535 0537 0538 053B 053D 053E 0540 0542 0545
LDA STA LDX STX LDX LDA STA JSR BVC CLV LDA CMP BNE BVC CLV LDA STA INX CPX BNE JSR JMP
#$03 $0501 #$5A $4B #$00 #$52 $24 $F556 $052B
0548 054A 054C 054E
DEC BNE LDA JMP
$4B Zähler für Fehlversuche verringern $0522 verzweige wenn weitere Versuche #$02 Fehlermeldung ($02=Blockheader nicht $F969 gefunden) ausgeben und Programm beenden
$1C01 $24 $0548 $0535 $1C01 $25,x #$07 $0535 $F497 $FD9E
Einspung wieder normalisieren (JMP $0503) 90 Leseveruche im Zähler speichern Zeiger auf 0 setzen GCR-Codierung $08 (Headerkennzeichen) in Arbeitsspeicher speichern auf SYNC warten auf BYTE-READY beim Lesen warten BYTE-READY wieder löschen gelesenes Byte vom Port holen mit gespeichertem Header vergleichen verzweige, wenn kein Blockheader gefunden sonst auf BYTE-READY warten Leitung rücksetzen gelesenes Byte holen und in Arbeitsspeicher schieben Zeiger erhöhen schon ganzen HEADER gelesen? verzweige, wenn noch nicht alle Zeichen GCR-BYTEs in Bitform wandeln Rücksprung aus dem Interrupt (ok)
Das Programm wird ab $0500 gestartet und stellt den JMP $0503 auf JMP $0519 um_ Daraufhin wird die Tracknummer übergeben und der Jobcode $EO in den Jobspeicher $02 (Pufffer 2 $0500) übergeben. Dieser Code wird nun von der interruptgesteuerten Jobschleife erkannt, die mit der Bearbeitung des Codes anfängt. Als erstes wird der Tonkopf auf den entsprechenden Track positioniert und das auszuführende Programm ab $0500 (für Puffer 2) gestartet. Da der erste Befehl jedoch JMP $0519 ist, wird der Programmteil, der vom Benutzer gestartet wurde,
Diskettenkopierschutz
237
übersprungen und der JMP Befehl wieder auf JMP $0503 zurückgesetzt. Das jetzt arbeitende Programm läuft interruptge steuert. Das Hauptprogramm wartet bei $0514 bis $0516 darauf, daß eine Rückmeldung vom Interruptprogramm ankommt. Das Hauptprogramm muß immer mit einem R TS abgeschlossen werden, da sonst keine Meldung an den Computer geht und die Floppy sich aufhängt. Das Interruptprogramm darf nicht mit einem R TS abgeschlossen werden. Am Ende dieses Programms muß wieder in die Jobschleife zurückgekehrt werden, welche die im Akku enthaltene Rückmeldung an den Jobspeicher übergibt. Nachdem das Hauptprogramm die Rückmeldung erhält, wird es mit dem RTS beendet. Mit $0545 JMP $FD9E wird eine $01 in den Akku geladen und danach ebenfalls mit JMP $F969 in die Jobschleife gesprungen. Einer kleinen Erklärung bedarf es wohl noch bei $0524 LDA #$52
Die $52 stellt die ersten 8 Bits einer im GCR-Format geschriebenen $08 dar, die bekanntlich für das Kennzeichen des Anfangs eines Blockheaders steht. Normalerweise kann nach dem Auffinden einer SYNC-Markierung nur eine $08 (Blockheader) oder $07 (Datenheader) gefunden werden. Da die GCR-Daten der beiden Zahlen sich jedoch schon nach den ersten 8 Bits unterscheiden, reicht es aus, nur diese zu vergleichen. HEX 7 8
BIN 00000111 00001000
GeR 01010101 11 = $55+%11 01010010 01 = $52+%01
Wenn wir schon von GCR-Codierung sprechen: Wie kann man Daten, die man auf Disk schreiben will, in das GCR-Format und umgekehrt wieder ins Binärsystem wandeln? Für diese Fälle gibt es einige Routinen im DOS der Floppy.
238
Das große Anti-Cracker-Buch
$F78F
Diese Routine wandelt den gesamten aktiven Puffer ins GCR-Format. Da bei der Codierung von 4 Bits zu 5 Bits selbstverständlich mehr Platz benötigt wird, werden die ersten GCS-Daten in dem Ausweichpuffer vom $OIBB bis $OIFF geschrieben. Die restlichen Daten stehen im aktiven Puffer.
$F5F2
Die Routine wandelt die GCR-Daten, die im Ausweichpuffer und im aktuellen Puffer stehen, wieder in den Binärcode zurück und speichert die Werte im aktuellen Puffer.
$F934
Wandelt den Blockheader, dessen Werte in den Speicherstellen $16 bis $IA stehen, in GCR-Daten um und speichert sie ab $24.
$F497
Wandelt den gelesenen Blockheader in Binärcode um. Die GCR-Daten stehen hierbei ab $24 und die Binärwerte ab $16 (siehe Beispielprogramm).
Das sollte als Einführung in die Programmierung der Floppy reichen. Zum Abschluß noch eine Beschreibung der Register der VIA 2, die von großem Interesse für die Programmierung der Floppy ist. Wie funktioniert die Formatroutille?
Jeder von Ihnen wird schon Erfahrung mit der Formatroutine der Floppy gemacht haben, doch hier wollen wir sie einmal nicht aus der Sicht des Anwenders, sondern aus der Sicht des Programmierers betrachten. Die Formatroutine, die bei $FAC7 beginnt, wird von der Routine ab $C8C6 mit dem Jobcode $EO aufgerufen. Dazu wird von dieser Routine in die Speicherstellen $0600 bis $0602 ein •JMP $FAC7' geschrieben und dieser mit dem $EO-Befehl gestartet. Ab jetzt beginnt die eigentliche Formatroutine. Sie prüft die Speicherstelle $51, die den Track angibt, der gerade formatiert
Diskettenko pier schul Z
239
wird. Sollte der Wert dieser Speicherstelle $FF sein, so erkennt das Programm, daß die Formatierung gerade begonnen wurde, und führt einen BUMP (Anschlag des Kopfes) aus. Im folgenden wird der Track genau vermessen, und aufgrund dieser Messung werden die Abstände zwischen den Sektoren festgelegt. Diese Messung ist auch der Grund dafür, daß das Formatieren einer Diskette so lange dauert. Die Sektoren werden nun mitsamt ihren Headern aufgetragen, womit auch schon die Formatierung eines Tracks beendet ist. Nun wird die aktuelle Tracknummer mit 36 verglichen. Sollte sie kleiner sein, verzweigt das Programm zur Trackpositionierung, von wo aus wieder in die Jobschleife gesprungen wird. Die Jobschleife prüft erneut, ob ein Jobbefehl ausgeführt werden soll, und erkennt den $EO-Job, der sich immer noch in $03 (Job-Speicher für Puffer 3) befindet. Das führt dazu, daß das Formatierungsprogramm ab $F AC? durch den 'JMP'-Befehl ab $0600 erneut aufgerufen wird. Wenn man in die Formatroutine eingreifen will, reicht es aus, ab $0600 eine eigene Routine einzusetzen. Das sollte als Einführung in die Programmierung der Floppy reichen. Viele weitere Erfahrungen und Anregungen können Sie durch häufiges und intensives Studium der DOS-Listings erhalten. Zum Abschluß noch die Registerbeschreibung der Drive Control- Register. 6.1.4
Registerbeschreibung der VIA 2
S1COO Drive-Control-Bus Bit 0 Steppermotorsteuerung Bit 1 Steppermotorsteuerung Bit 2 Laufwerksmotor; Bit=O: Motor aus Bit 3 Laufwerk-LED; Bit=O: LED aus Bit 4 Schreibschutz; Bit=O: Lichtschranke unterbrochen Bit 5 Zur Einstellung der Bitrate zum Tonkopf (Speedfl ag) Bit 6 Bedeutung wie Bit 5 Bit 7 SYNC-Signal beim Lesen gefunden; Bit=O: SYNC gefunden
240
Das große Anti-Cracker-Buch
S1C01 Port A Ein-/Ausgaberegister - Datenbus zum R/W-Kopf S1C02 Datenrichtungsregister Port B S1C03 Datenrichtungsregister Port A S1C04 Timer
(Lew)
S1C05 Timer
(HIGH)
S1C06 Timer (Lew Latch) Beim Schreib- oder Lesezugriff bleibt der Wert des Timers unverändert. Der Wert wird in diesem Zwischenspeicher gespeichert und im FREE-RUNNINGMODE (S1C11 Bit 6=1) bei jedem Null-Durchgang in den Zähler automatisch übernommen. S1C07 Timer 1 (HIGH Latch) Siehe S1C06 S1C08 Timer 2 (Lew) S1C09 Timer 2 (HIGH) S1COA Schieberegister $1COB Hilfssteuerregister Bit 0 PA (Latch-Enable-Disable) Bit 1 PB (O=Disable 1=Enable Latching) Bit 2 Schiebesteuer-Bit Bit 3 Schiebesteuer-Bit Bit 4 Schiebesteuer-Bit Bit 5 T2 Timercontrol (O=Zeitlicher Interrupt 1=Abwärtszählen mit Signal am Anschuß PB6) Bit 6 T1 Timercontrol Bit 7 T1 Timercontrol
Diskettenkopierschutz
241
$1COC Peripheristeuerregister (PCR) Bit 0 CA1 Interruptsteuerung (O=Negative Flanke des Signals/1=Positive Flanke) Bit CA2 Control Bit 2 CA2 Control Bit 3 CA2 Control Bit 4 CB1 Interruptsteuerung siehe Bit 0 Bit 5 CB2 Control Bit 6 CB2 Control Bit 7 CB2 Control $1COD Interrupt-Flag-Register Dieses Register signalisiert das Eintreffen von Ereignissen durch setzen des entsprechenden Bits dieses Registers. Bit 0 Aktive Flanke an CA2 Bit 1 Aktive Flanke an CA1 Bit 2 acht Schiebeimpulse von SR ($1COA) Bit 3 Aktive Flanke an CB2 Bit 4 Aktive Flanke an CB1 Bit 5 Timer-Unterlauf von Timer 2 Bit 6 Timer-Unterlauf von Timer Bit 7 Wird gesetzt wenn eines der Bits sowohl in diesem Register, als auch in $1COE gesetzt ist. $1COE Interrupt Enable Register Die Bits dieses Registers korrespondieren mit den Bits aus $1COD. Ist in diesem Register ein Bit gesetzt und der entsprechende Zustand, der in $1COD angezeigt wird, erfüllt, wird ein IRQ ausgeführt. $1COF Datenregister A Dieses Register spiegelt den Inhalt aus $1COO, jedoch ohne Handshake-Betrieb.
6.2
Die Formatroutine
Durch Änderung der Formatroutine lassen sich emlge gut zu gebrauchende Effekte erzielen. Es ist möglich, einen einzelnen
Das große Anti-Cracker-Buch
242
Track oder die Spuren 36 bis 41 zu formatieren sowie sämtliche Blockheader- Parameter zu verändern. 6.2.1
Formatieren eines einzelnen Tracks
Im Kapitel 6.1.3 wurde schon allgemein auf die Arbeitsweise der Formatroutine eingegangen. Dort haben Sie erfahren, daß die Formatroutine vor der Formatierung eines Tracks jedesmal neu über den 'JMP $FAC7' bei $0600 aufgerufen wird. An dieser Stelle greifen wir nun ein und zwingen die Routine, nur einen Track zu formatieren. Für dieses Vorhaben reicht das folgende kleine Programm, das mit dem Job-Code 'EO' im Job-Speicher $03 aufgerufen werden muß.
0600 0603 0605 0607 0609
JMP LDA STA LDA STA
S0603 SOC S51 #$OF S0601
060C JMP SFAC7 060F JMP SFD9E
Der JMP wird vom Programm noch verändert Nummer des zu formatierenden Tracks holen und übergeben JMP-Befehl auf S060F zeigen lassen um beim erneuten Aufruf die Formatierung zu beenden zur Formatroutine springen beim zweiten Aufruf Programm beenden
Hier wird das Programm mit einem 'B-E'-Befehl gestartet 0612 0614 0616 0618 061A
LDA STA LDA STA RTS
#$01 SOC #$EO S03
Nummer des und an den Job-Befehl und in den Rücksprung
zu formatierenden Tracks laden Job übergeben EO (Programm aufführen) laden Job-Speicher schreiben
Als nächstes folgt ein BASIC-Programm, das es erlaubt, die entsprechende Nummer des zu formatierenden Tracks einzugeben_ Zu Beginn des Programms wird initialisiert, damit die Formatroutine den Track auch mit der richtigen ID formatiert_ Sollte dies nicht der Fall sein, läßt sich ein Block auf diesem
243
Diskettenkopierschutz
Track nicht mehr ohne eigene Software lesen. Ein so "zerstörter" Track ist jedoch für Kopierprogramme des heutigen Standards kein Problem. Er kann lediglich genutzt werden, um Knackern den Zugriff auf den Block, auf dem die Kopierschutzabfrage steht, zu erschweren. Jetzt aber das angekündigte Programm.
OPEN1,8,15,II" 20 READ X:IF X=-1THEN 100 30 PRINT#1,"M-~ICHR$(N)CHR$(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 INPUT II~ELCHER TRACK";T 110 IF T >35 OR T<1 THEN100 120 PRINT#1,"M-~IICHR$(19)CHR$(6)CHR$(1)CHR$(T) 130 PRINT#1,IM-E"CHR$(18)CHR$(6) 140 PRINT#1,IM-R"CHR$(3)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 160 IF A>127 THEN130 170 IFA =1 THENPRINT"FORMATING OK":END 180 PRINT"FORMAT ERROR":END 302 DATA 76, 3, 6,165, 12,133,81,169, 15,141, 158,253 304DATA169, 1,133,12,169,224,133, 3,96, -1
1,
6, 76,199,250, 76,
Wie schon gesagt, wird als erstes initialisiert. Daraufhin werden die Daten in die Floppy ab $0600 geschickt. Nachdem die Track-Nummer eingegeben wurde, wird sie ebenfalls zur Floppy geschickt und das Programm ab $0612 mit dem 'M-E'-Befehl gestartet. Im Anschluß daran wird auf die Rückmeldung im Job-Speicher $03 gewartet und nach ihrem Erhalt die entsprechende Meldung ausgegeben. Es ist nicht möglich, mit diesem Programm die Spuren oberhalb von 35 zu formatieren, was nicht von der Abfrage in Zeile 110, sondern von dem DOS der Floppy abhängt. Will man Track 18 formatieren, so stößt man auf etwas größere Probleme. War dieser vor der Formatierung zerstört, so daß es nicht möglich ist zu initialisieren, muß vor der Formatierung die ID 'per Hand' angegeben werden. Für diesen Zweck müssen Sie
244
Das große Anti-Cracker-Buch
mit dem 'M-W'-Befehl die ID in die Floppy-Speicherstellen $12 (18) und $13 (19) schreiben. Sie werden sich vielleicht fragen, wie Sie die ID Ihrer Diskette erfahren sollen, falls Sie das Directory nicht mehr laden können. Für diesen Fall können Sie das im vorherigen Kapitel beschriebene Programm verwenden, das den nächsten Blockheader, den es findet, liest und decodiert. Die ID des Blockheaders steht dann in Speicherstelle $16 (22) und $17 (23), von wo Sie sie ohne weiteres auslesen können. Nach dem Formatieren des Tracks 18 kann man jedoch nicht gleich Programme auf der Diskette speichern. Zuvor muß noch die BAM und das Directory erstellt und auf Diskette gespeichert werden. Dies erledigt für uns zum größten Teil eine Routine im DOS der Floppy. Wir müssen ihr lediglich den Namen unserer Diskette übergeben. Es ist nicht möglich, die Diskette 'soft' zu formatieren (Formatierungsbefehl ohne ID-Angabe), da dieser Befehl nur angewendet werden kann, wenn die BAM schon vorher vorhanden war. Das folgende Programm wird in der Floppy ab $0400 mit einem 'M-E'-Befehl gestartet. Die Daten für den Diskettennamen müssen in diesem Fall ab $0420 im Speicher stehen und mit einem Komma abgeschlossen werden.
0400 0402 0405 0408 0409 040B 0400 0410
LOX LOA STA OEX BPL LOA STA JMP
#$10 16 Buchstaben des Namens $0420,x vom $0420 in den $0200,x Befehlspuffer schieben Zähler verringern $0402 verzweige, wenn noch nicht fertig #$10 16 Buchstaben für den Namen $0274 in Puffer zulassen SEE40 Befehl ausführen, Rücksprung
Das folgende BASIC-Programm erledigt alle Aufgaben für Sie. Der Name wird der Floppy übergeben, und daraufhin wird das Programm ausgeführt.
Diskettenkopierschutz
245
o OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 30 PRINT#1,"M-W"CHR$(N)CHR$(4)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 INPUT "DISKNAME"iN$:N$=N$+"," 110 FORN=OTO LEN(N$)-1 120 PRINT#1,"M-W"CHR$(32+N)CHR$(4)CHR$(1)MID$(N$,N+1,1): NEXT 130 PRINT#1,"M-E"CHR$(0)CHR$(4) 302DATA162, 16,189,32, 4,157, 0, 2,202, 16,247,169, 16,141,116, 76, 64 304 DATA238,-1
2,
Das Programm fügt selbstständig das Komma am Ende des Namens an. Mit diesen Programmen ist es Ihnen also möglich, einen einzelnen Track zu formatieren, solange er nicht oberhalb von Spur 35 liegt.
6.2.2
Formatieren der Spuren 36 bis 41
Es ist der Floppy ohne weiteres möglich, ihren RjW-Kopf auf die Spuren oberhalb der Spur 35 zu positionieren. Wollen Sie diese Spuren für die Ablage von Daten verwenden, so stoßen Sie noch auf ein Problem_ Sie können den RjW-Kopf zwar ohne Probleme auf die Spuren oberhalb von 35 fahren, dort jedoch nicht ohne weiteres lesen, denn die Tabelle, aus der sich das DOS die Anzahl der Sektoren pro Track und den Speed der entsprechenden Spur holt, gilt nur für die Spuren unterhalb von 36. Wir müssen deswegen sowohl beim Lesen als auch beim Schreiben den Speed und die Anzahl der Sektoren selber übergeben. Damit steht dem Formatieren der Spuren 36 bis 41 nichts mehr im Wege. Zu beachten ist lediglich, daß die Formatroutine nach der Formatierung eines Tracks erkennt, daß sie sich oberhalb von Spur 35 befindet und den Vorgang daraufhin abbricht. Wie müssen somit diese Routine vor der Formatierung eines weiteren Tracks erneut starten, was bei der Formatierung unter-
246
Das große Anti-Cracker-Buch
halb von 36 nicht gemacht werden mußte. Im folgenden zeigen wir das Programm, das diese Spuren formatiert. Es wird in der Floppy mit dem 'M-E'-Befehl ab $0415 gestartet.
0400 0402 0404 0407 0409 040B 040E 0410 0412
LDA STA LDA AND ORA STA LDA STA JMP
#$11 $43 S1COO #$9F #$00 S1COO S08 S51 SFAC7
Anzahl der Sektoren für die Spuren über 35 übergeben Control'Port laden und Speed für diese Tracks einstellen (kann hier geändert werden) Wert speichern Track'Nummer laden und di e Nummer übergeben Formatroutine anspringen
Das Programm wird hier gestartet 0415 0418 041A 041C 041E 0420 0422 0424 0426 0428 042A 042C 042E 0430 0432
JSR LDA STA STA LDA STA LDA BMI CMP BCS INC LDA CMP BNE RTS
$0042 #$24 S37 S08 #$EO S01 S01 S0422 #$02 S0432 S37 S37 #$2A S041A
initialisieren Track, bei dem die Formatierung beginnt laden S24 (36) und zwischenspeichern Track'Nummer an Job übergeben Job'Code EO für Programm ausführen in Job'Speicher schreiben Rückmeldung erwarten verzweige, wenn Job nicht abgearbeitet Rückmeldung auf Fehler prüfen verzweige, wenn Fehler aufgetreten Track-Nummer erhöhen und vergleichen, ob Spur 41 schon überschritten wenn nicht, nächsten Track formatieren Rücksprung
Im folgenden ein BASIC-Programm, das Ihnen die Eingabe des Anfangs- und des End-Tracks erlaubt. Der End-Track sollte nicht höher als Spur 42 liegen, da der R/W-Kopf sonst 'a nschlägt und sich dadurch das Laufwerk dejustieren kann. Track 42 schaffen die meisten Floppys noch gerade zu formatieren, sofern
Diskettenkooierschutz
247
eine qualitativ hochwertige Diskette verwendet wird. Diesen Track erreicht bis heute kein von uns bekanntes Kopierprogramm.
10 OPEN 1,8,15,"1" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHR$(N)CHR$(4)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IF SU <> 5381 THENPRINT"FEHLER IN DATAS":STOP 102 INPUT "STATRTRACK >35 "iS 105 INPUT "ENDTRACK <42 "iE 120 PRINT#1,"M-W"CHR$(25)CHR$(4)CHR$(1)CHR$(S) 125 PRINT#1,"M-W"CHR$(47)CHR$(4)CHR$(1)CHR$(E+1) 130 PRINT#1,"M-E"CHR$(24)CHR$(4) 140 PRINT#1,"M-R"CHR$(1)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 160 IF A>127 THEN140 170 !FA =1 THENPRINT"FORMATING OK":END 180 PRINT"FORMAT ERROR":END 302 DATA169, 17,133, 67,173, 0, 28, 41,159, 9, 0,141, 0, 28,165, 8, 133, 81 304 DATA 76,199,250,32,66,208,169,36,133,55,133, 8,169,224,133, 1~ 165, 1 306 DATA 48,252,201, 2,176, 8,230,55,165, 55,201, 42,208,232, 96, -1
Wir können uns jetzt mit Hilfe dieses Programms die Spuren 36 bis 41 formatieren und sie somit für die Speicherung von Daten vorbereiten. Doch die Speicherung und das Laden von Daten auf diesen Spuren ist nicht so leicht wie auf den normal verwendeten Tracks. Wie schon gesagt, kann das DOS für diese 'illegalen' Spuren den Speed und die Sektorenanzahl nicht richtig bestimmen. Aus diesem Grund kann man weder mit den USERBefehlen 'U I' (Block laden) und 'U2' (Block speichern) noch mit den Job-Codes '80' (Block laden) sowie '90' (Block speichern) arbeiten. Die einzige Möglichkeit, die uns bleibt, ist, über den Job-Code 'EO' ein Programm aufzurufen, das den Speed und die
248
Das große Anti-Cracker-Buch
Anzahl der Sektoren setzt und danach die Block-Lese- oder Schreib routine anspringt. Das Programm, das einen Block liest, sieht dann folgendermaßen aus:
0600 0603 0605 0607 060A 060C 060E 0610 0612
LOA ANO ORA STA LOA STA LOA STA JMP
S1COO #$9F #$00 S1COO #$11 $43 #$05 S31 SF401
Control-Port laden Bits für Speed löschen und mi t neuem Speed verknüpfen Wert wieder speichern Anzahl der Sektoren laden und übergeben HIGH-Byte der Pufferadresse, in den geladen werden soll, angeben _ zur Laderoutine springen
Das Programm wird hier gestartet 0615 0617 0619 061B 0610 061F 0621 0623 0625
LOA STA LOA STA LOA STA LOA BMI RTS
#$24 SOC #$00 SOO #$EO S03 S03 S0621
zu ladenden Track für den Job übergeben Sektor-Nl.I1I11er an den Job übergeben Job-Code für Programm ausführen in den Job-Speicher schreiben Rückmeldung abwarten verzweige, wenn noch nicht fertig Rücksprung
Das Programm wird ab $0615 mit dem 'M-E'-Befehl gestartet und lädt den angegebenen Sektor in Puffer 2 ($0500)_ Auch hier muß vor dem Laden des Blocks der Speed und die Anzahl der Sektoren übergeben werden, worauf die Routine zum Lesen eines Blocks angesprungen wird. Wenn Sie einen Block auf die oberen Spuren sichern wollen, so reicht es aus, den 'JMP $F4Dl' bei Adresse $0612 in ein 'JMP $F575' zu ändern. Das nachfolgende BASIC Programm erlaubt Ihnen die Eingabe des Tracks und des Sektors, von dem Sie laden wollen.
Diskettenkopierschutz
249
100PEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"H-W"CHR$(N)CHR$(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 3608 THEN PRINT"ERROR IN DATAS":STOP 105 INPUT "WELCHER TRACK";T 110 INPUT "WELCHER SEKTOR";S 120 PRI NT#1 , "H-W"CHR$(22)CHR$(6)CHR$( 1)CHR$(T) 125 PRINT#1,"H-W"CHR$(26)CHR$(6)CHR$(1)CHR$(S) 130 PRINT#1,"H-E"CHR$(21)CHR$(6) 135 FORN=1TO 500:NEXT 140 PRINT#1,"H-R"CHR$(3)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 160 IF A>127 THEN130 170 IFA =1 THENPRINT"OK":END 180 PRINT"ERROR":END 302DATA173, 0,28,41,159, 9, 0,141, 0, 28,169, 17,133,67,169, 5, 133, 49 304 DATA 76,209,244,169,36,133, 12,169, 0,133, 13,169,224,133, 3,165, 3, 48 306 DATA252, 96, -1
Der gelesene Block wird, wie schon gesagt, in Puffer 2 ($0500) geschrieben, von wo aus er in den Computer geholt werden kann_ Um mit diesem Programm einen Block zu schreiben, ist es notwendig, die Zeile 304 und die Prüfsumme über die Daten zu ändern_ Anstatt 304 DA T A 76,209,244 ___ muß hier 304 DA T A 76,117,245 ___ stehen. Die Prüfsumme in Zeile 100 muß dann '3517' anstelle von '3608' lauten. Die Daten, die auf den eingegebenen Track und Sektor geschrieben werden, werden natürlich auch wieder aus Puffer 2 ($0500) geholt. Es ist nicht möglich, mit diesem Programm Blöcke auf den Tracks unterhalb von Track 31 zu schreiben, da diese Tracks mit einem anderen Speed beschrie ben werden.
250 6.2.3
Das große Anti- Cracker-Buch Doppelte Spuren
Ein Kopierschutzsystem, daß sich weniger durch seine Kompliziertheit als durch seine Raffiniertheit auszeichnet, soll nun beschrieben werden. Es ist schwer auszumachen, weil keine Fehler auf der Diskette zu erkennen sind. Bei diesem Kopierschutz handelt es sich auch, wie Sie sich sicher denken können, um eine Änderung der Formatroutine. Der Gedanke, der hinter diesem Kopierschutz steckt, ist recht einfach. Die unteren Spuren werden doppelt angelegt. Das heißt, daß die Tracks auf Diskette anstelle der gewohnten Reihenfolge Track I, Track 2, Track 3 usw. die Folge Track I, Track 2, Track I, Track 2, Track 3, Track 4, Track 5 usw. aufweisen. Sie erkennen, daß der Track I und Track 2 doppelt auf Diskette vorhanden sind. Sie werden sich vielleicht fragen, was das in Hinblick auf Kopierschutz bedeutet. Die Antwort ist einfach, wenn man sich daran erinnert, wie das DOS auf die einzelnen Tracks zugreift. Als erstes wird "nachgesehen", auf welchem Track sich der R j W-Kopf gerade befindet. Danach wird die Differenz zwischen der jetzigen und der zu ereichenden Spur errechnet und der RjW - Kopf um die entsprechende Anzahl von Spuren verschoben. Der Kopf erreicht nach diesem Prinzip somit nie auf normalem Wege die gedoppelten, unterhalb der echten liegenden Tracks I und 2. Nach genau diesem Prinzip arbeitet auch die Positionierung des RjW-Kopfes bei den Kopierprogrammen. Für diese ist es nicht erkennbar, daß unterhalb des gefundenen Track I sich noch I, 2 oder mehr Tracks befinden. Es gibt keine Probleme beim Arbeiten mit der Diskette, auch wenn der Kopf einmal einen 'Bump' (Anschlag des Kopfes) machen sollte, denn er fährt zu der vor dem Anschlag errechneten Spur, erkennt, daß er sich noch um einige Spuren zu tief befindet, und fährt den Kopf an die richtige Position. Wie schon anfangs gesagt, ist es sehr einfach, eine Diskette derart zu formatieren. Man muß nur vorher den Kopf um die eingestellte Anzahl von Spuren nach innen verschieben. Von dieser Verschiebung merkt das DOS nichts und "denkt", es würde den Kopf auf Spur 1 fahren. Stattdessen beginnt es jedoch die Formatierung einige Spuren höher.
Diskettenkopierschutz
251
Nach der ausführlichen Erklärung des Prinzips wollen wir zur Praxis übergehen und ein solches Programm ausprobieren. Das folgende Maschinenspracheprogramm, das wie immer in der Floppy gestartet wird, erfüllt diesen Zweck. Es wird ab $0450 mit einen 'M-E'-Befehl gestartet
0400 0403 0405 0408 040A 040C 040F 0411 0413 0415 0417
JMP lOA STA lOA STA JSR OEC BNE lOA STA JMP
$0403 #$17 $0401 #$04 $37 $041A $37 $040C #$01 $51 $FAC7
für ersten Aufruf belanglos lOW·Byte für die Änderung des 'JMP' lOW-Byte des 'JMP' auf $0417 ändern Anzahl der Halbspuren, die verschoben werden, zwischenspeichern Kopf um eine Halbspur verschieben Zähler verringern verzweige, wenn nicht genug verschoben Nummer des Tracks, ab dem formatiert werden soll, übergeben zur Formatroutine springen
Kopf - Verschieberoutine 041A 0410 041E 041F 0421 0423 0426 0428 042A 0420 042F 0431 0432 0434 0435 0437
lOX INX TXA ANO STA lOA ANO ORA STA lOX lOY OEX BNE OEY BNE RTS
$1COO
#$03 $48 $1COO #SFC $4B $1COO #$00 #$05 $0431 $0431
Control-Port laden erhöhen für Kopfbewegung nach innen Wert in Akku schieben erste zwei Bits isoliern und zwischenspeichern Control-Port laden Bit 0 und 1 löschen mit den errechneten Bits verknüpfen und Speichern Zähler für Warteschleife setzen, um dem Kopf genügend Zeit zu lassen positioniert zu werden verzweige, wenn lOW-Byte nicht abgelaufen HIGH-Byte verringern springe, wenn HIGH-Byte nicht abgelaufen Rücksprung
252
Das große Anti-Cracker-Buch
Das Programm wird hier gestartet 0438 043A 043C 043E 0440 0442 0444 0446 0448 044A 0440 0450 0453 0454 0456 0459
LOA STA LOA STA LOA BMI CMP BCS LOX STX LOA STA OEX BPL JMP RTS
#$12 S08 #$EO S01 S01 S0440 #$02 S0459 #$10 S0274 S0480,X S0200,X S0440 SEE40
Track 18 für Job übergeben Job-Code 'EO' laden und in Job-Puffer schreiben Rückmeldung holen verzweige, wenn noch nicht fertig Rückmeldung auf Fehler prüfen verzweige, wenn Fehler erkannt Oisknamen für BAM 16 Buchstaben erlauben Anzahl übergeben Namen von S0480 in den Befehls-Puffer schreiben Zähler verringern verzweige, wenn nicht alle Buchstaben BAM schreiben, Rücksprung Rücksprung bei Fehler
Im Anschluß an dieses Programm folgt ein BASIC-Programm, das uns die Eingabe der Anzahl der Tracks ermöglicht, die bei der Formatierung doppelt anglegt werden_ Um diese Spezialformatierung nicht zu auffällig zu machen, sollte man nicht mehr als zwei Tracks höher mit der Formatierung beginnen_ Damit dieses Programm richtig arbeitet, müssen die zu behandelnden Disketten schon fehlerfrei formatiert gewesen sein. Die neue Diskette wird mit der gleichen ID formatiert, mit der sie zuvor formatiert wurde. Doch nun zu dem angekündigten BASIC-Programm, das das Maschinensprachprogramm als erstes zur Floppy schickt, um es nach der Eingabe der Parameter dort zu starten. 100PEN1,8,15,"I" 20 REAO X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHRS(4)CHRS(1)CHRS(X) 40 N=N+1:GOTO 20 100 IF SU <> 9284 THENPRINT"FEHLER IN OATAS":STOP
Diskettenkopierschutz
253
102 INPUT "WIEVIELE TRACKS VERSCHIEBEN <8 ";AN 104 INPUT "DISKNAME";NS:NS=NS+"," 106 FORN=OTO LEN(NS)-1 108 PRINT#1,IM-W"CHRS(128+N)CHRS(4)CHRS(1)MIDS(NS,N+1,1): NEXT 120 PRINT#1,"M-W"CHRS(9)CHRS(4)CHRS(1)CHRS(AN*2) 130 PRINT#1,IM-E"CHRS(56)CHRS(4) 140 PRINT#1,IM-R"CHRS(1)CHRS(0)CHRS(1) 150 GET#1,AS:A=ASC (AS+CHRS(O» 160 IF A>127 THEN140 170 IFA =1 THENPRINT"FORMATING OK":END 180 PRINT"FORMAT ERROR":END 402 DATA 76, 3, 4,169, 23,141, 1, 4,169, 4,133, 55, 32, 26, 4,198, 55,208 404 DATA249,169, 1,133,81, 76,199,250,174, 0, 28,232,138, 41, 3,133, 75,173 406 DATA 0, 28, 41,252, 5,75,141, 0,28,162, 0,160, 5,202,208,253, 136,208 408 DATA250, 96,169, 18,133, 8,169,224,133, 1,165, 1, 48,252,201, 2, 176, 17 410 DATA162, 16,142,116, 2,189,128, 4,157, 0, 2,202, 16,247, 76, 64, 238, 96 412 DATA -1
Nachdem wir uns mit dem Auftragen des Kopierschutzes beschäftigt haben, wollen wir uns um die Abfrage desselben kümmern. Natürlich kann man nicht einfach mittels eines 'U 1'Befehls der Floppy mitteilen, daß man gedenkt, einen Block auf Track I zu lesen, der unterhalb des vermuteten Track I liegt. Um dieses zu können, ist es wieder notwendig, mit einem Maschinensprachprogramm in der Floppy zu arbeiten. Dieses Programm fährt den RjW - Kopf auf den "normalen" Track 1, verschiebt mit Hilfe der schon verwendeten Routine den Kopf der Floppy um eine Spur nach außen und sucht erneut die Spur 1. Das erneute Suchen ist wichtig, da das DOS "denkt", es würde sich noch auf Track 1 befinden. Würde man einen anderen Track suchen lassen, würde der Kopf auf diese Spur fahren, die jedoch nicht unserer doppelten entspricht. Sucht man jedoch die Spur 1, auf der sich der Kopf vermeintlich befindet, so stellt das DOS fest, sofern mehr als eine Spur höher formatiert wurde,
254
Das große Anti-Cracker-Buch
daß der Kopf sich auf einer höheren Spur befindet, und fährt den Kopf noch weiter nach unten, bis er die doppelte Spur 1 findet und sie als die richtige erkennt. Nach dieser Prozedur kann man ohne Probleme die Blöcke auf diesen doppelten Spuren mit dem 'UI'-Befehl lesen oder schreiben. Das folgende Maschinensprachprogramm leistet das gerade besprochene. Es wird ab $0627 in der Floppy gestartet. 0600 JSR $0609 0603 JSR $0609 0606 JMP $FD9E
R/W-Kopf un eine Spur verschieben (2 mal eine Halbspur) Rücksprung
0609 060C 0600 060E 0610 0612 0615 0617 0619 061C 061E 0620 0621 0623 0624 0626
Control-Register laden verringern, un Kopf nach außen zu fahren Wert in Akku schieben und die ersten zwei Bit isolieren Wert zwischenspeichern Control-Register laden Bit 0 und 1 löschen mit gespeichertem Wert verknüpfen und Bewegung ausführen Werte für die Zeitschleife laden Zähler verringern verzweige, wenn nicht abgelaufen HIGH-Byte des Zählers verringern verzweige, wenn nicht abgelaufen Rücksprung
LDX DEX TXA AND STA LDA AND ORA STA LDX LDY DEX BNE DEY BNE RTS
$1COO
#$03 $4B $1COO #$FC $4B $1COO #$00 #$05 $0620 $0620
Das Programm wird hier gestartet 0627 062A 062C 062E 0630 0632 0634
JSR LDA STA LDA STA LDA BMI
$0042 #$01 $OC #$EO S03 S03 S0632
Disk initialisieren Track auf den der Kopf gefahren wird übergeben Job-Code 'EO' laden und in Job-Speicher schreiben Rückmeldung erwarten verzweige, wenn noch keine Rückmeldung
Diskettenkopierschutz 0636 0638 063A 063C 063E 0640 0642 0644 0646
LDA STA LDA STA LDA STA LDA BMI RTS
#$01 SOA #$00 SOB #$80 S02 S02 S0642
255
Nummer für doppelten Track 1 laden und übergeben Sektor 0 laden und übergeben Job-Code '80' (Block lesen) laden und übergeben auf Rückmeldung warten verzweige, wenn keine Rückmeldung Rücksprung
Sie können anhand des Programms das Prinzip noch einmal nachvollziehen. Das Programm lädt Block 1 des doppelten (unteren) Tracks automatisch in Puffer 2 ($0500). Hier nun der dazugehörige BASIC-Loader.
100PEN1,8,15,11" 20 READ X:IF X=-1THEN 100 30 sU=SU+X:PRINT#1,IM-IJ"CHRS(N)CHRS(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IF SU<>7037THENPRINT"FEHLER IN DATAS":STOP 130 PRINT#1,IM-E"CHRS(39)CHRS(6) 302 DATA 32, 9, 6,32, 9, 6,76,158,253,174, 0,28,202,138,41, 3, 133, 75 304DATA173, 0,28,41,252, 5,75,141, 0,28,162, 0,160, 5,202,208, 253,136 306 DATA208,250, 96, 32, 66,208,169, 1,133, 12,169,224,133, 3,165, 3, 48,252 308DATA169, 1,133, 10,169, 0,133, 11,169,128,133, 2,165, 2,48,252, 96, -1
Wie schon erwähnt, können Sie nach dem Aufruf des Programms mit den normalen Block-Lese- und -Schreibbefehlen auf die Blöcke der unteren Tracks zugreifen. Wollen Sie nach Beendigung der Kopierschutzabfrage wieder auf die "richtigen" Tracks zugreifen, reicht es aus, die Diskette zu initialisieren oder einen nicht doppelt vorhandenen Track zu lesen.
256
6.2.4
Das große Anti-Cracker-Buch
Änderung der Headerparameter: Read-Errors
Ein weiteres sehr sicheres Kopierschutzsystem läßt sich auch mit einer anderen Änderung der Formatroutine erreichen. In diesem Fall handelt es sich um die Änderung der Blockheader-Parameter. Wenn Sie schon etwas über die Änderung der Reader-Parameter gelesen oder es schon selbst gemacht haben, werden Sie wahrscheinlich sehr skeptisch sein, ob man mit einem solchen Eingriff einen wirklich guten Kopierschutz erstellen kann. Aber lassen Sie sich überraschen. Die einfache Blockheader-Manipulation dient nur dem Zweck, Daten für den Knacker schwerer zugänglich zu machen, was auch nicht zu verachten ist. Den Kopierprogrammen heutigen Standards ist es jedoch ein leichtes, diese Blöcke zu kopieren. Bevor wir besprechen, wie man diese veränderten Parameter auf Diskette aufträgt, wollen wir Sie jedoch erst noch weiter in die Arbeitsweise der Formatroutine einführen um die nachfolgenden Programme auch verstehen zu können. Wir hatten schon gesagt, daß die Formatroutiene bei $F AC7 im Speicher der Floppy beginnt und auch dort aufgerufen wird. Nachdem der R/W-Kopf auf dem angegebenen Track positioniert wurde, wird die Spur genau vermessen, um fe~tzustellen, wie groß die Lücke zwischen den einzelnen Sektoren sein soll. Diese Zahl darf nicht kleiner als 4 sein. Nach der Errechnung wird die Länge der Lücke in $0626 zwischen gespeichert. Nach dieser Speicherung beginnt der zweite Teil der Formatroutine ab $FC36. Rier werden jetzt die Blockheader für die zu formatierenden Sektoren vorbereitet. Zu diesem Zweck werden die Parameter aus der Zero page der Floppy geholt. Der Blockheader-Code $08 steht in $39, die Track-Nummer wird aus der Adresse $51 geholt. Die ID steht in $12, $13, und die zwei Lückenwerte sind $OF. Sie werden direkt geladen und sind, wie auch die Track-Nummer und die Sektornummer, nicht veränderbar. Daraufhin wird die Prüfsumme für den Blockheader berechnet.
DiskettenkoDierschutz
257
Diese 8 Daten werden ab $0300 hintereinander liegend für jeden Reader abgelegt und gemeinsam in das GCR-Format umgerechnet. Die Anzahl der Sektoren wird beim Erstellen der Blockheader in $0628 gespeichert. Vor der Umrechnung steht im YRegister die Anzahl der Bytes, die zu den Blockheadern gehören. Nach dieser Arbeit beginnt erst die eigentliche Formatierung, was bedeutet, daß die Blöcke mit ihren Blockheadern und alle SYNC-Markierungen auf Diskette geschrieben werden. Nach der Formatierung des Tracks wird die sich in $51 befindende TrackNummer überprüft. Sollte sie höher oder gleich $24 (56) sein, wird die Formatierung beendet. Soweit die nähere Erläuterung der Formatroutine, die jedoch zum Verständnis des folgenden Programms nötig ist. Um die Reader-Parameter zu ändern, wollen wir diese nicht von der Formatroutine erstellen lassen, sondern sie selber im Computer erstellen und dann an die richtige Stelle in der Floppy schreiben. Natürlich dürfen wir dann die Formatroutine nicht von Anfang an starten, sondern erst dort einspringen, wo die Routine die Reader-Parameter in das GCR-Format wandelt. Um die Formatroutine dort starten zu können, müssen auch die richtigen Parameter an die Routine übergeben werden, die sonst bis zu diesem Punkt von der Formatroutine selbst errechnet worden wären, wie beispielsweise die Länge der Lücke zwischen den Sektoren. Alle diese Aufgaben erledigt für Sie das folgende etwas längere Programm. Mit ihm ist es auf komfortable Weise möglich sämtliche Blockheader-Parameter zu ändern und somit einzelne Sektoren vor dem Zugriff anderer zu schützen oder einen guten Kopierschutz aufzutragen. 10 20 30 40 50 60
A=49152 READ X:IF X=·1 THEN40 POKEA,X:A=A+1:SU=SU+X:GOT020 IF SU <> 17072 THENPRINT"FEHLER IN DATAS":STOP PO=29:PRINTCHRS(147):PRINT:PRINT:PRINT PRINT" SPEZIAL FORMATIERUNG EINES TRACKS"
258
Das große Anti-Cracker-Buch
GEBEN SIE DEN TRACK AN";T 70 PRINT:PRINT:INPUT " 80 SE=21:L=9:SP=3 90 IF T>17 THENSE=19:L=9:SP=2 100 IF T>24 THENSE=18:L=10:SP=1 110 IF T>30 THENSE=17:L=11:SP=0 120 IF T<1 OR T>41 THEN PRINTCHR$(145)CHR$(145)CHR$(145);: GOT070 BITTE LEGEN SIE DIE DISKETTE EIN" 130 PRINT:PRINT" 140 GET A$:IF A$=""THEN140 150 OPEN1,8,15,"I" 160 PRINT#1,"M-R"CHR$(18)CHR$(0)CHR$(2) 170 GET#1,11$,12$ ID 1 DER DISKETTE IST ";ASC(l1$+CHR$(0» 180 PRINT:PRINT" ID 2 DER DISKETTE IST ";ASC(l2$+CHR$(0» 190 PRINT:PRINT" NEUE ID 1 FUER DEN TRACK "; ASC (11 $+CHR$( 0»; : GOSUB 200 PRINT:PRINT" 840 210 POKE249,EI NEUE ID 2 FUER DEN TRACK ";ASC(12$+CHR$(0»;:GOSUB8 220 PRINT:PRINT" 40 230 POKE250,EI 240 POKE248,T:POKE 49285,T 250 POKE251,SE:POKE 49257,SE 260 POKE 49264,L 270 POKE49249,SE*8 280 SYS49216 HEADER-MANIPULATION (J/N)?" 290 PRINT:PRINT" 300 GEl A$:IFA$=""THEN300 310 IF A$="N"THEN630 320 PRINTCHR$(147); 330 PRINTCHR$(19)CHR$(17)CHR$(17);" WELCHER HEADER 0 -";SE-1;:INPUTH 340 IF H SE-1 THEN 330 350 X=49664+H*8 360 PO=20:PRINT:PRINT" HEADER-POSITION ";H 370 PRINT:PRINT:PRINT" HEADER -KENNZEICHEN " ;PEEK(X);:GOSUB 840:POKEX,EI U;PEEK(X+5);:GOSUB 840:POKEX+5,EI 380 PRINT" ERSTE ID U;PEEK(X+4);:GOSUB 840:POKEX+4,EI 390 PRINT" ZWEITE ID U;PEEK(X+3);:GOSUB840:POKEX +3,EI 400 PRINT" TRACK-NUMMER U;PEEK(X+2);:GOSUB840:POKEX +2,EI 410 PRINTU SEKTOR-NUMMER U;PEEK(X+6);:GOSUB840:POKEX +6,EI 420 PRINTU LUECKE 1 U;PEEK(X+7); :GOSUB840:POKEX +7, EI 430 PRINTU LUECKE 2
Diskettenkopierschutz
259
440 PR=PEEK(X+1) 450 POKE49152+83,H:SYS 49152+82 460 PRINT" ECHTE PRUEFSUMME ";PEEK(X+1) 470 PRINT" EIGENE PRUEFSUMME ";PR;:GOSUB840:POKEX+1,EI 480 PRINTCHR$(17)CHR$(17);" ";CHR$(18);"G";CHR$(146);"LEICHER HEADER" 490 PRINT" ";CHR$(18);"A";CHR$(146);"NDRER HEADER" 500 PRINT" ";CHR$(18);"T";CHR$(146);"AUSCHE HEADER" 510 PRINT" ";CHR$(18);"K";CHR$(146);"OPIERE HEADER" 520 PRINT" ";CHR$(18);"E";CHR$(146);"NDE, FORMATIEREN" 530 GET A$:IF A$=""THEN530 540 IF A$="A" THEN GOT0320 550 IF A$="G" THEN PRINTCHR$(147);:GOT0360 560 IF A$="E"THEN630 570 IF A$="T"THEN1020 580 IF A$="K"THEN880 590 GOT0530 600 IF H>SETHENH=O 610 IF H3 THEN640 660 POKE49277,EI*32:PRINTCHR$(147); 670 FORN=OTO SE*8-1:PRINTCHR$(19);" ";CHR$19);SE*8-1-N 680 PRINT#1,"M-W"CHR$(N)CHR$(3)CHR$(1)CHR$(PEEK(49664+N» :NEXT 690 FORN=OTO 48:PRINTCHR$(19);" ";CHR$19);48-N 700 PRINT#1,"M-W"CHR$(N)CHR$(4)CHR$(1)CHR$(PEEK(49248+N» :NEXT 710 PRINT#1,"M-E"CHR$(36)CHR$(4) 720 PRINT#1,"M-R"CHR$(1)CHR$(0)CHR$(1) 730 GET#1,A$:IF ASC (A$+CHR$(0»>128THEN720 740 IF ASC (A$+CHR$(O» >1THENPRINT"FORMAT ERROR":GOTOOOO 750 PRINT"FORMAT OK " 760 PRINT:PRINT:PRINT" WEITERE TRACKS " 770 GET A$:IF A$="" THEN770 780 IF A$<>"N" THEN CLOSE1 : GOT050 790 CLOSE1:END 800 PRINT:PRINT:PRINT" WEITERER VERSUCH" 810 GET A$:IF A$="" THEN810 820 IF A$<>"N" THEN PRINTCHR$(147):GOT0670
260
Das große Anti-Cracker-Buch
830 GOT0760 840 POKE211,PO:GOT0850 850 SYS58732:INPUT EI 860 IF EI <0 OR EI>255 THEN840 870 RETURN 880 PRINTCHR$(147)CHR$(17)CHR$(17); 885 PRINT" AUF ";CHR$(18);"E";CHR$(146);"INE OOER "; 887 PRINTCHR$(18);"A";CHR$(146);"LLE POSITIONEN KOPIEREN" 890 GET A$:IF A$=""THEN890 900 IF A$="E" THEN 970 910 IF A$ <>"A"THEN 890 920 FORN=OTO SE-1:Y=49664+N*8 930 FORM=OTO 7 940 POKEY+M,PEEK(X+M):NEXT 950 NEXT 960 GOT0480 970 PRINT:PRINT:PRINT" AUF WELCHE POSITION -";SE-1; 980 PO=30:GOSUB840:IF EI>SE-1 THEN PRINT CHR$(147);:GOT0980 990 Y=49664+EI*8 1000 FORM=OTO 7 1010 POKEY+M,PEEK(X+M):NEXT:GOT0480 1020 PRINTCHR$(147)CHR$(17)CHR$(17)" MIT WELCHER POSITION TAUSCHEN oll; SE-1 1030 PRINT:PRINT:PO=17:GOSUB840 1040 IF EI>SE-1 THENPRINTCHR$(147);:GOT01030 1050 Y=49664+EI*8 1060 FORN=O TO 7 1070 HE=PEEK(Y+N):POKEY+N,PEEK(X+N) 1080 POKEX+N,HE 1090 NEXT:GOT0480 1100 REM 1110 DATA169,8,153,0,194,200,200,165,247,153,0, 194,200, 165,248, 153,0, 194 ,200 1020 DATA165,250,153,0,194,200,165,249,153,0,194,200,169,15,153,0,194,20
°
°
°1130 DATA153,0,194,200,169,0,89,250,193,89,251,193,89,252,193,89,253,193 ,153 1140 DATA249,193,96,0,0,0,0,0,169,0,133,247,160,0,32,0,192,230,247,165,2 47
Diskettenkopierschutz
261
1150 DATA197,251,144,245,96,169,0,10,10,10,24,105,8,168,76,41,192,0,0,16
o 1160 DATA136,162,0,169,35,133,81,169,17,133,67,141,40,6,169,11,141,38,6, 141 1170 DATA32,6,173,0,28,41,159,9,0,141,0,28,76,132,252,169,35,133,8,169,2 24 1180 DATA133,1,165,1,48,252,96,-1
Anleitung zum Programm: Wenn Sie das Programm starten, müssen Sie ein wenig Geduld aufbringen, denn die Daten in den DA TA-Zeilen müssen erst in den Speicher gepoket werden. Daraufhin wird die eingelegte Diskette initialisiert und die entsprechende ID angegeben. Es besteht die Möglichkeit, die ID zu ändern. Wenn Sie keine Blockheader-Manipulation durchführen wollen, somit die entsprechende Frage verneinen, wird der angegebene Track fehlerlos formatiert. Zuvor müssen Sie jedoch noch den Speed des Tracks angeben. Soll er nicht vom normalen abweichen, brauchen Sie nur RETURN zu drücken. Änderung der Reader-Parameter: Als erstes werden Sie nach der Position des zu ändernden Readers gefragt. Die einzige Angabe, die einer Erklärung bedarf, ist die Angabe der Prüfsumme. Die unter 'echte Prüfsumme' ausgegebene Zahl ist die aus den zuvor gemachten Angaben errechnete Prüfsumme. Unter 'eigene Prüfsumme' wird diejenige angezeigt, die man selbst gewählt hat. Im Anschluß an diese Eingaben erscheint ein Menü, bei dem Sie unter folgenden Punkten auswählen können: 1.
Gleicher Reader erlaubt eine erneute Modifikation desselben Readers.
2.
Anderer Reader ermöglicht die Änderung eines weiteren Readers.
262
Das große Anti-Cracker-Buch
3.
Tausche Header ermöglicht die Vertauschung von zwei Headern und somit eine Änderung der Reihenfolge der Sektoren auf einem Track, was auch einen recht guten Kopierschutz abgibt.
4.
Kopiere Header erlaubt, einen Header zu duplizieren und dadurch zwei gleiche Sektoren auf einem Track zu erzeugen, was nicht von allen Kopierprogrammen korrekt kopiert werden kann. Rufen Sie diesen Menüpunkt auf, werden Sie gefragt, ob Sie Ihren zuvor erstellten Header auf einen weiteren oder alle anderen Positionen kopieren wollen. Kopieren Sie Ihren Header auf alle anderen Positionen, erhalten Sie einen Track, bei dem alle Sektoren gleich sind. Ein so veränderter Track kann nur sehr schwer kopiert werden. Doch darauf werden wir noch zu einem späteren Zeitpunkt eingehen.
5.
Ende, formatieren Formatiert den angegebenen Track. Eine Änderung der Speed-Flags ist möglich.
Das Programm besteht aus drei Teilen. Der eine ist der BASICTeil, über den alle Eingaben sowie die Vertauschung der Header vorgenommen werden. Der zweite ist ein Maschinenspracheteil, der die Blockheader, die später auf Disk geschrieben werden, im Computer erstellt. Er entspricht in etwa der Routine, die sich auch in der Formatroutine der Floppy befindet. Der dritte Teil ist ein Maschinenspracheprogramm, das in der Floppy gestartet wird. Es übergibt die Parameter, wie beispielsweise die Länge der Lücke zwischen den Sektoren, an die Formatroutine. Wie schon gesagt, werden die Blockheader erst im Computer erzeugt, um nach ihrer Änderung in die Floppy ab Adresse $0300 geschickt zu werden, wo sie in das / GCR-Format gewandelt und auf Diskette aufgetragen werden.
Diskettenkopierschutz
263
Im folgenden zeigen wir Ihnen die zwei erwähnten Maschinenspracheteile. Als erstes den Teil, der im Computer abläuft und die Blockheader im Speicher des Rechners erzeugt. Das Programm, das die Daten für die Blockheader erzeugt, wird ab $C040 (49216) gestartet und legt sie ab $C200 (49664) im Speicher ab.
COOO C002 COOS C006 C007 C009 COOC COOD COOF C012 C013 C01S C018 C019 C01B C01E C01F C021 C024 C02S C028 C029 C02B C02E C031 C034 C037 C03A
lOA STA INY INY LOA STA INY LOA STA INY LOA STA INY LOA STA INY lOA STA INY STA INY lOA EOR EOR EOR EOR STA RTS
1$08 S08 laden (Zeichen für Blockheader) SC200,Y und übergeben Zeiger erhöhen erhöhen, um Lücke für Prüfsumme zu lassen SF7 Sektornummer holen SC200,Y und speichern Zeiger erhöhen SF8 Track'Nummer holen SC200,Y und speichern Zeiger erhöhen SFA zweite 10 holen SC200,Y und übergeben Zeiger erhöhen SF9 erste 10 holen SC200,Y und übergeben Zeiger erhöhen tsOF SOF (Lückenbyte) holen $C200,Y und speichern Zeiger erhöhen SC200,Y und Lückenbyte erneut speichern Zeiger erhöhen #SOO Akku mit $00 laden (normalisieren) $C1FA,Y SC1FB,Y Prüfsumme über den SC1FC,Y Blockheader berechnen SC1FO,Y SC1F9,Y und Prüfsumme abspeichern Rücksprung
264
Das große Anti-Cracker-Buch
Für die Erstellung der Blockheader wird des Programm hier gestartet. C040 C042 C044 C046 C049 C04B C04D C04F C051
LDA STA LDY JSR INC LDA CMP BCC RTS
#$00 SF7 #$00 SCOOO SF7 SF7 SFB SC046
Sektornummer auf 0 stellen Zeiger auf 0 setzen und Blockheader erstellen Sektornummer erhöhen Nummer laden und mit maximaler Zahl vergleichen. verzweige, wenn Maximum nicht erreicht Rücksprung
Für die Errechnung der Prüfsumme eines einzelnen Blockheaders wird die Routine hier gestartet. C052 C054 C055 C056 C057 C058 C05A C05B
LDA #$00 ASL ASL ASL CLC ADC #S08 TAY JMP SC029
Position des Headers laden (variabel) Mit 8 multiplizieren Carry für Addition löschen und mit 8 addieren, als Zählwert übergeben Prüfsumme errechnen
Die folgende Routine wird in der Floppy ab $0400 gestartet, nachdem die Blockheader ebenfalls in die Floppy gesandt wurden. Alle nötigen Parameter werden mit der zu startenden Routine mitgesandt. Sie wird ab $0424 gestartet.
0400 0402 0404 0406 0408 040A 040C
LDY LDX LDA STA LDA STA STA
#S88 #$00 #$23 S51 #$11 S43 S0628
LOW·Byte der Endadresse der Blockheader X·Register löschen Zeiger auf Ende der Formatierung setzen damit nur ein Track formatiert wird Maximale Sektoranzahl laden (variabel) und übergeben
Diskettenkopierschutz 040F 0411 0414 0417 041A 041C 041E 0421
lDA STA STA lDA AND ORA STA JMP
#$OB S0626 S0620 S1COO #S9F #SOO S1COO $FC84
265
lücke zwischen den Sektoren laden und übergeben gleichzeitig als Zähler für Fehler nehmen Control-Port laden Speed-Flags löschen Hit eigenem Speed verknüpfen (hier 00) und spei chern Formatierung beginnen
Das Programm wird hier gestartet 0424 0426 0428 042A 042C 042E 0430
lDA STA lDA STA lDA BMI RTS
#$23 S08 #SEO S01 S01 S042C
Nummer des zu formatierenden Tracks übergeben Job-Code 'EO' für Programm starten in Job-Speicher schreiben Code für Rückmeldung empfangen? verzweige, wenn nicht empfangen Rücksprung
Nachdem wir nun das Programm erklärt haben, wollen wir uns nun mit seiner Anwendung beschäftigen.
6.2.5
Das Arbeiten mit zerstörten Blöcken
Wie wir schon sagten, kann man mit dem obigen Programm die Blockheader-Parameter ändern und dadurch Sektoren gezielt zerstören. Dieses Verfahren dient, bis auf einige Sonderfälle, kaum zum Schutz gegen das Kopieren, sondern nur zur Verhinderung des Zugriffs anderer auf einen so geschützten Block. Jetzt stellt sich jedoch die Frage, wie man überhaupt Daten auf diesen Blöcken speichern oder wieder lesen soll, denn die Floppy fängt jedesmal an zu blinken, wenn man versucht, den Block auf die herkömmliche Weise zu laden. Sie können sich sicher denken, daß es natürlich einen Weg gibt, diese Blöcke zu nutzen. Die einfachste Methode, die sich auch für alle Arten von zerstörten Blockheadern einsetzen läßt, ist
266
Das große Anti-Cracker-Buch
die: Man liest den Blockheader vor dem zerstörten Reader ein. Die darauffolgende SYNC-Markierung ist folglich die des Datenblocks des gelesenen Readers. Wir ' überspringen mit unserem Floppy-Programm einfach diese Markierung. Die nächste SYNC-Markierung, die gefunden wird, ist dann die unseres zerstörten Blockheaders. Diese Markierung wird ebenfalls nicht beachtet. Die daraufhin gefundene SYNC-Markierung muß dann die des Datenblocks sein, der zu dem zerstörten Reader gehört. Nachdem diese Markierung gefunden wurde, wird der Datenblock gelesen. Sie sehen also, daß es uns mit diesem Trick möglich ist, Daten von einem auch noch so zerstörten Block zu lesen, was uns sonst nicht möglich gewesen wäre. Nach dem gleichen System können wir auch Daten speichern. Sollten Sie mehrere Blockheader hintereinander zerstört haben, so brauchen Sie nur entsprechend viele SYNC- Markierungen zu überlesen. Mit dem folgenden Floppy-Programm ist es möglich, eine beliebige Anzahl von Sektoren zu überspringen und dann den gewünschten Block zu lesen. Das Programm wird ab $0522 gestartet. 0500 0503 0505 0508 0509 0508 050D 050F
JSR LDX JSR DEX BNE LDA STA JMP
$F50A #$01 $0512 $0505 #$06 $31 $F4D4
Blockheader suchen, auf Datenblock warten Zähler für Sektor überspringen (variabel) Routine zum überspringen eines Sektors Zähler verringern verzweige, wenn nicht abgelaufen HIGH·Byte für Pufferzeiger auf S06 stellen damit die Daten ab S0600 geladen werden in Routine für Block laden springen
0512 0515 0517 051A 051D 051F
LDA BPL JSR LDA BPL JMP
$1COO $0512 $F556 $1COO S051A SF556
Control·Port laden verzweige, wenn SYNC noch anliegt auf neue SYNC-Markierung warten Control-Port laden verzweige, wenn SYNC noch anliegt auf neue SYNC-Markierung warten, RTS
D;skeuenkopierschutz
267
Das Programm wird hier gestartet 0522 0524 0526 0528 052A 052C 052E 0530 0532
LDA STA LDA STA LDA STA LDA BMI RTS
#S01 SOA #$00 SOB #SEO S02 S02 S052E
Track auf dem geladen wird (variabel) übergeben Sektor, der geladen wird (variabel) übergeben Job-Code für Programm ausführen, laden und übergeben Rückmeldung abwarten verzweige, wenn keine Rückmeldung Rücksprung
Im Anschluß daran das gewohnte BASIC-Listing. Es erlaubt, die Eingabe des zu lesenden Tracks, des Sektor Sektors sowie die Eingabe der zu überspringenden Sektoren. Die Nummer des tatsächlich gelesenen Sektors, ergibt sich aus der Nummer des eingegebenen Sektors, addiert mit der Anzahl der zu überspringenden Sektoren. Die Daten des zu lesenden Blocks werden ab $0600 (Puffer 3) in der Floppy abgelegt.
10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHRS(5)CHRS(1)CHRS(X) 40 N=N+1:GOTO 20 100 IFSU <> 54n THEN PRINT"ERROR IN DATAS":STOP 105 INPUT "WELCHER TRACK"iT 110 INPUT "WELCHER SEKTOR"iS 115 INPUT "ZU UEBERLESENE SEKTOREN"iU 120 PRINT#1,"M-W"CHRS(35)CHRS(5)CHR$(1)CHRS(T) 125 PRINT#1,"M-W"CHR$(39)CHRS(5)CHRS(1)CHR$(S) 130 PRINT#1,"M-W"CHRS(4)CHRS(5)CHRS(1)CHRS(U) 135 PRINT#1,"M-E"CHRS(34)CHRS(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,"M-R"CHRS(2)CHRS(0)CHRS(1) 150 GET#1,AS:A=ASC (AS+CHRS(O» 160 IF A>127 THEN130 170 IFA =1 THENPRINT"QK":ENO
268
Das große Anti-Cracker-Buch
180 PRINT"ERROR":END 302 DATA 32, 10,245,162, 1,32, 18, 5,202,208,250,169, 6,133,49,76, 212,244 304DATA173, 0,28, 16,251,32,86,245,173, 0,28, 16,251,76,86,245, 169, 1 306DATA133, 10,169, 0,133, 11,169,224,133, 2,165, 2, 48,252, 96, -1
Wie das Lesen eines Blocks, so erfolgt auch das Schreiben. Das hierzu benötigte Programm sieht jedoch etwas anders aus, da die SAVE-Routine der Floppy später angesprungen werden muß. Die von ihr abgearbeiteten Programmteile müssen zum Teil in das eigene Programm übernommen werden. Hier nun die entsprechende SA VE-Routine, die ab $0400 in der Floppy gestartet wird.
0500 0502 0504 0507 0509 050C 050E 0510 0512 0515 0518 051B 0510 0520 0521 0523
LDA STA JSR STA LDA AND BNE LDA JMP JSR JSR LDX JSR DEX BNE JMP
#$06 S31 SF5E9 S3A S1COO #$10 S0515 #$08 SF969 SF78F SF510 #$01 S0526 S051D SF58C
0526 LDA S1COO 0529 BPL S0526 052B JSR SF556
HIGH-Byte des Puffers auf S06 stellen, um die Daten von $0600 zu speichern Prüfsumme über Datenblock berechnen und abspeichern Control-Port laden und auf Schreibschutz prüfen verzweige, wenn kein Schreibschutz Fehlermeldung im Akku und zur Ausgabe springen Pufferinhalt ins GCR-Format wandeln Blockheader suchen Zähler für Sektor überspringen (variabel) Routine zum überspringen eines Sektors Zähler verringern verzweige, wenn nicht abgelaufen Block auf Diskette schreiben Control-Port laden verzweige, wenn SYNC-Signal noch anliegt auf nächste SYNC-Markierung warten
Diskettenkopierschutz 052E LDA S1COO 0531 BPL S052E 0533 JMP SF556
269
Control-Port lesen verzweige, wenn SYNC-Signal noch anliegt auf nächste SYNC-Markierung warten, RTS
Das Programm wird hier gestartet 0536 0538 053A 053C 053E 0540 0542 0544 0546
LDA STA LDA STA LDA STA LDA BMI RTS
#$01 SOA #SOO SOB #SEO S02 S02 S0542
Track, auf dem geladen wird (variabel) übergeben Sektor, der geladen wird (variabel) übergeben Job-Code für Programm ausführen, laden und übergeben Rückmeldung abwarten verzweige, wenn keine Rückmeldung Rücksprung
Jetzt folgt wieder der BASIC-Loader, bei dem Sie Track und Sektor sowie die Anzahl der zu überspringenden Blöcke eingeben können_
100PEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHR$(5)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 7633 THEN PRINT"ERROR IN DATAS":STOP 105 INPUT "WELCHER TRACK";T 110 INPUT "WELCHER SEKTOR";S 115 INPUT "ZU UEBERLESENE SEKTOREN";U 120 PRINT#1,"M-W"CHRS(55)CHR$(5)CHR$(1)CHR$(T) 125 PRINT#1,"M-W"CHR$(59)CHRS(5)CHR$(1)CHR$(S) 130 PRINT#1,"M-W"CHRS(28)CHR$(5)CHRS(1)CHR$(U) 135 PRINT#1,"M-E"CHRS(54)CHR$(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,"M-R"CHR$(2)CHR$(0)CHR$(1) 150 GET#1,AS:A=ASC (A$+CHR$(O» 160 IF A>127 THEN130
270
Das große Anti-Cracker-Buch
170 IFA =1 THENPRINT"OK":END 180 PRINT"ERROR":END 302DATA169, 6,133,49,32,233,245,133,58,173, 0,28,41, 16,208, 5, 169, 8 304 DATA 76,105,249,32,143,247,32, 16,245,162, 1,32,38, 5,202,208, 250, 76 306DATA140,245,173, 0, 28, 16,251, 32, 86,245,173, 0, 28, 16,251, 76, 86,245 308DATA169, 1,133, 10,169, 0,133, 11,169,224,133, 2,165, 2,48,252, 96, -1
Nachdem wir gesehen haben, wie wir uns mit Hilfe von Blockheader-Manipulationen vor dem Zugriff Fremder auf unsere Blöcke schützen können, wollen wir uns einmal ansehen, wie man mit dieser Methode einen sehr guten Kopierschutz aufbauen kann. Bei der Änderung von mehreren Blockheadern auf dem gleichen Track kann es zu Störungen beim Einlesen eines Blocks kommen. 6.2.6
Gleiche Blöcke auf einem Track
Eine sehr sichere Kopierschutzmethode ist es, einen Track nur mit gleichen Blockheader Blockheadern zu beschreiben. Einen solchen Track kann man mit dem schon beschriebenen Formatierungsprogramm mühelos erstellen. Wie arbeitet ein solcher Kopierschutz und warum ist er so schwer, fast unmöglich, zu kopieren? Um dies zu verstehen, müssen wir uns etwas näher mit dem System der Kopierprogramme beschäftigen. Das Kopierprogramm lädt einen oder mehrere Blöcke zugleich in den Speicher der Floppy und schickt daraufhin die Daten zum Computer, da der Speicher der Floppy nicht ausreicht, weitere Daten zu speichern. Durch das Senden der Daten an den Computer muß das Kopierprogramm die Stelle wiederfinden, an der es aufgehört hat, Daten zu lesen. Es sucht wieder die Anfangs stelle und überliest eine entsprechende Anzahl von Daten, um daraufhin die neuen Daten einzulesen. Das Programm erkennt
Diskettenkopierschutz
271
einen schon vollständig eingelesenen Track daran, daß es dieselben Daten findet, die schon zu Beginn eingelesen wurden. Wenn Sie sich jetzt daran erinnern, wie unser modifizierter Track aussieht, werden Sie feststellen, daß das Kopierpogramm keine Möglichkeit hat, sich auf dem Track zu orientieren, da alle Blöcke gleich aussehen. Für das Programm ist es somit fast unmöglich, die Anzahl der SYNC.:..Markierungen sowie die Anzahl der Bytes auf diesem Track festzustellen. Auch die Kopierprogramme, die mit einem parallelen Bus arbeiten, welcher die Daten ohne "abzusetzen" einlesen kann, bekommen Probleme, die Anzahl der SYNC-Markierungen richtig festzustellen. Dieser Track ist so wirkungsvoll, weil es für Kopierprogramme sehr schwer ist, ihn richtig zu analysieren. Wir wissen also, womit die Kopierprogramme zu "kämpfen" haben, doch stellt sich hier die Frage, wie wir selbst einen solchen Track auf seine Richtigkeit überprüfen. Diese Überprüfung ist recht aufwendig, da sie in drei Etappen durchgeführt wird. Als erstes wird eine große Anzahl von Bytes . eingelesen und gleichzeitig die Anzahl der SYNC-Markierungen gezählt. Diese Zahl ist, falls der Track korrekt ist, recht konstant. Als nächstes wird überprüft, wie lang die SYNC-Markierungen selber sind, damit das Kopierprogramm nicht, wenn es nicht genügend Daten feststellt, die entstehende Lücke mit längeren SYNC-Markierungen vertuschen kann. Als drittes und letztes muß noch geprüft werden, ob die Blockheader des entsprechenden Tracks auch wirklich alle gleich sind. Sollten alle Prüfungen positiv ausfallen, so liegt ein "Original" vor, was durch eine entsprechende Rückmeldung von der Kopierschutzabfrage signalisiert wird. Ein Programm, das all diese Prüfdurchgänge enthält, werden Sie jetzt anschließend sehen. Es liegt (in der Floppy) ab Adresse $0400 im Speicher und wird ab $04DF gestartet. Die entsprechende Rückmeldung an den Rechner wird in $10 der Floppy gespeichert. 0 steht für "Kopierschutz nicht richtig erkannt" und $FF für "alles OK".
Das große Anti-Cracker-Buch
272 0400 0402 0404 0407 0408
LDA LDX CMP DEX BCS
D40A TXA 040B ASL 040C ASL 0400 ASL 040E ASL 040F ASL 0410 STA 0412 LDA 0415 ANO 0417 ORA 0419 STA 041C LOX 041E LDY 0420 LOA 0422 STA 0424 LOA 0427 BPL 0429 CLV 042A BVC 042C CLV 0420 DEX 042E BNE 0430 DEY 0431 BEQ 0433 LOA 0436 BMI 0438 INC 043A JMP 0430 LOX 043F STX
S08 Nummer des zu prüfenden Tracks laden #$04 Zähler für die vier Zonenabschnitte S04DA,X Nummer mit Zahl aus Tabelle vergleichen Zähler verringern $0404 verzweige, wenn Nummer größer gleich der Zahl aus der Tabelle entsprechenden Zählerwert in den Akku schieben und dann die Bits an Position 5 und 6 schieben, um sie als Maßzahl für den Speed auf dem entsprechenden Track zu benutzen $44 $1COO #$9F $44 $1COO #$00 #$1F #$00 $37 S1COO $0424 S042A
$0433 $0430 $1COO $0429 $37 $0424 $37 $04FF
Wert zwischenspeichern Control-Port laden Bits für Speed löschen mit eigenem Speed verknüpfen und Wert übergeben LOW und HIGH-Byte der Anzahl der zu lesenden Bytes laden (7936) Zählwert für SYNCs auf Null setzen Control-Port laden verzweige, wenn SYNC noch anliegt Byte-Ready-Leitung löschen warten, bis Byte anliegt Byte-Ready-Leitung wieder löschen Byte-Zähler verringern verzweige, wenn kein übertrag sonst auch HIGH-Byte verringern verzweige, wenn Zähler abgelaufen Control-Port laden verzweige, wenn kein SYNC anliegt sonst SYNC-Zähler erhöhen und auf neue SYNC-Markierung warten Zähler für gefundene SYNC-Markierungen in $04FF speichern
Diskettenkopierschutz
273
Der jetzt folgende Programm teil prüft die Länge der SYNCMarkierungen. Zu diesem Zweck wird der Timer I der Ein! Ausgabe VIAs benutzt.
0442 0444 0447 044A 044C 044F 0451 0453 0456 0459 045B 045E 0460 0461 0463 0465
LDA STA LDA BPL LDA BMI LDA STA LDA BPL LDA BPL DEX BNE LDA STA
#SCO $1804 S1COO S0447 S1COO S044C #S80 S1805 S1COO S0456 $1805 S0465 S0442 #SFF S04FE
LOW·Byte des Zählwerts für SYNC Länge ins LOW·Byte des Timers schreiben Control·Port laden und auf Ende der SYNC·Markierung warten Control-Port laden und auf Anfang der SYNC-Markierung warten HIGH-Byte des Timerwertes laden und übergeben, Timer starten Control-Port laden und auf Ende der SYNC-Markierung warten HIGH·Byte des Timerwertes laden verzweige, wenn Wert kleiner $80 Zähler für Anzahl der SYNCs verringern und nächste Markierung abfragen Rückmeldewert laden und in S04FE speichern
Als nächstes folgt der Teil, in dem 25 aufeinanderfolgende Blockheader geladen und ab $0500 gespeichert werden. Es gibt zwar im Normalfall keine 25 verschiedenen Blockheader auf einem Track, jedoch geht man so sicher, daß alle geladen werden. Dies Teilprogramm kann auch separat verwendet werden, um die Reihenfolge der Sektoren oder ähnliches zu überprüfen.
0468 046A 0460 046F 0471 0472 0475 0477 0479
LDX JSR LDY BVC CLV LDA CMP BNE STA
#$00 SF556 #S09 S046F
Zähler auf Null setzen Auf SYNC-Markierung warten Zähler für Anzahl der Bytes pro Header warten, bis ein Byte anliegt Byte-Ready-Leitung löschen Byte vom Port holen S1C01 mit Wert für Blockheader vergleichen #S52 S046A verzweige, wenn kein Blockheader S0500,X sonst Wert speichern
274
Das große Anti-Cracker-Buch
047C INX 0470 NOP 047E BVC S047E 0480 CLV 0481 LDA S1C01 0484 STA S0500,X 0487 INX 0488 BEQ S048F 048A DEY 048B BNE S047E 0480 BEQ S046A 048F LDA #SOS 0491 STA S31 0493 JSR SF8EO 0496 LDX #SFF 0498 LDA S04FF,X 049B STA S0500,X 049E DEX 049F CPX #SFF 04A1 BNE S0498 04A3 LDA S38 04A5 STA S0500
Zähler erhöhen auf neues Byte warten Byte-Ready-Leitung löschen Byte vom Port holen und speichern Zähler erhöhen verzweige, wenn alle Header geholt Zähler für Bytes pro Header verringern verzweige, wenn nicht alle Bytes geholt unbedingter Sprung HIGH-Byte des zu bearbeitenden Puffers auf SOS stellen (Puffer 2) Bytes ins Bin_-Format wandeln Zähler setzen, um die Daten umzukopieren Daten holen und ein Byte weiter abspeichern Zähler verringern vergleiche, ob schon alles kopiert verzweige, wenn nicht allen kopiert erstes Byte, das bei der Umwandlung ins Bin-Format gespeichert wurde, zurückschreiben
Der folgende Teil dient zur Überprüfung der gelesenen Blockheader_ 04A8 04AA 04AC 04AF 04B2 04B4 04B5 04B6 04B8 04BA 04BC 04BE
LDY LDX LDA CMP BNE DEX DEY BEQ CPX BNE BEQ LDA
#SC8 #S08 S04FF,X S04FF,Y S04C6
S04BE #SOO S04AC S04AA S04FE
Zeichenanzahl laden Zeichenanzahl pro Header Zeichen holen mit erstem Header vergleichen verzweige , wenn Zeichen ungleich Zähler verringern Zähler verringern verzweige, wenn alle Zeichen verglichen kontrollieren, ob ganzer Header verglichen verzweige, wenn nein unbedingter Sprung Rückmeldung der SYNC-Prüfroutine holen
Diskettenkopierschutz 04C1 04C3 04C5 04C8 04CA 04CC 04CE 0400 0402 0404 0407 0409
CMP BNE lOA CMP BCC CMP BCS lOA STA JMP lOA BEQ
#$FF S0407 S04FF #$2A S0407 #S2E S0407 #$FF S10 SF09E #$00 S0402
275
auf Richtigkeit überprüfen verzweige, wenn Fehler Anzahl der gefundenen Sektoren holen mit 42 vergleichen Fehler, wenn der Wert kleiner ist Wert mit 46 vergleichen Fehler, wenn Wert größer oder gleich ist Positive Rückmeldung an Computer übergeben Rückmeldung schreiben Rücksprung negative Rückmeldung laden unbedingter Sprung
040B 2B 1F 19 12 Werte für die vier Zonenabschnitte
Das Programm wird hier gestartet 040F 04E1 04E3 04E5 04E7 04E9 04EB
lOA STA LOA STA LOA BMI RTS
#S01 S08 #$EO S01 S01 S04E7
Track-Nummer laden und an Job übergeben
Job-Code 'EO' Laden (Programm ausführen) in Job-Seicher schreiben Rückmeldung abwarten verzweige, wenn noch keine Rückmeldung Rücksprung
In diese Routine wurde eine eigene Routine eingebaut, die den Speed des jeweiligen Tracks bestimmt, da diese Routine im Betriebssystem der Floppy diese Aufgabe nur bis Track 35 fehlerlos durchführt. Im Anschluß an das Maschinenprogramm folgt wieder unser BASIC-Loader. 10 OPEN1,8,15,"I" 20 REAO X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHRS(4)CHRS(1)CHRS(X) 40 N=N+1:GOTO 20 100 IFSU <> 28329 THEN PRINT"ERROR IN OATAS":STOP
276
Das große Anti-Cracker-Buch
110 INPUT "YELCHER TRACK";T 120 PRINT#1,"M -Y"CHR$(224)CHR$(4)CHR$(1)CHR$(T) 130 PRINT#1,"M-E"CHR$(223)CHR$(4) 135 FORN=1TO 500:NEXT 140 PRINT#1,"M-R"CHR$(16)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 170 IFA =255 THENPRINT"SCHUTZ OK":END 180 PRINT"SCHUTZ ERROR":END 302DATA165, 8,162, 4,221,218, 4,202,176,250,138,10,10,10,10, 10, 133, 68 304DATA173, 0, 28,41,159, 5,68,141, 0,28,162, 0,160,31,169, 0, 133, 55 306DATA173, 0, 28, 16,251,184, 80,254,184,202,208, 3,136,240, 10,173, 0, 28 308 DATA 48,241,230, 55, 76, 36, 4,166, 55,142,255, 4,169,192,141, 4, 24,173 310DATA 0,28, 16,251,173, 0,28,48,251,169,128,141, 5,24,173, 0, 28, 16 312DATA251,173, 5, 24, 16, 5,202,208,223,169,255,141, 254, 4,162, 0 , 32, 86 314DATA245,160, 9,80,254,184,173, 1,28,201,82,208,241,157, 0, 5, 232,234 316 DA TA 80,254,184,173, 1,28,157, 0, 5,232,240, 5,136,208,241,240, 219,169 318 DA TA 5,133,49,32,224,248,162,255,189,255, 4,157, 0, 5,202,224, 255,208 320 DATA245,165, 56,141, 0, 5,160,200,162, 8,189,255, 4,217,255, 4, 208, 18 322DATA202,136,240, 6,224, 0,208,240,240,236,173,254, 4,201,255,208, 18,173 324DATA255, 4,201, 42,144, 11,201, 46,176, 7,169,255,133, 16, 76,158, 253 , 169 326DATA 0,240,247,43,31,25, 18,169, 1,133, 8,169,224,133, 1,165, 1, 48 328 DATA252, 96, -1
Aufbringen können Sie diesen Schutz, indem Sie das Programm zum Ändern der Blockheader-Parameter aus Kapitel 6_2.4 ver-
Diskettenkopierschutz
277
wenden und dort, wie in der Anleitung schon erwähnt, "kopiere Header" anwählen, um daraufhin den entsprechenden Header auf alle anderen Positionen zu kopieren.
6.3
Half tracks
. Was sind überhaupt Half tracks, und wie kann man sie für einen Kopierschutz einsetzen? Diese Fragen sollen in dem folgenden Kapitel erörtert werden. Um das Arbeiten mit Half tracks (Halbspuren) zu verstehen, müssen Sie wissen, daß sich der RjW - Kopf der Floppy nicht nur in der von uns bekannten Schrittweite von einer Spur, sondern in halben Spuren bewegt. Diese halben Spuren sind für die Floppy problemlos zu lesen, das Schreiben hingegen funktioniert nicht so gut. Dieser Umstand ist leicht zu erklären. Das nackte Laufwerk der Diskettenstation 1541 ist eigentlich für 80 Spuren ausgelegt. Commodore nutzt es jedoch nur als 40 Spur-Laufwerk, weshalb man auch einen 40 Spur-Schreibkopf verwendet hat. Den ursprünglichen Lesekopf, der für 80 Spuren gedacht war, hat man jedoch beibehalten. Beim Beschreiben einer Halbspur überschreibt man wegen des breiteren Schreibkopfes daher auch einen Teil der benachbarten Spuren, auf denen man Daten zerstört. Es ist also nicht möglich, drei nebeneinanderliegende Halbspuren mit der 1541 zu schreiben. Diesen Umstand haben sich einige Softwarehäuser zunutze gemacht und haben auf ihren Disketten mit Hilfe eines anderen Laufwerks drei Halbspuren aufgetragen, die mit der 1541 zwar gelesen und somit auf ihre Richtigkeit überprüft, jedoch nicht reproduziert werden können. Sie werden einsehen, daß es sich bei diesem Verfahren um den absolut sicheren Kopierschutz handelt. Wie haben allerdings einen Weg gefunden, wie man solche Halbspuren auch mit der 1541 auftragen und wieder abfragen kann. Man kann mit Gewißheit sagen, daß dieser Kopierschutz zu den sichersten gehört, die mit der 1541 aufgetragen werden können. Möglicherweise ist er sogar der sicherste.
278
Das große Anti-Cracker-Buch
Das Auftragen des Kopierschutzes geschieht folgendermaßen: Zuerst werden die drei benachbarten Halbspuren von allen SYNC-Markierungen befreit. Dann wird auf der ersten Halbspur eine SYNC-Markierung geschrieben, worauf einige wenige Daten folgen. Danach wird der R/W-Kopf schnell auf die benachbarte Halbspur gefahren und ebenfalls eine SYNC-Markierung mit Daten aufgetragen. Derselbe Vorgang vollzieht sich noch ein drittes Mal. Dieses dreimalige Schreiben von Daten und das Verschieben des Kopfes erfolgt so schnell, daß sich die Diskette wäre nd dieser Operation nicht vollständig drehen kann. Es werden beim Schreiben der Halbspuren die benachbarten Halbspuren überschrieben, jedoch nur an Stellen, bei denen auf den benachbarten Spuren keine Daten liegen. Dies ist nur zu realisieren, wenn der gesamte Schreibprozeß, wie in unserem Fall, weniger Zeit beansprucht als die Zeit, in der sich die Diskette einmal dreht. Das Kopierprogramm liest immer einen gesamten Track ein und schreibt diesen auch wieder als ganzen Track. Durch dieses Vorgehen werden alle Daten der benachbarten Halbspuren zwangsläufig zerstört. Selbst wenn das Kopierprogramm "wissen" sollte, um welchen Schutz es sich handelt, kann es noch nicht wissen, an welchen Stellen es die Halbspur innerhalb einer Umdrehung wechseln soll. Sie sehen sicher ein, daß es sich auch bei diesem mit der 1541 aufgetragenen Verfahren um einen absolut sicheren Kopierschutz handelt. Folgend zeigen wir Ihnen das Programm, mit dem Sie selbst den eben erklärten Kopierschutz auftragen können. Das Programm wird in der Floppy ab $0503 gestartet. 0500 JMP $0519
Uberspringen des Hauptprogramms
Diskettenkopierschutz
Das Programm wird hier gestartet 0503 0506 0508 050A 050C 050E 0510 0512 0514 0516
JSR LDA STA LDA STA LDA STA BIT BMI JMP
$0042 #S27 SOA #SOO SOB #SEO S02 S02 S0512 $0042
Disk initialisieren Track-Nummer (39) an Job übergeben Sektor-Nummer laden und übergeben (nicht nötig) Job-Code 'EO' laden und übergeben auf Rückmeldung warten verzweige, wenn noch keine Rückmeldung Disk initialisieren, Rücksprung
0519 051C 051E 0520 0523 0526 0529 052B 0520 0530 0533 0536 0538 053A 0530 053F 0541 0544 0546 0549 054C 054E 0550 0551 0552 0554 0557
LDA AND ORA STA JSR JSR LDA STA JSR JSR JSR DEC BNE LDA AND ORA STA LDA STA STA LDX BVC CLV DEX BNE LDA BVC
S1COO #S9F #S08 S1COO SFEOE SFEOO #S02 S3B S05AO SFEOE SFEOO S3B S052D S1COC #S1F #SCO S1COC #SFF S1C03 S1C01 #SC8 S054E
Control-Port laden Speed auf 00 setzen und LED aum Laufwerk anschalten Wert speichern Track mit S55 löschen (Löschen der SYNCs) wieder auf Lesen umschalten Zähler für die Anzahl der Halbspuren setzen Kopf un eine Halbspur verschieben Track mit #55 löschen Kopf wieder auf Lesen umschalten Zähler verringern nächste Halbspur löschen
Kopf auf Schreiben umschalten und Port auf Ausgang schalten
SFF zum Kopf schicken um SYNC zu schreiben Zähler für Länge der SYNC-Markierung warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler verringern S054E verzweige, wenn nicht fertig geschrieben S0580,X Daten zum Schreiben holen $0557 und warten, bis Byte geschrieben
279
280
Das große Anti-Cracker-Buch
0559 CLV 055A STA 0550 INX 055E CPX 0560 BNE 0562 BVC 0564 CLV 0565 JSR 0568 JSR 056B LDA 056E CLC 056F ADC 0571 STA 0574 CHP 0576 BNE 0578 JSR 057B JHP 057E BRK 057F BRK
Byte-Ready-Leitung löschen Byte zu R/W-Kopf schicken Zähler für Byte-Anzahl erhöhen schon alle Bytes geschrieben verzweige, wenn noch Bytes zu schreiben warten, bis letztes Byte geschrieben Byte-Ready-Leitung löschen Kopf auf Lesen stellen R/W-Kopf um eine Halbspur zurückschieben LOW-Byte der Adresse, von der die zu schreibenden Daten geholt werden, um 8 erhöhen und Wert wieder speichern vergleichen, ob alle Daten geschrieben verzweige, wenn nein R/W-Kopf auf Anfangsposition stellen und Rücksprung
$1C01 #$08 $0554 $0562 SFEOO $05A3 $0555 #$08 S0555 #$98 S053A $05AO $FD9E
0580 0588 0590 0598
69 59 66 00
A5 9A 95 00
5A 6A 96 00
96 65 69 00
05AO 05A2 05A3 05A5 05A8 05AB 05AC 05AD 05AF 05B1 05B4 05B6 05B8 05BB 05BD
LDA #$CA Byte S2C LDA #$E8 STA $05AB LDX S1COO DEX TXA AND #$03 STA $4B LDA S1COO AND #$FC ORA $4B STA S1COO LDX #$10 LDY #$00
56 66 A5 00
59 55 5A 00
A6 99 6A 00
A9 AA Die zu schreibenden 6A Daten 00
Wert für Befehl 'DEX' laden Skip nach S05A5 Wert für Befehl 'INX' laden und Wert nach S05AB schreiben
R/W-Kopf um eine Halbspur nach innen oder auBen fahren
Diskettenkopierschutz 05BF 05CO 05C2 05C3 05C5
DEY BNE S05BF DEX BNE S05BF RTS
281
Zeitschleife, um dem R/W-Kopf Zeit zu geben, sich zu positionieren
Rücksprung
Im Anschluß an das Assemblerlisting wieder der BASIC-Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"H -W"CHRS(N)CHR$(5)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 lFSU <> 20644 THEN PRINT"ERROR IN DATAS":STOP 130 PRINT#1,"H-E"CHR$(3)CHR$(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,"H-R"CHRS(2)CHRS(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHRS(O» 160 IF A >127 THEN 145 170 IFA =1 THENPRINT"OK":END 180 PRINT"ERROR":END 302 DATA 76, 25, 5,32,66,208,169,39,133, 10,169, 0,133, 11,169,224, 133, 2 304 DATA 36, 2, 48,252, 76, 66,208,173, 0, 28, 41,159, 9, 8,141, 0, 28, 32 306 DATA 14,254, 32, 0,254,169, 2,133, 59, 32,160, 5, 32, 14,254, 32, 0,254 308 DATA198, 59,208,243,173, 12,28,41,31, 9,192,141, 12,28,169,255, 141, 3 310 DATA 28,141, 1,28,162,200,80,254,184,202,208,250,189,128, 5,80, 254,184 312 DATA141, 1, 28,232,224, 8,208,242,80,254,184,32, 0,254, 32,163, 5,173 314 DATA 85, 5, 24,105, 8,141, 85, 5,201,152,208,194, 32,160, 5, 76, 158,253 316 DATA 40, 0,105,165, 90,150, 86, 89, 166, 169, 89,154,106,101,102, 85, 153,170 318 DATA102,149,150,105,165, 90,106,106, 0, 0, 0, 0, 0, 0, 0, 0, 169,202
282
Das große Anti-Cracker-Buch
320 DATA 44,169,232,141,171, 0, 28 322 DATA 41,252, 5, 75,141, 250, 96 324 DA TA -1
5,174,
0,28,202,138,41,
0, 28,162, 16,160,
3,133, 75,173,
0,136,208,253,202,208,
Wie Sie auch schon anhand des dokumentierten Listings sehen konnten, wird der Schutz auf den Spuren oberhalb von 35 aufgetragen. Doch kommen wir jetzt zu dem Abfrageprogramm, das auch ab $0503 gestartet wird. 0500 JMP S0519
Überspringen des Hauptprogramms
Das Programm wird hier gestartet: 0503 0506 0508 050A 050C 050E 0510 0512 0514 0516
JSR LDA STA LDA STA LDA STA LDA 8MI JMP
SD042 #$27 SOA #$00 S08 #$EO S02 S02 S0512 $0042
Disk initialisieren Track-Nummer (39) an Job übergeben Sektor-Nummer laden und übergeben
0519 051C 051E 0520 0523 0524 0525 0528 0529 0528 052D 0530
LDA AND ORA STA NOP NOP JSR NOP LDA STA LDA 8PL
S1COO #$9F #$08 S1COO
Control-Port laden Speed auf 0 stellen und LED anschalten Wert speichern
S0593
Zähler für SYNC auf 0 setzen
#$00 S38 S1COO S052D
Zähler für Fehlversuche beim Finden der richtigen Anfangsstelle auf 0 setzen Control-Port laden warten, bis keine SYNC-Markierung anliegt
DiskettenkoDierschutz 0532 LDA S1COO 0535 BPL S0544 0537 INC S05FE 053A BNE S0532 053C INC S05FF 053F BNE S0532 0541 JMP S058F 0544 LDA S1C01 0547 CLV 0548 LDX #$00 054A BVC S054A 054C CLV 054D LDA S1C01 0550 CMP S05AO,X 0553 BNE S0587 0555 INX 0556 CPX #$06 0558 BNE S054A 055A JSR S05C3 055D LDA #$3F 055F STA S3B 0561 JSR S0593 0564 NOP 0565 NOP 0566 NOP 0567 LDA S0551 056A CLC 056B ADC #$08 0560 STA S0551 0570 CMP #$B8 0572 BNE S0532 0574 JSR S05CO 0577 JSR S05CO 057A JSR S05CO 0570 LDA #$FF 057F STA S0010 0582 STA S0011 0585 BNE S0541 0587 INC S3B 0589 LDA S3B
Control-Port laden verzweige, wenn SYNC-Signal anliegt sonst Zähler erhöhen verzweige, wenn kein Uberlauf HIGH-Byte des Zählers erhöhen verzweige, wenn kein Uberlauf sonst kein SYNC-Signal gefunden, Fehler Byte-Ready Leitung freigeben Zähler für zu lesende Bytes auf Null auf Byte warten Byte-Ready-Leitung löschen Byte holen mit Wert aus der Tabelle vergleichen wenn ungleich, dann Fehler, Rücksprung sonst Zähler erhöhen vergleiche, ob alle Bytes gelesen verzweige, wenn noch Bytes zu lesen R/W-Kopf um eine Halbspur verschieben Zähler für Fehler so stellen, daß kein Fehler mehr erlaubt ist_ Zähler für Länge des nicht SYNC-Bereichs auf 0 stellen
LOW-Byte des Zeigers auf die zu vergleichenden Daten um acht erhöhen Wert speichern vergleiche, ob schon alle Bytes verglichen verzweige, wenn nicht alles verglichen R/W -Kopf auf Ausgangsposition stellen positive Rückmeldung an den Computer Ubergeben unbedingter Sprung, Rücksprung Zähler für Fehlversuche erhöhen Zähler laden
283
Das große Anti-Cracker-Buch
284 058B 0580 058F 0591
CMP BHE LOA BEQ
#$40 S0532 #$00 S057F
0593 0595 0598 059B 059C 0590 059E 059F
LOA #$00 STA S05FE STA S05FF RTS BRK BRK BRK BRK
Löschen der Zähler für die Länge des nicht SYNC-Bereichs
05AO 05A8 05BO 05B8
69 59 66 00
56 66 A5 00
05CO 05C2 05C3 05C5 05C8 05CB 05CC 05CO 05CF 0501 0504 0506 0508 050B 0500 050F 05EO 05E2 05E3 05E5
LOA #$CA Byte S2C LDA #$E8 STA S05CB LOX S1COO OEX TXA AHO #$03 STA S4B LOA S1COO AHO #$FC ORA S4B STA S1COO LOX #$10 LDY #$00 OEY BHE S050F OEX BNE S050F RTS
A5 9A 95 00
5A 96 6A 65 9669 00 00
mit 64 vergleichen wenn kleiner, dann erneuter Versuch sonst negative Rückmeldung an Computer unbedingter Sprung
Rücksprung
59 55 5A 00
A6 99 6A 00
A9 AA Oaten, die mit den auf Oisk 6A verglichen werden 00
Wert für Befehl 'OEX' laden Skip nach S05A5 Wert für Befehl 'IHX' laden und Wert nach S05AB schreiben
R/W-Kopf um eine Halbspur nach innen oder außen fahren
Zeitschleife, um dem R/W-Kopf Zeit zu geben, um sich zu positionieren
Rücksprung
Diskettenkopierschutz
285
Auch hier wieder der entsprechende BASIC-Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-IJ"CHR$(N)CHR$(5)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 24740 THEN PRINT"ERROR IN DATAS":STOP 135 PRINT#1,"M-E"CHR$(3)CHR$(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,"M-R"CHR$(16)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 170 IFA =255 THENPRINT"SCHUTZ OK":END 180 PRINT"SCHUTZ ERROR":END 302 DATA 76, 25, 5,32,66,208,169,38,133, 10,169, 0,133, 11,169,224, 133, 2 304 DATA 36, 2,48,252, 76,66,208,173, 0, 28,41,159, 9, 8,141, 0, 28,234 306 DATA234, 32,147, 5,234,169, 0,133,59,44, 0, 28, 16,251,44, 0, 28, 16 308 DATA 13,238,254, 5,208,246,238,255, 5,208,241, 76,158,253,173, 1, 28,184 310 DATA162, 0,80,254,184,173, 1, 28,221,160, 5,208, 50,232,224, 6, 208,240 312 DATA 32,195, 5,169,63,133,59,32,147, . 5,234,234,234,173, 81, 5, 24,105 314 DATA 8,141,81, 5,201,184,208,190,32,192, 5,32,192, 5,32,192, 5,169 316 DATA255,141, 16, 0,141, 17, 0,208,186,230,59,165, 59,201, 64,208, 163,169 318 DATA 0,240,236,169, 0,141,254, 5,141,255, 5,96, 0, 0, 0, 0, 105,165 320 DATA 90,150,86,89,166,169, 89,154,106,101,102, 85,153,170,102,149, 150,105 322 DATA165, 90,106,106, 0, 0, 0, 0, 0, 0, 0, 0,169,202, 44,169, 232,141 324 DATA203, 5,174, 0, 28,202,138, 41, 3,133, 75,173, 0, 28, 41,252, 5, 75 326 DATA141, 0, 28,162, 10,160, 0,136,208,253,202,208,250, 96, -1
286
Das große Anti- Cracker-Buch
Wie bei den anderen von uns vorgestellten Kopierschutzverfahren wird auch hier die entsprechende Rückmeldung der Floppy in Speicherstelle $10 der Floppy abgelegt. Sollte die Speicherstelle den Wert 00 haben, so liegt eine Kopie vor.
6.4
Mit Nullen beschriebene Spuren
Der in diesem Kapitel beschriebene Kopierschutz ist einer von jenen, die bis heute noch nicht kopiert werden können. Die einzige Chance für das Kopierprogramm, ein Duplikat auzufertigen, ist die, daß es den Schutz erkennt, und reproduziert. Kopieren, also die Daten lesen und genauso wieder schreiben, kann man diesen Schutz aufgrund der Hardware der Floppy nicht. Daß das Kopierprogramm den Schutz richtig erkennt, ist ebenfalls so gut wie unmöglich, weshalb sich dieser Kopierschutz, wie auch die anderen in diesem Buch vorgestellten, ausgezeichnet für den professionellen Einsatz nutzen läßt. Um das System des Schutzes zu verstehen, müssen wir uns näher mit der Lese- und Schreib technik des Drive-Controllers beschäftigen. Wie werden die Daten auf der Magnetschicht der Diskette abgelegt? Die Daten, die beim Schreiben in die Speicherstelle $1 CO 1 geschrieben werden, werden bitweise zum RjW-Kopf geholt, wo sie in Magnetsignale umgewandelt werden. Diese Signale sehen nicht so aus, wie man vielleicht vermutet, nämlich daß ein NullBit durch ein nach Norden und ein Eins-Bit durch ein nach Süden gerichtetes Magnetfeld dargestellt wird. Statt dessen wird ein Eins-Bit durch eine Änderung des Magnetfelds und ein Null-Bit durch ein gleichbleibendes Magnetfeld dargestellt. Sie werden sich vielleicht fragen, wie man nach dieser Methode die Null-Bits beim Lesen überhaupt erkennen soll, da doch beim Anlegen eines Null-Bits "nichts passiert". Um diese Bits zu erkennen, läuft wäre nd des Lesens und Schreibens ein Timer mit, nach dessen Unterlauf ein Bit auf Disk geschrieben oder von Disk gelesen wird. Sollte bis zum Ablauf des Timers ein Magnetwechsel auf der Diskette festgestellt worden sein, wird
287
DiskettenkoDierschutz
ein Eins-Bit vermerkt und der Timer erneut gestartet. Wenn bis zum Ablauf des Timers kein Magnetwechsel vorliegt, wird ein Null-Bit registriert. Aufgrund der Gleichlaufschwankungen des Laufwerks würden schnell Lesefehler auftreten, wenn zu viele Null-Bits hintereinander auf der Diskette stehen, da der Controller sich nur auf seinen Timer verlassen muß und keinen Anhaltspunkt auf der Diskette hat. Aus diesem Grund liegen bei der GCR-Codierung nie mehr als zwei Null-Bits hintereinander. Den Umstand, daß der Con.troller beim Finden zu vieler NullBits durcheinander kommt, machen wir uns in diesem Kopierschutz zunutze. Wir schreiben mitten in einen Block eine Anzahl von Nullen, was bedeutet, daß sich die Magnetschicht auf dem von uns beschriebenen Stück nicht ändert. Der Controller kann diese Daten jedoch nicht richtig verarbeiten und versucht, sie irgendwie zu entziffern. Bei dieser Entzifferung fügt er jedoch Eins-Bits ein, die er eigentlich gar nicht gelesen hat. Wenn er versucht, dieses Stück erneut zu lesen, weichen die vermeintlich erkannten Daten jedoch stark von den zuvor gelesenen ab. Das Kopierprogramm, das diesen Block liest, erhält ebenfalls irgendwelche "Fantasiewerte", welche es beim Schreiben auch so auf Diskette speichert. Wenn man jetzt diese vom Kopierprogramm geschriebenen Daten mehrmals einliest, weichen diese nicht voneinander ab, da für den Drive-Controller richtige Daten geschrieben wurden und somit auch fehlerfrei gelesen werden können. Sie werden uns beipflichten, daß es sich bei diesem Kopierschutz um ein sehr sicheres System handelt. Nachdem wir nun die theoretischen haben, wollen wir in die Praxis Maschinensprachprogramm erzeugt Kopierschutz auf dem eingestellten ab $0552 gestartet. 0500 0503 0505 0507 0508
JSR $F510 LDX #$09 BVC $0505 CLV DEX
Voraussetzungen geschaffen Übergehen. Das folgende einen zuvor beschriebenen Sektor. Das Programm wird
Blockheader suchen Zähler für zu überlesende Byte·Anzahl warten, bis ein Byte anliegt Byte-Ready-Leitung löschen Zähler verringern
288 0509 BNE S0505 050B LOA #$FF 0500 STA S1C03 0510 LOA S1COC 0513 ANO #$1F 0515 ORA #$CO 0517 STA S1COC 051A LOA #$FF 051C LOX #$05 051E STA S1C01 0521 CLV 0522 BVC S0522 0524 CLV 0525 OEX 0526 BNE S0522 0528 LOA #$55 052A STA S1C01 0520 BVC S0520 052F CLV 0530 LOA #SCE 0532 STA S1C01 0535 BVC S0535 0537 CLV 0538 INX 0539 CPX #S20 053B BNE S0535 0530 LOA #$00 053F LOX #$20 0541 STA S1C01 0544 BVC S0544 0546 CLV 0547 OEX 0548 BNE S0544 054A JSR SFEOO 0540 LOA #$01 054F JMP SF969
Das große Anti-Cracker-Buch verzweige, wenn Zähler nicht abgelaufen Port auf Ausgang stellen um R/~-Kopf auf Schreiben umzustellen R/~-Kopf auf Schreiben stellen
SFF zum Schreiben der SYNC-Markierung Zähler für Länge der SYNC-Markierung SYNC-Markierung schreiben Byte-Ready-Leitung löschen warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler für SYNC verringern verzweige, wenn nicht abgelaufen S55 laden und auf Oisk schreiben warten, bis Byte geschrieben Byte-Ready-Leitung löschen SCE laden und auf Oisk schreiben warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler für zu schreibende Bytes erhöhen vergleiche, ob alle Bytes geschrieben verzweige, wenn noch Bytes zu schreiben Akku mit SOO laden Zählwert für Byte-Anzahl auf 32 SOO auf Oisk schreiben warten bis Byte geschrieben Byte-Ready-Leitung löschen Zähler verringern verzweige, wenn noch Bytes zu schreiben R/W-Kopf auf Lesen stellen OK Rückmeldung laden und Job beenden, Rückmeldung ausgeben
Diskettenkopierschutz
289
Hier wird das Programm gestartet: 0552 0554 0556 0558 055A 055C 055E 0560 0562
LDA STA LDA STA LDA STA LDA BMI RTS
#$01 SOA #$00 SOB #SEO S02 S02 S055E
Track-Nummer laden und an Job übergeben Sektor-Nummer laden und an Job übergeben Job-Code 'EO' (Programm ausführen) in Job-Speicher schreiben Rückmeldung abwarten verzweige, wenn noch keine Rückmeldung Rücksprung
Anschließend der entsprechende BASIC-Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHR$(5)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 12308 THEN PRINT"ERROR IN DATAS":STOP 105 INPUT "WELCHER TRACK";T 110 INPUT "WELCHER SEKTOR";S 1-Z0 ·PRINT#1,"M-W"CHR$(83)CHR$(5)CHR$(1)CHR$(T) 125 PRINT#1,"M-W"CHR$(87)CHR$(5)CHR$(1)CHR$(S) 135 PRINT#1,"M-E"CHR$(82)CHR$(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,"M-R"CHR$(2)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 160 IF A >127 THEN 145 170 IFA =1 THENPRINT"OK":END 180 PRINT"ERROR":END 302 DATA 32, 16,245,162, 9, 80,254,184,202,208,250,169,255,141, 3, 28, 173, 12 304 DATA 28,41,31, 9,192,141, 12,28,169,255,162, 5,141, 1, 28,184, 80,254 306 DATA184,202,208,250,169, 85,141, 1, 28, 80,254,184,169,206,141, 1, 28, 80 308 DATA254,184,232,224, 32,208,248,169, 0,162, 32,141, 1, 28, 80,254, 184,202
290 310 DATA208,250, 32, 133, 11 312 DATA169,224,133,
Das große Anti-Cracker-Buch 0,254,169, 2,165,
1, 76,105,249,169, 18,133, 10,169,
0,
2, 48,252, 96, -1
Die Daten, die vor den Nullen auf Disk geschrieben werden, sind so gewählt, daß kein '22er Read-Error' entsteht. Ins BinFormat umgesetzt steht an der ersten Stelle nach der SYNCMarkierung eine $07 (Kennzeichen für Datenblock). Die Abfrage des Kopierschutzes ist noch einfacher als das Auftragen. Die Daten des geschützten Blocks werden zweimal gelesen, in der Floppy gespeichert und daraufhin miteinander verglichen. Sollten die Daten nicht übereinstimmen, liegt keine Kopie vor. Die Übereinstimmung der Daten ist das Kennzeichen der Kopie. Die Rückmeldung an den Computer wird in $10 gespeichert. $FF bedeutet positive und $00 negative Rückmeldung. Das Floppy-Programm liegt wieder ab $0500 und wird ab $0543 gestartet. 0500 LDA #$05 Zähler für Fehlversuche 0502 STA S37 setzen 0504 JSR SF50A entsprechenden Datenblock suchen 0507 LDX #$00 Zähler für die zu lesenden Bytes auf Null 0509 Bve S0509 warten, bis Byte gelesen 050B eLV Byte-Ready-Leitung löschen 050e LDA S1e01 Byte vom Port holen 050F STA S0300,X und speichern 0512 INX Zähler erhöhen 0513 BNE S0509 verzweige, wenn nicht alle Bytes geholt 0515 JSR SF50A gleichen Datenblock erneut holen 0518 LDX #$00 Zähler auf 00 setzen 051A Bve S051A auf Byte warten Byte-Ready-Leitung löschen 051e CLV 051D LDA S1C01 Byte vom Port holen 0520 STA S0400,X und speichern 0523 INX Zähler erhöhen 0524 BNE S051A verzweige, wenn nicht alle Bytes geholt 0526 LDA S0300,X Bytes des zuerst geladenen Blocks mit
Diskettenkopierschutz 0529 052C 052E 052F 0531 0533 0535 0537 0539 053C 053E 0540
CMP S0400,X denen des zweiten vergleichen BNE S053C verzweige, wenn ungleich INX sonst Zähler erhöhen BNE S0526 verzweige, wenn nicht alle Bytes verglichen DEC S37 Fehlerzähler verringern BNE S0504 verzweige, wenn erneuter Versuch erlaubt LDA #$00 sonst negative Rückmeldung STA S10 übergeben Rücksprung JMP SFD9E LDA #$FF positive Rückmeldung laden STA S10 und übergeben JMP SFD9E Rücksprung
Das Programm wird hier gestartet: 0543 0545 0547 0549 054B 054D 054F 0551 0553
LDA STA LDA STA LDA STA LDA BMI RTS
#$01 SOA #$00 SOB #$EO S02 S02 S054F
Track-Nummer laden und übergeben Sektor-Nummer laden und übergeben Job-Code 'EO' laden und an den Job-Speicher übergeben auf Rückmeldung warten verzweige, wenn noch keine Rückmeldung Rücksprung
Folgend wieder unser kleiner BASIC-Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHR$(N)CHRS(5)CHR$(1)CHRS(X) 40 N=N+1:GOTO 20 100 IFSU <> 9963 THEN PRINT"ERROR IN DATAS":STOP 105 INPUT "WELCHER TRACK"iT 110 INPUT "WELCHER SEKTOR"iS 120 PRINT#1,"M-W"CHR$(68)CHRS(5)CHRS(1)CHRS(T) 125 PRINT#1,IM-W"CHRS(72)CHRS(5)CHRS(1)CHRS(S)
291
292
Das große Anti-Cracker-Buch
135 PRINT#1,IM-E"CHR$(67)CHR$(5) 140 FORN=1TO 500:NEXT 145 PRINT#1,IM-R"CHR$(16)CHR$(0)CHR$(1) 150 GET#1,A$:A=ASC (A$+CHR$(O» 170 IFA =255 THENPRINT"SCHUTZ OK":END 180 PRINT"SCHUTZ ERROR":END 302DATA169, 5,133, 55,32, 10,245,162, 0,
0,80,254,184,173,
1, 28,157,
3
304 DATA232,208,244, 32, 10,245,162, 0,80,254,184,173, 1, 28,157, 0, 4,232 306DATA208,244,189, 0, 3,221, 0, 4,208, 14,232,208,245,198, 55,208, 207,169 308DATA 0,133, 16,76,158,253,169,255,133, 16, 76,158,253,169, 1,133, 10,169 310DATA 0,133, 11,169,224,133, 2,165, 2,48,252,96,-1
6.5
Überprüfen eines kompletten Tracks
Das Überprüfen jedes Bytes eines gesamten Tracks dient auch zur Erstellung eines sehr guten Kopierschutzes, der bisher nicht kopiert werden kann_ Dieses Schutzsystem wird auch jetzt noch von großen Firmen mit Erfolg eingesetzt. Warum Kopierprogramme es nicht schaffen, diesen Track zu kopieren, liegt daran, daß die Tracks beim Schreiben nicht gleich lang sind und darum nicht exakt dieselbe Anzahl von Bytes enthalten. Die variable Byte-Anzahl eines Tracks kommt durch die Schwankungen des Laufwerkmotors zustande. Die nächste Hürde, die Kopierprogramme zu überwinden haben, ist die Tatsache, daß das letzte Daten-Byte sich nahtlos an die SYNC-Markierung anschließt. Selbst wenn das Kopierprogramm alle Daten richtig erkennt und versucht, das Ende der Daten korrekt an den Anfang der Daten zu bringen, ist es sehr leicht möglich, daß beim Umschalten des R/W-Kopfes auf den Lesebetrieb in die SYNC-Markierung noch Null-Bits geschrieben werden, welche das Abfrageprogramm erkennt und signalisiert, daß eine Kopie vorliegt.
Diskettenkopierschutz
293
Mit dem Problem, daß beim Umschalten des Kopfes vom Schreib- in den Lesebetrieb die SYNC-Markierung "beschädigt" werden kann, müssen wir in unserem Schutzauftragungsprogramm auch fertig werden. Wir lösen das Problem durch mehrere Schreibversuche. Es wird so lange geschrieben, bis der Kopierschutz fehlerfrei aufgetragen wurde. Im folgenden gehen wir näher auf unser Lese- und Schreibprogramm ein. Zu Beginn des Auftragens des Schutzes wird der angegebene Track mit SYNCs gelöscht. Daraufhin werden die Daten auf den Track geschrieben. In unserem Fall handelt es sich um die immer im Wechsel geschriebenen Daten $45 und $79. Nachdem wir diese Zahlen ca. 6000mal geschrieben haben, folgt eine $35 als Endmarkierung, eine $66 und danach wieder die SYNCMarkierung. Das Abfrageprogramm wartet auf die SYNC-Markierung und liest die Daten, die auch auf ihren Wert geprüft werden, bis es auf die $35 stößt und die Anzahl der bisher geschriebenen Daten kontrolliert. Nun werden weitere sechs Bytes geholt. Das erste Byte muß $66 sein. Sollte die SYNC-Markierung korrekt sein, so wird noch ein $FF (Anfang der SYNC-Markierung) gefunden und danach das Lesen von Daten hardware mäßig unterbunden, da eine SYNC-Markierung anliegt. Folglich sind die als nächstes gelesenen Daten wieder die Anfangs-Bytes. Bei einer nicht korrekten SYNC-Markierung, die durch das Umschalten vom Schreib- auf den Lesebetrieb zerstört wurde, wird beim Lesen vor dem Auffinden der Anfangsdaten noch ein Byte gefunden, was wieder auf eine Kopie schließen läßt. Nachdem wir jetzt näher auf die Programme eingegangen sind, stellen wir sie Ihnen jetzt in Form der Listings vor. Als erstes folgt das Kopierschutzerstellungs-Programm, das ab $0571 gestartet wird.
294 0500 LOA S1COO 0503 ANO #$9F 0505 ORA #$08 0507 STA S1COO 050A JSR SFOA3 0500 LOX #$02 050F LOY #$18 0511 LOA #$45 0513 STA S1C01 0516 BVC S0516 0518 CLV 0519 INX 051A LOA #$79 051C STA S1C01 051F BVC S051F 0521 CLV 0522 INX 0523 BNE S0511 0525 OEY 0526 BNE S0511 0528 LOA #$35 052A STA S1C01 0520 BVC S0520 052F CLV 0530 LOA #$66 0532 STA S1C01 0535 BVC S0535 0537 CLV 0538 LOA #$FF 053A STA S1C01 0530 BVC S0530 053F CLV 0540 JSR SFEOO
Das große Anti-Cracker-Buch Control-Port laden Speed auf 00 stellen und LEO anschalten und Wert speichern Track mit SYNC löschen Zähler für Byte Anzahl setzen erstes zu schreibendes Byte zum R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler erhöhen zweites zu schreibendes Byte laden und zu R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler erhöhen weiter, wenn kein Oberlauf sonst HIGH-Byte des Zählers verringern verzweige, wenn noch Bytes zu schreiben Markierungs-Byte laden und auf Oisk schreiben warten, bis Byte geschrieben Byte-Ready-Leitung löschen $66 laden und auf Oisk schreiben warten, bis Byte geschrieben Byte-Ready-Leitung löschen SFF für SYNC-Markierung laden und schreiben warten, bis Byte geschrieben Byte-Ready-Leitung löschen Kopf auf Lesen umstellen
Der folgende Programmteil kontrolliert, ob in der SYNC-Markierung beim Umschalten des Kopfes auf den Lesebetrieb kein Fehler entstanden ist_
Diskettenkopierschutz 0543 0546 0548 054B 054C 054E 054F 0552 0554 0556 0558 055A 055B 055E 0561 0563 0564 0566 0568
LDA BMI LDA CLV BVC CLV LDA CMP BNE LDX BVC CLV LDA CMP BNE INX CPX BNE JMP
S1COO S0543 S1C01
Control-Port laden und auf SYNC-Markierung warten Port wieder freimachen Byte-Ready-Leitung löschen S054C Warten, bis Byte gelesen Byte-Ready-Leitung löschen S1C01 Byte vom Port holen #$35 mit Endmarkierung vergleichen S054C warten, bis Byte gefunden #$00 Zähler für zu lesende Bytes laden S0558 warte, bis Byte anliegt Byte-Ready-Leitung löschen S1C01 Byte vom Port holen S056B,X mit Bytes aus Tabelle vergleichen S050A erneut schreiben, wenn ungleich Zähler erhöhen #$06 vergleichen, ob alle Bytes verglichen S0558 verzweige, wenn noch Bytes zu holen SFD9E Rücksprung
056B 66 FF 45 79 45 79 Bytes, mit denen verglichen wird 0571 0573 0575 0577 0579 0578 0570
LDA STA LDA STA LDA BMI RTS
#$01 SOA #$EO S02 S02 S0579
Track-Nummer laden und übergeben
Job-Code 'EO' fahren und übergeben auf Rückmeldung warten warten, bis Rückmeldung erhalten Rücksprung
Nachfolgend der zugehörige BASIC-Loader: 100PEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHRS(5)CHRS(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 15481 THEN PRINT"ERROR IN DATAS":STOP 110 INPUT "TRACK-NUMMER"iT
295
296
Das große Anti-Cracker-Buch
120 PRINT#1,"M-W"CHR$(114)CHR$(5)CHR$(1)CHR$(T) 130 PRINT#1,IM-E"CHR$(113)CHR$(5) 140 PRINT#1,"I" 150 CLOSE1 302DATA173, 0,28,41,159, 9, 8,141, 0,28,32,163,253,162, 2,160, 24,169 304 DATA 69,141, 1,28,80,254,184,232,169,121,141, 1, 28, 80,254,184, 232,208 306 DATA236,136,208,233,169, 53,141, 1,28,80,254,184,169,102,141, 1, 28, 80 308DATA254,184,169,255,141, 1,28,80,254,184,32, 0,254,173, 0,28, 48,251 310DATA173, 1, 28,184,80,254,184,173, 1,28,201,53,208,246,162, 0, 80,254 312DATA184,173, 1,28,221,107, 5,208,167,232,224, 6,208,240,76,158, 253,102 314 DATA255, 69,121, 69,121,169, 1,133, 10,169,224,133, 2,165, 2,48, 252, 96 316 DATA -1
Wundern Sie sich nicht, wenn das Auftragen des Schutzes etwas Hinger dauert, denn das Auftragungsprogramm schreibt so lange, bis der Schutz korrekt aufgetragen ist_ Als nächstes folgt das Assemblerlisting des Abfrageprogramms, das ab $0561 gestartet wird: 0600 0603 0605 0607 060A 060C 060E 0611 0613 0616 0617 0619
LDA AND ORA STA LDX LDY LDA BMI LDA CLV BVC CLV
$1COO #$9F #$08 $1COO #$00 #$00 $1COO $060E $1C01 $0617
Control -Port laden Speed auf Null stellen und LED einschalten Zähler für Anzahl der Bytes auf Null setzen Control-Port laden und warten, bis SYNC-Markierung gefunden Port freimachen Byte-Ready-Leitung löschen warten, bis Byte gelesen Byte-Ready-Leitung löschen
297
Diskettenkopierschutz 061A LOA S1C01 0610 CMP #$45 061F BNE S0632 0621 INX 0622 BVC S0622 0624 CLV 0625 LOA S1C01 0628 CMP #$79 062A BNE S0632 062C INX 0620 BNE S0617 062F INY 0630 BNE S0617 0632 CMP #$35 0634 BNE S0657 0636 CPX #$FE 0638 BNE S0657 063A CPY #$17 063C BNE S0657 063E LOX #$00 0640 BVC S0640 0642 CLV 0643 LOA S1C01 0646 CMP $065B,X 0649 BNE $0657 064B INX 064C CPX #$06 064E BNE S0640 0650 LOA #$FF 0652 STA $10 0654 JMP SF09E 0657 LOA #$00 0659 BEQ $0652
Byte vom Port holen und mit $45 vergleichen Fehler, wenn ungleich Byte-Zähler erhöhen warten, bis Byte anliegt Byte-Ready-Leitung löschen Byte vom Port holen mit S79 vergleichen Fehler, wenn ungleich Byte-Zähler erhöhen verzweige, wenn kein Oberlauf HIGH-Byte des Zählers erhöhen verzweige, wenn kein Oberlauf Byte mit Markierungs-Byte vergleichen Fehler, wenn ungleich LOW-Byte des Zählers mit SFE vergleichen Fehler, wenn ungleich HIGH-Byte des Zählers mit $17 vergleichen Fehler, wenn ungleich Zähler für zu lesende Bytes löschen warten, bis Byte gelesen Byte-Ready-Leitung löschen Byte vom Port holen mit Byte aus Tabelle vergleichen Fehler, wenn ungleich Zähler für Byte-Anzahl erhöhen vergleiche, ob alle Bytes geholt weiter, wenn noch Bytes zu lesen positive Rückmeldung laden und übergeben Rücksprung negative Rückmeldung laden unbedingter Sprung
065B 66 FF 45 7945 79 0661 0663 0665 0667
LOA STA LOA STA
#$01 $OC #$EO $03
Bytes, mit denen verglichen wird
Track-Nummer laden und an Job übergeben Job-Code 'EO' .laden und in Job-Speicher schreiben
298 0669 LDA $03 066B BMI $0669 0660 RTS
Das große Anti-Cracker-Buch überprüfen, ob Rückmeldung erhalten verzweige, wenn noch keine Rückmeldung Rücksprung
Im Anschluß an das Assemblerlisting folgt wie üblich der BASIC- Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-\.I"CHR$(N)CHR$(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 13656 THEN PRINT"ERROR IN DATAS":STOP 110 INPUT "TRACK-NUMMER";T 120 PRINT#1,"M-\.I"CHR$(98)CHR$(6)CHR$(1)CHR$(T) 130 PRINT#1,"M-E"CHR$(97)CHR$(6) 140 FORN=1 TO 500:NEXT 150 PRINT#1,"M-R"CHR$(16)CHR$(0)CHR$(1) 160 GET#1,A$ 170 IF ASC(A$+CHR$(0»<>255 THENPRINT" SCHUTZ ERROR":END 180 PRINT" SCHUTZ OK" 302 DATA173, 0, 28,41,159, 9, 8,141, 0,28,162, 0,160, 0,173, 0, 28, 48 304DATA251,173, 1,28,184,80,254,184,173, 1,28,201,69,208, 17,232, 80,254 306 DATA184,173, 1,28,201,121,208, 6,232,208,232,200,208,229,201, 53, 208, 33 308 DATA224,254,208, 29,192, 23,208, 25,162, 0,80,254,184,173, 1, 28, 221, 91 310 DATA 6,208, 12,232,224, 6,208,240,169,255,133, 16, 76,158,253,169, 0,240 312 DATA247,102;255, 69,121, 69,121,169, 1,133, 12,169,224,133, 3,165, 3, 48 314 DATA252, 96, -1
Diskettenkopierschutz
6.6
299
SYNC-Manipulationen
Eine recht gute Möglichkeit zur Erstellung eines Kopierschutzes, ergibt sich aus der Änderung der SYNC-Makierungen auf einem Track. Wie man mit Hilfe von veränderten SYNC-Markierungen einen Kopierschutz aufbauen kann, wollen wir in den folgenden Kapiteln zeigen. 6.6.1
Killertracks
Killertrack! Dieser Name hört sich ziemlich bedrohlich an, aber was steckt eigentlich dahinter? Killertracks sind nichts anderes als mit SYNC-Markierungen beschriebene Tracks. Ein ausschließlich mit SYNC-Markierungen beschriebener Track brachte die früheren Kopierprogramme oder jedes andere Programm, mit dem man versucht, von einen solchen Track Daten zu lesen, unweigerlich zum Absturz, woraus sich wohl auch der Name ableiten läßt. Die Erklärung für dieses Verhalten ist leicht zu geben, doch um sie zu verstehen, müssen wir noch einmal daran erinnern, wie die normale Routine zum Lesen eines Bytes von Diskette aussieht.
L1
CLV BVC L1 LDA $1C01
Byte·Ready·Leitung löschen warten, bis Byte anliegt Byte vom Port holen
Meistens wird vor dem Lesen eines Bytes noch auf die SYNCMarkierung gewartet, die bei einem Killertrack natürlich sofort gefunden wird. Nachdem der Controller der Floppy eine SYNCMarkierung erkannt hat, wird das Lesen von Daten automatisch hardwaremäßig unterbrochen und erst nach Ende des Signals wieder freigegeben. Die Byte-Ready-Leitung ist somit während des Anliegens einer SYNC-Markierung gesperrt. Wenn wir uns jetzt das kleine Teilprogramm ansehen, so erkennen wir, daß das Programm zwangsläufig in einer Endlosschleife laufen muß.
300
Das große Anti-Cracker-Buch
Die Kopierprogramme, die die Diskette nicht auf Existenz eines solchen Tracks überprüfen, werden automatisch "gekillt". Kopierprogramme heutigen Standards lassen sich von reinen Killertracks nicht mehr beeindrucken, verarbeiten aber Abwandlungen eines solchen Tracks nicht so souverän, wie man es erwarten könnte. Zum Auftragen eines Killertracks reicht ein Aufruf einer Unterroutine des Formatprogramms, welche einen Track wie beschrieben mit SYNCs löscht. Diese Routine läßt sich mit 'JMP $FDA3' in der Floppy aufrufen.
6.6.2
Verlängerte SYNC-Markierungen
Einen sehr einfachen, aber trotzdem sehr guten Kopierschutz erhält man durch das Einsetzen von leicht veränderten Killertracks. Diese Veränderung sieht so aus, daß man einen Track mit SYNCs löscht, um danach einige wenige Daten auf den Track zu schreiben. Das Abfrageprogramm prüft sowohl die Richtigkeit der Daten als auch die Länge der SYNC-Markierung. Uns ist kein Programm bekannt, das es schafft, einen solchen Track zu kopieren, was weniger an der· Raffiniertheit des Schutzes als an der mangelnden Qualität der Kopierprogramme liegt. Die Kopierprogramme, die es schaffen, die Daten, die auf den Track geschrieben wurden, zu kopieren, versagen, wenn es um die Länge der SYNC-Markierung geht, und diejenigen, die es in etwa schaffen, die Länge der SYNC-Markierung zu kopieren, kopieren die Daten nicht richtig. Das Ergebnis ist, daß der Schutz trotz seiner Einfachheit nicht kopiert werden kann. Es folgt das Programm, mit dem Sie den Schutz auftragen können. Es wird ab $0527 gestartet: 0500 0503 0505 0507
LDA AND ORA STA
$1COO #$9F #$08 $1COO
Control-Port laden Speed auf 0 stellen und LED anschalten Wert speichern
Diskettenkopierschut z 050A 0500 050F 0512 0514 0515 0518 0519 051B 0510 0520
JSR lOX lOA BVC ClV STA INX CPX BNE JSR JMP
SFOA3 #SOO S0523,X S0512 S1C01 #S06 S050F SFEOO SF09E
301
Track mit SYNC löschen Zähler für Byte-Anzahl auf Null stellen Bytes holen warten, bis Byte geschrieben wird Byte-Ready-leitung löschen Byte zum R/W-Kopf schicken Zähler erhöhen vergleiche, ob alle Bytes geschrieben verzweige, wenn noch Bytes zu schreiben R/W-Kopf auf lesen schalten Rücksprung
0523 53 54 55 56 A9 Die zu schiebenden Bytes 0527 0529 052B 0520 052F 0531 0533
lOA STA lOA STA lOA BMI RTS
#S24 SOA #SEO S02 S02 S052F
Track-Nummer laden und übergeben Job-Code 'EO' laden und in Job-Speicher schreiben Rückmeldung abwarten verzweige, wenn keine Rückmeldung Rücksprung
Im Anschluß folgt wieder unserer BASIC-Loader: 100PEN1,8,15 20 READ X:IF X=-1THEN 100 30 SU=SU+X:PRINT#1,"M-W"CHRS(N)CHRS(5)CHRS(1)CHRS(X) 40 N=N+1:GOTO 20 100 IF SU <> 5576 THENPRINT"FEHlER IN OATAS":STOP 110 INPUT "WELCHER TRACK";T 120 PRINT#1,"M-W"CHR$(40)CHRS(5)CHRS(1)CHRS(T) 130 PRINT#1,"M-E"CHR$(39)CHRS(5) 140 ClOSE1 3020ATA173, 0,28,41,159, 9, 8,141, 0, 28,32,163,253,162, 0,189, 35, 5 304 OATA 80,254,184,141, 1, 28,232,224, 6,208,242,32, 0,254, 76,158, 253, 83 306 OATA 84,85,86,169,36,133, 10,169,224,133, 2,165, 2,48,252, 96,
-,
302
Das große Anti-Cracker-Buch
Folgend finden Sie das entsprechende Programm zur Schutzabfrage. Als erstes stellen wir Ihnen das Maschinen-Listing vor. Es liegt ab $0600 im RAM der Floppy und wird bei $064A gestartet. 0600 LDA S1COO 0603 AND #$9F 0605 ORA #$08 0607 STA S1COO 060A LDA S1COO 060D BMI S060A 060F CLV 0610 LDA S1C01 0613 LDX #$00 0615 BVC S0615 0617 CLV 0618 LDA S1C01 0618 CMP S0646,X 061E BNE S0642 0620 INX 0621 CPX #$05 0623 BNE S0615 0625 LDA S1COO 0628 BMI S0625 062A LDX #$00 062C LDY #$00 062E INX 062F BNE S0632 0631 INY 0632 LDA S1COO 0635 BPL S062E 0637 CPY #$38 0639 BCC S0642 063B LDA #$FF 063D STA S10
Control·Port laden Speed auf 0 stellen und LED anschalten Wert wieder speichern Control·Port laden warte, bis SYNC gefunden Byte·Ready-Leitung löschen Byte vom Port holen Zähler für Bytes auf 0 stellen warte, bis Byte anliegt Byte-Ready-Leitung löschen Byte vom Port holen Byte mit Tabelle vergleichen verzweige, wenn Bytes ungleich (Fehler) Zähler erhöhen vergleiche, ob alle Bytes gelesen verzweige, wenn noch Bytes zu lesen Control-Port laden warte, bis SYNC gefunden Zähler für SYNC Länge löschen Zähler erhöhen verzweige, wenn kein Oberlauf HIGH-Byte erhöhen Control-Port laden verzweige, wenn SYNC noch anliegt HIGH-Byte mit 38 vergleichen Fehler, wenn Wert kleiner positive Rückmeldung laden und übergeben
Diskettenkopierschutz 063F JMP SFD9E 0642 LDA #SOO 0644 BEQ S063D
303
Rücksprung negative Rückmeldung laden unbedingter Sprung
0646 53 54 55 56 A9 Bytes mit denen verglichen wird 064A 064C 064E 0650 0652 0654 0656
LDA STA LDA STA LDA BMI RTS
#S24 SOC #SEO S03 S03 S0652
Track-Nummer laden und übergeben Job-Code 'EO' laden und in Job-Speicher schreiben Job-Speicher laden warte, bis Job ausgeführt Rücksprung
Als nächstes folgt der zugehörige BASIC-Loader: 100PEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M -W"CHRS(N)CHR$(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 9624 THEN PRINT"ERROR IN DATAS":STOP 110 INPUT" WELCHER TRACK";T 120 PRINT#1,"M-W"CHR$(75)CHR$(6)CHR$(1)CHR$(T) 135 PRINT#1,"M-E"CHRS(74)CHR$(6) 140 FORN=1TO 500:NEXT 145 PRINT#1,"M-R"CHRS(16)CHRS(0)CHR$(1) 150 GET#1,AS:A=ASC (AS+CHRS(O» 170 IFA =255 THENPRINT"SCHUTZ OK":END 180 PRINT"SCHUTZ ERROR":END 302 DATA173, 0,28,41,159, 9, 8,141, 0, 28,173, 0, 28, 48,251,184, 173, 1 304 DATA 28,162, 0,80,254,184,173, 1,28,221, 70, 6,208,34,232,224, 5,208 306 DATA240,173, 0,28,48,251,162, 0,160, 0,232,208, 1,200,173, 0, 28, 16 308 DATA247,192, 56,144, 7,169,255,133, 16,76,158,253,169, 0,240,247, 83, 84 310 DATA 85,86,169,34,133, 12,169,224,133, 3,165, 3,48,252,96, -1
304 6.6.3
Das große Anti-Cracker-Buch Der 3000-SYNC-Schutz
Ein weiteres hervorragendes Schutzsystem, das auf der Änderung der SYNC-Markierung basiert, soll hier besprochen werden. Es handelt sich dabei um das Anlegen von zu vielen kurzen SYNCMarkierungen. Aufgrund ihrer Kürze und ihrer Dichte passen bei diesem System über 3000 SYNC-Markierungen auf einen Track (bei einem normalen Track sind es maximal 42). Schon an hand dieser Aussage werden Sie sich vorstellen können, daß ein Kopierprogramm mit diesem Track Probleme bekommen kann. Die SYNC-Markierungen sind so angelegt, daß zwischen den einzelnen Markierungen nur eine 13 Bit lange Nicht-SYNCZone liegt. Die Zonen zwischen den SYNC-Markierungen sind bis auf eine gleich. Diese eine abweichende Lücke dient zur Orientierung auf dem Track. Um den Schutz zu prüfen, wird gewartet, bis die erwähnte abweichende Lücke gefunden wird. Daraufhin wird die Anzahl der SYNC-Markierungen gezählt, bis der Ausgangspunkt wieder erreicht ist. Die Anzahl der gefundenen SYNC-Markierungen darf nicht von der beim Auftragen des Schutzes gezählten Anzahl abweichen. Warum es für das Kopierprogramm so gut wie unmöglich ist, den Track zu kopieren, ist recht einleuchtend. Aufgrund der Laufwerkschwankungen (die nicht zu vermeiden sind) hat jeder Track beim erneuten Schreiben desselben eine andere Anzahl von Bytes. Das zweite Problem, das das Kopierprogramm zu lösen hat, ist, einen Anhaltspunkt auf diesem Track zu finden, denn die kleine Abweichung in der Lücke, die uns als Orientierung dient, ist schwer zu finden, vor allem, wenn man nicht weiß, wonach man suchen soll. Bis heute ist kein Kopierprogramm in der Lage, einen solchen Track originalgetreu zu kopieren. Nachdem wir uns mit dem Abfragen des Kopierschutzes befaßt haben, wenden wir uns dem Auftragen des Schutzes zu. Beim Erstellen des Tracks ist einiges zu beachten. Aufgrund der Tatsache, daß die Byte-Anzahl beim Schreiben variiert, wird ein Zählen der SYNC-Markierungen auch schon beim Auftragen des
Diskettenkooierschutz
305
Schutzes nötig, um die entsprechende Anzahl danach an das Prüfprogramm zu übergeben. Zusätzlich zu der Anzahl der SYNCs muß noch unser Markierungs-Byte bei jedem Auftragen des Schutzes neu ermittelt werden. Dieses Byte ist ebenfalls variabel. Durch die sich bei jedem Auftragen des Schutzes ändernden Parameter sind wir bei diesem System andere Wege gegangen, um ein komfortables Arbeiten zu gewährleisten. Das folgende BASIC-Programm ermöglicht die Wahl des Tracks, auf dem der Schutz aufgetragen werden soll. Anders als bei den bisherigen Beispielen wird es nicht nötig sein, die Kopierschutzabfrage bei jedem Abfragen in die Floppy zu schicken. Stattdessen wird sie hier auf einem zuvor wählbaren Track und Sektor gespeichert. Um den Kopierschutz abzufragen, muß lediglich der entsprechende Sektor in den Puffer der Floppy geladen werden, um dort mit 'M-E' ab $0503 gestartet zu werden. Die Rückmeldung nach der Überprüfung des Schutzes wird auch, wie bei den anderen Beispielen, in $10 der Floppy abgelegt. Analog zu den bisherigen Programmen bedeutet die Rückmeldung $00, daß eine Kopie vorlag. $FF kennzeichnet das 'Original' . Doch jetzt wollen wir uns nach dieser ausführlichen Einleitung das BASIC-Programm ansehen, das wie beschrieben den Kopierschutz sowie die Abfrage auf Diskette installiert.
10 PRI NTCHRSC 147); "BITTE \JARTEN" 20 FORI=1T0306STEP15:FORJ=OT014:READAS:B$=RIGHTS(AS,1) 30 A=ASC(AS)·48:IFA>9THENA=A-7 40 B=ASCCB$)-48:IFB>9THENB=B-7 50 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A:NEXT:READA:IFC= A THENC=O:NEXT : GOT070 60 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 70 PRINTCHRS(147)CHRS(17)CHRS(17):INPUT" ERROR TRACK";T 80 IFT>410RT<1THENPRINTCHRS(145)CHR$(145)CHR$(145):GOT070 90 PRINT:PRINT:INPUT" TRACK,BLOCK DER KONTROLLE";TK,BK 100 PRINT:PRINT:PRINT" EINGABEN IN ORDNUNG?"
306
Das große Anti-Cracker-Buch
110 GETA$:IFA$=""THEN110 120 IFAS="N"THENFORK=1TO 8:PRINT CHRS(147):NEXT:GOT070 130 PRINT:PRINT:PRINT" DISKETTE EINLEGEN" 140 GETAS:IFA$=""THEN140 150 PR 1NTCHR$( 147) i "BITTE IoIARTEN" 1600PEN1,8,15 170 POKE49159,T:POKE49350,T 180 FORN=OT0191: PRI NT#1 , "M-IoI"CHR$(N )CHR$(5 )CHR$( 1)CHR$( PEEK(49152+N»: N EXT 190 PRINT#1,"M-E"CHR$(3)CHR$(5) 200 PRINT#1,"M-R"CHR$(254)CHR$(5)CHR$(2) 210 GET#1,A$:POKE49437,ASC(A$+CHR$(0» 220 GET#1,A$:POKE49441,ASC(A$+CHR$(0» 230 PRINT#1,"M-R"CHR$(128)CHR$(5)CHR$(1) 240 GET#1,A$:POKE49394,ASC(A$+CHR$(0» 250 POKE49417,ASC(A$+CHRS(0» 260 FORN=OT0116:PRINT#1, "M-IoI"CHRS(N)CHR$(5 )CHR$( 1 )CHRS( PEEK(49343+N»:N EXT 270 OPEN2,8,2,"#2" 280 PRINT#1,"U2 2 O"iSTR$(TK)iSTR$(BK) 290 PRINT#1,"U1 2 O"iSTR$(TK)iSTR$(BK) 300 PRINT#1,"M -E"CHRS(3)CHR$(5) 310 PRINT#1,"M-R"CHR$(16)CHR$(0)CHR$(1) 320 PRINTCHR$(147) 330 GET#1,AS:IFASC(A$+CHR$(0»<>255THENPRINT:PRINT" SCHUTZ ERROR":GOT03 50 340 PRINT:PRINT:PRINT:PRINT" SCHUTZ OK" 350 PRINT:PRINT:PRINT" IoIEITERE DISKETTE?" 360 GETA$:IFA$=""THEN360 370 IFA$="J"THEN GOT070 380 END 390 PRINT:PRINT:PRINT" ERNEUTER VERSUCH?" 400 GETA$:IFA$=""THEN400 410 IFAS="J"THEN160 420 DATA20,13,05,20,42,DO,A9,24,85,OA,A9,EO,85,02,A5, 123 430 DATA02,30,FC,60,AD,00,1C,29,9F,09,08,8D,00,1C,20, 249 440 DATAOE,FE,AO,10,A2,00,A9,27,8D,01,1C,50,FE,B8,A9, 135 450 DATAFF,80,01,1C,50,FE,B8,CA,DO,ED,88,DO,EA,A9,FF, 32 460 DATA8D,01,1C,50,FE,B8,50,FE,B8,SO,FE,B8,A9,33,8D, 37 470 DATA01,1C,50,FE,B8,20,00,FE,AD,00, 1C,30,FB,B8,AD, 154
307
Diskettenkopierschutz 480 490 500 510 520 530 540 550 560 570 580 590 600 610 620
DATA01,1C,A2,00,50,FE,B8,AD,01,1C,C9,27,FO,EB,80, DATA80,05,80,97,05,AD,00,1C,30,FB,B8,AD,01,1C,A2, DATAOO,50,FE,B8,AD,01,1C,C9,37,DO,EB,A2,00,AO,00, DATAAD,00,1C,30,FB,B8,AD,01,1C,50,FE,B8,AD,01,1C, DATAC9,37,FO,OA,E8,DO,EA,C8,DO,E7,A9,03,FO,19,8E, DATAFE,05,8C,FF,05,CO,OB,90,09,CO,OE,BO,05,A9,01, DATA4C,69,F9,C6,37,DO,B3,A9,03,DO,F5,20,13,05,20, DATA42,DO,A9,24,85,OA,A9,EO,85,02,A5,02,30,FC,60, DATAA9,05,85,37,AD,00,1C,29,9F,09,08,80,00,1C,AD, DATAOO,1C,30,FB,B8,AD,01,1C,A2,00,50,FE,B8,AD,01, DATA1C,C9,37,DO,EB,A2,00,AO,00,AD,00,1C,30,FB,B8, DATAAD,01,1C,50,FE,B8,AD,01,1C,C9,37,FO,OA,E8,DO, DATAEA,C8,DO,E7,A9,00,FO,19,8E,FE,06,8C,FF,06,EO, DATA31,DO,OB,CO,OC,DO,07,A9,FF,85,10,4C,9E,FD,C6, DATA37,DO,B1,A9,00,FO,F3,00,00,00,00,00,00,00,00,
231 198 205 70 94 36 247 177 98 31 197 76 30 153 68
Die in den Datazeilen stehenden Daten entsprechen den Maschinenspracheteilen (Auftragungs- und Abfrageprogramm), die zuerst in den Speicherbereich ab $COOO des C64 geschrieben werden, von wo sie nach der Übergabe der Parameter in die Floppy geschrieben und dort gestartet werden. Auf die einzelnen Maschinenspracheprogramme werden wir später noch eingehen. Gleich im Anschluß an dieses Programm stellen wir Ihnen das entsprechende Abfrageprogramm vor.
o PRINTCHRS(147) 5 INPUT"TRACK,SEKTOR DES ABFRAGEBLOCKS " i T,S 100PEN1,8,15:0PEN2,8,2,1#2" 20 PRINT#1,"U1 2 O"iSTRS(T)iSTRS(S) 30 PRINT#1,IM-E"CHRS(3)CHRS(5) 40 PRINT#1,IM-R"CHRS(16)CHRS(0)CHRS(1) 50 GET#1,AS:IFASC(AS+CHRS(0»<>255THENPRINT" SCHUTZ ERROR":END 60 PRINT" SCHUTZ OK"
Der einzugebende Track und Sektor richtet sich nach der Eingabe, die Sie beim Erstellen des Schutzes gemacht haben. Für diejenigen, die sich noch näher mit diesem Schutz befassen wollen, folgen nun die zugehörigen Assemblerlistings zum Auf-
Das große Anti-Cracker-Buch
308
tragen und Abfragen desselben. Beide Programme werden ab $0503 gestartet. Als erstes zeigen wir Ihnen das Programm zum Auftragen des Schutzes. 0500 JSR S0513
'JMP' zum Überspringen des Hauptprogramms
Das Programm wird hier gestartet: 0503 0506 0508 050A 050C 050E 0510 0512
JSR LOA STA LOA STA LOA BMI RTS
S0042 #$24 SOA #$EO S02 S02 S050E
0513 LOA S1COO 0516 ANO #$9F 0518 ORA #$08 051A STA S1COO 0510 JSR SFEOE 0520 LOY #$10 0522 LOX #$00 0524 LOA #$27 0526 STA S1C01 0529 BVC S0529 052B CLV 052C LOA #$FF 052E STA S1C01 0531 BVC S0531 0533 CLV 0534 OEX 0535 BNE S0524 0537 OEY 0538 BNE S0524 053A LOA #$FF 053C STA S1C01 053F BVC S053F
Oisk initialisieren Track -Nummer laden und an Job übergeben Job-Code 'EO' laden und in Job-Speicher schreiben auf Rückmeldung vom Job warten verzweige, wenn keine Rückmeldung Rücksprung Control-Port laden Speed auf Null stellen und LEO anschalten Wert speichern Track mit S55 löschen Zähler für Anzahl der zu schreibenden S27, SFF Blöcke auf 4096 stellen erstes zu schreibendes Byte laden und zum R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen zweites zu schreibendes Byte laden und zum R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler verringern verzweige, wenn kein Unterlauf HIGH-Byte verringern verzweige, wenn noch Bytes zu schreiben SFF (SYNC-Byte) laden und zum R/W-Kopf schicken warten, bis Byte geschrieben
Diskettenkopierschutz 0541 0542 0544 0545 0547 0548 054A 0540 054F
CLV BVC CLV BVC CLV LOA STA BVC CLV
$0542 $0545 #$33 $1C01 $0540
309
Byte-Ready-Leitung löschen warten, bis Byte geschrieben Byte-Ready-Leitung löschen warten, bis Byte geschrieben Byte-Ready-Leitung löschen Markierungs-Byte laden und zum R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen
Hier beginnt der Überprüfungs teil der zuvor geschriebenen Spur: 0550 JSR $FEOO 0553 LOA $1COO 0556 BMI S0553 0558 CLV 0559 LOA $1C01 055C NOP 0550 NOP 055E BVC S055E 0560 CLV 0561 LOA $1C01 0564 CMP #$27 0566 BEQ $0553 0568 STA S0580 056B STA $0597 056E LOA S1COO 0571 BMI S056E 0573 CLV 0574 LOA $1C01 05n NOP 0578 NOP 0579 BVC $0579 057B CLV 057C LOA S1C01 057F CMP #$37 0581 BNE $056E 0583 LOX #$00 0585 LOY #$00
R/W-Kopf auf Lesen schalten Control-Port laden warten, bis SYNC-Signal anliegt Byte-Ready-Leitung löschen Port freimachen
warten, bis Byte gelesen Byte-Ready-Leitung löschen Byte vom Port holen mit Normalwert vergleichen weitersuchen, bis Markierung gefunden Markierungs-Byte an die entsprechenden Stellen schreiben Control-Port laden warten, bis SYNC-Signal anliegt Byte-Ready-Leitung löschen Port freimachen
warten, bis Byte anliegt Byte-Ready-Leitung löschen Byte vom Port holen mit Markierungs-Byte vergleichen weitersuchen, wenn kein Markierungs-Byte Zähler für Anzahl der SYNCs auf Null setzen
Das große Anti-Cracker-Buch
310 0587 058A 058C 0580 0590 0592 0593 0596 0598 059A 059B 0590 059E 05AO 05A2
LOA BMI CLV LOA BVC CLV LOA CMP BEQ INX BNE INY BNE LOA BEQ
S1COO S0587
S0587 #$03 S05BO
Control -Port laden warten, bis SYNC-Signal anliegt Byte-Ready-Leitung löschen Port freimachen warten, bis Byte gelesen Byte-Ready-Leitung löschen Byte vom Port holen mit Markierungs-Byte vergleichen weitersuchen, bis Markierungs-Byte gefunden Zähler für SYNCs erhöhen verzweige, wenn kein überlauf HIGH-Byte des Zählers erhöhen verzweige, wenn kein Überlauf sonst Fehlermeldung laden und übergeben, Rücksprung
05A4 05A7 05AA 05AC 05AE 05BO 05B2 05B4 05B7 05B9 05BB 05BO
STX STY CPY BCC CPY BCS LOA JMP OEC BNE LOA BNE
S05FE S05FF #$OB S05B7 #$OE S05B7 #$01 SF969 S37 S056E #$03 S05B4
Zählerwerte speichern Zähler auf Zulässigkeit prüfen erneuter Schreibversuch, wenn falscher Wert Zähler auf Zulässigkeit prüfen erneuter Versuch, wenn nicht zulässig positive Rückmeldung laden und übergeben , Rücksprung Zähler für Fehlversuche verringern verzweige, wenn noch Versuche erlaubt, sonst negative Rückmeldung laden und Übergeben, Rücksprung
S1C01 S0590 S1C01 #$37 S05A4 S0587
Nach dem Auftragungsprogramm nun das Abfrageprogramm, welches auch ab $0503 im Speicher der Floppy gestartet wird: 0500 JSR S0513
'JMP' zum Überspringen des Hauptprogramms
Diskettenkopierschutz Das Programm wird hier gestartet: 0503 JSR $0042 Disk initialisieren 0506 LDA #$24 Track-Nummer laden 0508 STA SOA und an Job übergeben 050A LDA #$EO JOB-Code 'EO' laden und in Job-Speicher schreiben 050C STA S02 warten, bis Rückmeldung 050E LDA S02 0510 BMI S050E vom Job erfolgt Rücksprung 0512 RTS 0513 LDA #$05 Zähler für Anzahl der Fehlversuche laden und Wert speichern 0515 STA S37 0517 LDA S1COO Control-Port laden 051A AND #$9F Speed auf Null stellen 051C ORA #$08 und LED anschalten 051E STA S1COO Wert speichern 0521 LDA S1COO Control-Port laden warten, bis SYNC-Signal anliegt 0524 BMI S0521 Byte-Ready-Leitung löschen 0526 CLV Port wieder freimachen 0527 LDA S1C01 052A NOP 052B NOP 052C BVC S052C warten, bis Byte anliegt Byte-Ready-Leitung löschen 052E CLV Byte vom Port holen 052F LDA S1C01 vergleiche, ob Markierungs-Byte gelesen 0532 CMP #$37 erneut lesen, wenn nicht gefunden 0534 BNE S0521 Zähler für Anzahl der SYNC-Markierungen 0536 LDX #$00 0538 LDY #$00 auf Null stellen 053A LDA S1COO Control-Port laden 053D BMI S053A warten, bis SYNC-Signal anliegt Byte-Ready-Leitung löschen 053F CLV Port freimachen 0540 LDA S1C01 0543 BVC S0543 warten, bis Byte gelesen Byte-Ready-Leitung löschen 0545 CLV 0546 LDA S1C01 Byte vom Port holen mit Markierungs-Byte vergleichen 0549 CMP #$37 054B BEQ S0557 verzweige, wenn gefunden Zähler für Anzahl der SYNCs erhöhen 054D INX 054E BNE S053A verzweige, wenn kein überlauf
311
Das große Anti-Cracker-Buch
312 0550 0551 0553 0555 0557 055A 0550 055F 0561 0563 0565 0567 0569 056C 056E 0570 0572
INY BNE LOA BEQ STX STY CPX BNE CPY BNE LOA STA JMP OEC BNE LOA BEQ
6.6.4
S053A #$00 S0570 S06FE S06FF #$31 S056C #$OC S056C #$FF S10 SF09E S37 S0521 #$00 S0567
HIGH-Byte des Zählers erhöhen verzweige, wenn kein Überlauf sonst negative Rückmeldung laden unbedingter Sprung Zählerwerte speichern Zähler auf Richtigkeit prüfen verzweige, wenn Wert falsch HIGH-Byte des Zählers überprüfen verzweige, wenn Wert falsch positive Rückmeldung laden und Wert übergeben Rücksprung Zähler für Fehler verringern erneuter Versuch, wenn noch erlaubt sonst negative Rückmeldung laden unbedingter Sprung
Daten ohne SYNC-Markierung
In diesem Kapitel soll ein Kopierschutz besprochen werden, der mit gutem Erfolg professionell eingesetzt wird. Er basiert auf der Tatsache, daß Daten ohne eine SYNC-Markierung auf einen Track geschrieben, aber trotzdem ohne weiteres abgefragt werden können. Dieses System ist ohne aufwendige Programme zu verwirklichen. Wahrscheinlich werden Sie jetzt verständnislos mit dem Kopf schütteln, denn ohne SYNC-Markierung läuft bei der FloppyProgrammierung normalerweise nichts. Kommen wir jetzt zur Erklärung des Systems. Ein Track wird von allen SYNC-Markierungen befreit und vollständig mit einer bestimmten Byte-Kombination beschrieben. Beginnt man nun, die Bytes auf dem Track zu lesen, findet man irgendwann die gewünschte Byte-Kombination. Das Ganze hat jedoch einen Haken, denn nach diesem System kann es sehr lange dauern, bis die richtigen Daten gefunden werden. Warum dies so ist, läßt sich an einem Beispiel zeigen.
Diskettenkopierschutz
313
Stellen wir uns vor, wir hätten einen Track vollständig mit dem Byte $33 beschrieben, dessen Bitkombination wie folgt aussieht: S33 S33 S33
= 00110011
00110011 00110011
Wenn wir anfangen, Daten von diesem Track zu lesen, ist jedoch nicht gewährleistet, daß die ersten Bits, die der Controller registiert, auch wirklich zwei Null-Bits sind. Sollte das nicht der Fall sein, d.h. findet er beispielsweise zuerst die Bits 0 und 1, dann wird immer anstelle der $33 eine $66 gefunden. Genauso ist es möglich, daß $CC oder $99 erkannt wird. Da es beim Lesen von Daten immer wieder dazu kommt, daß Bits "verschluckt" werden, falls zuvor der Controller nicht mit einer SYNC-Markierung synchronisiert wurde, kommt es somit auch zu einer Veränderung der Daten, die auf dem Track gefunden werden, so daß auch irgendwann das Byte $33 gefunden wird, was aber unter Umständen erst recht spät geschieht. Sie werden sich sicher vorstellen können, daß das Auffinden einer ganzen Byte-Kombination noch zeitaufwendiger ist. Es kann vorkommen, daß eine Byte-Kombination von drei Bytes erst nach 50000 oder mehr Leseversuchen gefunden wird. Aus diesem Grund sind wir bei unserem Schutz andere Wege gegangen. Wir haben nach mehrmaligem Schreiben unserer Byte-Kombination die Bits der Daten um ein Bit verschoben und das letzte Bit am Ende wieder angehängt. Übertragen auf das Beispiel mit der $33 bedeutet das, daß wir den Track mit $33, $66, $CC und $99 beschreiben. S33 = 00110011 S66 = 01100110 see = 11001100 S66 = 10011001
314
Das große Anti-Cracker-Buch
Wenn wir nach diesem Prinzip vorgehen, muß der Controller, unabhängig davon, welche Bits er zuerst gefunden hat, sehr schnell unsere gewünschten Daten finden. Unser Kopierschutzerstellungsprogramm befreit einen Track von allen SYNC-Markierungen und schreibt 42mal die Bytefolge $2F, $53, $77 auf den Track. Daraufhin werden die Bits der drei Bytes, wie geschildert, um ein Bit verschoben und die neuentstandenen Bytes erneut 42mal geschrieben. Diese Prozedur wiederholt sich so lange, bis jede mögliche Bit-Kombination auf Disk geschrieben wurde. Im folgenden zeigen wir Ihnen das entsprechende Programm, das einen solchen Kopierschutz erzeugt. Das Programm wird ab $054B in der Floppy gestartet. 0500 0503 0505 0507 050A 0500 050F 0511 0513 0515 0517 0519 051B 051D 051F 0522 0524 0525 0527 052A 052C 0520 052F
LOA ANO ORA STA JSR LDA STA LDA STA LDA STA LOX LDY LOA STA BVC CLV LOA STA BVC CLV LDA STA
S1COO #S9F #S08 S1COO SFEOE #S2F S06 #S53 S07
#sn S08 #S30 #S2A S06 S1C01 S0522 S07 S1C01 S052A S08 S1C01
Control-Port laden Speed auf Null stellen LEO anschalten Wert speichern Track mit S55 beschreiben erstes zu schreibendes Byte speichern zweites Byte speichern drittes Byte speichern Zähler für Anzahl der Byte-Kombinationen Zähler für Anzahl der gleichen Byte-Folgen ersten Wert laden und zum R/W·Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung l öschen zweites Byte holen und zum R/W-Kopf schicken warten, bis Byte geschrieben Byte-Ready-Leitung löschen drittes Byte laden und zum R/W-Kopf schicken
Diskettenkopierschutz 0532 0534 0535 0536 0538 053A 053C 053E 0540 0542 0543 0545 0548
BVC CLV OEY BNE ASL ROL ROL BCC INC OEX BNE JSR JMP
S0532
S0510 S08 S07 S06 S0542 S08 S051B SFEOO SF09E
315
warten, bis Byte geschrieben Byte-Ready-Leitung löschen Zähler verringern verzweige, wenn noch Bytes zu schreiben zu schreibende Werte um ein Bit nach links schieben verzweige, wenn letztes Bit gelöscht war sonst Bit vorne wieder einfügen Zähler verringern verzweige, wenn noch Bytes zu schreiben R/W-Kopf auf Schreiben schalten Rücksprung
Das Programm wird hier gestartet: 054B 0540 054F 0551 0553 0555 0557
LOA STA LOA STA LOA BMI JMP
#S24 SOA #SEO S02 S02 S0553 $0042
Track-Nummer laden und übergeben Job-Code 'EO' laden und in Job-Speicher schreiben warte, bis Rückmeldung erhalten verzweige, wenn noch keine Rückmeldung Oisk initialisieren, Rücksprung
Im Anschluß an das Assembler-Listing folgt nun der dazugehörige BASIC-Loader, der eine Eingabe des Tracks erlaubt, auf den der Schutz aufgetragen werden soll:
100PEN1,8,15,"I" 20 REAO X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-W"CHRS(N)CHRS(5)CHRS(1)CHRS(X) 40 N=N+1:GOTO 20 100 IFSU <> 9205 THEN PRINT"ERROR IN OATAS":STOP 110 INPUT "TRACK-NUMMER";T 120 PRINT#1,"M-W"CHRS(76)CHRS(5)CHRS(1)CHR$(T) 130 PRINT#1,"M -E"CHR$(75)CHR$(5)
Das große Anti-Cracker-Buch
316
150 ClOSE1 302DATA173, 0,28,41,159, 9, 8,141, 0,28,32, 14,254,169,47,133, 6,169 304 DATA 83,133, 7,169,119,133, 8,162,48,160,42,165, 6,141, 1,28, 80,254 306DATA184,165, 7,141, 1,28,80,254,184,165, 8,141, 1, 28, 80,254, 184,136 308 DATA208,229, 6, 8, 38, 7, 38, 6,144, 2,230, 8,202,208,214, 32, 0,254 310 DATA 76,158,253,169,36,133, 10,169,224,133, 2,165, 2,48,252,76, 66,208 312 DATA -1
Unser Abfrageprogramm arbeitet wie bereits beschrieben. Es fängt an, Daten auf dem Track zu lesen, und vergleicht diese auf ihre Richtigkeit. Sollte nach ca. einer Umdrehung noch nicht die richtige Byte-Kombination gefunden worden sein, so wird eine negative Rückmeldung an den Computer übergeben. Anderenfalls wird noch überprüft, ob sich auf dem Track eine SYNC-Markierung befindet. Beim Auffinden einer solchen wird ebenfalls eine negative Rückmeldung übergeben. Als nächstes zeigen wir Ihnen das dazugehörige Abfrageprogramm, das ab $0644 gestartet wird.
0600 0603 0605 0607 060A 060C 060E 0610 0612 0614 0615 0618
lDA AND ORA STA lDX lDA STA lDY BVC ClV lDA CMP
$1COO #$9F #$08 $1COO #$20 #$00 $11 #$02 $0612
Control-Port laden Speed auf Null stellen lED anschalten Wert speichern Zähler für Fehlversuche lOW-Byte des Zählers setzen Zeiger auf zu vergleichendes Byte warten, bis Byte gelesen Byte-Ready-leitung löschen $1C01 Byte vom Port laden $0653,Y mit Byte aus Tabelle vergleichen
Diskettenkooierschutz 061B BNE S0639 061D DEY 061E BPL S0612
317
verzweige, wenn ungleich sonst Zeiger auf Byte verändern warten, bis alle Bytes verglichen
Im folgendem Programmteil wird die Existenz einer SYNC-Markierung überprüft. 0620 0622 0624 0627 0629 062A 062B 062C 062D 062F 0630 0632 0634 0636 0639 063B 063D 063E 0640 0642
LDX LDY LDA BPL NOP NOP NOP DEY BNE DEX BNE LDA STA JMP DEC BNE DEX BNE LDA BEQ
#S20 #SOO S1COO S0640
S0624 S0624 #SFF S11 SFD9E S11 S0613 S0613 *'00 S0634
Zähler für Länge des Tracks LOW-Byte des Zählers Control-Port laden Fehler, wenn SYNC gefunden
Zähler verringern verzweige, wenn kein Unterlauf HIGH-Byte verringern verzweige, wenn noch nicht fertig positive Rückmeldung laden und übergeben Rücksprung Zähler für Fehler verringern verzweige, wenn noch weitere Versuche HIGH"Byte des Zählers verringern verzweige, wenn noch weitere Versuche negative ROckmeldung laden unbedingter Sprung
Das Programm wird hier gestartet: 0644 0646 0648 064A 064C 064E 0650
LDA STA LDA STA LDA BMI JMP
#S24 SOC #SEO S03 S03 S064C $0042
Track-Nummer laden und übergeben Job-Code 'EO' laden und in Job-Speicher schreiben Rückmeldung abwarten verzweige, wenn noch keine Rückmeldung Disk initialisieren, Rücksprung
318 0653
Das große Anti-Cracker-Buch
n 53 2F
Daten, die auf dem Track gesucht werden
Nun folgt wieder der dazugehörige BASIC-Loader: 10 OPEN1,8,15,"I" 20 READ X:IF X=-1THEN 100 25 SU=SU+X 30 PRINT#1,"M-~"CHR$(N)CHR$(6)CHR$(1)CHR$(X) 40 N=N+1:GOTO 20 100 IFSU <> 10459 THEN PRINT"ERROR IN DATAS":STOP 110 INPUT "TRACK-NUMMER";T 120 PRINT#1,"M-~"CHR$(69)CHRS(6)CHR$(1)CHR$(T) 130 PRINT#1,"M-E"CHR$(68)CHR$(6) 140 FORN=1 TO 500:NEXT 150 PRINT#1,"M-R"CHRS(17)CHR$(0)CHR$(1) 160 GET#1,AS 170 IF ASC(AS+CHRS(0»<>255 THENPRINT" SCHUTZ ERROR":END 180 PRINT" SCHUTZ OK" 302 DATA173, 0, 28, 41,159, 9, 8,141, 0, 28,162,128,169, 0,133, 17, 160, 2 304 DATA 80,254,184,173, 1,28,217,83, 6,208, 28,136, 16,242,162,32, 160, 0 306DATA173, 0, 28, 16, 23, 234,234,234,136,208,245,202,208,242,169,255 ,133 308DATA 17,76,158,253,198, 17,208,211,202,208,208,169, 0,240,240,169, 36 310 DATA133, 12,169,224,133, 3,165, 3, 48,252, 76, 66,208,119, 83, 47, -1
6.7
Der "Disketten-Killer"
Falls Sie nicht wissen, wie Sie eine kopierte Diskette zerstören sollen, nachdem festgestellt wurde, daß es sich um eine Kopie handelt, so haben wir hier eine kurze, aber nichtsdestoweniger wirkungsvolle Methode anzubieten_ Bei dieser Methode handelt es sich um einen BASIC-Einzeiler, der den RjW-Kopf der Floppy auf den Schreibbetrieb umschal-
Diskettenkopierschutz
319
tet und dadurch alle Daten löscht, über die sich der Kopf bewegt. Sämtliche Disketten, die in das Laufwerk geschoben werden, werden zerstört, sobald sich der Laufwerksmotor zu drehen beginnt. Das einzige, was Sie vor diesem "Killer" schützt, ist das Ausschalten des Laufwerks. 10 OPEN1,8,15:PRINT#1,"M -W"CHRS(12)CHRS(28)CHRS(1)CHRS(200) :PRINT#1,11" :CLOSE1
Achtung! Dieses Programm zerstört (mindestens) die Tracks 1 bis 18, das heißt, alle darauf befindlichen Daten sind so unwiederbringlich gelöscht, als wenn Sie die Diskette formatiert hätten.
320
Das große Anti-Cracker-Buch
Wie man sich gegen Knackmodule schützt
7. 7.1
321
Wie man sich gegen Knackmodule schützt Einleitung
Im Zuge der in allen Bereichen der Technik um sich greifenden Rationalisierung hat man es vor einigen Monaten geschafft, den ersten Schritt zur Wegrationalisierung der "Knacker" zu tun. Damals erschienen die ersten vollautomatischen Knackmodule. Diese Module bestehen meistens aus einer in ein Gehäuse gepackten Platine mit einem Druckknopf oder Schalter. Man braucht nur das Modul in den Modulport des C64 einzustecken, dann das geschützte Programm zu laden, und nachdem dieses Programm seinen Kopierschutz abgefragt hat, den Knopf zu betätigen. Das Modul speichert dann den gesamten Speicherinhalt des Rechners, oft sogar gepackt und mit einem Schnelllader versehen, auf einer Diskette oder Kassette ab, selbstverständlich ohne Kopierschutz. Wenn man das Programm wieder einlädt und startet, wird der Rechner in denselben Zustand wie zum Zeitpunkt des Knopfdruckes versetzt. Wie man sein Pro gramm gegen solche Module sichert, soll in diesem Kapitel beschrieben werden. 7 .2
Vorläufer der Knackmodule
Wie kam es überhaupt zu der Entwicklung von Knackmodulen? Viele Raubkopierer, denen es zu mühsam war, sich um einen Programmschutz herumzuwinden, gingen dazu über, mit Hilfe eines RE SET -Schalters aus dem laufenden Programm herauszuspringen und die benutzten Teile des Speichers abzuspeichern. Sie brauchten dann nur noch die Startadresse herauszusuchen, und schon war die Kopie fertig. Die Programmierer, die das störte, brachten in ihren Programmen eine 'CBM80'-Kennung unter, wodurch ein RESET wirkungslos wurde. An dieser Stelle tauchten die ersten Module und Betriebssysteme auf, die speziell das "Knacken" unterstützen sollten. Mit ihnen war es möglich, die 'CBM80'-Kennung zu umgehen. Einige unterstützten sogar
322
Das große Anti-Cracker-Buch
das Sichern von Speicher bereichen, die normalerweise bei einem RESET grundsätzlich gelöscht wurden_ Folgende Bereiche sind davon betroffen: $0000-$0101 $0200-$0802 $AOOO $DOOO-$DDOF $FD30-$FD4F
= 0- 257 = 512- 2048 = 40960 = 53248-56591 = 64816-64847
Systemadressen Systemadressen und Bildschirm durch RAH-Test gelöscht I/O-Bereich beim Initialisieren der Vektoren
Gegen die meisten "Knack"-Betriebssysteme kann man sich schon dadurch schützen, indem man wichtige Programmteile im Bereich $0400 (=1024) bis $07FF (=2048) unterbringt_ Dazu muß man aber den Bildschirmspeicher , der normalerweise ab $0400 liegt, in einen freien Teil des Speichers verschieben_ Folgendes Programm legt den Bildschirmspeicher nach $CCOO und den Anfang des BASIC-Speichers nach $0400, was zwei Effekte hat: Einerseits erhält man so ein Kilobyte mehr Speicherplatz für seine Programme, andererseits wird bei einem RESET der Anfang des BASIC-Programms unwiederbringlich gelöscht.
100 105 110 120 125 130 300 301 302 303 304
FORI=1T066STEP15:FORJ=OT014:READA$:BS=RIGHTS(AS,1) A=ASC(A$)-48:IFA>9THENA=A-7 B=ASC(B$)-48:IFB>9THENB=B-7 A=A*16+B:C=(C+A)AND255:POKE49151+I+J,A NEXT:READA:IFC=ATHENC=0:NEXT:SYS49152:NEW PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP DATA78,A2,33,86,01,AO,00,84,FB,A9,DO,85,FC,B1,FB, 153 DATAE6,01,91,FB,86,01,C8,DO,F5,E6,FC,A5,FC,C9,EO, 179 DATADO,ED,A9,37,85,01,58,AD,00,DD,29,FC,BD,00,DD, 148 DATAA9,35,BD,18,DO,A9,CC,8D,88,02,8C,00,04,C8,84, 187 DATA2B,A9,04,85,2C,A9,93,4C,D2,FF,A4,A4,A4,A4,A4, 22
Assemblerlisting: COOO SEI C001 LDX #$33 C003 STX S01
Interrupts sperren Speicherkonfiguration umstellen: Zeichen-ROM einschalten
Wie man sich gegen Knackmodule schützt C005 LOY #SOO C007 STY SFB C009 LOA #$00 COOB STA SFC COOO LOA (SFB),Y COOF INC S01 C011 STA (SFB),Y C013 STX S01 C015 INY C016 BNE scooo C018 INC SFC C01A LOA SFC C01C CHP #SEO C01E BNE SCOOO C020 LOA #S37 C022 STA S01 C024 cu C025 LOA $0000 C028 ANO #SFC C02A STA $0000 C020 LOA #S35 C02F STA $0018 C032 LOA #SCC C034 STA S0288 C037 STY S0400 C03A INY C03B STY S2B C030 LOA #S04 C03F STA S2C C041 LOA #S93 C043 JHP SFF02
323
Schleifenzähler SFB/SFC auf $0000 setzen
Byte aus Zeichen-ROM holen RAH in $0000 einschalten Byte in RAH schreiben Zeichen-ROM einschalten Schleifenzähler LOW erhöhen verzweige, wenn ungleich null Schleifenzähler HIGH erhöhen schon den Bereich SEOOO erreicht? verzweige, wenn nein alte Speicherkonfiguration wieder herstellen Interrupts zulassen VIC auf obersten 16K-Bereich einstellen Bildschirmspeicher bei SCCOO und Zeichengenerator bei $0000 Adresse des Bildschirms dem Betriebssystem mitteilen Null an neuen BASIC-Speicher-Anfang S0401 in den Zeiger auf den BASIC-Anfang S2B/S2C schreiben S93 (=147) ist ASCII-~ert für CLR/HOME BASOUT, hier: Bildschirm löschen
Am besten arbeiten Sie mit dem Programm so, daß Sie den NEW-Befehl in Zeile 125 durch einen LOAD-Befehl ersetzen, der das eigentliche Hauptprogramm in den Speicher bringt_ Dieses sollte als erstes den Befehl 'CLR' ausführen, damit keine Probleme mit Variablenüberlappungen auftreten können_ Außerdem darf dieses Programm ohne das vorhergehende Ladeprogramm nicht lauffähig sein, was sich am einfachsten durch die
324
Das große Anti-Cracker-Buch
Übergabe eines Wertes von dem ersten Programm an das zweite per POKE in eine freie Speicherstelle realisieren läßt. Das ganze könnte zum Beispiel so aussehen: Nach erfolgter Kopierschutzabfrage in dem Teil, der den DA T A - Lader enthält, wird der Befehl 'POKE 2,123' benutzt. Im Hauptprogramm wird dann abgefragt, ob der Wert der Speicherstelle Zwei wirklich gleich 123 ist. Falls nein, wird das Programm abgebrochen. Es ist wohl unnötig, zu erwähnen, daß Sie daran denken, Ihr Programm gegen die Einblicke anderer zu schützen. Gegen gute Knackmodule wird man mit dieser Methode allerdings nicht sehr weit kommen, da diese immer über ein eigenes RAM verfügen, in das sie die Speicherbereiche, die sie benötigen, hinüberretten, also auch den Bildschirmspeicher. Um sich auch hiervor zu sichern, muß man schon schwerere Geschütze auffahren. 7.2
Knackmodule neueren Datums
Alle Schutzmethoden gegen Knackmodule haben eines gemeinsam: sie enthalten eine periodisch oder auch nur an wichtigen Stellen des Programms aufgerufene Schutzabfrage, da ein nur einmalig abgefragter Kopierschutz gegen ein solches Modul sinnlos ist. Es hängt unter Umständen stark von dem Programm ab, wie man diese Abfrage installiert. Zum Beispiel könnte man in einem "Adventure" alle 100 Schritte den Spieler fragen, welches Wort sich auf einer bestimmten Seite der Anleitung an einer bestimmten Stelle befindet. Allerdings lassen sich Anleitungen ebenfalls kopieren, wenn auch meist mit größerem Aufwand als Programme. Daher gehen einige Firmen dazu über, ihren Programmen eine spezielle Linse beizufügen, ohne die es nicht möglich ist, die von Zeit zu Zeit verschlüsselt angezeigten Buchstaben zu entziffern. Solche Schutzsysteme stellen nicht nur ein Handikap gegenüber den "Knackern" dar, sondern auch eine Zumutung gegenüber dem Anwender. Außerdem animieren so geschützte Programme gerade dazu, den Schutz zu entfernen, da eine ungeschützte Kopie wesentlich anwenderfreundlicher ist.
Wie man sich gegen Knackmodule schützt
325
Sinnvoller und mit Sicherheit von keinem Knackmodul zu kopieren ist das mehrfache Abfragen des auf der Diskette angebrachten Schutzes. Dies sollte am besten dann geschehen, wenn das Programm Daten nachlädt. Falls man größeren Aufwand treiben will, kann man auch ein komplett neues Diskettenformat verwenden. Man sollte aber darauf achten, daß der Anwender davon nicht betroffen wird, also seine geschützten Disketten nicht öfter wechseln muß als die ungeschützten. Was aber, wenn Ihr Programm nach Einladen des Hauptpro gramms nur noch mit einer Datendiskette arbeitet und die Originaldiskette nicht mehr benötigt? Oder was, wenn Sie Ihr Programm gar nicht auf einer Diskette unterbringen, sondern auf einer Kassette? Betrachten wir zuerst den Fall, daß eine Diskettenstation vorhanden ist. Dann ist es am einfachsten, nach erfolgter Kopierschutzabfrage einige Bytes, wenn nicht sogar ein komplettes Programm, im Speicher der Floppy abzulegen und regelmäßig das Vorhandensein dieser Bytes abzufragen. Bei normalem Diskettenbetrieb bleiben folgende Speicherstellen ungenutzt: Hexadezimal 0005 0010 / 0011 0014 / 0015 001B 0010 001F 0021 0023 0035 0037 0038 / 003e 0046 0096 0100 0102 / 0103 02FB 02FD
Dezimal 5 16 / 17 20 / 21 27 29 31 33 35 53 55 59 / 60 70 150 256 258 / 259 763 765
326
Das große Anti-Cracker-Buch
Mit folgendem Befehl schreibt man ein oder mehrere Bytes in eine bestimmte Speicheradresse der Floppy: OPEN 1,8,15,IM-W"+CHR$(LOW-Byte)+CHR$(HIGH-Byte) +CHR$(Anzahl)+CHR$(Byte1)+CHR$(Byte2)+ ___ :CLOSE 1
Die Bezeichnungen LOW- und HIGH-Byte beziehen sich auf die Anfangsadresse des zu belegenden Bereichs_ Beispiel: Sie wollen in die Speicherstelle 258 den Wert 123 eintragen. Die Anzahl der zu sendenden Bytes beträgt eins, das LOW-Byte von 258 (=$0102) ist zwei, das HIGH-Byte eins. Der Befehl sieht dann so aus: OPEN 1,8,15,IM-W"+CHR$(2)+CHR$(1)+CHR$(1)+CHR$(123):CLOSE 1
Um den Inhalt einer Speicherstelle wieder auszulesen, benötigt man folgenden Befehl: OPEN 1,8,15,IM-R"+CHR$(LOW-Byte)+CHR$(HIGH-Byte) GET #1,A$:A=ASC(A$+CHR$(O»:CLOSE 1
A enthält dann den Wert der angesprochenen Speicherstelle. Zu dem oben angegebenen Beispiel sieht die Abfrage dann so aus: OPEN 1,8,15,IM-R"+CHR$(2)+CHR$(1) GET #1,A$:A=ASC(A$+CHR$(O»:CLOSE 1
A muß dann den Wert 123 enthalten. Der Schutz beruht einfach darauf, daß Knackmodule den Inhalt des Floppy-Speichers nicht sichern. Denken Sie bitte immer daran, daß das Schreiben des Bytes nur ein einziges Mal im Pro. gramm vorkommen darf, nämlich bei der Kopierschutzabfrage, und daß der Test, ob das Byte vorhanden ist, regelmäßig durchzuführen ist, am besten vor dem Aufruf oft gebrauchter Programm teile.
Wie man sich gegen Knackmodule schützt
327
Was jedoch läßt sich tun, wenn Sie keine Diskettenstation zur Verfügung haben? Gibt es eine Methode, Knackmodule auszutricksen, die völlig speicherintern abläuft? Die Antwort ist: Ja, es gibt sie. Die Idee ist einfach die, daß im C64 einige Bausteine vorhanden sind, in deren Register man zwar einen Wert hineinschreiben kann, aber aus denen sich dieser Wert nicht direkt wieder auslesen läßt. Das erste Beispiel, das wir in diesem Zusammenhang betrachten, ist der Soundchip des C64, der sog. SID. Dort Hißt sich für jede der drei erzeugbaren Stimmen eine Hüllkurve festlegen. Dabei handelt es sich um den Lautstärkeverlauf eines gespielten Tones. Normalerweise kann man die einmal in ein Register geschriebenen Hüllkurvenwerte nicht wieder auslesen, was das Knackmodul daran hindert, diese Werte zu kopieren. Dummerweise bedeutet das aber auch, daß man selbst nicht testen kann, ob die Werte stimmen. Für Stimme Drei existiert allerdings ein Leseregister, das es ermöglicht, den Lautstärkeverlauf eines gerade gespielten Tones mitzuverfolgen. Die Abfrage der Hüllkurve geschieht also einfach durch Anspielen dieser Stimme. Für diesen Schutz benötigen wir folgende Register:
Hex Dezimal $0412 54290
$D413 54291
$0414 54292
$041C
54300
Funktion Steuerregister für Stimme Drei. Wird in diesem Register das Bit 1 von Null auf Eins geschaltet, so wird ein Ton gepielt. Attack/Decay: Bits 0 bis 3: Zeit, in der Lautstärke vom Maximum auf den Sustain·Pegel abfällt. Bits 4 bis 7: Zeit, in der Lautstärke von Null auf das Maximum ansteigt. Sustain/Release: Bits 0 bis 3: Zeit, in der Lautstärke vom Sustain-Pegel auf Null abfällt. Bits 4 bis .7: Sustain-Pegel: Lautstärke, die kurz nach Anspielen eines Tones erreicht wird. augenblickliche Lautstärke der Stimme Drei
328
Das große Anti-Cracker-Buch
Beim Anspielen der Stimme Drei braucht man weder die Gesamtlautstärke des SIDs noch eine Wellenform für diese Stimme einzuschalten, wodurch der Ton unhörbar bleibt. Am einfachsten ist es, die Attack-, Decay- und Releasezeiten auf null zu stellen und den Sustainpegel auf einen Wert, der sich durch Auslesen von $D4IC (=54300) feststellen läßt. Das Einstellen könnte dann so aussehen: 10 A=10:REM (änderbarer) Sustain·Wert 20 POKE54291,0:POKE54292,A*16
Setzt man in Zeile 10 A auf einen anderen Wert zwischen 0 und 15, erhält man dementsprechend einen anderen Sustain-Pegel. Gleiches gilt auch für die Abfrage:
10 20 30 40
A=10:REM (AENDERBRER) SUSTAIN·WERT POKE 54290,0:POKE 54290,1:REM TON SPIELEN FORI=1T050:NEXT:REM ATTACK/DECAY·ZEIT ABWARTEN IF INHPEEK(54300)/16)<>A THEN PRINT "DU RAUBKOPIERERI"
Sie können selbstverständlich auch kompliziertere Hüllkurven programmieren und abfragen. Allerdings wird durch diesen Schutz die Stimme Drei belegt. Was aber, wenn man die Schutzabfrage zu einem Zeitpunkt benötigt, bei dem gerade diese Stimme schon anderweitig benutzt wird? Nun, es gibt noch andere Register, die sich normal nicht auslesen lassen, deren Wert man aber mit einem Trick erfahren kann: die Alarmzeiten der Echtzeituhren. Der C64 besitzt zwei Chips, genannt CIAs, die beide eine Echtzeituhr . besitzen. Die folgenden Erklärungen beziehen sich nur auf den ersten der beiden Chips, dessen Basisadresse bei $DCOO (=56320) liegt. Wenn Sie den anderen Chip benutzen wollen, so denken Sie bitte daran, daß sich dessen Basisadresse bei $DDOO (=56576) befindet.
Wie man sich gegen Knackmodule schützt
329
Der Trick, mit dem man die Alarmzeiten überprüfen kann, ist einfach der, daß man die Echtzeituhr auf einen Wert knapp vor dem erwarteten Alarm setzt und kontrolliert, ob der Alarm kurz darauf ausgelöst wird. Dieser Schutz ist besonders schwer zu kopieren. Bei der Hüllkurve wäre es ja denkbar, daß das Knackmodul ebenfalls die Stimme Drei spielt und aufgrund des ausgelesenen Lautstärkeverlaufs die Werte für Attack, Decay, Sustain und Release herausfindet. Bei der Echtzeituhr müßte man aber, wenn man die Alarmzeit nicht schon kennt, die Uhr bis zu 24 Stunden laufen lassen, um die Zeit zu rekonstruieren. Folgende Register braucht man zur Programmierung der Uhr: Hex Dezimal $DC08 56328 $DC09 56329
$DCOA 56330
SOCOB 56331
Funktion Bit 0 bis Bit 4 bis Bit 0 bis Bit 4 bis Bit 7: Bit 0 bis Bit 4 bis Bit 7: Bit 0 bis Bit 4: Bit 5 bis Bit 7:
SOCOO
56333
Bit 2:
$DCOF
56335
Bit 7:
3: 7: 3: 6: 3: 6: 3: 6:
Zehntel sekunden müssen null sein Einersekunden Zehnersekunden muß null sein Einerminuten Zehnerminuten muß null sein Einerstunden Zehnerstunde müssen null sein O=vormittags (AM) 1=nachmittags (PM) 1=Gleichheit von Uhrzeit und Alarmzeit; Achtung: beim Lesen dieses Registers werden alle Bits gelöscht. O=Schreibzugriffe auf die vorhergehenden Register beziehen sich auf die Echtzeituhr. 1=Schreibzugriffe auf die vorhergehenden Register beziehen sich auf die Alarmzeit
330
Das große Anti-Cracker-Buch
Die Echtzeituhr wird gestellt, indem man Bit 7 von $DCOF auf null setzt und dann die Uhrzeit, beginnend mit der Stunde, in die Register einträgt. Die Uhr hält beim Ansprechen des Stundenregisters an und läuft erst beim Setzen der Zehntelsekunden weiter. Entsprechend sollte man beim Auslesen ebenfalls zuerst auf das Stundenregister zugreifen, da dann die Uhr scheinbar bis zum Lesen der Zehntelsekunden gestoppt wird. Intern läuft sie allerdings weiter. Die Alarmzeit wird genauso festgesetzt, nur muß Bit 7 von $DCOF den Wert eins haben. Ein Programm, das beispielsweise die Alarmzeit 7 Uhr 35 Minuten 11 Sekunden und 1 Zehntelsekunde nachmittags setzen soll, sieht so aus: 10 20 30 40 50
POKE56335,PEEK(56335)OR128:REM BIT 7 setzen POKE56331,128+7:REM 7 UHR NACHMITTAGS POKE56330,3*16+5:REM 35 MINUTEN POKE56329,1*16+1:REM 11 SEKUNDEN POKE56328,1:REM 1 ZEHNTELSEKUNDE
Beim Test auf die Alarmzeit stellen wir die Uhr auf eine Zehntelsekunde vor der erwarteten Zeit, warten diese Zehntelsekunde ab und testen dann Bit 2 von Register $DCOD. Da dieses Register beim ersten Lesezugriff wieder gelöscht wird, müssen wir verhindern, daß das Betriebssystem nach erfolgtem Alarm noch vor uns auf $DCOD zugreift. Man erreicht das durch Abschalten des Timer-IRQs (siehe auch: CIA-Beschreibung im Teil "Kassettenkopierschutz"), indem man nach $DCOD (=56333) den Wert 1 schreibt. Hinterher muß man den Timer-IRQ durch 'POKE 56333,129' wieder einschalten. Bei der CIA zwei ($DDOO) kann man sich diese Prozedur sparen, da sie nicht vom Betriebssystem benutzt wird. Hier also das Programm, welches die oben eingetragene Alarmzeit testet: 10 POKE56335,PEEK(56335)AND128:Bit 7 LOESCHEN 20 POKE56331,128+7:REM 7 UHR NACHMITTAGS 30 POKE56330,3*16+5:REM 35 MINUTEN
Wie man sich gegen Knackmodule schützt 40 50 60 70 80 90
331
POKE56329,1*16+1:REM 11 SEKUNDEN POKE56328,0:REM 0 ZEHNTELSEKUNDEN POKE56333,1:REM TIMER-IRQ ABSCHALTEN FORI=1T0200:NEXT:REM ALARM ABWARTEN A=PEEK(56333):POKE56333,129:REM ALARM TESTEN, IRQ EIN IF (A AND 4)<>4 THEN PRINT"VERFLlXT, EIN KNACKMOOUL!"
Zur Benutzung dieser Programme gilt das gleiche wie bei den vorhergehenden Beispielen. Die Alarm-Setz-Routine sollte ein einziges Mal in Verbindung mit der Kopierschutzabfrage ausgeführt werden, die Testroutine dagegen an allen wichtigen Stellen im Programm. Denken Sie daran, daß ein guter Kopierschutz nur in Verbindung mit einem mindestens genauso guten Programmschutz sinnvoll ist. Es gibt schließlich noch genügend "Knacker", die sich nicht auf ein Knackmodul verlassen. Gerade in BASIC-Programmen lassen sich besonders leicht Schutzabfragen finden. Kompilieren Sie daher nach Möglichkeit Ihr Programm, um es den Raubkopierern nicht leichter zu machen als nötig. 7.3
Knackmodule: Zukunftsaussichten
Es ist abzusehen, daß in Zukunft Knackmodule auf dem Markt erscheinen · werden, die sich durch die beschriebenen speicherinternen Tricks nicht abschrecken lassen. Theoretisch könnte nämlich ein Modul den Adreß- und Datenbus des C64 überwachen und so alle Schreibzugriffe auf nicht lesbare Register zusätzlich in einem eigenen RAM nachvollziehen. Außerdem ist ein Modul-System denkbar, welches das Sichern des FloppySpeichers ermöglicht. In beiden Fällen ist aber der notwendige Hardware-Aufwand wesentlich höher als bei den jetzigen Knackmodulen. Der sicherste Schutz ist und bleibt daher eine mehrfache Abfrage der Originaldiskette. Es bleibt dabei nur zu hoffen, daß nicht doch eines Tages das "alles kopierende" Kopierprogramm entwickelt wird.
332
Das große Anti-Cracker-Buch
Gerüchteküche
8. 8.1
333
GerOchtekOche Der rechnerzerstörende Kopierschutz
Die Diskussion um Programme, die den C64 zerstören können, ist schon älter. Sie besteht mindestens seit der Zeit, als man sich daran erinnerte, daß zum Beispiel einige der früheren Personalcomputer nach Eingabe einiger bestimmter POKE-Befehle nur noch durch eine Reparatur wieder zum Laufen zu bekommen waren. Als ein Mitarbeiter der Firma DA TABECKER einer bekannten Homecomputer-Zeitschrift gegenüber äußerte, wahrscheinlich würden in Zukunft alle DATA BECKER-Programme, an denen man einen illegalen Kopierversuch unternehmen würde, dem benutzten Computer das Lebenslicht ausblasen, flammten die Gerüchte wieder auf. Natürlich war diese Drohung ohne jede Grundlage. Man stelle sich vor: Ein ehrlicher Anwender kauft sich ein Programm, das aufgrund einer Fehlbedienung oder einer ungenau justierten Diskettenstation seinen Kopierschutz nicht erkennt und deshalb dem Rechner ein jähes Ende bereitet. Der Hersteller des Programms sähe einer Flut von Schadensersatzklagen entgegen. Außerdem sind, soweit uns bekannt, noch nirgendwo rechnerzerstörende Programme veröffentlicht worden. Wir haben daher keine Risiken gescheut, um festzustellen, was wirklich an dem Gerücht "dran" ist. Um es gleich vorwegzunehmen: Unsere Computer sind noch alle voll funktionstüchtig. Wir haben keine Möglichkeit gefunden, per Software irgendwelche Chips im C64 zu zerstören. Wir können selbstverständlich nicht dafür garantieren, daß die im folgenden beschriebenen Methoden bei jedem C64 unwirksam bleiben. Man kann einen Computer auf elektronischem Wege an sich nur durch eine zu hohe Spannung oder einen Kurzschluß zerstören. Ersteres läßt bei unserem Gerät von vornherein ausschließen, da die vorhandenen Chips nur ein Schalten von Spannungen von null bis fünf Volt, also im für Chips harmlosen Bereich, ermöglichen. Ein Kurzschluß dagegen läßt sich theoretisch erreichen, indem man Leitungen von Ein-/ Ausgabe-Chips, die an sich nur
334
Das große Anti-Cracker-Buch
als Eingang konzipiert sind, auf Ausgang schaltet und eine der Eingangsspannung entgegengesetzte Spannung programmiert. Dazu bieten sich der Prozessor 6510 und die beiden CIAs 6526 an. Beipielsweise lassen die von der Tastatur kommenden Anschlüsse dieses Verfahren zu. Wir haben hier alle in Frage kommenden Leitungen überprüft, ohne daß es den Chips etwas geschadet hätte. Vielleicht kann es im Dauerbetrieb oder bei einigen "empfindlicheren" Rechnern zu einem Schaden kommen. Gleiches gilt auch für die Ein- / Ausgabe-Chips der Diskettenstation. Ein anderes Vorgehen zur Schädigung des Rechners könnte eine mechanische Überbeanspruchung des Steppermotors des FloppyLaufwerks sein. Erfahrungsgemäß hält der Motor aber einiges aus. Außerdem kann man das harte Anschlagen des Steppers an die obere oder untere Begrenzung deutlich hören. Bevor ein zu harter Anschlag etwas ausrichten kann, wird sicherlich kein Computerbesitzer den Griff zum Ein- / Aus-Schalter scheuen. Wir sehen daher keinen Ansatz für ein computerzerstörendes Programm. Sollten Sie vielleicht doch eine Methode finden, die wir übersehen haben, so wären wir für Ihre Mitteilung äußerst dankbar. 8.2
Viren
Vor einigen Monaten fand man in der Presse Berichte über Programme, die in große Datenbanksysteme eingespeist worden waren und Wochen später zur Löschung aller Daten führten. Diese Programme waren teilweise geschickt genug, sich selbst zu reduplizieren und sich an andere Programme anzuhängen, weshalb man ihnen den Namen "Viren" gab. Es war natürlich naheliegend zu fragen, ob sich auch ein Virus für den C64 konstruieren läßt. Ein solches Virus sollte sich in alle Programme auf einer Diskette einbinden. Wenn eines dieser Programme auf eine andere Diskette kopiert wird, kann sich das Virus weitervervielfältigen. Weiterhin könnte mitgezählt werden, die wievielte Kopie von einem Programm angefertigt wurde bez,jehungsweise
Gerüchteküche
335
wie oft das Programm schon eingeladen wurde. Würde hier eine Höchstzahl überschritten werden, so sollte das Virus alle Daten der aktuellen Diskette zerstören. Theoretisch ist es ohne weiteres denkbar, ein Virus zu erstellen. Dazu schreibt man sich ein Programm, das schon während des Ladens einen Autostart ausführt. Dieses Programm kopiert dann die Blöcke, in denen es sich selbst befindet, in freie Blöcke hinein und stellt die Blockverbindungszeiger so um, daß alle anderen Programme ebenfalls das Virus enthalten. Dadurch wird das Virus beim Kopieren mit einem Kopierprogramm auf eine neue Diskette mitübertragen. Praktisch gesehen hat ein Virus aber keinen Wert. Damit es sich ausbreiten kann, muß es unbedingt unauffällig sein. Das bedeutet aber, daß sich ein Programm mit Virus zumindest am Bildschirm genauso verhalten muß wie ohne es. Daraus folgt jedoch, daß der Anwender das Programm nach dem Einladen mit dem Befehl SA VE wieder abspeichern kann, wodurch das Virus wieder entfernt wird. Weiterhin wird sich wohl jeder Computerbesitzer wundern, wenn alle Programme auf einer Diskette plötzlich vier Blöcke mehr benötigen oder wenn ein Programm beim Einladen unnatürlich viele Stepperbewegungen ausführt. Viele 64er-Besitzer haben auch Betriebssysteme, die die Startadresse des geladenen Programms anzeigen, wodurch das Virus sofort zu erkennen ist. Hat man einen Schreibschutz auf seinen Disketten, so funktioniert das Virus sowieso nicht. Zudem können Viren den Ablauf von nachladenden Programmen oder die Verwendung eines Schnelladeprogramms stören. Weder die Raubkopierer noch die ehrlichen Anwender brauchen sich deshalb Sorgen zu machen, ein Virus programm könnte ihre Disketten zerstören. Wer absolut kein Risiko eingehen will, sollte bei allen selbststartenden oder nachladenden Programmen einen Schreibschutz verwenden.
336
Das große Anti-Cracker-Buch
Allgemeine Tios
9.
337
Allgemeine Tips
Neben den speziellen Erklärungen und Hinweisen in den einzelnen Kapiteln wollen wir Ihnen hier einige allgemeine Tips im bezug auf den Programmschutz geben. Als erstes müssen Sie darauf achten, daß von der Kopierschutzabfrage bis zum Directory alles codiert oder durch andere Methoden geschützt wird. Zuerst muß also die Kopierschutzabfrage selbst geschützt werden. Hier wird es von Vorteil sein, die Abfrage nicht zu offensichtlich in dem Programm stehen zu lassen, damit sie nicht zu einfach (zum Beispiel mit einem Monitor) auszubauen ist. Die Diskettenzugriffsbefehle, zum Beispiel B-E, sollten nach Möglichkeit in einer verschlüsselten Form vorliegen. Noch besser ist es, das Programm mittels M-W in die Floppy zu schicken und es dort zu starten. Danach sollte das Programm mit dem Kopierschutz codiert werden, um auch hier den Zugriff zu erschweren. Die Decodierroutine kann dann ihrerseits durch eine Routine, die wesentlich kürzer und unauffälliger ist, gesichert werden. Schließlich kommt es auch auf den Schutz des Programm-Files an. Das File kann durch diverse Directory-Manipulationen und vor allem durch einen guten Autostart gesichert werden. Durch diese Kombination der verschiedenen Schutzsysteme wird ein Fremdzugriff erheblich erschwert. Darüber hinaus existieren eine Reihe weiterer Tricks, die vor allem darauf abzielen, jeden unbefugten Benutzer zu verwirren und den Einsatz von Hilfsprogrammen, wie Monitore und Single-Step-Simulatoren, zu verhindern. Allgemein sollte bei der Benutzung der Betriebssystemroutinen nicht über die Sprung tabelle am Ende des ROMs verzweigt werden, da diese Adressen zu einprägsam sind. Diese Routinen können besser direkt oder über indirekte Sprünge aufgerufen werden. Eine weitere Alternative bieten selbst modifizierende Pro-
338
Das große Anti- Cracker - Buch
gramme. Dabei wird die Zieladresse des lMP-Befehls erst während des Programmablaufs auf ihren .endgültigen Wert gesetzt. Als zweckmäßig hat sich ein Prinzip erwiesen, bei dem der untere Bereich des Stapels mit den Adressen der Unterprogramme vollgeschrieben oder überladen wird. In der ersten Unterroutine wird der Stapel zeiger dann umgestellt und die Routine mit einem RTS-Befehl abgeschlossen. Durch den umgesetzten Stapelzeiger wird nun das nächste Unterprogramm aufgerufen. Dieser Vorgang kann beliebig oft wiederholt werden, wenn der Zeiger umgesetzt wird. Die Verwendung von Illegal-Opcodes zerstört die Programmstruktur, was teilweise von Vorteil sein kann, wie in Kapitel 3 beschrieben wurde. Um den Einsatz von Hilfsprogrammen, zum Beispiel Monitore und Single-Step-Simulatoren, zu verhindern, sollte das Programm in möglichst viele Speicherbereiche zerstreut werden. So kann kein Programm geladen werden, ohne dabei einen Teil des eigentlichen Programms zu zerstören. Hierbei sollten die Speicherbereiche unter dem ROM und der Bildschirmspeicher benutzt werden. Außerdem sollten die einzelnen Speicherbereiche nach dem Abarbeiten der Routinen, die nicht mehr benötigt werden, zerstört werden. Um die Single-Step-Simulatoren "unschädlich" zu machen, sollten Sie folgende Punkte beachten: - durch den SEI-Befehl den Interrupt maskieren, - den IRQ- und NMI- Vektor öfter initialisieren, das heißt, die Vektoren in der Zeropage auf ihre Originaladressen zurücksetzen. Die oben aufgezählten Tips sollen Ihnen Hinweise zum Schutz Ihrer eigenen Programme geben. Genauere Erläuterungen finden Sie in den einzelnen Kapiteln dieses Buches.
Komplettlösung
339
10. Komplettlösung Da in diesem Buch sehr viele Kopier- und Programmschutzanwendungen beschrieben werden, ist es unerläßlich, einige Beispiele dafür zu liefern, welche verschiedenen Schritte man durchzuführen hat, bis ein Programm "gründlich" geschützt ist. Schutz eines BASIC-Programms, welches mit der Datasette aufgezeichnet werden soll: Folgendes Programm bietet dem Benutzer drei Menüpunkte an, die nichts weiter machen, als den Text "MENUEPUNKT X" anzuzeigen. Es könnte somit das Gerüst eines umfangreicheren Programmes darstellen. 100 110 120 130 140 150 160 170 180 190 300 400 500 600 610
PRINT CHR$(147) FOR 1=1 TO 6:PRINT:NEXT PRINT" 1. MENUEPUNKT A":PRINT PRINT" 2. MENUEPUNKT B":PRINT PRINT" 3. MENUEPUNKT C":PRINT PRINT"BITTE IJAEHLEN SIE AUS!" GET A$ IF AS<"1" OR AS>"3" THEN 160 ON VAL(A$) GOSUB 300,400,500 GOTO 100 PRINT CHR$(147)i"MENUEPUNKT A":GOTO 600 PRINT CHR$(147)i"MENUEPUNKT B":GOTO 600 PRINT CHRS(147)i"MENUEPUNKT C":GOTO 600 GET AS:IF A$="" THEN 600 RETURN
Gegen das Betätigen von RUNjSTOP oder RUNjSTOP- RESTORE braucht das Programm nicht geschützt werden, da dieser Schutz schon durch unseren Kassetten-Schnellader erzeugt wird. Um es auch gegen einen RESET -Schalter (siehe Kapitel 2.3) zu sichern, ergänzen Sie es bitte um folgende Zeilen:
340 20 30 40 50
Das große Anti-Cracker-Buch
POKE 56,128:CLR POKE32768,226:POKE32769,252:POKE32770,226 POKE32771,252:POKE32772,195:POKE32773,194 POKE32774,205:POKE32775, 56:POKE32776, 48
Da wir das Programm hinterher mit einem Autostart versehen werden, werden diese Befehle sofort nach dem Einladen ausgeführt, wodurch sie überhaupt erst sinnvoll werden. Selbstverständlich darf unser Programm nicht von einem Knackmodul zu kopieren sein. Daher verwenden wir eine Abfrage der Alarmzeit einer Echtzeituhr (siehe Kapitel 7.2). Zuerst muß die Alarmzeit gesetzt werden: 60 65 70 75 80
POKE56335 , PEEK(56335)OR128 POKE56331,3 POKE56330,35 POKE56329,84 POKE56328,8
Die hier verwendete Uhrzeit ist drei Uhr vormittags, 25 Minuten, 54 Sekunden und acht Zehntelsekunden. Die eigentliche Abfrage wird als Unterprogramm ab Zeile 700 installiert. Sie muß in regelmäßigen Abständen aufgerufen werden, am besten vor der Ausführung jedes Menüunterpunktes: 175 GOSUB 700
Hier nun die eigentliche Abfrage: 700 710 720 730 740 750 760
POKE56335,PEEK(56335)AND127 POKE56331,3 POKE56330,35 POKE56329, 84 POKE56328,7 POKE56333,1 FOR 1=1T0200:NEXT
Komplettlösung
341
770 A=PEEK(56333):POKE56333,'29 780 IF (A AND 4)<>4 THEN SYS 64738 790 RETURN
Nachdem nun alle Änderungen im Programm stattgefunden haben, können wir es jetzt vor den neugierigen Blicken anderer und vor eventuellen Änderungen sichern. Sie speichern dazu das Programm erst einmal ab, laden dann das Packer-Programm aus Kapitel 2.1.4 und starten es mit 'RUN'. Laden Sie als nächstes wieder das Beispielprogramm und schützen Sie es durch 'SYS 49152' und 'SYS 49730'. Speichern Sie bitte auch diese Version noch einmal ab. Um die endgültige Aufnahme vorzubereiten, laden Sie dann das Programm "KKS" aus Kapitel 5.5.3 und starten es mit 'RUN' und 'SYS 49152'. Danach holen Sie zum dritten Mal das Beispiel in den Speicher und nehmen es mit dem KKS auf. Das Programm ist nun rundum geschützt. Man kann es mit keinem Kopierprogramm kopierens und es läßt sich auch nicht nach dem Einladen ohne weiteres unterbrechen. Selbst ein RESET -Schalter oder ein Knackmodul hilft einem nicht weiter. Sicherlich können Sie den Schutz noch weiter verbessern. Hier sind Ihrer Kreativität keine Grenzen gesetzt. Komplettlösung für Diskette
Im folgenden Beispiel wollen wir Ihnen zeigen, wie Sie mit Hilfe der von uns gezeigten Schutzsysteme ein Maschinenspracheprogramm mit zusätzlicher Kopierschutzabfrage wirkungsvoll schützen können. Bevor Sie sich an den Computer setzen und beginnen, Ihr Programm zu schützen, müssen Sie sich natürlich überlegen, wie Ihr Programm geschützt werden soll. In diesem Beispiel wollen wir unser Programm als erstes codieren, und unsere Wahl fiel auf den Single-Step-Codierer aus Kapitel 3.3.4, da diese Codierung von einem Knacker nur sehr
342
Das große Anti-Cracker-Buch
schwer nachzuvollziehen ist. Der Nachteil bei dieser Codierung ist jedoch die drastische Verringerung der Ablaufgeschwindigkeit unseres geschützen Programms. Aus diesem Grund haben wir nicht das gesamte Programm mit dieser Codierung versehen, sondern wir haben eine andere, wesentlich kürzere Entcodierschleife benutzt. Diese Schleife entcodiert unser restliches Programm. Für diese zweite Codierung wird jedes Programmbyte mit 'EOR #$45' verknüpft und daraufhin um eins verringert. Weil wir unser Programm auch dagegen schützen wollten, daß man es mit 'RESET' abbricht und sich das entcodierte Programm ansehen kann, haben wir es in das Bildschirm-RAM kopiert und den Bildschirm auf $0800 (2048) verlegt. Bei einem 'RESET' wird so das Programm unweigerlich zerstört. Wir wollten verhindern, daß ein Knacker eine CBM80- Erkennung benutzt und dann beim Betätigen eines 'RESET' zu seiner eigenen Routine verzweigt, mit der er das BildschirmRAM rauskopiert und sich somit trotzdem unser Programm ansehen kann. Deshalb haben wir selber eine CBM80-Erkennung geschrieben, mit der wir die 'RESTORE-Taste' sperren und einen 'RESET' unbrauchbar machen. Selbstverständlich haben wir nicht auf eine Kopierschutzabfrage verzichtet. In unserem Beispiel verwenden wir den Kopierschutz aus Kapitel 6.6.3, der einen Track mit über 3000 SYNC-Markierungen beschreibt. Um die Reihe der Schutzsysteme noch zu erweitern, fragen wir die Rückmeldung der Kopierschutzabfrage immer wieder ab und erhalten so einen sicheren Schutz gegen Knackmodule, denn ein Knackmodul kann unmöglich auch das RAM der Floppy speichern. Als erstes zeigen wir Ihnen das Programm, das wir schützen wollen. Die Single-Step-Entcodierroutine muß in unserem Beispiel ab $091 E im Speicher stehen, von wo sie nach $COOO kopiert und gestartet wird:
Komplettlösung 0810 0812 0815 0818 0819 081B 081E 081F 0820
LDX LDA STA INX BNE JSR NOP NOP NOP
343
#$00 X-Register Löschen S091E,X Entcodierroutine SCOOO,X nach SCOOO schreiben S0812 verzweige, wenn noch Bytes zu kopieren SCOOO Routine aufrufen
Der folgende Programmteil muß vor dem Start des Programms schon codiert worden sein: 0821 0823 0826 0827 0829 082C 082E 0831 0832 0834 0835 0838 083B
LDX INC INX BNE LDA EOR STA INX BNE SEI JSR JSR JMP
#$00 ZähLer für Entcodierung setzen S083B,X jedes Byte um eins erhöhen ZähLer erhöhen S0823 verzweige, wenn nicht fertig S083B,X Byte Laden #$45 entcodieren S043B,X und in den BiLdschirm schreiben ZähLer erhöhen S0829 verzweige, wenn noch nicht fertig Step-Codierung unterbrechen SFF84 und ganz SFF8A beenden S043E zum Programm im BiLdschirm springen
Der folgende Programmteil liegt bei der Ausführung des Programms im Bildschirmbereich ab $043E: 083E 0840 0843 0846 0847 0849 084C 084E 0850
LDX LDA STA DEX BPL LDA AND ORA STA
#$OC ZähLer setzen S0511,X CMB80-Erkennung S8000,X nach S8000 schreiben ZähLer verringern S0840 verzweige, wenn noch Daten zu schreiben $0018 BiLdschirm #$OF nach S0800 #$20 verLegen $0018
344
Das große Anti-Cracker-Buch
0853 0855 0858 085B 085E 0861 0864
LDA STA JSR JSR INC JSR JMP
#$08 $0288 $E544 $0467 $0020 $04F2 $045E
und dem Betriebssystem mitteilen wo er sich jetzt befindet Bildschirm löschen Kopierschutz abfragen Rahmenfarbe erhöhen auf Knackmodul testen in Endlosschleife laufen
0867 086A 0860 086F 0872 0874 08n 087A 087B 0870 087F 0882 0884 0887 0889 088C 088F 0890 0892 0894 0897
JSR JSR LDX JSR LDX LDA JSR INX CPX BNE JSR LDX JSR LDX LDA JSR INX CPX BNE JSR JSR
$04CD $04DE #$01 $FFC9 #$00 $04FA,X $FFD2
OPEN1 ,8, 15 OPEN2,8,2,1#2" Ausgabe auf KanaL 1 Legen (CMD1)
089A 0890 089F 08A2 08A5 08A8
JSR LDX JSR LDA JSR JMP
'U1 2 0 18 2' zur Floppy schicken #$OB $0874 $FFCC Ausgabe wieder auf Bildschirm #$01 und danach wieder auf KanaL $FFC9 1 legen #$00 $0505,X $FFD2 'M-E'CHR$(3)CHR$(5) zur Floppy schicken #$05 $0889 $FFCC Ausgabe wieder normaLisieren $04AB 'M-R'CHR$(16)CHR$(0)CHR$(1) senden (Kopierschutz,KnackmoduL testen) SFFCC Ausgabe normaLisieren #$01 Ausgabe auf Kanal 1 $FFC9 Legen $0510 Disk initiaLisieren $FFD2 $FFCC Ausgabe normaLisieren, Rücksprung
08AB LDX #$01 08AD JSR $FFC9 08BO LDX #$00
Ausgabe auf Kanal 1 legen
345
KompleUlösung 08B2 08B5 08B8 08B9 08BB 08BO 08CO 08C2 08C5 08C8
lOA JSR INX CPX BNE JSR lOX JSR JSR CMP
S050A,X SFF02 'M-R'CHRS(16)CHR(0)CHR(1) #$06 S08B2 SFFCC #$01 SFFC6 SFFCF #$FF
Eingabe auf Kanal 1 stellen Zeichen von Floppy holen auf Richtigkeit prüfen Endlosschleife, wenn Zeichen falsch sonst Rücksprung
08CA BNE S08CA 08CC RTS 08CO 08CF 0801 0803 0806 0808 080B
lOA lOX lOY JSR lOA JSR JMP
#$01 #$08 #$OF SFFBA #$00 SFFBO SFFCO OPEN 1,8,15
080E 08EO 08E2 08E3 08E6 08E8 08EA 08EC 08EF
lOA lOX TAY JSR lOA lOX lOY JSR JMP
#$02 #$08 SFFBA #$02 #$F8 #$04 SFFBO SFFCO OPEN 2,8,2,"#2", Rücksprung
08F2 JSR S04CO 08F5 JMP S04AB 08F8 0900 0908 0910 0918
23 20 03 49 38
32 31 05 OC 30
55 38 40 80 4C
OPEN 1,8,15 Rückmeldung von Floppy abfragen 31 20 20 09 81
20 32 52 80 EA
32 40 10 C3 02
20 20 00 C2 00
30 #2U1 2 0 45 18 2M-E 01 M-R CO CBM 00 80
Das große Anti-Cracker-Buch
346
Nachdem das Programm soweit geschrieben ist, daß es den Kopierschutz abfragt, sich entcodiert und sich selbst in den Bildschirm schreibt, müssen wir es entsprechend codieren. Zu diesem Zweck benutzen wir, wie bereits gesagt, den StepCodierer aus Kapitel 3.3.4, mit dem wir den Speicherbereich von $081B bis 0838 codieren. Der nächste Schritt ist das Codieren des restlichen Programms mit der folgenden Routine: 1008 100A 1000 100F 1012 1015 1016 1018
LOX LOA EOR STA OEC INX BNE RTS
#$00 S083B,X #$45 S083B,X S083B,X S100A
Zähler auf Null setzen Byte laden mit #$45 verknüpfen und speichern zusätzlich noch um Eins verringern Zähler erhöhen verzweige, wenn noch Bytes zu codieren Rücksprung
Danach hängen wir das Step-Entcodierprogramm an das Ende des zu schützenden Programms ab $091E an. Dieses StepProgramm wird beim Start nach $COOO kopiert und dort aufgerufen. Nachdem wir jetzt unser Programm vollständig codiert haben, versehen wir es noch mit einem Autostart, wie er in Kapitel 3 vorgestellt wird. Zum guten Schluß müssen wir noch die Diskette mit dem abzufragenden Kopierschutz versehen. In diesem Fall haben wir den Kopierschutz aus Kapitel 6.6.3 gewählt und dessen Abfrage mit Hilfe des dort vorgestellten Programms auf Track 18 Sektor 2 gespeichert. Das Hauptprogramm, das in diesem Beispiel geschützt werden soll, ist der Teil von $085E bis $0864, der lediglich die Rahmenfarbe erhöht und testet, ob ein Knackmodul zum Knacken eingesetzt wurde.
Über Cracker ...
347
11. Was ein Software-Haus über "Cracker" wissen sollte In diesem Kapitel wollen wir deutlich machen, wie Knacker bei ihrer Arbeit vorgehen, so daß Sie sich beim Entwurf Ihres Schutzes daran orientieren können. Bitte verstehen Sie folgende Erläuterung nicht als Anleitung zur Erstellung von Raubkopien, auch wenn wir es manchmal nicht vermeiden konnten, Informationen zu liefern, die einem Knacker dienlich sein könnten. Es gibt für einen "Cracker" mehrere Möglichkeiten, an ein geschütztes Programm heranzugehen. Welche dieser Möglichkeiten er verwendet, hängt in erster Linie von seinem jeweiligen Wissensstand ab. Der "Möchtegern-Knacker", der sich lediglich mit der Bedienung seines Kopierprogramms oder seines Knackmoduls auskennt, gehört zu der für uns ungefährlichsten Gruppe. Gegen ihn kann man sich schon durch einen einfachen Kopier- oder Knackmodulschutz sichern. Der "Durchschnitts-Knacker" besitzt schon etwas Erfahrung im Umgang mit Maschinensprachemonitoren. Eine sehr beliebte Knackmethode ist es, mit Hilfe eines RESET -Schalters das Programm zu verlassen und dann die benutzten Teile des Speichers abzusichern. Nachträglich wird dann die Startadresse ermittelt. Diese Art des Knackens ist seit Erscheinen von Knack-Betriebssystemen wieder sehr in Mode gekommen. Um sich gegen diese Leute zu wehren, muß man schon etwas mehr Aufwand treiben. Zum Ermitteln der Startadresse durchsucht der "DurchschnittsKnacker" mit seinem Monitor den Speicher nach einem möglichen Programmanfang. Meistens testet er dabei die Vielfachen von $1000 (=4096) durch. Um es ihm nicht zu einfach zu machen, sollten Sie Ihr Programm nach Möglichkeit nicht bei einer dieser Adressen beginnen lassen. Wenn Ihr Programm eine Unterroutine enthält, die nur ganz am Anfang ein einziges Mal
348
Das große Anti-Cracker-Buch
aufgerufen wird, so sollten Sie diese Routine hinterher löschen, damit sich das Programm nach einem RE SET nicht ein zweites Mal starten läßt. Sie können allerdings auch anders vorgehen, um einem solchen Knacker "das Leben schwer zu machen". Das Einbringen einer "CBM80"-Erkennung reicht allerdings oft nicht aus, um einen RESET -Schalter funktionsuntüchtig zu machen, da Betriebssysteme, die diese Erkennung umgehen, inzwischen relativ weit verbreitet sind. Daher ist es sicherer, Daten oder Programmteile in Speicherbereichen unterzubringen, die bei einem RESET mit Sicherheit gelöscht werden (siehe Kapitel 7.1). In den meisten Fällen ist dieser Schutz schon hinreichend. Viele Knacker nutzen jedoch gerade das "CBM80" dazu aus, beispielsweise den Bildschirmspeicher mit Hilfe eines RESETs abzusichern. Dazu lassen sie den RESET - Vektor der Erkennung auf eine eigene Routine zeigen, die ihnen die sonst verlorenen Daten in einen freien Teil des Speichers schiebt. Es ist dazu noch nicht einmal unbedingt erforderlich, diese Erkennung zu verwenden. Denselben Effekt erzielt man auch durch Ändern des NMIVektors $0318/$0319 (792/793) und Betätigen der RESTORETaste. Das funktioniert sogar dann, wenn das zu "knackende" Programm die "CBM80"-Erkennung benutzt, da der Vektor $0318/$0319 höhere Priorität gegenüber dem NMI-Vektor der Erkennung besitzt. Hiergegen schützt man sich durch Auffüllen aller vom Programm nicht benötigten Speicherbereiche, wodurch der Knacker keinen Platz für seine Verschiebe-Routine hat. Weiterhin sollten Sie beim Schutz Ihrer Programme daran denken, daß ein Knacker mit einem Diskmonitor das File auf der Diskette nach der "CBM80"-Erkennung oder nach anderen markanten Bytes durchsuchen und diese ändern kann. Der einfachste Schutz dagegen ist eine Codierung Ihrer Programmdaten oder eine Prüfsumme (siehe Kapitel 3.2/3.3), anhand derer Sie die Änderungen erkennen und abfangen können. Die ge zielte Suche nach Schutzroutinen ist sowieso eine beliebte Methode, um sich die Arbeit des exakten Nachverfolgens eines Programmschutzes zu ersparen. Dieses wird den Knackern be-
Über Cracker ...
349
sonders dadurch erleichtert, daß nahezu alle Maschinensprachemonitore über Suchbefehle verfügen. Eine Decodierroutine finden sie beispielsweise, indem sie nach dem einzigen uncodierten Programmteil Ausschau halten. Wenn man allerdings seine Decodierroutine mit illegalen Opcodes (siehe 3.4) ausstattet, so kann ein Knacker nicht mehr ohne weiteres die codierten von den uncodierten Programm teilen unterscheiden. Die Grundlage zur Durchsuchung eines Programm teils ist, diesen Programm teil zuerst einmal auf der Diskette zu finden. Der Directory-Schutz hat die Aufgabe, gerade das zu verhindern. Jegliche Manipulation am Inhaltsverzeichnis der Diskette läßt sich jedoch mit einem Diskmonitor oder einem Directory-Editor wieder entfernen. Wir müssen nämlich davon ausgehen, daß unter Knackern solche Programme weit verbreitet sind. Alle uns bekannten Tricks beruhen schließlich auf Änderungen am Filenamen, am Diskettennamen oder an den Blockverbindungszeigern, was bedeutet, daß kein Schutz gegenüber einem Diskmonitor wirksam bleibt. Da jeder den exakten Aufbau des Directorys schon aus dem mitgelieferten Handbuch zur 1541 entnehmen kann, ist dieser auch relativ bekannt. Die einzige Methode, ein File so zu verstecken, daß man es nicht wiederfindet, ist, es mit Direktzugriffsbefehlen frei über die Diskette zu verteilen, wodurch es sich nur über ein eigenes Ladeprogramm wieder einladen läßt. Eine Erweiterung dieses Schutzes ist die Verwendung eines eigenen Diskettenformates. So wird der Knacker dazu gezwungen, sich mit der Laderoutine auseinanderzusetzen. Findigen Knackern gelingt es auch, ein nicht laufendes Programm, das von einem "Original" kopiert wurde, trotzdem zum Laufen zu bringen, indem sie einfach die Kopierschutzabfrage von der kopierten Diskette entfernen. Eine Schutzmethode dagegen ist, Daten vom Kopierschutz zu holen und diese später im Programm noch einmal zu verwenden. Überhaupt ist es für einen Knacker relativ schwer, Daten, Werte oder Vektoren, die im Zusammenhang mit dem Kopierschutz geholt, gesetzt oder berechnet werden (zum Beispiel als Prüf-
350
Das große Anti-Cracker-Buch
summe) und die für das geschützte Programm "lebenswichtig" sind, herauszufinden und dementsprechend diese Werte zu rekonstruieren. Wer sich hier nicht an die genaue Analyse des Programmschutzes herantraut, hat kaum eine Chance, das Programm zu knacken. Entsprechend sind Kopierschutzabfragen, die mitten im Programm benutzt werden, wesentlich schwerer zu finden als solche, die direkt am Programmanfang aufgerufen werden. Man sollte sich aber nicht zu sicher sein, daß so eine Abfrage nicht doch gefunden wird, wenn man sie nicht gesondert schützt. Beispielsweise kann der Knacker eine Dongle-Abfrage (Kapitel 3.5) sehr leicht ausschalten, indem er das Programm nach allen Befehlen durchsucht, die die entsprechenden Ein- / AusgabePorts adressieren. Besonders leicht wird es für einen Knacker, dessen Knackmodul zwar an einem Programm gescheitert ist, das mit einer der in Kapitel 7 beschriebenen Methoden gesichert ist, wenn diese Schutzabfrage zu leicht "von Hand" entfernbar ist. Genauso wie bei der Dongle-Abfrage sucht er ebenfalls nach auf die Register des SIDs oder der Echtzeituhren zurückgreifenden Befehlen. Sie sollten daher solche Abfragen codiert im Programm stehen lassen und immer nur kurzzeitig vor dem eigentlichen Aufruf decodieren. Die gefährlichste Gruppe von Knackern bilden die "eingefleischten Freaks", die nicht davor zurückschrecken, sich stunden- und tagelang mit einem Bleistift und Papier bewaffnet an den Rechner zu setzen und den Programmschutz bis ins kleinste Detail zu sezieren. Um sich auch hiergegen zu wehren, reicht zum Beispiel eine einfache Codierung bei weitem nicht aus. Ein Autostart läßt diese Leute weitgehend kalt, da man ihn grundsätzlich mit denselben Methoden entfernen kann, mit denen man ihn aufgetragen hat. Beispielsweise erzeugen wir ja einen Autostart auf Diskette durch nachträgliches Ändern der Startadresse des Programms mit der Routine aus Kapitel 3.1.4. Umgekehrt geht nun ein Knacker hin und stellt genauso die Startadresse um, allerdings auf einen freien Bereich. Ent-
Über Cracker ...
351
sprechend verfährt der Knacker bei einem Autostart von Kassette (siehe Kapitel 5.3). Prinzipiell können wir uns nicht dagegen schützen. Die nächste Hürde, die allerdings nur dann auftaucht, wenn wir ein BASIC-Programm geschützt haben, ist der List-Schutz. Dummerweise ist die Anzahl der möglichen List-Schutzsysteme relativ gering und beschränkt sich nahezu auf die in diesem Buch beschriebenen Verfahren. Ein erfahrener Knacker kennt selbstverständlich diese Methoden. Es existieren sogar einige Betriebssysteme, die viele List-Schutzverfahren außer Kraft setzen. Wer sich zudem noch mit dem Aufbau eines BASIC-Programms im Speicher auskennt, wie er in Systemhandbüchern wie dem "64 Intern" beschrieben ist, kann mit Hilfe eines Monitors auch alle restlichen Schutzsysteme überwinden. Daher ist auch ein List-Schutz noch nicht optimal. Es gibt allerdings einige wirksame Methoden, dem Knacker das Nachverfolgen zu erschweren. Eine besteht darin, illegale Opcodes zu verwenden. Wer diese Codes nicht kennt, hat keine Möglichkeit, sich das Programm anzusehen. Aber auch für den, der weiß, welche Bedeutung diese Codes haben, stellen sie ein Handikap bei der Analyse dar. Allerdings ist es natürlich genauso schwer, solchen Code zu schreiben, wie ihn hinterher wieder zu entziffern. Daher muß man schon ziemlichen Aufwand mit den "Illegals" treiben, bevor ein "echter" Knacker aufgibt. Gleiches gilt für alle anderen Verfahren, bei denen man versucht, durch einen möglichst komplizierten und verschachtelten Programmablauf, der außerdem noch von mehrfachen Codierungen und Sicherheitsabfragen begleitet wird, den Knacker zu verwirren. Im Zweifelsfalle findet sich nämlich doch irgendwann einmal ein Knacker, der eine größere Ausdauer als der Konstrukteur des Schutzes besitzt. Die Knacker haben in Bezug auf das Nachverfolgen von Programmen noch einen anderen Vorteil als den, daß sie sich gegenüber den Schützern in der Überzahl befinden. Für den C64 existieren nämlich einige sogenannte "Einzelschritt-Simulatoren", mit deren Hilfe man jeden einzelnen Schritt eines Maschinenprogrammes beobachten kann. Diese Programme sind aber rela-
352
Das große Anti-Cracker-Buch
tiv leicht auszutricksen. Die sicherste Methode dagegen ist, alle unbenutzten Speicherbereiche zu überschreiben oder die Interruptvektoren $0314/$0315 beziehungsweise $0318/$0319 zu initialisieren. Die einzige uns bekannte Möglichkeit, mit relativ geringem Aufwand einen extrem schwer zu entschlüsselnden Code zu produzieren, stellt das Compilieren eines BASIC-Programms dar. Der von einem Compiler erzeugte P-Code ist dermaßen schwer zu verstehen, daß selbst hartgesottene Knacker daran verzweifeln. Die meisten Knacker würden bei einem compilierten Programm zuerst einmal versuchen, mit einem Diskmonitor oder Monitor eventuelle Floppy-Befehle im P-Code zu finden. Die Arbeit, Texte wie zum Beispiel "B-E" zu verschlüsseln, nimmt uns nämlich der Compiler nicht ab. Floppy-Programme sollte man daher nicht ungeschützt auf einem Block der Diskette unterbringen, sondern am besten als codierte Daten mit in das compilierte BASIC-Programm übernehmen. Es ist natürlich nicht auszuschließen, daß ein sehr versierter Knacker den P-Code des verwendeten Compilers analysiert. Dies sind und bleiben jedoch Einzelfälle, die normalerweise nicht zu einer ausgedehnten Verbreitung des Programms als Raubkopie führen. Gefährlich wird es nur, wenn ein solcher Knacker sein Wissen dazu nutzt, einen Recompiler zu schreiben, mit dessen Hilfe jedermann ein compiliertes Programm wieder in ein BASIC-Programm umwandeln kann. Solche Fälle sind tatsächlich schon vorgekommen. Achten Sie also bei der Wahl ihres Compilers darauf, daß dazu noch kein solcher Recompiler existiert.
Kopierschutz auf dem C128
353
12. Kopierschutz auf dem C128 Alle in diesem Buch beschriebenen Kopierschutzverfahren lassen sich selbstverständlich auch auf dem C128, dem Nachfolgemodell des C64, verwenden, sofern man ihn in den 64er-Modus schaltet und statt einer l570/l57l-Diskettenstation eine 1541 anschließt. Uns interessiert aber vielmehr, wie man Kopierschutz im l28er-Modus und mit einer 1570 beziehungsweise 1571 betreibt.
12.1
Übereinstimmungen und Unterschiede zum C64
Da der C128 der Nachfolger des C64 ist, stimmt er in vielen Strukturen mit ihm überein. Daher funktionieren viele Programmschutzsysteme ohne oder mit wenigen Änderungen auch auf dem neuen Rechner. Unterschiede treten bei der Verwaltung des BASIC-Speichers, der RAM-Bänke und in einigen Fällen auch bei Betriebssystemroutinen auf. Außerdem ist die Floppy 1571 nicht völlig kompatibel zur 1541, was auch hier Änderungen notwendig werden läßt. Im folgenden werden wir erst einmal beschreiben, wie man die meisten der bisher abgedruckten Programme auch auf dem C128 zum Laufen bringt. Der C128 ist ein Rechner, der wesentlich mehr Speicherplatz verwalten muß als der C64. Daher stellt das BASIC uns den Befehl "BANK" zur Verfügung, mit dessen Hilfe wir durch die Befehle "SYS", "PEEK" und "POKE" den gesamten Speicher erreichen können. Programme aus diesem Buch, die Werte in den VIC, den SID oder die CIAs "poken", funktionieren ohne Änderung, da das BASIC als voreingestellten Wert für den "BANK"-Befehl den Wert 15 vorgibt und diese Chips dann im gleichen Bereich wie im 64er liegen. Sollten Sie allerdings in Ihren Programmen Bank-Umschaltungen vornehmen, so denken Sie bitte daran, durch "BANK 15" vor entsprechenden "PEEK"und "POKE"-Sequenzen wieder den richtigen Bereich anzuwählen. Auf Einzelheiten werden wir später noch zu sprechen kommen.
Das große Anti-Cracker-Buch
354
Ein weiteres Problem betrifft die Unterbringung von Maschinenprogrammen. Der Bereich $COOO (=49152) bis $CFFF (=53247) steht uns leider nicht mehr in der Weise zur Verfügung wie im C64. Dagegen finden wir in der RAM-Bank 0 unterhalb des BASIC-Speichers jede Menge freien Speicher. Folgende Bereiche stehen uns zur Verfügung: Hex Dez SOBOO SOCOO SOEOO S1300 S1800
-
SOBFF SODFF SOFFF S17FF S1BFF
2816 3072 3584 4864 6144
-
3071 3327 4095 6143 7167
Kassettenpuffer RS232-Puffer Bereich für Sprite-Daten unbenutzter Bereich (!) RAH für Funktionstastenanwendungen, normalerweise frei
Zur Kompatibilität der Floppy 1571 zur 1541 ist zu sagen, daß sich die meisten Schwierigkeiten dadurch umgehen lassen, indem man die Floppy in den l54l-Modus schaltet. Zu Kapitel 2:
Die meisten Listschutzsysteme funktionieren nicht nur auf dem C64, sondern auch auf dem l28er. Im folgenden haben wir die wichtigsten Unterschiede des alten Rechers zum neuen aufgelistet. Zu Kapitel 2.1.1:
Der Listschutz mit SHIFT -L klappt leider nicht mehr, da der BASIC-Interpreter des C128 etwas anders aufgebaut ist als der des C64. Zu Kapitel 2.1.2:
Diese Listschutzmethode funktioniert unverändert auch auf dem l28er.
Kopierschutz auf dem C128
355
Zu Kapitel 2.1.3: Die Adresse des LIST-Vektors hat sich beim C128 nicht verändert, jedoch die möglichen Adressen, auf die er zeigen kann. Um den LIST-Vektor auf die RESET -Routine zeigen zu lassen, benötigt man folgende POKEs: POKE 774,61:POKE 775,255
Zu Kapitel 2.1.4: Beim LIST -Schutz "Nur Zeilen nummern" hat sich prinzipiell nichts verändert. Nur der BASIC-Start liegt jetzt nicht mehr bei $0800, sondern bei $ICOO. Außerdem besitzt der 128er schon einen eingebauten Monitor, mit dem man die nötigen Änderungen vornehmen kann. Zu Kapitel 2.1.5/2.1.6 Hier gilt das gleiche, was schon zu Kapitel 2.1.4 angemerkt wurde. Zu Kapitel 2.1.7: Diese Schutzmethode hat sich im Bezug zum C64 nicht verändert, nur das Schutzprogramm muß dem 128er angepaßt werden. Hier das Schutzprogramm für den C128: Hauptprogramm 1300 LDX #$00 1302 LDA S1380,X 1305 STA S1C01,X
Zähler auf Null BASIC-Zeile ins
356 1308 INX 1309 CPX #$28 130B BNE S1302 1300 lOA #$93 130F JSR SFF02 1312 lOX #$00 1314 lOA S13A8,X 1317 JSR SFFD2 131A INX 131 B CPX #$OE 1310 BNE S1314 131F lOX #$00 1321 JSR SFFCF 1324 STA S13B8,X 1327 INX 1328 CMP #$00 132A BNE S1321 132C OEX 1320 STX S72 132F LDX #$08 1331 lOY #$00 1333 JSR SFFBA 1336 lDA S72 1338 lOX #$B8 133A lDY #$13 133C JSR SFFBD 133F lOA #$00 1341 lOX #$29 1343 lOY #$1C 1345 JSR SFF05 1348 lDX S72 134A lOA #$2F 134C STA S13B8,X 134F lOA #$53 1351 STA S13B9,X 1354 INX 1355 INX 1356 STX S72 1358 lOX #$08 135A lOY #$01
Das große Anti-Cracker- Buch RAM kopieren 28 Bytes erreicht? verzweige, wenn nicht erreicht Bildschirm löschen PROGRAMMNAME: auf Bildschim ausgeben Zähler erhöhen schon 15 Bytes? verzweige, wenn nicht erreicht I Zähler auf Null Zeichen von Tastatur holen und zwischenspeichern Zähler erhöhen mit RETURN vergleichen verzweige, wenn nicht gedrückt Return-Code abschneiden Zähler speichern Geräteadresse und Sekundäradresse laden File-Parameter übergeben länge des Filenamens laden Adresse des Filenamens in lOW-HIGH-Byte Format laden Filenamen-Parameter übergeben Flag für lOAD Startadresse bestimmen lOAD Länge des Filenamen laden Code für '/' laden und speichern Code für 's' laden und speichern Zähler erhöhen Zähler erhöhen und speichern Geräteadresse laden Sekundäradresse laden
Kopierschutz auf dem C128 135C 135F 1361 1363 1365 1368 136A 136C 136E 1370 1372 1374 1376 1379
JSR LOA LOX LOY JSR LOX LOY STX STY LOA LOX LOY JSR JMP
SFFBA S72 #SB8 #S13 SFFBO #S01 #S1C SFB SFC #SFB SAE SAF SFF08 S4F4F
File·Parameter übergeben Länge des Filenamens laden Adresse des Filenamens im LOW · HIGH·Byte Format laden Filenamen·Parameter übergeben Startadresse im LOW'HIGH Format laden und Speichern Zeiger auf Startadresse laden Endadresse im LOW'HIGH'Byte Format bestimmen SAVE BASIC'Zeilen neu binden und Rücksprung
BASIC-Zeile: 137C 00 00 00 00 OC OC OA 00 •••••••• 1384 9E 20 3731 3834 00 00 • 7184 •• 138C 00 00 00 18 A9 28 65 2D ••• • )(e·
Programm hinter Sys-Zeile: 1390 1392 1394 1396 1398 139A 139C 139F 13A2
LOA ADC STA LOA ADC STA JSR JSR JMP
#S28 S20 S20 #$00 S2E S2E S4F4F SAF7E S4AFO
Länge des Programms laden zu LOW'Byte addieren und speichern falls Obertrag HIGH'Byte +1 BASIC'Zeilen neu binden CLR RUN
13A5 00 00 00 50 52 4F 47 52 ••• PROGR 13AO 41 40 40 4E 41 40 45 3A AMMNAME: 13B5 20 00 00
357
358
Das große Anti-Cracker-Buch
Nachfolgend der BASIC-Loader: 100 FORI=1T0192STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)-48:IFA>9THENA=A-7 110 B=ASC(B$)-48:IFB>9THENB=B-7 120 A=A*16+B:C=(C+A)AND255:POKE4863+I+J,A:NEXT:READA: I FC=ATHENC=O:NEXT:END 130 PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP 300 DATAA2,00,BD,80, 13,90,01, 1C,E8,EO,28,DO,F5,A9,93, 157 301 DATA20,D2,FF,A2,00,BD,A8,13,20,D2,FF,E8,EO,OE,DO, 162 302 DATAF5,A2,00,20,CF,FF,9O,B8,13,E8,C9,OD,DO,F5,CA, 58 303 DATA86,72,A2,08,AO,00,20,BA,FF,A5,72,A2,B8,AO,13, 63 304 DATA20,BD,FF,A9,00,A2,29,AO,1C,20,D5,FF,A6,72,A9, 193 305 DATA2F,9O,B8,13,A9,53,9O,B9,13,E8,E8,86,72,A2,08, 110 306 DATAAO,01,20,BA,FF,A5,72,A2,B8,AO, 13,20,BD,FF,A2, 28 307 DATA01,AO,1C,86,FB,84,FC,A9,FB,A6,AE,A4,AF,20,D8, 1 308 DATAFF,4C,4F,4F,00,00,00,00,OC,OC,OA,00,9E,20,37, 309 DATA31,38,34,00,00,00,00,00, 18,A9,28,65,2D,85,2D, 202 310 DATAA9,00,65,2E,85,2E,20,4F,4F,20,7E,AF,4C,FD,4A, 141 311 DATAOO,00,00,50,52,4F,47,52,41,4D,4D,4E,41,4D,45, 134 312 DATA3A,20,00,00,50,52,4F,42,45,2F,53,00,CA,3D,CA, 37
°
Noch ein wichtiger Hinweis: Nach dem Einlesen der DATA-Zeilen in den Speicher muß unbedingt ein RESET durchgeführt werden, da sonst die Funktionsweise des Schutzprogramms beeinträchtigt wird_ Das zu schützende Programm muß sich vorher abgespeichert auf Diskette befinden_ Das Schutzprogramm startet sich anschließend mit SYS DEC(" 1300")_ Nach dem Start fragt das Programm nach dem Filenamen des zu schützenden BASIC-Programms_ Sobald das Schutzprogramm das BASIC-Programm eingeladen hat, wird es sofort wieder mit dem gleichen Filenamen auf Diskette gespeichert. Nur wird hinter dem Filenamen noch ein / S angehängt_ Deshalb sollte der Filename nicht länger als 14 Zeichen sein_
Kopierschutz auf dem C128
359
Zu Kapitel 2.2.1:
Bei dieser Schutzmethode hat sich außer den veränderten Zeigeradressen, die jetzt auf $1210 $1211 liegen, nichts verändert.
Zu Kapitel 2.2.2:
Hier gilt das gleiche, was schon zu Kapitel 2.1.7 angemerkt wurde. Nachfolgend das Schutzprogramm für den C128:
1300 LOX #$00 1302 LDA 51380,X 1305 STA 51C01,X 1308 INX 1309 CPX #$3B 130B BNE 51302 130D JSR 5C142 1310 LDX #$00 1312 LOA S13CO,X 1315 JSR 5FFD2 1318 INX 1319 CPX #$OE 131B BNE S1312 1310 LOX #$00 131F JSR SFFCF 1322 STA S13DO,X 1325 INX 1326 CMP #$00 1328 BNE 5131F 132A DEX 132B STX 572 1320 LDX #$08 132F LDY #$00 1331 JSR 5FFBA 1334 LDA 572 1336 LOX #$DO
Zähler auf Null BASIC-Zeile ins RAM kopieren Zähler erhöhen schon alle Bytes kopiert? verzweige, wenn nicht Bildschirm löschen PROGRAMMNAME: auf dem Bildschirm bringen Zähler erhöhen schon 15 Bytes? verzweige, wenn nicht Zähler auf Null Zeichen von Tastatur holen und zwischenspeichern Zähler erhöhen Vergleich mit RETURN verzweige, wenn kein RETURN-Code RETURN-Code abschneiden und Zähler speichern Geräteadresse laden Sekundäradresse laden Fileparameter übergeben Länge des Filenamens laden Adresse des Filenamens im
360 1338 LDY #S13 133A JSR SFFBD 1330 LDA #SOO 133F LDX #S3C 1341 LDY #S1C 1343 JSR SFFD5 1346 NOP 1347 NOP 1348 NOP 1349 NOP 134A NOP 134B NOP 134C NOP 1340 NOP 134E LDX sn 1350 LDA #S2F 1352 STA S13DO,X 1355 INX 1356 LDA #$41 1358 STA S1300,X 135B INX 135C STX sn 135E LOX #S08 1360 LOY #S01 1362 JSR SFFBA 1365 LOA S72 1367 LOX #$00 1369 LDY #S13 136B JSR SFFBD 136E LDX #S01 1370 LDY #S1C 13n STX SFB 1374 STY SFC 1376 LDA #SFB 1378 LDX SAE 137A LDY SAF 137C JSR SFF08 137F JMP S4F4F
Das große Anti-Cracker-Buch LOW-HIGH-Byte Format laden Filenamen-Parameter übergeben Flag für LOAD Startadresse bestinmen LOAD
Länge des Filenamens laden Code für 'I' laden und speichern Zähler erhöhen Code für 'A' laden und speichern Zähler erhöhen und speichern Geräteadresse laden Sekundäradresse laden File-Parameter übergeben Länge des Filenamens laden LOW-HIGH-Byte Adressformat laden und übergeben Startadresse laden und
speichern Zeiger auf Startadresse laden Endadresse bestinmen SAVE BASIC-Zei len neu Bi lden und Rücksprung
Kopierschutz auf dem C128
361
BASIC-Zeile und Bildschimtext: 1382 138A 1392 139A 13A2 13AA 13B2 13BA 13C2 13CA
FF 03 80 60 1C 80 08 00 4F 4D
FF 1C 02 A2 A9 03 02 00 47 45
8F CA 03 FF C6 03 00 00 52 3A
20 8E A9 8E 80 4C 9E 00 41 20
22 A2 04 1C 1C 80 03 1C 02 03 C6 4D 3731 00 00 4D 4D 00 00
01 BE A9 1C 03 03 8E 04 A9 4D 00 3C 3736 50 52 4E 41 51 DO
•• J ••• ). ... ) .... I
.)F ••• )M •.. LFM.< •••• 7176 •••••• PR OGRAMMNA ME: •• QP
Nachfolgend der BASIC-Loader: 100 FORI=1T0208STEP15:FORJ=OT014:READA$:B$=RIGHT$(A$,1) 105 A=ASC(A$)·48:IFA>9THENA=A·7 110 B=ASC(B$)·48:IFB>9THENB=B·7 120 A=A*16+B:C=(C+A)AND255:POKE4863+I+J,A:NEXT:READA: IFC=ATHENC=O:NEXT: END 130 PRINT"FEHLER IN ZEILE:"iPEEK(63)+PEEK(64)*256:STOP 300 DATAA2,00,BD,80,13,9D,01,1C,E8,EO,3B,DO,F5,20,42, 214 301 DATAC1,A2,00,BD,CO,13,20,D2,FF,E8,EO,OE,DO,F5,A2, 33 302 DATAOO,20,CF,FF,9D,DO,13,E8,C9,OD,DO,F5,CA,86,72, 179 303 DATAA2,08,AO,00,20,BA,FF,A5,72,A2,DO,AO,13,20,BD, 60 304 DATAFF,A9,00,A2,3C,AO,1C,20,D5,FF,EA,EA,EA,EA,EA, 200 305 DATAEA,EA,EA,A6,72,A9,2F,9D,DO,13,E8,A9,41,9D,DO, 109 306 DATA13,E8,86,72,A2,08,AO,01,20,BA,FF,A5,72,A2,DO, 160 307 DATAAO,13,20,BD,FF,A2,01,AO,1C,86,FB,84,FC,A9,FB, 147 308 DATAA6,AE,A4,AF,20,D8,FF,4C,4F,4F,FF,FF,8F,20,22, 87 309 DATAA2,01,8E,03,1C,CA,8E,04,1C,A9,1C,8O,02,03,A9, 200 310 DATA1C,8O,03,03,60,A2,FF,8E,03,1C,8E,04,1C,A9,C6, 122 311 DATA8O,02,03,A9,4D,8O,03,03,4C,C6,4D,OO,3C,08,02, 192 312 DATAOO,9E,37,31,37,36,00,00,00,00,00,00,50,52,4F, 100 313 DATA47,52,41,4D,4D,4E,41,4D,45,3A,20,00,00,51,DO, 16
362
Das große Anti-Cracker-Buch
Zu Kapitel 2.3:
Um beim 128er die Stop-Taste außer Betrieb zu setzen, muß folgender POKE eingegeben werden: POKE 808,107
Der Sprung vektor ist liegt wie beim C64 in der gleichen Adresse, nur der Zeiger des LOW - Bytes muß geändert werden. Leider kann man diesen Schutz umgehen, wenn man beim Drücken des RESET -Knopfs gleichzeitig die Stop-Taste gedrückt hält. Man hat anschließend das unversehrte BASIC-Programm, wenn der Benutzer den Monitor mit X verlassen hat. Deshalb ist es ratsam, zusätzlich noch einen RESET -Schutz einzubauen: BANK 1
POKE 65528,33
Nach diesen beiden Befehlen hilft nur das Ausschalten des Rechners. Zu Kapitel 2.4 und 2.4.1:
Dieses Verfahren funktioniert unverändert auch auf dem C128. Zu Kapitel 3.1: Autostartmethoden
Allgemeine Anmerkungen Prinzipiell unterscheiden sich die Methoden zur Erzeugung eines Autostarts beim C64 nicht wesentlich von denen des C128. Alle Ideen zum Autostart, die wir in Kapitel 3 beschrieben haben, lassen sich auch den C128 übertragen, wenn man einige Punkte berücksichtigt.
Kopierschutz auf dem C128
363
Als erstes ist zu beachten, daß die Speicher konfiguration sich in wesentlichen Punkten ändert. Unter anderem ändert sich die Länge und die Adresse des Kassettenpuffers. Auch das ROM belegt beim C128 nicht genau denselben Bereich wie beim C64. Aus diesen Veränderungen folgt, daß sich auch die Adressen der ROM-Routinen und der Zeiger in der Zeropage ändern. Allgemein ist es empfehlenswert, die Zeiger ab $0300 und die Sprungtabelle am Ende des ROMs zu benutzen, da diese im we sentlichen identisch sind. Ein Autostart auf dem C128, der nach einer in Kapitel 3 beschriebenen Methode arbeitet, ist nur als Schutzmethode sinnvoll, da der Cl28 über eine Boot-Routine verfügt, die wir unten erläutern werden. Diese Routine stellt eine sehr komfortable und einfache Möglichkeit dar, um einen Autostart zu erzeugen. Größter Nachteil dabei ist, daß diese Autostartprogramme leicht verstanden und modifiziert werden können. Um die Programme zu schützen, muß man auch hier auf die "alten" Methode zurückgreifen. Boot-Sektor und -Routine
Im Gegensatz zum C64 verfügt der Cl28 bereits über eine Betriebssystemroutine, die zum automatischen Laden und Starten eines Programms dient, das auf Diskette vorliegt. Diese Routine wird sofort nach dem Einschalten des Rechners angesprungen, was sich durch ein kurzes Anlaufen der Floppy äußert. Erst danach meldet sich der Rechner mit dem blinkenden Cursor zur Stelle, um Befehle des Benutzers zu erwarten und seine eigentliche Arbeit aufzunehmen. Es ist dabei unerheblich, ob Sie bereits eine Diskette in das Laufwerk eingelegt haben. Diese Routine sucht auf der Diskette nach einem sogenannten Boot-Sektor, in dem diverse Informationen über das zu ladende Programm oder die zu ladenden Blöcke stehen. Da die BootRoutine natürlich nicht die ganze Diskette nach einem solchen Boot-Sektor absuchen kann, gibt es nur eine fest definierte
364
Das große Anti-Cracker-Buch
Stelle auf der Diskette, die als Bootsektor genutzt werden kann. Hierbei handelt es sich um: Seite 0, Spur 1, Sektor 1 Vorsicht ist geboten, wenn Sie einen eigenen Boot-Sektor auf einer Diskette installieren wollen. Auch wenn der Boot-Sektor physikalisch der erste Block auf der Diskette ist, kann es doch vorkommen, daß dieser Platz schon von einem anderen Programm belegt ist. Vor dem Installieren sollten Sie immer erst prüfen, ob der Block nicht schon benutzt wurde. Um den Aufbau des Bootsektors verstehen zu können, sollten Sie den schematischen Ablauf der im Kernal verankerten Routine kennen, der folgendermaßen abläuft: 1.
Im DOS-Puffer der erweiterten Zeropage wird ein BlockRead-Befehl auf die Spur 1, Sektor 1 aufgebaut.
2.
Der Befehl wird ausgeführt und der gelesene Block, sofern sich eine formatierte Diskette iJll Laufwerk befindet, in den Kassettenpuffer geladen.
3.
Anhand der ersten drei Bytes des gelesenen Blocks wird geprüft, ob es sich um einen Boot-Sektor handelt. Der Identifizierungscode lautet CBM, die ersten drei Bytes müssen also die Werte $43, $42 und $4D haben. Ist dieser Code nicht vorhanden, wird die Boot-Routine abgebrochen.
4.
Die auf den CBM-Code folgenden vier Bytes werden in vier Zeropage-Adressen geladen. Normalerweise sind diese vier Bytes auf den Wert $00 gesetzt. Die ersten beiden Bytes können eine Startadresse enthalten, die jedoch nichts mit der Anfangsadresse zu tun hat, an die das gewünschte Programm geladen werden soll. Das dritte Byte ist der entsprechende Konfigurationsindex zur genannten Startadresse. Das nun folgende vierte Byte gibt die Anzahl der Blöcke an, die außer dem Boot-Sektor noch von Diskette geladen werden sollen. Weist dieses Byte den Wert
Kopierschutz auf dem C J 28
365
$00 auf, so sind die oben erwähnten Bytes für die Startadresse und das dazugehörige Konfigurations-Byte unerheblich, da kein Programm nachgeladen wird. 5.
Unabhängig von der Anzahl der zu ladenden Blöcke, werden die nun folgenden Bytes, also ab dem 8. Byte des Blocks, über die BSOUT - Routine auf dem Bildschirm ausgegeben. Hiermit läßt sich der Bildschirm löschen oder ein beliebiger Text auf dem Bildschirm ausgeben. Die Zeichenausgabe erfolgt so lange, bis die Endmarkierung $00 gefunden wird.
6.
Nun bekommen die unter Punkt 4 erwähnten Steuer-Bytes eine Bedeutung, sofern der Blockzähler nicht auf den Wert $00 gesetzt ist. In diesem speziellen Fall wird die nun folgende Routine übersprungen. Ist dies aber nicht der Fall, wird im DOS-Befehlspuffer einer neuer Befehls-String gebildet, der weitere Blöcke nachlädt. Die Bestimmung der Folgeblocks fällt dabei denkbar einfach aus: Die Sektornummer wird immer um eins erhöht, es wird also der nächste Block der Spur eingelesen. Da nur 21 Sektoren, mit den Nummern 0 bis 20, existieren, wird bei der Sektornummer 21 die Spur um eins erhöht. Danach werden die weiteren Blöcke von der neuen Spur wieder ab Sektor o eingelesen. Wie schon oben erwähnt, ist die Anzahl der zu lesenden Blöcke im ersten Boot-Block festgelegt. Die Speicheradresse, an die die Blöcke geladen werden, wird durch die ersten drei Bytes des zuerst nachgeladenen Blocks bestimmt. Die ersten beiden Bytes geben dabei die Speicheradresse an. In dem dritten Byte ist die Konfiguration gespeichert. Alle weiteren Blöcke werden im Speicher hinter dem vorherigen abgelegt.
7.
Anschließend setzt die Boot-Routine wieder hinter die (wenn vorhanden) Textkonstanten des ursprünglichen Boot-Blocks im Kassettenpuffer an. Hier kann nun ein Dateiname stehen, wie er auch im Directory der Diskette vorhanden ist. Mit Ausnahme der Tatsache, daß die den Dateinamen bildenden Zeichen nicht auf dem Bildschirm ausgegeben werden, werden auch hier alle Bytes gelesen,
Das große Anti-Cracker-Buch
366
bis die Boot-Routine abermals auf das Endezeichen $00 stößt. Die Länge des Dateinamens wird dabei in einem Zeiger festgehalten. 8.
Ist die Länge des Dateinames im Speicher größer null, so werden dem Namen die Zeichen "0:" vorangestellt und die Länge des Namens entsprechend erhöht. Danach wird das entsprechende Programm in den Speicher eingeladen. Wenn das geschehen ist, oder wenn die Länge des Dateinamens mit null festgehalten wurde, so setzt die Boot-Routine wieder hinter dem $00 Trenncode ein, der das Ende des Dateinamens kennzeichnet.
9.
Die auf den Dateinamen folgenden Bytes werden nun als Maschinenprogramm des Boot-Sektors angesehen, und die Bootroutine übergibt die Steuerung an dieses Programm. Von nun an können Sie den gesamten weiteren Ablauf selbst bestimmen. Es steht Ihnen dabei offen, ob Sie weitere Programme nachladen oder eventuell eingeladene Blöcke oder Programme an einer bestimmten Stelle starten.
Wenn Sie bei der Erstellung des Bootsektors die oben genannten Schritte des Betriebssystems beachten, so werden Sie bald feststellen, daß es gar nicht so schwer ist, eigene Bootsektoren zu erzeugen und diese auch sinnvoll einzusetzen. Hier noch einmal die wichtigsten Merkmale und Anordnungen: Byte 0, 1, 2: Byte 3, 4: Byte 5: Byte 6: Byte 7: Dann: Dann:
CBM·ldentifikationscode Startadresse für Folge·Boot·Sektoren Konfigurationsindex Blockzähler für die Anzahl der Folgesektoren Bis zum ersten Trenncode SOO eigener Text Name des nachzuladenden Programms mit SOO Eigenes Maschinenprogramm
Kopierschutz auf dem C128
367
Zu Kapitel 3.2: Prü!summen und Selbstzerstärung
Die in diesem Kapitel erklärten Schutzsysteme sind nicht rechnerspezifisch. Daher braucht für den Cl28 nichts geändert werden. Zu Kapitel 3.3: Codierung
Allgemein können die Codiersysteme aus Kapitel 3, die für den C64 entwickelt wurden, auch auf den Cl28 übernommen werden. Da diese Programme nicht mit den Betriebssystemroutinen arbeiten, treten bei der Verwendung des anderen Betriebssystems keine Probleme auf. Auch die Timercodierung kann unverändert übernommen werden, da es sich beim Cl28 um die gleichen Register handelt. Zu Kapitel 3.4: Illegal-Opcodes
Der Prozessor des Cl28 unterscheidet sich so wenig vom Prozessor des C64, daß er sich auch bei illegalen Opcodes genauso verhält. Daher ergeben sich hier keine Änderungen. Zu Kapitel 3.5: Dongle
Wenn man beachtet, daß man vor Abfrage der Joystick-Ports mit dem Befehl "BANK 15" die richtige Speicherbank anwählt, braucht man an den bestehenden Programmen nichts zu ändern. Zu Kapitel 3.6:
Die in diesem Buch aufgeführten Schutzmethoden funktionieren auch auf dem 128er. Die dazugehörigen Beispielprogramme müssen nur den Speicheradressen des C128 angepaßt werden.
368
Das große Anti-Cracker-Buch
Hier nun die modifizierten Beispielprogramme: Pass 1 10 20 30 40 50 60 70 80
POKE 808,98:PRINT CHR$(147):A=0 POKE 19,1 PRINT CHR$(19):INPUT "PASSWORD :"iAS POKE 19,0:PRINTCHR$(13) IF A$="" THEN 20 IF A$="HARAlD" THEN PRINT CHRS(17);"PASWORD AKZEPTIERTI":END A=A+1:IF A=3 THEN SYS 65341 GOTO 20
Pass 2 1300 JSR SC142 1303 lDX #$00 1305 lDA S1363,X 1308 JSR $FFD2 130B INX 130C CPX #$09 130E BNE S1305 1310 lDX #$00 1312 JSR SFFCF 1315 STA S1380,X 1318 INX 1319 CMP #$00 131B BNE S1312 131D DEX 131E STX $02 1320 lDX #$00 1322 lDA S136C,X 1325 EOR #$24 1327 STA S136C,X 132A INX 132B CPX #$06 132D BNE $1322 132F lDX #$00 1331 lDA S1380,X
Bildschirm löschen Zähler auf Null PASSWORD: auf Bildschim bringen Zähler erhöhen schon alle Bytes? verzweige, wenn nicht Zähler auf Null Byte von der Tastatur holen und speichern Zähler erhöhen auf RETURN warten verzweige, wenn noch nicht gedrückt RETURN-Code abschneiden Zähler speichern Password Decodieren
Zähler auf Null Eingegebenes Password
Kopierschutz auf dem C128 1334 1337 1339 133C 1330 133F 1341 1343 1346 1348 134B 134C 134E 1350 1351 1353 1356 1358 135B 135C 135E 1360
CHP BEQ JMP INX CPX BNE LDX LDA EOR STA INX CPX BNE RTS LDX LDA EOR STA INX CPX BNE JMP
5136C,X 5133C 51351 502 51331 #$00 S136C,X #$24 S136C,X
369
mit vorgegebenem vergleichen verzweige, wenn richtig ansonsten RESET Zähler erhöhen vergleich mit Länge des Passwortes verzweige, wenn Länge nicht erreicht Password wieder codieren
#$06 S1343 #$00 S136C,X #$24 S136C,X #$06 S1353 SFF3D
Rücksprung Password wieder codieren
RESET
Bildschirmtext und Passwort: 1363 136B 1373 1378
50 3A 00 00
41 6C 68 00
53 65 60 00
53 57 7665 00 00 00 00
4F 68 00 00
52 60 00 00
54 PASSWORD 00 :leveh'. 00 .h' ..... 00
Nachfolgend der BASIC-Loader: 100 105 110 120 END 130
FORI=1T0120STEP15:FORJ=OT014:READAS:BS=RIGHTS(AS,1) A=ASC(AS)-48:IFA>9THENA=A-7 B=ASC(BS) -48:IFB>9THENB=B-7 A=A*16+B:C=(C+A)AND255:POKE4863+I+J,A:NEXT:READA: IFC=ATHENC=O:NEXT: PRINT"FEHLER IN ZEILE:"iPEEK(63)+PEEK(64)*256:STOP
370 300 301 302 303 304 305 306 307 308
Das große Anti-Cracker-Buch
OATA20,42,C1,A2,00,BO,63,13,20,02,FF,E8,EO,09,00, OATAF5,A2,00,20,CF,FF,9D,80,13,E8,C9,00,00,F5,CA, OATA86,02,A2,00,BO,6C,13,49,24,9D,6C,13,E8,EO,06, OATAOO,F3,A2,00,BO,80,13,00,6C, 13,FO,03,4C,51, 13, OATAE8,E4,02,00,FO,A2,00,BO,6C,13,49,24,9D,6C,13, OATAE8,EO,06,00,F3,60,A2,00,BO,6C,13,49,24,9D,6C, OATA13,E8,EO,06,00,F3,4C,30,FF,50,41,53,53,57,4F, OATA52,54,3A,6C,65,76,65,68,60,00,00,68,60,00,00, OATAOO,OO,OO,OO,OO,OO,OO,OO,OO,OO,OO,OO,OO,OO,OO,
138 2 189 180 245 69 9 28 0
Pass 3 1300 SEI 1301 LOA #$00 1303 STA $OCOO 1306 LOA $DC01 1309 CMP #$FF 130B BEQ $1301 1300 LOX #$00 130F LOA #$FE 1311 PHA 1312 STA $DCOO 1315 LOA $DC01 1318 CMP $1320,X 131B BEQ $1321 1310 PLA 131E CLC 131F BCC $1301 1321 PLA 1322 INX 1323 CPX #$08 1325 BEQ $132B 1327 SEC 1328 ROL 1329 BNE $1311 132B CU 132C RTS
Interrupt verhindern Port A auf LOW setzen Port B holen Taste gedrückt verzweige, wenn nicht gedrückt Zähler auf Null Alle Bits bis auf das erste setzen Wert speichern an Port A übergeben Wert von Port B holen Mit Wert aus der Tabelle vergleichen verzweige, wenn Wert identisch ansonsten Stapel rücksetzen und um Anfang verzweigen gespeicherten Wert holen Zähler erhöhen mit Endwert vergleichen verzweige, wenn gleich Carry setzen gelöschtes Bit um eins verschieben unbedingter Sprung Interrupt wieder ermöglichen Rücksprung
Kopierschutz auf dem C128
371
Nachfolgend der BASIC-Loader: 100 105 110 120 END 130 300 301 302 303
FORI=1T056STEP15:FORJ=OT014:READAS:BS=RIGHTS(AS,1) A=ASC(AS)-48:IFA>9THENA=A-7 B=ASC(B$)-48:IFB>9THENB=B-7 A=A*16+B:C=(C+A)AND255:POKE4863+I+J,A:NEXT:READA: IFC=ATHENC=O:NEXT: PRINT"FEHLER IN ZEILE:";PEEK(63)+PEEK(64)*256:STOP DATA78,A9,00,8D,00,DC,AD,01,DC,C9,FF,FO,F4,A2,00, 98 DATAA9,FE,48,8D,00,DC,AD,01,DC,DD,2D,13,FO,04,68, 91 DATA18,90,EO,68,E8,EO,08,FO,04,38,2A,DO,E6,58,60, 132 DATAFF,FF,FB,F7,EF,FF,FF,FF,00,00,00,01,08,00,00, 229
Zu Kapitel 4:
Einige Schutzmethoden, die auf dem C64 einwandfrei funkionieren, laufen leider auf dem 128er nicht, weil das Directory nicht wie beim C64 in den BASIC-Speicher, sondern direkt in den Bildschirmspeicher geladen wird. Deshalb werden wir nur auf diejenigen Schutzmethoden zu sprechen kommen, die funktionsfähig sind. Die Schutzmethoden, die nicht erwähnt werden, laufen nicht auf dem 128er. Die funktionierenden Schutzmethoden bedürfen keiner weiteren Erklärung, da sie identisch mit denen des C64 sind. Hier nun die Auflistung der funkionierenden Schutzmethoden: Kapitel 4.1 Versteckter Filename Kapitel 4.2 Verstecken der Filenamen durch Steuerzeichen Kapitel 4.7 Gelöschte Files
372
Das große Anti-Cracker-Buch
zu Kapitel 5: Kopierschutz mit der Datasette
Die Datasette ist als Speichermedium beim C128 dermaßen unpopulär, daß es sich wohl kaum lohnen würde, komplexe Kopierschutzmaßnahmen für Bandaufzeichnungen zu beschreiben. Für Interessierte sei aber gesagt, daß die Ansteuerung des Datenrekorders hier genauso vor sich geht wie beim C64. Es ist dabei lediglich zu beachten, daß die Speicheraufteilung des l28er sich von der des C64 stark unterscheidet. Näheres dazu finden Sie beispielsweise in dem DA T A - BECKER - Buch "128 Intern". Zu Kapitel 6: Diskettenkopierschutz
Diskettenkopierschutz auf dem C128. Wie sieht das in der Praxis aus? Um diese Frage zu beantworten, sehen wir uns das Diskettenlaufwerk des C128 (1570/1571) einmal genauer an. Bei dieser näheren Betrachtung stellen wir fest, daß sich das Laufwerk, verglichen mit dem Laufwerk des C64 nicht sehr verändert hat. Die 1570/71 hat schnellere Busübertragungsroutinen, ein paar "Macken", die auch in der 1541 zu finden sind, und darüber hinaus noch weitere nette Ungereimtheiten, die es in der 1541 zum Glück nicht gibt. Beide Diskettenlaufwerke verfügen über den gleichen Controller, wodurch es theoretisch möglich ist, alle in diesem Buch gezeigten Kopierschutzverfahren für den C128 zu übernehmen. Das einzige Problem bei dieser Übernahme sind die zu ändernden DOS-Routinen-Einsprünge und die Tatsache, daß die ByteReady-Leitung nicht über das Overflow-Flag, sondern über Bit 8 der Adresse $180F der VIAI abgefragt werden muß. Aufgrund dieser Umstände haben wir uns zu einer recht einfachen, jedoch bestimmt nicht schlechten Lösung entschlossen, um den C128Besitzern die Möglichkeit zu geben, alle unsere Kopierschutzverfahren anzuwenden. Das Zauberwort heißt Umschalten. Anders ausgedrückt bedeutet dies, daß Sie einfach vor dem Auftragen oder Abfragen des Ko-
Kopierschutz auf dem C128
373
pierschutzes die Floppy auf den 1541-Modus herunterschalten. Nach diesem Vorgang kann der Schutz ohne Schwierigkeiten verwendet werden. Nach der Abfrage können Sie wieder in den 1570/ 71-Modus zurückschalten. Die folgenden Befehle dienen zum Umschalten in die verschiedenen Modi. OPEN 1,8,15,IUO>:MO" OPEN 1,8,15,IUO>M1"
=
Einschalten des 1541-Modus Zurückschalten in den 1570/71-Modus
Selbstverständlich ist es auch möglich, auf den jeweiligen Modus erst in der Floppy umzuschalten. Zu diesem Zweck müssen Sie die in der Floppy auszuführenden Programme um den entsprechenden Unterprogrammaufruf erweitern. Die Einsprünge dieser Routinen liegen folgendermaßen: JSR $9032 ; Umschalten auf 1541-Mode JSR $904E ; Umschalten auf 1571-Mode
Einige Kopierschutzauftrageprogramme arbeiten leider nur auf dem C64, weshalb es nötig ist, beim Auftragen des Schutzes in den C64-Modus umzuschalten. Die Abfrage funktioniert jedoch fehlerfrei auch im CI28-Modus. Darunter fallen die Programme aus den Kapiteln 6.2.4 (Änderung der Block-Reader) und 6.6.3 (Der 3000-SYNC-Schutz). Zu Kapitel 7: Wie man sich gegen Knackmodule schützt
Es ist in Kürze damit zu rechnen, daß die ersten Knackmodule und -Betriebssysteme für den 128er erscheinen. Prinzipiell kann man hier die gleichen Schutzmethoden anwenden wie beim C64. Abweichungen ergeben sich nur durch die andere Speicherverwaltung.
Das große Anti-Cracker-Buch
374
Zu Kapitel 7.1: Speicherstellen, die bei einem RESET gelöscht werden: RAM-Bank 0: SOOOO-S0101 S0200-$OAC5 S1000-$12FC
= = =
o - 257 Systemadressen 512 - 2048 Systemadressen und Bildschirm Systemadressen
I/O-Bank: ("BANK 15") SDOOO-SDDOF
= 53248-56591
I/O-Bereich
Alle RAM - Bänke: SFF05-SFF44 SFFFA-SFFFF
=
IRQ/NMI-Routinen IRQ/NMI/RESET'Vektoren
=
Zu Kapitel 7.2: Die Methode, Daten im Floppy-Speicher abzulegen, funktioniert unverändert auf dem C128. Lediglich die Anzahl der freien Speicherstellen, die das Betriebssystem unbenutzt läßt, hat sich verringert. Übrig geblieben sind folgende Adressen:
Hexadezimal 0005 0010 I 0011 0014 I 0015 0010 001F 0021 0100 0102 I 0103
Dezimal 5 16 I 17 20 I 21 29 31 33 256 258 I 259
Kovierschutz auf dem C128
375
Das Programm zur Abfrage der Hüllkurve der Stimme drei SIDs braucht nicht geändert zu werden. Nur hinsichtlich "BANK"-Befehls sollten Sie das in der Einleitung gesagte achten. Im Zweifelsfalle ist es also besser, vor der Abfrage "BANK 15" auszuführen.
des des beein
Der Test auf die Alarmzeit der Echtzeituhr wird besser mit der zweiten CIA durchgeführt, da Betriebssystem und BASIC-Interpreter trotz Abschalten des Timer-Interrupts auf das InterruptKontrollregister der CIA eins zurückgreifen. Hier nun also die geänderten Programme: Setzen der Alarmzeit: 5 BANK 15:REM FALLS NOTWENDIG: I/O· BEREICH ANSPRECHEN 10 POKE56335+256,PEEK(56335+256)OR128:REM BIT 7 SETZEN 20 POKE56331+256,128+7:REM 7 UHR NACHMITTAGS 30 POKE56330+256,3*16+5:REM 35 MINUTEN 40 POKE56329+256,1*16+1:REM 11 SEKUNDEN 50 POKE56328+256,1:REM 1 ZEHNTELSEKUNDE
Test der Alarmzeit: 5 BANK 15:REM FALLS NOTWENDIG: I/O·BEREICH ANSPRECHEN 10 POKE56335+256,PEEK(56335+256)AND127:REM BIT 7 LEOSCHEN 20 POKE56331+256,128+7:REM 7 UHR NACHMITTAGS 30 POKE56330+256,3*16+5:REM 35 MINUTEN 40 POKE56329+256,1*16+1:REM 11 SEKUNDEN 50 POKE56328+256,0:REM 0 ZEHNTELSEKUNDEN 70 FORI=1T0400:NEXT:REM ALARM ABWARTEN 80 A=PEEK(56333+256):REM ALARM TESTEN 90 IF (A AND 4)<>4 THEN PRINT"VERFLIXT, EIN KNACKMOOUL!"
Zu Kapitel 8:
Alles, was zum Thema "rechnerzerstörender Kopierschutz" zum C64 gesagt wurde, trifft auch auf den 128er zu. Die in dieses
376
Das große Anti-Cracker-Buch
Gerät neu eingebauten Chips lassen keinen Ansatz für "KillerProgramme" erkennnen. Zudem sind sogar alle wichtigen Bausteine durch Wärmeableitplatten vor Überhitzung geschützt. Trotzdem geben wir auch hier den Hinweis: Falls wir uns irren, wären wir für eine Mitteilung dankbar. Viren treffen auch im 128er-Modus auf dieselben Schwierigkeiten, die schon für den C64 beschrieben wurden. Höchstens im CP/M-Modus ließe sich vielleicht eine Möglichkeit finden, da: hier das Betriebssystem von der Diskette geladen wird. Aber auch hier läßt sich durch die Verwendung von Schreibschutzetiketten jedes Virus bekämpfen.
Index
377
Index 1641- M odus .. .. ... ...... .... ..... ... ......... 364 Adreßverschiebung ... .. ........ ............ 82 Alarmzeit ..... ....................... .. 330, 376 AND - Gatter .............. .......... ......... 123 Änderungsschutz .............. ........... ... 43 ASCII - Code ............. .. ..... .... .......... l01i Assemblercode .... ...... .... .. ............... 97 Aufzeichnu ngsformat .. .......... 167, 177 Aufzeichnungsgeschwindigkeit .. .. .. 167
CBM80 .. ................................ . 48 , 348 CBM80- Kennung ............ .. ..... .. .... 321 Checksumme ... ...................... ....... 227 CIA ......... ........ .. ............................. 80 CIA-Timer ...................... .... ......... 194 Codierung .. ............... .............. 87,367 Compiler ...... ........... ........................ 60 Compiler-Schutz .......................... 146 Compilercode .............. ... ....... ... .. .... . 51
Aufzeichnungsmethode .. .. .. ...... .....166 Autostart .............. ..... .. ....... ... .. . 66, 80
Control-Port ..... ..... ...................... 120 Control - Port - Stecker ................... 121 Control-Register .. .............. .......... 234
Autostartmethoden .. ... .. ........... ..... 362
Controller ... .. .......... .... .. .... ............ 313
Band ... ....... .. .... .. ........ ... ... ... ....... ... 161 Bandaufnahme .............................. 149 Bandaufzeichnung ...... ........ ......... .155
Datasette .. ............................ 149, 372 Datasettenmotor .......................... 184 Daten- Header ...................... 232, 237
Bandpuffer ... .. ... .. .... .. ........... ......... 173
Daten-Port .................................. 130
BANK .... ..... ... ... .... ........................ 363
Datenfile ...................................... . 166
BASIC .. .... ... .. .... ... ....... ................. 126 BASIC-Programmende .................. 42
Datenkanal ................................... 163 Datenregister ................................ 130 Datenrichtungsregister ......... 130, 240 Decodierprogramm ............... ........ .. 97 Directory ...................................... 136 Directory-Manipulation ............... 135
BASI C - Zeilen ....... .................... ..... 17 Betriebssystemroutine .................. 363 Bildschirm- Codes ......................... 106 Bildschirmspeicher ........... ....... ...... 322 Block-Header .... ... 226, 236ff, 270, 373 Block-Header- Parameter ............. 267 Block - Linker .. ...... ... .... ......... .... .... 139 Boot-Routine ........ ............. .......... 363 Boot-Sektor ..... .............. .. ....... .. .... 363 Busübertragungsroutinen ............ .372 BYTE- READY .................. ........ 229f
Diskettenkopierschutz .......... 216 , 372 Dongle .................................. 119, 367 Dongle-Abfrage ............... ..... ........ 121 DOS- Befehlspuffer ........... .... ... ..... 366 Drive-Control-Bus .............. ......... 239 Drive- Controller .......... .. .......... .. .. 286 Echtzeituhr-Trigger ........ .... .. ...... ... 81
CI28 .............. ... ....... ....... ...... ........ 363 CI28-Modu8 ..... .... .. ...... .... .... ........ 373 C64 -Modus ................. ...... ........... :373 CBM- Code ....... .. ........... ........ ....... 364
Echtzeituhren .... .. ................. 328, 376 Einsprungadresse .. .. ....... .. .... .. ...... . 202 Einzelschri tt - Decodierung .............. 97 End of Tape .. ................ .. ............. 156
378 Endmarkierung .... .. ........... .. .......... 188 EOT .............................................. 166 EOT-Block ................................... 166 Erkennungsroutine ....... ................ 189
Das große Anti-Cracker-Buch Jobschleife ............................ 219,289 Joystick-Abfrage ............ .. .... ..... ... 120
EXOR-Verknüpfung ................ ..... . 87
Kassettenpuffer ... ........................ . 178 Kassettenrecorder ...... .... ............... 1.9
File - Endekennzeichen ....... ....... .. ... 176 File- Endemarkierung ............ ..... ... 176 Floppy ....................................... ... 216 Floppy- Programmierung ............. . 216
Killertrack .. .... ... ...... .. .... ............... 299 KKS ... ........ ...... ...... .............. ........ 192 Knackmodule .. ....... ....... 821, 82., 878 Konfigurationsindex ................ .. ... 86. Kontrollregister .................. .. .... .... 179
Floppypuffer .............................. ... 218
Kopierprogramme .... ~ ........ .. .. 270, 287
Formatroutine .. ... ............... .. 288, 2.1
Kopierschutz ... ........ ............. 168, 216 Kopierschutzauftrageprogramme .. 878
GCR- Codierung ........................... 287 GCR-Format .... .... ... .. ... . 22., 286, 262 Gleichlaufschwankungen ..... .. 177, 287
Kopierschutzverfahren ......... 868, 872
Halbspuren ... .. ....... ... ............ 28., 277 Halftracks ................................. .... 277
Ladeanfangsadresse ............... .... ... 160 Ladegeschwindigkeit ............. 177, 191 LANGER NAME .......................... 176 Laufwerk-LED ...................... ....... 228
Hauptprogramm ........... ..... ... 218, 287
Laufwerksmotor ........... ......... ... .. ... 28.
Headerblock ............... ... ..... ......... .. 226 Hochkomma ................ .... .............. 186 Hochlaufzeit .................................. 18. Hüllkurve .... ................. ...... .......... .876
Lesen ........... ......... .. .............. 228, 281 LIFO ............................. ............. .. .. 72 LINKER ... ...... ..... .................... .. ..... 86 Lisbchutzsysteme ..... ... .. .. ... .... ... .. . 86. Lücke .......................................... . 80.
IC ................... ... .... ... ................. .. . 122 Illegal- Codes ....... ...... .. ............. ... .. 110 Illegal-Opcodes .. ................... 888, 867 Interrupt ............................ ... .. ...... . 79 Interrupt-Kontroll - Register 181, 188, 376 Interrupt - Masken - Speicher .......... 180 Interrupt-Programm ............ 219, 229 Interrupt-Routine ........................ 181 Interrupt- Vektor .......... ..... .. .......... 79 Interruptquelle ..................... ......... 19.
M-E-Befehl.. ................. ....... 281, 2.8 M-W ............................................ 2•• Magnetschicht .............................. 286 Magnetwechsel .................. ....... .. .. 286 Maschinencode-Compiler ....... .... .. .. 62 Maschinenprogramme ................. .. 169 Megahertz .. ................... ............... 198 Mini-Kopierschutz ......................... 66 Modifikation ........ .................. ....... 118 Motoren ...... .................... .. ............ 228
IRQ ... ....... ........ .... .................... ..... 888 IRQ-Vektor .............................. ... .. 79
NAND -G atter ........ ................ ...... 122
Jobcodes ...................... ......... ... ..... 229
NMI ................................................ 80 NMI - Vektor .............................. ... 888
Index Nullen ............................. .. ............ 140 Opcodes .. .... ......... .. ............. ......... . 110
379 Speed ............ .. ............... ...... ......... 275 Speed - Flags ................................. 229 Speicherbereich .............................. 69
OVERFLOW-Flag ....................... 229
Speichern ...................................... 228 Sprungvektoren ........................ 17, 62
P -Code ...... .... ......... .. ....... .......... .... 61 Password ..... ..... ...... .. ............ ........ 126 Password-Abfrage ........................ 126
Spuren ... ....................... ... .......... ... 228 Stapel-Autostart .. .. ........................ 71 Stapelzeiger ................. .. ................. 72
Programmende ......... .. .................... 88 Prüfsumme .. ......................... .. 84, 261 Prüfsummenbildung ....................... 86
Startadresse ............. ............. .... ...... 68 Start adressen änderung ................... 68 Steuer-Codes ................................ 106
RAM-Bank .... ... ... ..... ..... ..... ... ....... 864 RE-Compiler ...... .......................... . 68 Read-Error .................... 281, 266, 290 REM ............................................ .. 16 RESET .................................. ,48, 822 RESET-Schutz ....................... ......862 RESTORE ...................... ....... ........ 48 ROM-Routinen .......................... .. 868 RUN -STOP - RESTORE ................ 16
Steuerzeichen .................. 16, 186, 142 Stop-Taste ... ... ................ .. ... ..... ... 862 STOP-Vektor ................. ........ 79,170 SYNC-Makierungen .. .... 281, 299, 812 SYNC-Markierungen ....... ... ....... .. . 804 SYNC - Zeichen ........ .. .. ...... ........... 224 Synchroni.ations-Markierung ...... 198 SYS-Zeile ............. ............... ......... .. 88
SAVE- Vektor ............................... 199
Taktfrequenz ............. .. .. .. ...... ....... 198 Taktzyklen ........................... 118, 186 Tastaturpuffer ................. .. ............. 67
Schiebe register .............................. 197
Taetenabfrage ...................... .. ....... 181
Schreibbefehle ... ... .... ... ... ... ............ 146 Schreiben .... .................................. 229 Schrittweite ............... .... ........ ....... 277 Schutzaufzeichnung ...................... 189 Schutzprogramm .. ... ... ...... .. ........ ... 868 Scratch - Befehl .......................... .... 146 Sektor ........................................... 267 Sekundäradressen ..... .................... 166 selbstmodifizierender Code ............ 98 Selbstzerstörung ........................ ..... 86
Textkonstanten .. ........ .. ....... .. ... .... 866 Timer ............................... .. .... 92, 286 Tonkopf...... ....... .. ....... .. ........ .... .... 282 Tracks .......................................... 228 Turbo- Tape-Format ......... .. ......... 160
sequentielles File ...................... ..... 144 SHIFT-L ............................ ........... 16 SHIFT-RUN/STOP ...... ................ 66 Signalerzeugung ............................ 182 SOFT-ERROR .......... .. ....... .. ........ 282 Soundchip ... ..... .... ... ...................... 827
V-Flag ............................... .......... 280 VIA ....... ....... ................................ 289 VIA 1 ......... ..... ........ .......... .. ......... 216 VIA 2 .. ....................... .......... 216, 228 Videocontroller .. ........ ..... .............. 182 Viren ..... ........... ................... ........ . 884 Zeilennummern ..... .......................... 18 Zeit abständen ...................... ...... .. 182 Zerstörungsprogramm .. ...... .. .......... 86
_ _ _ _ _ _ _ _ _ _ _ Bücher zum C64
64 Intern ist ein Standardwerk zum Commodore 64, das vom ausführlich dokumentierten ROM-Listing über die detaillierte Hardwarebeschreibung bis zu nützlichen BASIC-Erweiterungen alles enthält, was man zum professionellen Einsatz des Commodore 64 wissen muß.
Aus dem Inhalt: Brückmann . Englisch· Feit Gelland . Gerits . Krsnik
&IM I~.. DlIs 9~dOre
t IA '" co'"
Ein DATA BECKER Buch
- Speicherbeleg ung spläne - Der Soundcontroller und seine Prog rammierung - Die Handhabung des AD- Wandlers - Der Videoco ntro ller - Prog rammierung von Farbe und Grafik - Die Zeichengenerator- Schnittstell e - Sp rites - Ein-/ Ausgabesteuerun g - Tim er und Ec htzeit uh r - Joystic kprog rammierun g - So arbeitet der BASIC- Interpreter - Math ematische Routinen - selbst ent wic kelt - Der seriell e IEC-Bu s - Prog rammi erun g der RS-232 - Di e Beleg ung der Zero- Page - Der Comm odore -64 -Schaltplan
Brückmann, Englisch, Feit, Gelfand, Krsnik, Gerits 64 Intern Hardcover, 628 Seiten, DM 69,ISBN 3-89011-000-2
Bücher zu COMMODORE 128 Ein Standardwerk zum COMMODORE 128, das für jeden unentbehrlich ist, der tiefer in den COMMODORE 128 einsteigen will. Das gesamte Betriebssystem ist ausführlich und gründlich kommentiert, Grafik, Soundbausteine, Prozessor und Peripherieanschlüsse sind genauestens beschrieben. Ein Buch, das für den professionellen Programmierer sehr schnell unentbehrlich wird.
Aus dem Inhalt: Sc:hMlb· Thnm · _
Intern EINDATA
Schieb, Thrun, Wrobel 128 Intern 846 Seiten, DM 69,ISBN 3-89011-098-3
- DerVIC-Chip Registerbelegung Betriebsarten Zeichendarstellung, Graphik, Sprites und Soft-Scrolling - Ein- und Ausgabesteuerung . Die CIAs im COMMODORE 128 Echtzeituhr Der serielle IEC-Bus des COMMODORE 128 - Der Sound-Chip SID - Der 8563-VDC-Chip Pinbelegung Nutzung derVDC-Register Hires-G rap hik mit 640x200 Punkten - Das Memory-Manager.1ent, die MMU - Assemblerprogrammierung (Nutzung der KernalRoutinen) - Einbinden neuer BASIC-Befehle - BASIC-Tokens - Die CPU 8502 - Zeilenweise dokumentiertes Kernal-ROM - ausführli ch dokumentiertes BASIC 7.0 - Z-80-ROM dokumentiert (Boot-Sektor) - Betriebssystem und Monitorli sting - Die Hardware
• • • • • • • •_ Bücher zu Commodore Das große FLOPPYBUCH ist eine Standardwerk zur FloppyProgrammierung mit COMMODORE-Computern für Änfänger, Fortgeschrittene und Profis. Ein Buch, das Ihnen ausführlich und verständlich die Arbeit mit der Floppy 1541 erklärt.
Aus dem Inhalt: Ellinger . EnglisclJ Gelfand . Szczepanowskl
pa
p
sgroße"
F"'O~ SlJ ... t llr
1541
-
-
Ein DATA BECKER Buch
-
Wie schließe ich die Floppy an? Laden und Speichern von Programmen Floppy-Systembefehle Sequentielle und relative Datenspeicherung (Adressenverwaltung , Haushaltsbuch) Fehlermeldungen und Ihre Ursachen Programmierung für Fortgeschrittene: die Direktzugriffsbefehle Zeilenweise dokumentiertes DOS-Listing mit Crossreference Floppyprogrammierung in Maschinensprache (Jobcodes, Betriebssystemroutinen usw.) Wie funktionieren Kopierschutzprogramme? Viele nützliche Programme und Tips und Tricks (Overlay, Merge, Spooling usw.) IEC-Bus und serieller Bus Superdiskmonitor bis 41 Tracks
Ellinger, Englisch, Gelfand, Szczepanowski Das große Floppybuch zur 1541 Hardcover, 520 Seiten, DM 49,ISBN 3-89011-005-3
_ _ _ _ _ _ Bücher zum Commodore 128 Das große Floppybuch zur 1570/1571 gibt Ihnen das notwendige Wissen zur Programmierung Ihres neuen Diskettenlaufwerkes. Für Anfänger, Fortgeschrittene und Profis. Dieses Buch beschreibt wirklich alle Leistungsmerkmale dieser schnellen Floppy.
Aus dem Inhalt: Ellinger
Das große Floppybuch
EINDATA
- Einführung für Einsteiger - Die Floppy und das COMMODOREBASIC - Sequentielle und relative Dateien - Fremde Diskettenformate verarbeiten - Programmierung im DOS-Puffer - Die CP/M-Fähigkeiten der 1570/1571 - Floppy intern: Schaltungsaufbau und Funktion - 1571 Fast-Load - Das DOS im Detail - Komplettes DOS-Listing (mit CrossReference)
Ellinger Das große Floppybuch zur 1570/1571 Hardcover, 554 Seiten, DM 49,ISBN 3-89011-124-6
_ _ _ _ _ _ _ _• Bücher zu Commodore
Endlich ist es soweit: Maschinensprache für Einsteiger ist das Buch, auf das alle, die sich schon immer für Maschinensprache interessierten, gewartet haben. Finden Sie heraus, was in Ihnen und in Ihrem Rechner steckt. Dieses Buch zeigt Ihnen, wie's geht: ohne Fachchinesisch, dafür einfach, schnell und effektiv. Nutzen Sie diese Chance.
Aus dem Inhalt: BalotJI
Ein DATA BECKER Buch
- Einführung in Assembler - Was man über Zahlen, Speicher und den Rech ner wissen sollte - Wie man mit einem Monitor arbeitet (Laden, Starten, Eingeben und Speichern eigener Programme) - Die ersten Befehle und die Adressierungsarten - Fortgeschrittene Assemblerprogrammierung - Noch mehr über Schleifen - Rechnen in Maschinensprache - Vergleichsbefehle - Stapeloperationen - Interruptprogrammierung - Die verschiedenen Rechner/Programmierhilfsmittel für C16, 116, Plus/4 und C128 - Und viele , viele Beispiele .. .
Baloui Maschinensprache für Einsteiger 346 Seiten, DM 29,ISBN 3-89011-182-3
DAS STEHT DRIN: Vom Einsteiger bis zum Softwarehaus - dieses Buch zeigt, wie man seine Programme optimal schützt. Dabei werden Pr09.ramm- und Kopierschutzverfahren vom einfachen List-Schutz bis zur Uberprüfung eines kompletten Tracks gezeigt. Und: Die Autoren sind nicht bei bekannten Schutzmechanismen der neuesten Generation stehengeblieben, sondern haben neue entwickelt, für die es bisher keine Kopiermöglichkeiten gibt. Das Buch zeigt nicht nur die Verfahren, sondern liefert auch gleich fertige Lösungen für C64 und C128 zum Abtippen.
Aus dem Inhalt: - BASIC-Programme schützen (List-Schutz, ÄnderungsSchutz, Password, RESTORE/RESET-Schutz, SYS-Bluff, Compiler-Schutz) - Alle Tricks des Autostart (Tastaturpuffer, Sprungvektoren, Stack, Interrupt, Adreßverschiebung beim Laden) - Prüfsummen und Selbstzerstörung (XOR-Codierung, TimerCodierung, Einzelschrittcodierung, ASCII-Codierung) - Illegale Opcodes - Cassettenkopierschutz (EOF, Cassettenpuffer, Autostart, Schnelladeverfahren, komplettes Kopierschutzsystem) - Directory-Schutz (versteckter Filename, "blinde" Blöcke, der Nullen-Trick, geteiltes Directory, gelöschte Files) - Grundlagen des Kopierschutzes - Alle Tricks der Formatierung (Track 35-41, einzelne Tracks, doppelte Tracks, Header-Parameter ändern, zerstörte Sektoren, gleiche Sektoren auf einem Track) - Halftracks, der Track mit den Nullen, kompletten Track prüfen - Alle Tricks der SYNC-Markierungen (Killertracks, überlange SYNC-Markierungen, 3000-SYNC-Schutz, Daten ohne SYNC- Markierungen) - Der Disketten-Killer (nur Ausschalten der Floppy hilft) - Alle Tricks der Knack-Module und wie man sich dagegen schützt - Was Software-Häuser über Cracker wissen sollten UND GESCHRIEBEN HABEN DIESES BUCH: Jacques Feit und Darko Krsnik sind Hardware- und Assemblerspezialisten. Ralf Gelfand ist absoluter Floppy-Experte. Er hat schon Kopierschutzverfahren für den professionellen Einsatz entwickelt. Von Michael Strauch stammt das Kapitel über die Datasette, die er bis zum letzten Byte kennt.
ISB N 3-89011-253-6 DM +039.00 304,sFr 37,-
ÖS
03900