Hi!
Da das Crackme in absehbarer Zeit wohl doch nicht mehr gecrackt wird, habe ich mich entschlossen, das Passwort, den Original-Quelltext, sowie ein Mini-Tutorial zu veröffentlichen. Ich beschreibe hier, wie ich beim Crackme vorgegangen bin. Es mag auch einfachere Wege geben.
Passwort: bruteFORCE
Entschlüsselung:
Edit1.Text enthält das eingegebene Passwort
EncProc ist die verschlüsselte Prozedur.
L ist vorerst nur ein Dummy-Wert, der später korrigiert wird.
1.
Damit bei einem falschen Passwort nicht unnötig versucht wird, Code zu entschlüsseln und auszuführen (was gar nicht mal so ungefährlich ist), habe ich eine kleine Prüfsumme des richtigen Passwortes eingebaut. Allerdings sollte man darauf achten, dass das Passwort genügend lang gewählt wurde, da man per Bruteforce möglicherweise durch den Hash das Passwort herausfinden könnte. Hier wurde folgende Hashmethode eingesetzt:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8:
| function Hash(S: String): Byte; var i: Integer; begin Hash := $00; for i := 1 to Length(S) do Hash := Hash xor Ord(S[i]); emd; |
Die Hashlänge beträgt hier nur 1 Byte, was in der Praxis viel zu wenig wäre. Zu Demonstrationszwecken genügt dies jedoch auf jeden Fall. Vor der Verschlüsselung wird das eingegebene Passwort gehasht und mit dem internen Wert (Hash des richtigen Passwortes) verglichen. Stimmen die beiden Werte überein, gehts mit der Entschlüsselung weiter, sind sie verschieden, wird eine Meldung angezeigt, dass das Passwort falsch ist.
2.
Nun entschlüsseln wir den entsprechenden Speicherbereich mit dem zuvor eingesetzten Verschlüsselungsverfahren. Hier habe ich eine der einfachsten Verfahren benutzt, die XOR-Verschlüsselung, die allerdings auf keinen Fall sicher ist.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18:
| const L = 123; var p: PByte; i: Integer; begin if Hash(Edit1.Text) = $2E then begin p := @EncProc; for i := 0 to L - 1 do begin p^ := p^ xor Ord(Edit1.Text[(i mod Length(Edit1.Text)) + 1]); inc(p); end; end else ShowMessage('Das eingegebene Passwort ist falsch.'); end; |
Der Zeiger
p wird Byte für Byte verschoben und an die Stelle im Speicher, an die er hinzeigt, der entschlüsselte Wert geschrieben.
3. Als letzen Schritt rufen wir nun die entschlüsselte Prozedur auf.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19:
| const L = 123; var p: PByte; i: Integer; begin if Hash(Edit1.Text) = $2E then begin p := @EncProc; for i := 0 to L - 1 do begin p^ := p^ xor Ord(Edit1.Text[(i mod Length(Edit1.Text)) + 1]); inc(p^); end; EncProc; end else ShowMessage('Das eingegebene Passwort ist falsch.'); end; |
Verschlüsselung:
1.
Als erstes muss das gesamte Programm fertig sein, mit Entschlüsselungsroutine und allem drum und dran. Lediglich die Prozedur, die später verschlüsselt werden soll, ist noch unverschlüsselt. Wir öffnen die fertige EXE-Datei mit einem Disassembler, z.B. OllyDbg, und suchen im Code die Stelle, die verschlüsselt werden soll. Am Besten geht das mit der String-Suche. Wenn wir die Stelle gefunden haben, lassen wir uns anzeigen, aus wieviele Bytes die gesamte Prozedur einschließlich
RETN besteht. Diesen Wert benötigen wir später, deshalb schreiben wir ihn uns auf. Im Disassembler kopieren wir uns die gesamte Prozedur noch in eine neue Datei.
2.
Diese Datei verschlüsseln wir nun mit dem gewünschten Passwort. Die Implementierung einer einfachen, aber unsicheren XOR-Verschlüsselung könnte so aussehen:
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
| procedure Encrypt; var i: Integer; psw: String; FSIn, FSOut: TFileStream; Buf: Byte; begin FSIn := TFileStream.Create('C:\proc.txt', fmOpenRead); FSOut := TFileStream.Create('C:\proc_enc.txt', fmCreate); i := 0; repeat FSIn.Read(Buf, 1); Buf := Buf xor Ord(psw[(i mod Length(psw)) + 1]); FSOut.Write(Buf, 1); inc(i); until FS.Position = FS.Size; end; |
3.
Mittels eines Hex-Editors oder eines Disassemblers kopieren wir nun den Inhalt der neuen Datei zurück in unser Programm, genau an die Stelle, wo die zu verschlüsselnde Prozedur ist.
4.
Da wir zur Entwicklungszeit noch nicht wussten, wie groß der zu verschlüsselnde Teil später einmal in der EXE sein wird, müssen wir diesen Wert nachträglich wieder korrigieren. Wir suchen also im Disassembler die Stelle, wo
L definiert wird (im Beispielprogramm bekommt L den Wert 123) und überschreiben diesen Wert mit der zuvor am Anfang aufgeschriebenen Zahl. Wir speichern alle Änderungen ab.
5.
Nun können wir unser Programm starten und die Passwortabfrage testen. Viel Spaß!
Anmerkungen:
Alle Variablen, die man in der Prozedur, die verschlüsselt wird, deklariert und initialisiert, werden
nicht verschlüsselt. Das liegt daran, dass die Daten für die Variablen nicht zwischen dem eigentlichen Quelltext stehen, sondern außerhalb der Prozedur.
Delphi-Quelltext
1: 2: 3: 4: 5: 6: 7:
| procedure EncProc; var S: String; begin S := 'Hallo, das kann niemand lesen'; ShowMessage('Das auch nicht'); end; |
Um diese "Sicherheitslücke" zu umgehen müsste man jede einzelne Variable verschlüsseln, oder aber Delphi dazubringen, die Daten für Variablen innerhalb des Codes zu speichern. Ob letzeres möglich ist und wie, kann ich euch auch nicht sagen.
Chryzler