C 64/VC 20
Software

22 Read Error - Theorie und Praxis

Ein Programm läßt sich wirkungsvoll vor dem Kopieren schützen, indem man einen Sektor zerstört und in diesem Bereich wichtige Daten unterbringt.

Bei Software, die auf Diskette gespeichert wird, dominiert eine Methode des Programmschutzes mit folgendem Prinzip:

Auf der Diskette mit diesem Programm ist ein Block mit Absicht zerstört worden. Wird jetzt dieses Programm geladen und gestartet, so wird vom Programm dieser fehlerhafte Block mit einem Direktzugriffsbefehl auf die Diskette abgefragt. Ist der Fehler vorhanden (was sich meist durch Blinken der Floppy-LED und Rattern des Schrittmotors äußert), so wird dies vom Programm erkannt. Es »merkt« dadurch sozusagen, daß »es« ein Original ist. Diese Methode funktioniert natürlich nur so lange, wie es nicht möglich ist, diesen fehlerhaften Block mitzukopieren. Bei älteren Kopierprogrammen ist dies nicht möglich. In letzter Zeit jedoch gibt es Kopierprogramme, die auf das Kopieren solcher Blöcke vorbereitet sind, indem sie die zerstörten Blöcke (in Form von Lesefehlern) erkennen und auf die Kopie »raufzaubern«. Bei dieser Prozedur wird aber in den meisten Fällen der Inhalt dieser Blöcke zerstört (auch zerstörte Blöcke können einen Inhalt haben).

Und hier zeigt sich ein Ansatzpunkt: Man müßte in diesen zerstörten Blöcken Daten unterbringen, die vom Originalprogramm gelesen werden können, von einer Kopie jedoch nicht. Das heißt, man macht sich den Effekt zunutze, daß diese zerstörten Blöcke durch das DOS nicht korrekt kopiert werden können. Wie aber kann man das DOS trotzdem dazu bringen, einen zerstörten Block kurzzeitig wieder lesbar zu machen?

Interner Aufbau eines Disketten-Sektors bei der 1541 (DOS 2A)

Diese Frage läßt sich nur nach intensivem Studium des DOS der C 1541 beantworten. Nur wenn die Zusammenhänge und der Aufbau dieses Operations-Systems klar sind, kann auf eine solche Frage eine befriedigende Antwort gefunden werden. Der Autor fand folgende Lösung: In dem Bild ist der interne Aufbau eines Diskettenblocks dargestellt. Für uns ist die Konstante zum Beginn des Datenblocks ($07) wichtig. Das DOS braucht diese Konstante zur Erkennung des Anfangs eines Datenblocks. Sollte diese Konstante einen anderen Wert erhalten, so kann das DOS diesen Block nicht mehr lesen, das heißt er ist zerstört. Der Vergleichswert für diese Konstante liegt in der Zero-Page der Floppy (das heißt im RAM) und kann also geändert werden.

Man hat also durch Manipulation dieses Vergleichswertes die Möglichkeit, einen Block zu zerstören oder zu reparieren. Stellen Sie sich einmal folgendes vor:

  1. Sie lesen einen Diskettenblock ein mit dem »U1: …«-Befehl der Floppy.
  2. Sie ändern den Vergleichswert für die Konstante $07 in der Zero-Page in irgend eine Zahl zwischen 0 und 255 außer 7.
  3. Sie schreiben den Block auf die Diskette zurück, mit dem USR-Befehl »U2: … «und
  4. Sie setzen den Vergleichswert in der Zero-Page auf 7 zurück.

Sie haben jetzt folgendes gemacht: In dem Diskettenblock, den Sie eingelesen haben, stand bisher die Konstante $07. Dann haben Sie den Vergleichswert für die Konstante geändert, den Block wieder auf die Diskette geschrieben (wobei die geänderte Konstante auf die Diskette geschrieben wurde) und den Vergleichswert wieder auf den Normalwert gesetzt. Im Endeffekt haben Sie also auf der Diskette eine andere Konstante stehen, als in der Vergleichs-Speicherzelle der Floppy-Zero-Page. Versuchen Sie jetzt, diesen Block zu lesen, so werden Sie merken, daß das Laufwerk nur Spot- und Blinkeffekte hervorbringt. Mit anderen Worten: Sie haben diesen Block zerstört, und zwar mit dem Fehler Nummer 22, der das Fehlen eines Datenblockheaders anzeigt.

