Mogeln mit Assembler - Wir cracken ein Spiel...

  • Hallo.


    Nachdem ich mich nun seit mehreren Wochen mehr oder weniger intensiv mit Assembler beschäftigt habe und zu dem Thema ergänzend zu Thoralf´s Threads einige Bücher gelesen habe, habe ich mir jetzt seit ein paar Tagen eine kleine Auszeit gegönnt und mehr oder weniger nur am C64 gedaddelt, sofern es die Zeit zugelassen hat.

    Neben Summer Games, Winter Games und einigen anderen Klassikern hatte ich mich auch einige Zeit mit dem Spiel Commando beschäftigt, was mir schon immer gut gefallen hat. Zugegeben, das Spiel ist nicht ganz einfach und ich kam auch nie wirklich weit. Die 5 Leben sind schnell verbraucht, aber es macht Spaß und den Sound find ich einfach klasse...


    Ich habe hier eine Version, die wohl jemand in 2011 mal mit einem schicken Crack-Intro versehen hat, aber wenn die 5 Leben verbraucht sind, ist man GameOver. Also nichts mit Trainerversion.

    Also dachte ich mir, ich mogel einfach ein bißchen und versuche meine neu erworbenen Kenntnisse in Sachen Assembler dazu ein zu bringen. Und siehe da, es hat funktioniert. Mein Erlerntes möchte ich mit euch an dieser Stelle teilen.


    Um das ganze zu bewerkstelligen brauchen wir ein Freezer-Modul mit eingebautem Maschinensprachemonitor. In meinem Fall das Retroreplay, welches quasi eine Neuauflage des legendären ActionReplay6 ist. Kleine Zwischenfrage. Wie haben das eigentlich die Jungs in den 80ern gemacht, bevor es Freezer-Module gab ? Hatten die den Speicherinhalt manuell durchsucht oder wurde ein geändertes Kernal mit Monitor benutzt und dann nach einem Warmstart-Reset durchstöbert ??


    Mit dem Maschinensprachemonitor sollte man ein wenig vertraut sein. Eigentlich funktionieren alle mir bekannten mit den selben Befehlen. Für unser Vorhaben brauchen wir folgende Befehle:


    .x

    = Exit


    .h aaaa eeee oo xx xx

    = hunt-Befehl. Sucht zwischen Speicherbereich aaaa und eeee den opcode oo mit den werten xx xx


    zB. Der Befehl LDA #$ 05 entspricht in der Maschinensprache A9 05. Wenn wir LDA #$05 im Speicherbereich von $0801 bis $1FFF suchen wollen, müssen wir eingeben

    .H 0801 1FFF A9 05



    .m aaaa eeee

    = das Programm wird als reines Maschinenspracheprogramm (hexdump) angezeigt von Anfangsadresse aaaa bis Endadresse eeee



    .d aaaa eeee

    = Dissassembler. Das Programm wird dissassembliert von Anfangsadresse aaaa bis Endadresse eeee.



    Zum Spiel.



    1. Wir laden das Programm und starten es.


    2. Sobald das Spiel beginnt. merken wir uns, wie viele Leben wir haben (es sind 5) und drücken den Freeze button.


    3. Mit der Taste M kommt man bei dem Retroreplay jetzt in den Maschinensprachemonitor.


    4. Die Programmstartadresse beim C64 ist für gewöhnlich $0801, weil da das freie RAM beginnt. Wie wir wissen, haben wir 5 Leben und diese müssen im Speicher irgendwo hinterlegt sein. Der Akku ist das einzige Register, mit dem man rechnen kann, also gehen wir zu Anfang einfach mal davon aus, dass irgendwo ein LDA #$05 stehen muss, das die 5 Leben in den Akku schreibt, um die später in eine Speicherstelle zu übertragen. Und diese Speicherstelle müssen wir jetzt finden. Als Endadresse habe ich einfach mal die 1FFF gewählt, da solche grundfundamentalen Informationen, wie Anzahl der Leben, Anzahl der Waffen etc. bzw. Sprünge ziemlich am Anfang des Programms liegen sollten.


    Wir suchen mit dem Befehl .H 0801 1FFF A9 05


    5. Als Ergebnis erhalten wir jetzt die beiden Speicherstellen 08B0 und 1ECD. Der Hunt-Befehl hat uns verraten, dass an diesen 2 Speicherstellen ein LDA #$05 stehen muss. Diese Speicherstellen müssen wir jetzt analysieren. Soviel sei verraten, mit der ersten Speicherstelle werden wir Glück haben. In manchen Fällen stehen da dann 50 Speicherstellen, dann wird es natürlich richtig kompliziert, die richtige zu finden. Aber nicht umsonst haben die Jungs Wochen gebraucht, um bei einigen Spielen mogeln zu können.


    6. Der Befehl LDA #$05 sagt uns ja zunächst nur, dass der Wert 05 in den Akku kopiert wird. Interessanter ist die Frage, in welche Speicherstelle der Akku den Wert ablegt. Deswegen müssen wir wissen, was nach dem LDA steht. Wir erwarten ein STA. Deswegen disassemblieren wir nur ein paar Stellen weiter mit dem Befehl


    .D 08B0 08B8


    und erhalten


    08B0 LDA #$05

    08B2 STA $04FF

    08B5 STA $0500

    08B8 LDA #$A5


    Wie wir sehen wird unser Wert 05 in die Speicherstellen $04FF und $0500 kopiert und an Adresse 08B8 kommt bereits ein neuer LDA Befehl.


    Wir erinnern uns, wir haben im Spiel 5 Leben und 5 Granaten. Hier ist es jetzt so, der Wert in $04FF besagt die Anzahl der Granaten und der Wert in $0500 besagt die Anzahl der Leben.


    Wir können jetzt das LDA #$05 durch ein LDA #$99 ersetzen, dann 2 mal return drücken und mit x den Monitor verlassen und mit F3 wieder in das Spiel einsteigen. Nach einmal sterben haben wir 98 Leben und 99 Granaten.


    Oder aber wir schauen uns die Speicherstellen an. Das ist interessant, wenn man sich nicht sicher ist. Dann schaut man in die Speicherstelle rein, da steht der Wert 05. Dann lässt man sich einmal sterben und schaut sich erneut den Speicherwert an. Wenn dann da Wert 04 steht, kann man sich sicher sein, dass man das richtige Byte erwischt hat.


    Mit


    .M 04ff


    kann man sich die Speicherstellen direkt im hexdump anzeigen lassen. Da stehen dann 8 bytes:


    :04FF 05 05 00 00 00 3F 20 20


    Der erste Wert 05 ist Speicherstelle $04FF, die zweite 05 ist Speicherstelle $0500. Diese kann man dann auf jeweils 99 abändern und mit return bestätigen. Dann mit x aus dem Monitor aussteigen und mit F3 wieder in das Spiel einsteigen. Nach einmal sterben hat man 99 Granaten und 98 Leben... :-))




    Wie gesagt, ich bin noch absoluter Anfänger bei Assembler. Für mich war das jetzt schon ein großer Schritt. Sicherlich kann man es wesentlich edler machen, in dem man verhindert, dass überhaupt ein Leben abgezogen wird. Dann noch ein Intro davor basteln und so weiter...


    Ich wollte hier mal zeigen, wie ich vorgegangen bin und ich bin mir sicher, es ist für den ein oder anderen sehr interessant ?! Wobei man sagen muss, es ist schon schwierig, überhaupt ein C64 Spiel zu finden, das nicht geknackt ist... :)



    Anbei noch ein drittklassiges Handyvideo zu dem Thema.


    Video




    Gruss Jan

    6 Mal editiert, zuletzt von Jan1980 ()

  • Wow ! Super erklärt.


    Habe leider wenig Erfahrung von Assembler, werde das aber auf jeden Fall mal nachmachen.


    Bitte noch mehr solcher "Fallbeispiele" :)


    --

    Elaay (aka WStyle) - Commodore 4 Ever !


    there are 10 types of people in this world, those who understand binary and those who dont

  • Sehr erfreulich zu sehen, wie der Assembler-Kurs hier im Forum zum Lernen und Umsetzen motiviert. Top!


    Als "Cracken" würde ich das Obige allerdings nicht bezeichnen - "Patchen" oder (ansatzweise) "Modden" trifft es viel eher. Cracken ist das Analysieren eines geschützten Programms und Entfernen des Programm- bzw. Kopierschutzes, dazu kam in den guten alten Zeiten von C64 und Amiga i.a. auch noch das Packen des vom Kopierschutz befreiten Programms und seiner Daten und das Einbinden eines netten Cracker-Intros. War das ursprüngliche Original eine Tape-Version, wurde es normalerweise auch gleich zur Diskettenversion "konvertiert" (Nachlader etc.).

    Kleine Zwischenfrage. Wie haben das eigentlich die Jungs in den 80ern gemacht, bevor es Freezer-Module gab ?

    Damals musste man die Programme noch schrittweise und mit den zur Verfügung stehenden Programmen auseinandernehmen oder sich selbst passende Tools schreiben. Unabdingbar war da ein *mehr* als fundamentales Wissen über den 6502/6510-Prozessor (anfangs reichte noch das "normale" Know-How, später waren dann Sachen wie die "illegalen" Opcodes auch gefragt), Tiefenwissen über die C64-Architektur und viel Logik.


    Hatten die den Speicherinhalt manuell durchsucht oder wurde ein geändertes Kernal mit Monitor benutzt und dann nach einem Warmstart-Reset durchstöbert ??

    Das war je nach Programm und Kopierschutz unterschiedlich... individuell halt. Ein geändertes ROM lässt sich aushebeln, indem man das ROM insgesamt abschaltet (dafür ist in der Zeropage Adresse $01 da) - Assemblercode braucht grundsätzlich erstmal kein ROM, solange er alle Routinen selbst mitbringt.


    Vereinfachter Exkurs:

    Der C64 bringt volle 64 Kilobyte RAM im Bereich von $0000-$FFFF mit. Damit ist der volle Adressraum des Prozessors mit RAM belegt. Um hier aber auch noch das ROM mit dem Betriebssystem und die Register des Chipsatzes (CIAs, VIA) unterzubringen, lassen sich über die Zeropage-Adresse $01 (Bits 0 bis 2) die entsprechenden Chips "über" das vorhandene RAM einblenden. Sind die ROMs aktiv (BASIC von $A000-BFFF, KERNAL von $E000-$FFFF, Zeichensatz-ROM von $D000-DFFF), bringt ein Auslesen einer Adresse im ROM-Bereich den Inhalt des ROMs, aber ein Schreibzugriff erfolgt immer auf das darunterliegende RAM. So kann man z.B. das BASIC-ROM ins darunterliegende RAM kopieren, es modifizieren oder austauschen und dann durch Schreiben eines entsprechenden Wertes "umschalten". Oder man nutzt den "versteckten" Speicher in Assembler wie normalen Speicher für Daten, indem man bei Lesezugriff immer oder kurzzeitig das ROM ausblendet.


    Besonders ist allerdings der Bereich $D000-$DFFF, weil er quasi 3fach belegt ist: hier wird auch der I/O-Bereich eingeblendet (CIAs, Videocontroller, SID), der normalerweise aktiv ist. Lesezugriffe liefern bei eingeschaltetem I/O-Bereich dann die Inhalte der I/O-Register (in $D020 z.B. die Rahmenfarbe), Schreibzugriffe gehen dann allerdings nicht ins RAM, sondern eben auch in die I/O-Register - sonst könnte man keinerlei Geräte etc. ansteuern...

    /Exkurs Ende


    Viele Spiele "überladen" also die ROMs durch eigene Routinen im RAM darunter und schalten dann das ROM schlicht ab. Ansonsten wäre ein geändertes ROM (als Eprom etc.) aber ein gutes Mittel zum Anfang. Vor allem läßt sich darüber auch ein simpler Reset-Schutz des originalen ROMs aushebeln: Module am Cartridge-Port des C64 starten automatisch, weil das Betriebssystem im ROM prüft, ob ab $8000 im RAM die Zeichenfolge "CBM80" vorhanden ist. Dort wird nämlich bei gestecktem Modul dessen Speicher eingeblendet. Hinter der Kennung folgt dann die Einsprungadresse des Moduls, und schon läuft das dort gespeicherte Programm. Ein Originalprogramm kann nun bei gestecktem Modul den eigenen Start verweigern, ansonsten kann es aber die Kennung selbst im RAM ablegen und dahinter auf seine eigene Reset-Routine etc. verweisen.


    Zurück zur Frage wieder...


    Erstmal sollte man das zu knackende Programm ein paarmal starten / spielen. Nur so bekommt man ein Gefühl dafür, welche Schritte beim Laden und direkt danach anstehen, was für die nachfolgend zwingend notwendige Codeanalyse erforderlich ist. Wie lange dauert das Laden? Gibt es Pausen (= mehrere Dateien nacheinander geladen)? Kommt eine Grafik und danach direkt das Spiel, oder wird bei stehendem Titelbild nachgeladen? Usw... ;)


    Dann schaut man sich den Loader des Programms (meist ja ein Spiel) an und versucht, einen Autostart zu verhindern. Schon das ist hochgradig individuell, meist werden durch "absolutes" Laden (von Disk per ",8,1") bestimmte Adressen der Zeropage überladen und Vektoren ausgetauscht, so dass die Laderoutine direkt in das eben geladene Programm einspringt / zurückkehrt und der Loader sofort übernimmt. Hat man den Loader seziert, kann man ihn entweder im Speicher (vor allem bei Tapes) oder auf Disk so anpassen, dass er nach dem Ladevorgang wieder in den Monitor/Debugger des Crackers springt oder zumindest das nachgeladene Programmteil nicht ausführt.


    Schon hier hilft nur noch Hardcore-Wissen über den Prozessor UND die Eigenheiten des C64 weiter. Viele Kopierschutzmechanismen sind ziemlich kreativ - eigene Packalgorithmen, timerbasierte Ladezeitverifizierung, Prüfsummen über Dateien oder Teile des geladenen Codes, eigene Tape- oder Disketten-Formate, illegale 6510-Opcodes, selbstmodifizierender Code, das alles kann man finden. Hat aber wirklich Spaß gemacht und war irgendwie auch sportlicher Ansporn, diese Tricks herauszufinden und dem Programmierer bzw. der Firma zu zeigen, dass man auch sowas cracken konnte... ;)


    Herausforderung war, die Programme ggf. auch zu verbessern. Trainer einzubauen. Mehrteilige Programme mit eigenen Laufzeit-Packern zu einteiligen Programmen machen. Einen (technisch meist ambitionierteren) Vorspann einbinden. Die Musik von "Commando" war übrigens echt klasse - die konnte man modular extrahieren und per Interrupt auch in eigenen Intros im Hintergrund einbinden... ;)


    Bevor das nun hier noch ein eigener Kurs zum Thema wird, mach ich mal lieber im Büro meine "richtige" Arbeit weiter. :D


    Good old times... ::solder::::hacking:::capone:


    Grüße,

    Ralph.

  • Vielen Dank für diese ausführliche Antwort !!


    Natürlich war das Wort „cracken“ nicht richtig gewählt. Deswegen hatte ich in dem Thread die Thematik mit „mogeln“ umschrieben.

    Für mich als Assembler Anfänger war das schonmal ein Erfolgserlebnis und Erfolgserlebnisse spornen an, weiter zu machen. Ich will als nächstes jetzt das Crackintro raus machen, den Zähler für Leben ubd Waffen so umbauen, dass nicht runtergezählt wird und das fertige Spiel dann abspeichern.


    Gruss Jan

  • Hallo Jan,


    so habe ich es auch bei div. Anwendungen gemacht.

    Patchen" oder (ansatzweise) "Modden ist schon mal notwendig.

    Bin aber noch in der Lernphase C64 und ASM.


    Ich habe ein Diagnose Tool an den Stellen verändert, wo ein Fehler wegen fehlendem Stecker gemeldet wird und das Programm wurde gestopt.


    RTS eingefügt - weiter im Programm.


    Ich arbeite mit The Final Catridge III.


    Gruß

    Kurt

    :)