Keygening / Kryptographie |
7.4.2003
|
BiSHoP's Lockless Keygenme #4
|
MD5 + RSA
|
Programmname
|
BiSHoP's Lockless Keygenme #4
|
Programmtyp
|
Crackme
|
Programmgrösse
|
50751 Bytes
|
Autor
|
Crackme: BiSHoP; Tutorial: figugegl
|
Tools
|
Softice (Olly) - IDA - RSA Tool
- Miracl - IDA Flirt-Signatur für Miracl 4.5
|
Schwierigkeitsgrad
|
Mittel
|
Grüezi mitenand
Fondue Ist Gut Und Gibt Eine Gute Laune!
Im zweiten Teil meiner Miniserie geht es um ein bekanntes, weit verbreitetes Duo: MD5
und RSA. Richtig angewandt, kann daraus ein unbezwingbarer Softwareschutz erstellt werden.
Wie gesagt gehe ich in dieser Serie weder auf Theorie noch dahintersteckende
Mathematik von RSA ein, sondern beschränke mich auf die Praxis: Wie reverse ich das?
Wer genauer Bescheid wissen will, kann oVeRFLoW's RSA-Artikel im
Kräcker #15 lesen, oder eines der meiner Meinung nach besten Dokumente in Englisch:
How the RSA Cipher Works.
Eigentlich wollte ich ein richtiges Programm nehmen, aber da das erstens illegal
ist (*rofl*) und zweitens beim nächsten Update garantiert die Protektion geändert
wird, macht ein Crackme mehr Sinn. Es geht ja hauptsächlich ums Verständnis.
Meine Wahl fiel auf BiSHoP's
Lockless Keygenme #4, welches ich vor ungefähr einem halben Jahr gelöst, und
dessen MD5/RSA Algorithmus ich in etlichen Sharewareprogrammen fast unverändert
angetroffen habe.
|
Das Crackme ist mit UPX gepackt, lässt sich jedoch mit Bratalarm's UPX-Unpacker oder
manuell schnell entpacken. Sogleich fällt uns ein Debuggerschutz auf, wenn das Crackme
gestartet wird. In den Strings finden wir folgendes:
0000B840 BCHK
0000B848 TRW2000
0000B850 \\.\TRW2000
0000B85C NTICE
0000B864 \\.\NTICE
0000B870 Please unload your debugger before running this program.
0000B8AC SICE
0000B8B4 \\.\SICE
Es handelt sich um die altbekannten Meltice- und Boundscheck-Tricks. Am einfachsten
neutralisiert man diese Checks mit Icedump, zur Sicherheit können auch alle Strings per
Hexeditor umbenannt werden. Wir finden auch noch einen Int68 an Adresse 401731 welcher ausgenopt
werden soll, um ungestört mit dem Ollydebugger arbeiten zu können.
Ausserdem finden wir den String "Miracl" und viele verdächtige Funktionsnamen, die auf
Bignums und/oder Kryptographie schliessen lassen. Wir disassemblieren die Datei in IDA und
applizieren auf gut Glück die Miracl Flirt-Signatur (Version 4.5) vom letzten Tutorial.
BINGO - es werden alle wichtigen Funktionen erkannt! Das vereinfacht unsere Aufgabe um einiges.
Aber fangen wir doch von vorne an, dort wo unsere Angaben mit "GetDlgItemTextA" eingelesen
werden:
00401551 push 32h
00401553 push edx
00401554 push 3EBh
00401559 push esi
0040155A call edi ; GetDlgItemTextA ; Name einlesen
0040155C test eax, eax ; Länge ln == 0 ?
0040155E jnz short loc_401581 ; nein: springe weiter
.
00401588 push 32h
0040158A push eax
0040158B push 3ECh
00401590 push esi
00401591 call edi ; GetDlgItemTextA ; Company einlesen
00401593 test eax, eax ; Länge lc == 0 ?
00401595 jnz short loc_4015B8 ; nein: springe weiter
Nach der Eingabe werden Name und Company aneinandergehängt:
004015CC push ecx
004015CD push edx
004015CE call ebx ; lstrcpy ; Name kopieren
004015D0 lea eax, [esp+0E8h]
004015D7 lea ecx, [esp+0B0h]
004015DE push eax
004015DF push ecx
004015E0 call ds:lstrlen
004015E6 lea edx, [esp+eax+124h]
004015ED push edx
004015EE call ebx ; lstrcpy ; Company anhängen --> (NameComp)
|
Jetzt wird es langsam interessant. Dem Call an Adresse 4015FD werden zwei Argumente übergeben: einen
Zeiger auf Name/Company in Register Ecx und Register Eax, welches auf einen ausgenullten Speicherbereich
zeigt. Mit F10 führen wir den Call aus, wobei wir uns aber vorher den Speicher mit d eax
anzeigen lassen.
004015F0 lea eax, [esp+2Ch]
004015F4 lea ecx, [esp+120h]
004015FB push eax ; ???
004015FC push ecx ; NameComp
004015FD call sub_401170 ; Berechnung 1
Das Resultat ist ein 32-stelliger String, der eine Hexzahl darstellt. Da wird also irgendwie unser Name
in einen String umgewandelt. Wir gucken deshalb genauer in diese Berechnung rein, wobei ich den Code
bereits vorgreifend kommentiert habe:
004011B1 push ecx ; Zeiger auf MD5_Context
004011B2 stosw
004011B4 call sub_401800 ; MD5_Init
004011B9 mov esi, [esp+10h+arg_1A00]
004011C0 add esp, 4
004011C3 push esi ; NameComp
004011C4 call ds:lstrlenA ; Länge NameComp --> (lnc)
004011CA push eax ; lnc
004011CB lea edx, [esp+10h+arg_34]
004011CF push esi ; NameComp
004011D0 push edx ; Zeiger auf MD5_Context
004011D1 call sub_401830 ; MD5_Update
004011D6 lea eax, [esp+18h]
004011DA push eax ; Zeiger auf MD5-Hash
004011DB call sub_4018E0 ; MD5_Finalize
Im Call an Adresse 4011B4 (MD5_Init) stossen wir auf folgendes:
00401800 mov eax, [esp+arg_0]
00401804 xor ecx, ecx
00401806 mov [eax+14h], ecx
00401809 mov [eax+10h], ecx
0040180C mov dword ptr [eax], 67452301h
00401812 mov dword ptr [eax+4], 0EFCDAB89h
00401819 mov dword ptr [eax+8], 98BADCFEh
00401820 mov dword ptr [eax+0Ch], 10325476h
00401827 retn
Wer es einmal gesehen hat, vergisst es nie wieder: Das ist die typische Initialisierungssequenz von
MD5. Aber gucken wir weiter, ob es sich auch wirklich um MD5 handelt. Wir tracen in jeden Call hinein
bis zum eigentlichen Hash-Algo:
0040197B mov eax, edi
0040197D mov edx, [esp+5Ch+arg_0]
00401981 not eax
00401983 mov ecx, ebx
00401985 and eax, ebp
00401987 and ecx, edi
00401989 or eax, ecx
0040198B mov ecx, [esp+5Ch+var_40]
0040198F add eax, ecx
00401991 lea ecx, [edx+eax-28955B88h]
00401998 mov edx, edi
0040199A mov eax, ecx
0040199C shr eax, 19h
0040199F shl ecx, 7
004019A2 or eax, ecx
004019A4 add eax, edi
..
0040224F add ebp, ebx
00402251 lea eax, [eax+ecx-14792C6Fh]
00402258 mov ecx, [esi]
0040225A add ecx, edx
0040225C mov edx, eax
0040225E shl edx, 15h
00402261 shr eax, 0Bh
00402264 or edx, eax
00402266 mov eax, [esi+4]
00402269 add edx, ebx
0040226B mov ebx, [esi+0Ch]
0040226E add eax, edx
00402270 add ebx, edi
00402272 mov [esi+4], eax
00402275 lea eax, [esp+64h+var_40]
00402279 push eax
0040227A mov [esi], ecx
0040227C mov [esi+8], ebp
0040227F mov [esi+0Ch], ebx
Mehrere hundert Zeilen Asm-Code ohne jegliche Sprünge oder Calls, das ist MD5! Message
Digest 5 berechnet aus einem beliebigen String bzw. Text eine 128-Bit lange Zahl, ein
sogenannter Hash. Dieser Hash ist eindeutig und nicht umkehrbar (was zwar methematisch nicht bewiesen
ist, mit an Sicherheit grenzender Wahrscheinlichkeit jedoch angenommen werden kann). Das heisst: Jeder
String liefert einen anderen Hashwert, oder noch anders ausgedrückt: Es gibt keine zwei unterschiedliche
Strings, die den gleichen MD5-Hash liefern.
MD5 wird vorwiegend dazu verwendet, um Dokumente zu signieren. Der Verfasser eines Dokuments berechnet
dessen MD5-Hash und stellt ihn separat zur Verfügung. So kann jeder Empfänger überprüfen,
ob es sich bei seiner Kopie des Dokuments um die Originalfassung handelt, oder ob daran etwas geändert
wurde. Ist nur ein einziger Buchstabe anders, ergibt dies einen unterschiedlichen MD5-Hash.
Wer es genauer wissen will, kann diesen Code Schritt für Schritt durchgehen. Mit ein bisschen
Erfahrung erkennt man MD5 aber sofort an der Initialisierung, den Konstanten im Algorithmus und am Ergebnis. Da
man bekanntlich nicht weit kommt, wenn man in jeden Call reingeht, kürzen wir die Sache ein wenig ab. Dies
geht, indem man alle Argumente, die vor einem Call auf den Stack gepusht werden, genau anguckt. Am Ergebnis kann
oft ziemlich schnell festgestellt werden, was geschieht. In unserem Beispiel machen wir dies mit Name = "figu",
und Company = "gegl", das ergibt "figugegl". Dies darum, weil ich meinen MD5-Hash (fast) auswendig kenne,
und somit gleich sagen kann, ob es wirklich MD5 ist oder nicht.
Nach dem Call zu MD5_Init sehen wir folgenden MD5_Context:
0065DF44 01 23 45 67 89 AB CD EF-FE DC BA 98 76 54 32 10 .#Eg........vT2.
0065DF54 00 00 00 00 00 00 00 00-10 00 00 00 80 FB 65 00 ..............e.
Dieser Context verändert sich nach MD5_Update so:
0065DF44 01 23 45 67 89 AB CD EF-FE DC BA 98 76 54 32 10 .#Eg........vT2.
0065DF54 40 00 00 00 00 00 00 00-66 69 67 75 67 65 67 6C @.......figugegl
Und schlussendlich der MD5-Hash nach MD5_Finalize:
0065DF0C 4D 80 80 E1 32 76 14 97-97 68 82 4F 7D 04 7A 70 M...2v...h.O}.zp
In unserem Fall wird dieser Hash in oben erwähnten String umgewandelt. Deshalb wusste ich auch gleich
auf Anhieb, dass es MD5 ist. Für uns als Reverser bedeutet MD5, dass wir dies nachcoden müssen,
denn wie gesagt: MD5 kann NICHT umgekehrt werden! MD5-Routinen sind in allen Programmiersprachen frei
erhältlich, man muss nur die entsprechenden Dateien in seinen Code einbinden. Wer den im Zipfile beigelegten
MD5 C-Code genauer anguckt, erkennt auch sogleich die Implementation hier im Crackme und meine Kommentare.
|
Jetzt wird unsere Serial eingelesen und geprüft, ob alle Zeichen aus '0'..'9' bzw. 'A'..'F' bestehen. Danach kommt die eigentliche Serialberechnugsroutine.
00401613 push 32h
00401615 push eax
00401616 push 3EDh
0040161B push esi
0040161C call edi ; Serial einlesen
0040161E test eax, eax
00401620 jnz short loc_401643
...
00401643 lea ecx, [esp+78h]
00401647 push ecx
00401648 call sub_401090 ; Serialzeichen prüfen
0040164D add esp, 4
00401650 cmp eax, 1
00401653 jnz short loc_40167B
00401655 lea edx, [esp+50h]
00401659 lea eax, [esp+78h]
0040165D push edx
0040165E push eax
0040165F call sub_4010D0 ; Berechnung
00401664 add esp, 8
00401667 lea ecx, [esp+2Ch]
0040166B lea edx, [esp+50h]
0040166F push ecx
00401670 push edx
00401671 call ds:lstrcmpA ; Vergleich
00401677 test eax, eax
00401679 jz short loc_40169C
Die Berechnung wird dank Miracl Flirt-Signatur und Funktionsnamen sehr übersichtlich, man könnte
schon fast sagen: einfach! Dies vorallem auch, weil der Algorithmus sehr konventionell implementiert wurde
und keine Überraschungen enthält.
Aber gehen wir doch ins Detail:
004010D5 push 0
004010D7 push 64h ; 100 Stellen pro Bignum
004010D9 call _mirsys ; Miracl initialisieren
004010DE push 0
004010E0 mov [esp+20h+var_4], eax
004010E4 call _mirvar ; Big1
004010E9 push 0
004010EB mov esi, eax
004010ED call _mirvar ; Big2
004010F2 push 0
004010F4 mov edi, eax
004010F6 call _mirvar ; Big3
004010FB push 0
004010FD mov ebx, eax
004010FF call _mirvar ; Big4
00401104 mov ecx, [esp+2Ch+arg_0]
00401108 mov ebp, eax
0040110A mov eax, [esp+2Ch+var_4]
0040110E push ecx ; Zeiger auf Serial
0040110F push ebp ; Big4
00401110 mov dword ptr [eax+238h], 10h ; IOBASE = 16 (Hexadezimalsystem)
0040111A call _cinstr ; Serial in Bignum umwandeln --> (BigSerial)
0040111F push offset a24dfda27fa14d3 ; "24DFDA27FA14D3F27DDF62CEA5D2381F9"
00401124 push edi ; Big2
00401125 call _cinstr ; "24DF..F9" in Bignum --> (BigModulus)
0040112A push offset aE401c1b ; "E401C1B"
0040112F push ebx ; Big3
00401130 call _cinstr ; "E401C1B" in Bignum --> (BigPubExp)
00401135 push esi ; Big1
00401136 push edi ; BigModulus
00401137 push ebx ; BigPubExp
00401138 push ebp ; BigSerial
00401139 call _powmod ; Big1 = (BigSerial ^ bigPubExp) mod BigModulus
0040113E mov edx, [esp+54h+arg_4]
00401142 add esp, 40h
00401145 push edx
00401146 push esi ; Big1
00401147 call _cotstr ; Big1 in String umwandeln
Als erstes werden vier Bignums erstellt, danach die Serial und zwei Strings in Bignum umgewandelt.
Schlussendlich wird eine PowMod-Berechnung durchgeführt mit diesen drei Zahlen: Big1 =
(BigSerial ^ bigPubExp) mod BigModulus. Deren Ergebnis wird in String umgewandelt und mit dem MD5-Hash
unseres Namens verglichen.
|
Dem geübten Auge fallen sogleich zwei Dinge auf: Die PowMod Funktion und die beiden Strings.
Die Funktion PowMod ist bereits ein deutlicher Hinweis auf RSA. Die Serial wird für die
Berechnung verwendet und das Ergebnis mit dem Hash useres Namens verglichen. Es lässt sich keine
Serial sniffen, was zwar eklig für alle faulen Reverser, jedoch ein Merkmal vieler kryptographischer
Verschlüsselungsverfahren ist. Die Serial kann nur berechnet werden, indem der Algorithmus
"gebrochen" wird. Die beiden erwähnten Strings sind auch auffällig, denn beim einen
handelt es sich um eine Zahl, welche das Produkt zweier Primzahlen ist. Natürlich bin ich kein Hellseher,
doch dazu gleich mehr.
Zuerst aber wollen wir in Kurzform unsere RSA-Kenntnisse auffrischen:
Chiffriertext (Verschlüsselung):
Originaltext (Entschlüsselung):
Zwei Primzahlen:
Öffentlicher Modulus:
Öffentlicher Exponent:
Privater Exponent:
|
C = M^E mod N
M = C^D mod N
P, Q
N = P * Q
E, es gilt: GCD (E, (P-1)*(Q-1)) == 1
D = E^(-1) mod ((P-1)*(Q-1))
|
In unserem Fall handelt es sich um die Gleichung zur Verschlüsselung C = M^E mod N , wobei M unserer
Serial entspricht. Mit der Entschlüsselungsgleichung M = C^D mod N erhalten wir sie zurück, aber
leider kennen wir den Privaten Exponenten D nicht. Diesen gilt es nun zu berechnen. Warum und wieso? Bitte lest die
beiden ganz oben erwähnten Dokumente! Kurz gesagt: Wir müssen den Modulus faktorisieren, daraus erhalten
wir D. Genau dazu dient nun tE's
RSA Tool.
Gucken wir uns doch noch einmal die Gleichung an:
Big1 = (BigSerial ^ bigPubExp) mod BigModulus
Big1 = (BigSerial ^ E401C1Bh) mod 24DFDA27FA14D3F27DDF62CEA5D2381F9h
Wir wählen das 16er-System aus (Number Base) und kopieren den Modulus N. Danach drücken wir auf
Exact size, das gibt uns dessen genaue Länge. Es handelt sich also um RSA-130. Jetzt faktorisieren
wir den Modulus mit Factor N. Je nach PC dauert das eine (ganz) kurze Weile, auf einem P4 - 2.53 GHz
zum Beispiel ganze 6 Sekunden. Danach kopieren wir den Öffentlichen Exponenten und drücken Calc D.
Somit haben wir alle Parameter beisammen, um die Serial zu berechnen:
P = 16F54CE422ACC40EB
Q = 19B2CF048CA1B2FAB
N = 24DFDA27FA14D3F27DDF62CEA5D2381F9
E = E401C1Bh
D = 1E2D9B52ADCBC20DCCDE3C721AA740E83
|
Unser Keygen wird wie gesagt relativ einfach: Wir lesen Name und Company ein, hängen beide aneinander und
bilden davon den MD5-Hash. Mit diesem Hash, dem Modulus N und dem privaten Exponenten D berechnen wir mit der
Funktion PowMod die Serial. So einfach ist das!
#include <miracl.h>
#include "ms32.lib"
#include "md5.c"
void CalculateSerial (HWND hWnd)
{
MD5_CTX md5ctx;
big bigSerial, bigHash, bigPrExp, bigModulus;
int iNameLen, iCompLen;
char szName[64], szCompany[32], szSerial[34] = "", cHash[16] = "";
// get input
iNameLen = GetDlgItemText (hWnd, EDF_NAME, szName, 32);
iCompLen = GetDlgItemText (hWnd, EDF_COMPANY, szCompany, 32);
// check input lengths
if ((iNameLen == 0) || (iCompLen == 0))
{
SetDlgItemText (hWnd, EDF_SERIAL, NULL);
return;
}
// initialize miracl: 100 digits per big, base 16
miracl *mip = mirsys (100, 16);
// initialize big numbers
bigSerial = mirvar (0);
bigModulus = mirvar (0);
bigPrExp = mirvar (0);
bigHash = mirvar (0);
// initialize constants
instr (bigModulus, "24DFDA27FA14D3F27DDF62CEA5D2381F9");
instr (bigPrExp, "1E2D9B52ADCBC20DCCDE3C721AA740E83");
// md5-hash on name+company
lstrcat (szName, szCompany);
iNameLen = lstrlen (szName);
MD5Init (&md5ctx);
MD5Update (&md5ctx, szName, iNameLen);
MD5Final (cHash, &md5ctx);
// convert result to bignum
bytes_to_big (16, cHash, bigHash);
// calculate serial (rsa-130): serial = hash ^ d % n
powmod (bigHash, bigPrExp, bigModulus, bigSerial);
// show serial
otstr (bigSerial, szSerial);
SetDlgItemTextA (hWnd, EDF_SERIAL, szSerial);
// miracl cleanup
mirkill(bigSerial);
mirkill(bigModulus);
mirkill(bigPrExp);
mirkill(bigHash);
mirexit();
return;
}
|
Es ist wie mit vielen anderen Dingen auch: Am Anfang erscheint das Problem unlösbar, wer es aber
zwei-, dreimal gemacht hat, findet sich sofort zurecht. Mit RSA ist das nicht anders. Das Problem besteht
hauptsächlich darin, dass man überhaupt erkennt, worum es sich handelt. Danach gilt es noch, die Grösse
des Modulus zu bestimmen. Ist dieser nämlich zu gross, kann er nicht mehr faktorisiert werden. Genauer
gesagt: Er kann schon, aber nicht in nützlicher Zeit! Ist der Modulus grösser als 200 Bit, wird
der Prozess schon langwierig, ab 300 Bit kann man es bereits vergessen! Das kann jeder selber ausprobieren
mit dem RSA-Tool, indem entsprechende Muduli generiert und danach faktorisiert werden.
Noch ein Wort zum Anfangs erwähnten "unbezwingbaren Softwareschutz". Wer sich einmal Asprotect
genauer angeguckt hat, findet ihn dort bereits: RSA-1024 und damit wichtige Teile seines Programms verschlüsseln,
z.B. die Funktion "Speichern". Diese würde somit nur bei einer gültigen Serial richtig entschlüsselt.
In dem Fall hilft auch Patchen nichts: kein Speichern ohne richtige Serial!
Den kompletten Sourcecode, das Crackme, das RSA-Tool und eine C-Routine für MD5 findet ihr
in der Zip-Datei. Sollte irgendwas noch unklar, nicht gut erklärt oder sogar falsch
sein, bitte mailt mir. Für Fragen,
Kritik, Morddrohungen, Heiratsanträge oder Lob stehe ich gerne zur Verfügung,
auch Geldgeschenke oder Naturalien werden gerne angenommen!
Ich freu mich auf jeden Fall schon aufs nächste Fondue... -={ figugegl }=-
Grüsse an die Mitglieder von TNP, alle Idler vom Board, Scarebyte, +Q (welcher mich auf den Geschmack
gebracht hat) und besonders an the Egoiste für sein 13370 RSA-Tool!
|
Leute, falls noch Fragen auftauchen, kommt auf unsere
Homepage, in unser
Forum oder in unseren IRC-Chan
#newbiez in Efnet! Dank geht an alle, die bereits an unserem Projekt
mitgearbeitet haben, alle Mitglieder und Freunde von TNP und an alle bewußten
und unbewußten Lehrmeister "all around the world". Für spezielle Sachen, z.B.
wenn Ihr ein Tutorial verfaßt habt und es gern veröffentlichen wollt,
schickts an unsere E-Mail-Adresse tN-p@gmx.net.
|
|
|