Dieser Diskettenfehler könnte jetzt wie oben beschrieben von dem zu schützenden Programm abgefragt werden.

Einigen Lesern wird wahrscheinlich jetzt schon klar sein, was passiert, wenn wir nun den Vergleichswert in der Floppy-Zero-Page auf den Wert setzen, den wir vorhin auf die Diskette geschrieben haben. Wir gehen also jetzt wie folgt vor:

  1. Vergleichswert in der Floppy-Zero-Page auf den Wert setzen, mit dem der Block vorhin auf die Diskette zurückgeschrieben wurde.
  2. Den Block mit dem »U1: …«-Befehl einlesen und eventuell auch mittels »GET #« in den Computer holen.
  3. Den Vergleichswert wieder auf den Standard-Wert $07 setzen.

Wir haben den zerstörten Block kurzfristig lesbar gemacht, indem wir der Floppy einen anderen Vergleichswert untergejubelt haben, als der Standard-Wert. Somit »denkt« die Floppy beim Einlesen des Blocks, es sei alles in Ordnung. Nachdem der Block dann weiterverarbeitet wurde, haben wir den Vergleichswert für die Konstante wieder auf den normalen Wert gesetzt. Der fragliche Block gilt jetzt also wieder als zerstört.

Wichtig dabei ist, daß nach wie vor die 256 Bytes Inhalt des Blocks unverändert vorliegen, das heißt durch die »Zerstörung« des Blocks wurde an seinem Inhalt nichts geändert.

Das ist der Grundgedanke dieser Erweiterung bekannter Methoden zum Schützen von Software. Dieses Prinzip läßt sich jetzt vielfach variieren:

Eines muß hier ganz deutlich gesagt werden: Es hängt nur von der Programmierfähigkeit des »Schützers« ab, wie wirksam der Schutz ist. Die hier vorgestellte Methode liefert eben doch nur das Prinzip.

Noch ein Hinweis: Ein Programmschutz kann nur dann wirksam sein, wenn es keine Möglichkeit gibt, das Programm nach dem Ladevorgang einfach abzubrechen und abzuspeichern. Versuchen Sie daher, Ihre Lade- beziehungsweise Vorprogramme so zu schützen, daß sie möglichst keine Art der Einsicht erlauben. Bewährt hat sich in diesem Zusammenhang compiliertes Basic. Schreiben Sie also Ihre Ladeprogramme ruhig in Basic und compilieren Sie sie dann oder lassen sie compilieren.

Das Programm »Son of Destroyer« soll den Einstieg in diese Technik des Programmschutzes erleichtern und dessen Arbeitsweise verdeutlichen. Es ist gewissermaßen ein Programm zum Experimentieren und Sammeln von Erfahrungen. Es bietet folgendes: Man kann eine Diskette zerstören und wieder restaurieren, wobei die Bereiche der Diskette, die behandelt werden sollen, grafisch auf dem Bildschirm dargestellt werden können. Dazu bietet »Son of Destroyer« folgende Kommandos:

Das Programm fragt Sie nach der Gerätenummer der aktuellen Floppy und nach der Konstante, die Sie als neuen Wert auf dem Header der Blöcke stehen haben wollen, die Sie zerstören. Danach sehen Sie das Arbeitsfeld vor sich und links unten blinkt ein Cursor. Diesen Cursor können Sie jetzt wie gewohnt mit den Cursor-Steuertasten bewegen. Wenn Sie auf dem Block oder der Spur angelangt sind, die zerstört (oder restauriert) werden sollen, dann drücken Sie F1 zum Belegen dieses Blockes oder F5 zum Belegen der ganzen Spur. Das Rücksetzen geschieht analog dazu mit F3 beziehungsweise F7.

