Checksum FAQ
-----------------
Der Begriff CHECKSUM setzt sich zusammen aus SUMmation CHECK und
bedeutet übersetzt Scheck- oder Prüfsumme. Da man durch Prüfsummen-
verfahren feststellen kann, ob Daten modifiziert wurden, findet diese
Kennzahl in vielen Bereichen Verwendung: Anti-Viren-Software setzt
genauso auf diese Technik, wie Kompressionsprogramme,
Kopierschutzmechanismen usw.
Die Standard-Prüfsumme ist einfach die Quersumme aller Bytes einer Datei.
Dieses Verfahren ist aber nicht besonders sicher, wenn man bedenkt,
dass die Schecksumme nach einer ByteFlip Operation unverändert bleibt.
Mehr Schutz bieten da schon die verschiedenen CRC-Algorithmen (Cyclic
Redundancy Code), deren Prüfsumme das Ergebnis einer Division von Daten
durch ein Generatorpolynom ist.
Neben den genannten existieren natürlich noch unzählige weitere Algorithmen,
aber da dieses FAQ nicht erklären soll, wie man Prüfsummen knackt, sondern
vielmehr, wie man diese umgeht, spielt der Checksum-Typ sowieso keine Rolle.
Kommen wir nach dem theoretischen Gelabere endlich zur Praxis.
Wer kennt das nicht? Da will man sich mal eben ein paar Tausend Mark
(meinetwegen auch €uro) dazu schummeln, indem man den Spielstand mittels
Hex-Editor manipuliert, doch was ist nun los? - Der modifizierte Spielstand wird
erbarmungslos abgewiesen; selbst nach einer geringfügigen 8-Bit-Änderung.
Folgendes spielt sich ab: Das Spiel bildet beim Speichern der Daten eine
Prüfsumme, welche direkt im Spielstand abgelegt wird. Wenn man nun die Datei
modifiziert, stimmt beim Laden die errechnete Prüfsumme nicht mehr mit der
abgelegten überein.
Was man dagegen machen kann, wird nun anhand zweier Spiele beschrieben.
"Final Fantasy VII Deutsch SCES00869"
Versucht man, einen manipulierten Spielstand zu laden,
erscheint folgende Fehlermeldung:
"Datei ist beschädigt."
Von wegen beschädigt, das stinkt geradezu nach Checksum...
Aber was tun? Wir wissen nicht, wo die Prüfsumme abgelegt wird, geschweige
denn, wie der Algorithmus arbeitet. Doch das brauchen wir auch gar nicht...
Als erstes hacken wir mit XLink den Menü-Modifizierer.
Dazu springen wir zwischen den Menüs (Hauptmenü, "Steckplatz auswählen" &
"Datei auswählen") hin u. her, und verwenden dabei die Suchoperationen GLEICH
bzw. UNGLEICH, bis wir den richtigen Code erhalten:
Menü-Modifizierer
$801E2D8C 00??
00 - Steckplatz auswählen
01 - Datei auswählen
02 - Suche nach Datei... (1)
03 - Suche nach Datei... (2)
04 - Ladevorgang. Karte nicht entfernen.
05 - ???
06 - Karte formatieren
07 - Hauptmenü
Will man einen Spielstand laden, erscheint zunächst: "Ladevorgang. Karte nicht entfernen."
An diesem Punkt wird die Prüfsumme errechnet u. verglichen. Es erfolgt eine Auswahl:
FALLS Prüfsummen übereinstimmen,
DANN lade Spiel,
SONST Ausgabe d. Fehlermeldung u.
Sprung zum Menü "Datei auswählen".
Um rauszukriegen, wo die Auswahl im Speicher erfolgt, benutzen wir den Breakpoint Editor von XLinkWIN.
Wir setzen einen BREAK ON WRITE (Schreibzugriff) auf die Menü-Modifizierer-Adresse ($801E2D8C)
u. aktivieren den BP, sobald die Meldung "Ladevorgang. Karte nicht entfernen." erscheint;
wenn der Breakpoint anspricht, JUMP RA.
Der Disassembler öffnet sich bei $801D4A10.
Schauen wir uns die unmittelbare Umgebung an:
[1] 801D4A10 8E030000 lw $v1,0000($s0) ; Breakpoint
[2] 801D4A14 3042FFFF andi $v0,$v0,FFFFFFFF
[3] 801D4A18 10620009 beq $v1,$v0,801D4A40 ; Auswahl
[4] 801D4A1C 34020001 ori $v0,$zero,0001
[5] 801D4A20 3C01801E lui $at,801E
[6] 801D4A24 AC222D8C sw $v0,2D8C($at)
[7] 801D4A28 0C074AEE jal 801D2BB8
[8] ...
Sofort sticht die Branch on Equal-Anweisung [3] ins Auge. Der Befehl vergleicht
den Inhalt zweier Register u. verzweigt, falls beide den gleichen Wert aufweisen.
Eventuell handelt es sich hierbei um die gesuchte Auswahl, denn es ist sehr gut
möglich, dass die Register die jeweiligen Prüfsummen enthalten.
Wir verwenden einen immerverzweigenden BRANCH und schauen, was passiert:
[3] 801D4A18 10000009 beq $zero,$zero,801D4A40 ; verzweige, wenn 0 = 0
Bingo!! Ohne Probleme akzeptiert das Spiel selbst manipulierte Spielstände.
Der fertige XploderCode sieht so aus:
//-----
"Final Fantasy VII Deutsch SCES00869"
Saved Game Checksum Bypass
$701D4A1A 1062
$801D4A1A 1000
//-----
Wer noch wissen will, wo die Prüfsumme im Spielstand abgelegt wird, macht bei
[3] einen BREAK ON EXECUTE, notiert sich den Wert aus Register $v1 und sucht
nach diesem in der Spielstandsdatei. Ergebnis: 0x236
"Silent Hill PAL SLES01514"
Bei SH werden alle Spielsituationen in nur einer Datei gespeichert.
Um das zu realisieren, unterteilt sich der Spielstand in mehrere "Sektoren".
Nach kurzem Herumexperimentieren stellen wir fest:
-
Die Prüfsumme wird für jede Spielsituation (u. damit für jeden Sektor) einzeln gebildet,
was gleichzeitig bedeutet, dass der Spielstand gut u. gern 10 Checks enthalten kann.
-
Es handelt sich um eine Standard-Prüfsumme. Vertauscht man nämlich 2 benachbarte
Bytes innerhalb eines Sektors, wird die Schecksumme immer noch akzeptiert.
Diesmal gehen wir komplett anders vor u. finden heraus, wo die Prüfsummen abgelegt werden.
Zunächst brauchen wir einen nicht-modifizierten Spielstand, von dem wir eine Kopie auf
der Festplatte ablegen. Anschließend laden wir das SaveGame von der Memory Card,
nehmen geringfügige Änderungen vor (Spielzeit ist ausreichend), speichern ab u. machen
einen Byte-für-Byte Vergleich mit der Kopie von der Festplatte.
Dabei kommen rund ein Dutzend Unterschiede heraus (u.a. Checksum).
Wir notieren diese Werte + einige Bytes aus der jeweiligen Umgebung,
sodass wir mehrere Byteketten erhalten.
Bsp.: 40 01 97 97 DC DC
Nun erzeugen wir einen ungültigen Spielstand, indem wir z.B. den
Wert für die Munition auf [FF] setzen. Beim Laden erscheint:
"The data is damaged!"
An dieser Stelle durchsuchen wir den Speicher, Bytekette für Bytekette (siehe oben).
Ist die Suche erfolgreich, machen wir dort einen BREAK ON READ (Maske: 0FFF FFFF)
u. versuchen erneut den Spielstand zu laden. Wenn der Breakpoint anspricht, JUMP RA.
Diese Prozedur wiederholen wir solange, bis wir irgendwann zur "richtigen" Routine gelangen:
[1] 8002F670 92230000 lbu $v1,0000($s1) ; Breakpoint
[2] 8002F674 304200FF andi $v0,$v0,00FF
[3] 8002F678 14620006 bne $v1,$v0,8002F694 ; Auswahl
[4] 8002F67C 02001021 addu $v0,$s0,$zero
[5] 8002F680 96220002 lhu $v0,0002($s1)
[6] 8002F684 00000000 nop
[7] 8002F688 3842DCDC xori $v0,$v0,FFFFDCDC
[8] 8002F68C 2C500001 sltiu $s0,$v0,0001
[9] 8002F690 02001021 addu $v0,$s0,$zero
[10] 8002F694 8FBF0018 lw $ra,0018($sp)
[11] 8002F698 8FB10014 lw $s1,0014($sp)
[12] 8002F69C 8FB00010 lw $s0,0010($sp)
[13] 8002F6A0 03E00008 jr $ra
Die Branch on Not Equal-Anweisung [3] erzwingt die o.g. Fehlermeldung, falls die
Prüfsummen nicht übereinstimmen. Ein NOP (keine Operation) bewirkt hier Wunder:
//-----
"Silent Hill PAL SLES01514"
Saved Game Checksum Bypass
$7002F678 0006
$8002F67A 2400
//-----
*** Ich widme dieses Dokument meiner Katze Max, gest. im Januar 2002. ***
Dieses FAQ wurde geschrieben von misfire