Danach können Sie mit F2 beziehungsweise F4 die als belegt gekennzeichneten Blöcke der im Laufwerk befindlichen Diskette zerstören beziehungsweise restaurieren.

Die beiden letztgenannten Funktionen können durch Druck auf die »*«-Taste vorzeitig beendet werden.

Mit »s« oder »l« kann das momentane Arbeitsfeld auf Diskette gespeichert beziehungsweise von Diskette geladen werden.

Mit »h« ist es Ihnen jederzeit möglich, sich eine Kommandoübersicht zu verschaffen, wobei allerdings das Arbeitsfeld gelöscht wird. Mit F8 schließlich beenden Sie das Programm.

Sie müssen beim Zerstören beziehungsweise Restaurieren der Diskette nur auf zwei Dinge achten:

  1. Die Konstante, die Sie eingeben, muß beim Zerstören dieselbe sein wie beim Restaurieren und
  2. Sie müssen beim Zerstören und Restaurieren immer dieselben Blöcke als belegt kennzeichnen.

Bleibt noch, Ihnen beim Erproben dieser Methode viel Spaß und Erfolg zu wünschen. Auch wenn’s beim ersten Mal nicht klappt: Bleiben Sie am Ball. Es lohnt sich!

(Andreas Wurf/rg)
aw$ Auswahl-Möglichkeiten am Zentralverteiler
fu$ Array mit Funktionsnamen
dw$ Hilfsstring zur Textausgabe
i Variable für Schleifen
vi Basispunkt des Cursors
t$ Variable für diverse Zwecke
gn Aktuelle Gerätenummer des benutzten Laufwerks
v Hilfsvariable für Bildschirmaufbau
s, t Schleifenvariable für Spur, Sektor
p Hilfsvariable
me Speicherstelle des Vergleichswertes in der Floppy-Zero-Page (71)
kn =7, Originalkonstante
ko vom Benutzer eingegebene Konstante
f1$-f4$ Variablen für Floppy- Fehlermeldungen
Variablen bei »Son of Destroyer«
0 rem" ******************************
1 rem" **                          **
2 rem" *      Son of Destroyer      *
3 rem" *      ----------------      *
4 rem" *     C64 - Version 1.19     *
5 rem" *  Entwurf und Programm von  *
6 rem" *        Andreas Wurf        *
7 rem" *                            *
8 rem" **                          **
9 rem" ******************************
10 :
11 :
15 gosub 10000:rem             title
20 gosub 20000:rem             helppage
30 gosub 30000:rem             set vars
35 gosub 40000:rem             params
40 gosub 50000:rem             workfield
45 aw$="slh{f1}{f3}{f5}{f7}{f2}{f4}{f8}{up}{down}{left}{rght}":dim fu$(len(aw$))
47 dw$="{home}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}{down}":fori=1 to len(aw$):read fu$(i):next
50 poke vi,peek(vi) or 128
60 poke 198,0:wait 198,1:get t$
70 poke vi,peek(vi)and not 128
80 for i=1 to len(aw$):if t$=mid$(aw$,i,1)then 87
85 next:goto 50
87 print dw$"{wht}Function: {rvon}"fu$(i)"{rvof}            {lblu}{home}"
90 on i gosub 4000,5000,200,500,800,1000,1300,1600,1900,2500,3000,3100,3200,3300
95 goto 50
200 gosub 20000:gosub 50000:return
500 poke vi,42:return
800 poke vi,46:return
1000 for i=0 to 20:if peek(vi+i*40)<>46 and peek(vi+i*40)<>42 then 1010
1005 poke vi+i*40,42:next
1010 for i=1 to 20:if peek(vi-i*40)<>46 and peek(vi-i*40)<>42 then 1020
1015 poke vi-i*40,42:next
1020 return
1300 for i=0 to 20:if peek(vi+i*40)<>46 and peek(vi+i*40)<>42 then 1310
1305 poke vi+i*40,46:next
1310 for i=1 to 20:if peek(vi-i*40)<>46 and peek(vi-i*40)<>42 then 1320
1315 poke vi-i*40,46:next
1320 return
1600 open 15,gn,15:open 2,gn,2,"#":v=1868
1605 for t=0 to 34:for s=0 to 20
1610 p=peek(v-(s*40)+t):poke v-(s*40)+t,peek(v-(s*40)+t) or 128
1620 if p<>42 then 1665
1630 print#15,"u1:"2;0;t+1;s
1640 print#15,"m-w";chr$(me);chr$(0);chr$(1);chr$(ko)
1650 print#15,"u2:"2;0;t+1;s
1660 print#15,"m-w";chr$(me);chr$(0);chr$(1);chr$(kn)
1665 poke v-(s*40)+t,peek(v-(s*40)+t) and 127
1666 get t$:if t$<>"*" then 1670
1668 printdw$;"{wht}{rvon} Function aborted {rvof}{lblu}          {home}":close2:close15:return
1670 next s,t:input#15,f1$,f2$,f3$,f4$:print"{home}{rght}{rght}{rght}{rght}{rght}{rght}{rvon}"f1$" "f2$" "f3$" "f4$
1680 close 2:close 15:return
1900 open 15,gn,15:open 2,gn,2,"#":v=1868
1905 for t=0 to 34:for s=0 to 20
1910 p=peek(v-(s*40)+t):poke v-(s*40)+t,peek(v-(s*40)+t) or 128
1920 if p<>42 then 1945
1925 print#15,"m-w";chr$(me);chr$(0);chr$(1);chr$(ko)
1930 print#15,"u1:"2;0;t+1;s
1935 print#15,"m-w";chr$(me);chr$(0);chr$(1);chr$(kn)
1940 print#15,"u2:"2;0;t+1;s
1945 poke v-(s*40)+t,peek(v-(s*40)+t) and 127
1950 get t$:if t$<>"*" then 1970
1960 printdw$;"{wht}{rvon} Function aborted {rvof}{lblu}          {home}":close2:close15:return
1970 next s,t:input#15,f1$,f2$,f3$,f4$:print"{home}{rght}{rght}{rght}{rght}{rght}{rght}{rvon}"f1$" "f2$" "f3$" "f4$
1980 close 2:close 15:return
2100 gosub 40000:goto 50
2500 print dw$"          {rvon}{wht}       Goodbye       {home}":end
3000 if peek(vi-40)=42 or peek(vi-40)=46then vi=vi-40:return
3005 return
3100 if peek(vi+40)=42 or peek(vi+40)=46then vi=vi+40:return
3105 return
3200 if peek(vi-1)=42 or peek(vi-1)=46 then vi=vi-1:return
3205 return
3300 if peek(vi+1)=42 or peek(vi+1)=46 then vi=vi+1:return
3305 return
4000 rem ** save workpage **
4010 :
4020 open 15,gn,15:v=1868
4030 print#15,"s:sod.temp":open 2,8,2,"sod.temp,u,w":print#2,gn:print#2,ko
4040 for t=0 to 34:for s=0 to 20
4050 print#2,chr$(peek(v-(s*40)+t));
4060 next s,t:close 2
4070 input#15,f1$,f2$,f3$,f4$:close 15
4080 print"{home}{rght}{rght}{rght}{rght}{rght}{rght}{rvon}"f1$" "f2$" "f3$" "f4$
4090 return
5000 rem ** load workpage **
5010 :
5020 open 15,gn,15:v=1868:open 2,gn,2,"sod.temp,u,r":input#2,gn:input#2,ko
5030 for t=0 to 34:for s=0 to 20
5040 get#2,a$:a$=a$+chr$(0)
5050 poke v-(s*40)+t,asc(a$)
5060 next s,t:close 2
5070 input#15,f1$,f2$,f3$,f4$:close 15
5080 print"{home}{rght}{rght}{rght}{rght}{rght}{rght}{rvon}"f1$" "f2$" "f3$" "f4$
5090 return
9999 stop
10000 rem ** ausgabe des kopfblattes **
10001 rem ** und vorbereiten des     **
10002 rem ** des bildschirms         **
10003 :
10010 poke 646,peek(53280):print"{clr}"chr$(9);chr$(14);chr$(8);
10020 print"{rvon}    S O N    O F    D E S T R O Y E R   "
10030 print"{up}{rvon}    =================================   "
10040 print"{up}{rvon}                                        "
10050 print"{up}{rvon}    ***                           ***   "
10060 print"{down}{down}{down}   Ein Programm zum Zerstoeren und"
10070 print"   Wiederherstellen von Disk-Blocks."
10075 print"{down}   Der Inhalt dieser Blocks bleibt"
10077 print"   vollstaendig erhalten."
10080 print"{down}{down}{down}{down}{down}{down}{down}       *** Druecke {rvon}RETURN{rvof} ***
10090 get t$:if t$<>chr$(13) then 10090
10095 return
10096 :
10097 :
20000 rem ** ausgabe des helpblattes **
20003 :
20020 print"{clr}{rvon}    S O N    O F    D E S T R O Y E R   "
20030 print"{up}{rvon}    =================================   "
20040 print"{up}{rvon}              Help - Page               "
20050 print"{down}{down}  Allocate Block  =>  {rvon} F1 {rvof}
20060 print"  Free Block      =>  {rvon} F3 {rvof}
20070 print"  Allocate Track  =>  {rvon} F5 {rvof}
20080 print"  Free Track      =>  {rvon} F7 {rvof}
20090 print"  Destroy Disk    =>  {rvon} F2 {rvof}
20100 print"  Rebuild  Disk   =>  {rvon} F4 {rvof}
20110 print"{down}  Abort Function  =>  {rvon} * {rvof}
20115 print"  Help-Page       =>  {rvon} h {rvof}
20117 print"  Save Page       =>  {rvon} s {rvof}
20118 print"  Load Page       =>  {rvon} l {rvof}
20120 print"{down}{down}  Quit Program    =>  {rvon} F8 {rvof}
20140 print"{down}{down}        ***  Press {rvon}RETURN{rvof}  ***"
20150 wait 198,1:get t$:if t$<>chr$(13) then 20150
20160 return
20161 :
20162 :
30000 rem ** setzen der parameter **
30001 :
30010 gn=8:ko=139:vi=1868:me=71:kn=7:return
30011 :
30012 :
40000 rem ** anpassen der parameter **
40001 :
40010 print"{clr}{rvon}    S O N    O F    D E S T R O Y E R   "
40020 print"{up}{rvon}    =================================   "
40030 print"{up}{rvon}             Set Parameters             "
40040 open 1,0:print"{down}{down}"
40050 print"  Device #     :"gn"{up}":print"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}";:input#1,gn:print"{down}"
40070 print"  Constant     :"ko"{up}":print"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}";:input#1,ko:print"{down}"
40080 close 1:if gn<8 or gn>14 then 40010
40100 if ko<0 or ko>255 then 40010
40110 return
50000 print"{clr}";
50005 print"{rvon} I/O: 00 ok 00 00                       {up}":print"{up}";
50010 tr$="    ..................................."
50020 for i=0 to 20:print tr$:next
50030 print"{home}{down}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}XXXXXXXXXXXXXXXXXX
50035 print"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}XXXXXXXXXXXXXXXXXX
50040 print"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}XXXXXXXXXXX
50050 print"{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}{rght}XXXXX
50060 print"{home}{down}";:for i=20 to 0 step-1:print" "i:next
50070 print"{rght}{rght}{rght}{rght}12345678901234567890123456789012345"
50080 print"    {rvon}Tracks{rvof}   11111111112222222222333333"
50090 print"{home}{down}{down}{down}{down}{down}{down}":tr$="Sectors"
50095 for i=1 to len(tr$):print"{rvon}"mid$(tr$,i,1):next:return
63040 :
63050 rem ** data's fuer funktionen **
63060 :
63065 data "Save Page","Load Page"
63070 data "Help-Page","Allocate Block","Free Block","Allocate Track"
63080 data "Free Track","Destroy Disk","Rebuild Disk"
63090 data "Quit Program","up","down","left","right"
Listing »Son of Destroyer«
PDF Diesen Artikel als PDF herunterladen
Mastodon Diesen Artikel auf Mastodon teilen
← Vorheriger ArtikelNächster Artikel →