Heute wird es Zeit für den nächsten Artikel und diesmal nehmen wir uns die Erweiterung von Diablo II vor. Es wird sich wieder um den Serial drehen. Ich denke das es ein leichtes wird, da sich wahrscheinlich die Installationsroutine ähnlich wie in Diablo II verhält. Einzig der Algorithmus wird wohl angepasst sein und das ein anderes ASCII-Memory-Sieb verwendet wird. Also auf geht es in eine neue Runde Let’s CrackIt!!!
Vorbereitung
Wieder einmal heißt es eine ISO von der CD erstellen, damit das CD-Laufwerk nicht die ganze Zeit rum rattert. Nach dem die ISO fertig erstellt wurde, wird sich wieder die Dateien auf der CD angeschaut und es befindet sich eine SETUP.EXE und eine INSTALL.EXE auf der CD. Da die INSTALL.EXE größer ist und die SETUP.EXE wahrscheinlich nur auf die INSTALL.EXE verweist, widme ich mich gleich der INSTALL.EXE. Als nächstes wird der PE-Header der Datei analysiert und wir bekommen folgendes Resultat: „Microsoft Visual C++ 6.0“. Sehr gut, somit ist die Datei nicht gepackt und wir können die INSTALL.EXE in OllyDbg öffnen. Ich benutze hierfür OllyDbg 2.01d, da die neue OllyDbg Version schon einige PlugIns mit übernommen hat, die man sonst für OllyDbg 1.10 installieren müsste.
Alt Bekanntes
Ich fange wieder einmal Blind an, ich habe Extra nicht die alten BreakPoints und Kommentare geladen. Hätte ich dies gemacht, würde man sofort alles sehen, wo sich die gesuchten Funktionen befinden (:D). Also werden zum Anfang wieder die Intermodularen Calls durchsucht und schnell findet man den Einstiegspunkt. Die erste Funktion die mir dann entgegenspringt, ist die Konkatinierungsfunktion der einzelnen Felder für die Serialnummer. Sie ist genauso wie in Diablo II aufgebaut.
CPU Disasm Address Hex dump Command Comments 0041EBEB |. /74 15 JE SHORT 0041EC02 0041EBED |. |6A 01 PUSH 1 0041EBEF |. |6A 11 PUSH 11 0041EBF1 |. |68 B0464601 PUSH OFFSET 014646B0 ; ASCII "4444444444444444" 0041EBF6 |. |8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] 0041EBF9 |. |51 PUSH ECX 0041EBFA |. |E8 E1050000 CALL 0041F1E0 ; Konkatinierungsfunktion fuer die Serialnummer 0041EBFF |. |83C4 10 ADD ESP,10 0041EC02 |> \833D 84464601 CMP DWORD PTR DS:[1464684],0
Weiter unten im Code stoße ich dann schon auf die Überprüfungsroutine. Hier zeige ich kurz den Einstiegspunkt:
CPU Disasm Address Hex dump Command Comments 0041EC49 |. 6A 01 PUSH 1 0041EC4B |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041EC4E |. 50 PUSH EAX 0041EC4F |. 68 B0464601 PUSH OFFSET 014646B0 ; ASCII "4444444444444444" 0041EC54 |. E8 E7010000 CALL 0041EE40 ; Ueberpruefung der Serial 0041EC59 |. 83C4 0C ADD ESP,0C
Auf die Länge kommt es an
Was ist das wichtigste am Serial. Genau die Länge ;)
CPU Disasm Address Hex dump Command Comments 0041EE72 |> \8B55 08 MOV EDX,DWORD PTR SS:[ARG.1] ; ASCII "4444444444444444" 0041EE75 |. 52 PUSH EDX ; /String => [ARG.1] 0041EE76 |. FF15 88D14400 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \KERNEL32.lstrlenA 0041EE7C |. 83F8 10 CMP EAX,10 ; Serial muss 0x10 (16 dezimal) lang sein 0041EE7F |. 74 1D JE SHORT 0041EE9E ; 0041EE81 |. 8B45 0C MOV EAX,DWORD PTR SS:[ARG.2] ; Fehlermeldungsfunktion 0041EE84 |. 50 PUSH EAX ; /Arg3 => [ARG.2] 0041EE85 |. 68 5A020000 PUSH 25A ; |Arg2 = 25A 0041EE8A |. 68 58020000 PUSH 258 ; |Arg1 = 258 0041EE8F |. E8 DC85FEFF CALL 00407470 ; \INSTALL.00407470 0041EE94 |. 83C4 0C ADD ESP,0C 0041EE97 |. 33C0 XOR EAX,EAX 0041EE99 |. E9 A1010000 JMP 0041F03F
Gut die Längenüberprüfung ist nun nicht so spannend.
Das Sieb und die Löcher!
Nach der Überprüfung geht es nun darum dass überprüft werden muss, ob auch alle gültigen Zeichen verwendet werden.
CPU Disasm Address Hex dump Command Comments 0041EEC1 |. 8D4D E0 LEA ECX,[LOCAL.8] 0041EEC4 |. 51 PUSH ECX 0041EEC5 |. E8 B6010000 CALL 0041F080 ; Memory Sieb 0041EECA |. 83C4 04 ADD ESP,4 0041EECD |. 85C0 TEST EAX,EAX 0041EECF |. 75 1D JNE SHORT 0041EEEE 0041EED1 |. 8B55 0C MOV EDX,DWORD PTR SS:[ARG.2] ;Fehlermeldung 0041EED4 |. 52 PUSH EDX ; /Arg3 => [ARG.2] 0041EED5 |. 68 59020000 PUSH 259 ; |Arg2 = 259 0041EEDA |. 68 58020000 PUSH 258 ; |Arg1 = 258 0041EEDF |. E8 8C85FEFF CALL 00407470 ; \INSTALL.00407470 0041EEE4 |. 83C4 0C ADD ESP,0C 0041EEE7 |. 33C0 XOR EAX,EAX 0041EEE9 |. E9 51010000 JMP 0041F03F ;Sprung zum Funktionsende
CPU Disasm Address Hex dump Command Comments 0041F080 /$ 55 PUSH EBP ; Memory Sieb 0041F081 |. 8BEC MOV EBP,ESP 0041F083 |. 51 PUSH ECX 0041F084 |> 8B45 08 /MOV EAX,DWORD PTR SS:[EBP+8] 0041F087 |. 0FBE08 |MOVSX ECX,BYTE PTR DS:[EAX] 0041F08A |. 85C9 |TEST ECX,ECX 0041F08C |. 74 28 |JE SHORT 0041F0B6 0041F08E |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP+8] 0041F091 |. 8A02 |MOV AL,BYTE PTR DS:[EDX] 0041F093 |. 8845 FF |MOV BYTE PTR SS:[EBP-1],AL 0041F096 |. 8A4D FF |MOV CL,BYTE PTR SS:[EBP-1] 0041F099 |. 51 |PUSH ECX ; /Arg1 0041F09A |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP+8] ; | 0041F09D |. 83C2 01 |ADD EDX,1 ; | 0041F0A0 |. 8955 08 |MOV DWORD PTR SS:[EBP+8],EDX ; | 0041F0A3 |. E8 18000000 |CALL 0041F0C0 ; \get_number 0041F0A8 |. 83C4 04 |ADD ESP,4 0041F0AB |. 83F8 17 |CMP EAX,17 0041F0AE |.^ 76 04 |JBE SHORT 0041F0B4 0041F0B0 |. 33C0 |XOR EAX,EAX 0041F0B2 |. EB 07 |JMP SHORT 0041F0BB 0041F0B4 |>^ EB CE \JMP SHORT 0041F084 0041F0B6 |> B8 01000000 MOV EAX,1 0041F0BB |> 8BE5 MOV ESP,EBP 0041F0BD |. 5D POP EBP 0041F0BE \. C3 RETN 0041F0BF CC INT3 0041F0C0 /$ 55 PUSH EBP ; get_number(guessed Arg1) 0041F0C1 |. 8BEC MOV EBP,ESP 0041F0C3 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041F0C6 |. 25 FF000000 AND EAX,000000FF 0041F0CB |. 0FBE80 98D744 MOVSX EAX,BYTE PTR DS:[EAX+44D798] ; an der Speicheradresse 0x44D798 befindet sich das Memorysieb 0041F0D2 |. 5D POP EBP 0041F0D3 \. C3 RETN
CPU Dump Address Hex dump ASCII 0044D7C8 FF FF 00 FF|01 FF 02 03|04 05 FF FF|FF FF FF FF| ¦¦2¦4¦6789¦¦¦¦¦¦ 0044D7D8 FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ¦¦BCDEFGH¦JK¦MN¦ 0044D7E8 11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| P¦R¦T¦VWX¦Z¦¦¦¦¦ 0044D7F8 FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ¦¦bcdefgh¦jk¦mn¦ 0044D808 11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| p¦r¦t¦vwx¦z¦¦¦¦¦
Das Sieb wurde gar nicht angepasst, hoffentlich werden die Hashing-Routinen interessanter. Noch einmal kurz zur Erläuterung des Memory-Sieb: Die Funktion gibt den ASCII-Wert an die Funktion get_number(arg1) weiter und schaut dann im Arbeitsspeicher an eine bestimmte Stelle (0x44D798 + ASCII-Wert). Steht im Arbeitsspeicher an dieser Stelle der Wert 0xFF wird eine Fehlermeldung ausgelöst.
Same Hashing all the Time
An dieser Stelle breche ich die weitere Ausführung ab, weil es vergeudete Zeit ist. Gut was hab ich erwartet, Blizzard will das Rad nicht neu erfinden. Ich habe mir nun alle Hashing Routinen angeschaut und mit dem aus Diablo II verglichen! Es ist genauso wie ich gedacht hab, es funktioniert genauso. Blizzard hat sich kaum die Mühe gemacht, das Memory-Sieb ein wenig anzupassen oder vielleicht das Hashing ein wenig abzuändern. Die einzige Änderung die Ich gesehen habe ist, dass bei Diablo II die Crazy-XOR-Funktion aus 2 Funktionen bestand. Diese wurden nun in eine Funktion implementiert. Der zweite Unterschied ist das beim letzten genrierten Hash, der Hash nicht mehr mit 06 oder 07 anfangen muss, sondern nun mit 0A oder 0C beginnen muss. Somit können wir auch den alten Bruteforce-Code nehmen und einfach die eine Abfrage abändern und fertig.
Bruteforcing the World
Mir ist bereits ein kleiner Einfall gekommen, um das Bruteforcing von Serialcodes zu beschleunigen. Da es viel zu aufwendig ist, von Anfang bis zum Ende alles durchzuprobieren, schlagen wir den Serial mit seinen eigenen Waffen: dem Zufall. Als erstes wird ein zufälliger Serial generiert und ab diesem Serial werden dann die nächsten 1000 Stellen durchprobiert. Kommt unter diesen 1000 Serials kein gültiger Serial, wird einfach ein neuer zufälliger Serial erzeugt und wieder die nächsten 1000 Stellen weiter ausprobiert. So bekommt man im Worst-Case-Szenario in 30 Min an einen einen Serial. Random Bruteforce hat doch schon was lustigen an sich ^.^
public void keygen() { //Startbedingungen und Variablen Random r; //int[] int_key_array = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23 }; string[] string_key_array = { "2", "4", "6", "7", "8", "9", "B", "C", "D", "E", "F", "G", "H", "J", "K", "M", "N", "P", "R", "T", "V", "W", "X", "Z" }; Int64 number = 0; int[] cd_code = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int[] cd_hash = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; int[] cd_hash2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; bool x = true; while (x) { //Zufälligen Key generieren r = new Random(); for (int i = 0; i < cd_code.Length; i++) { cd_code[i] = r.Next(0, 23); } //1000 Keys ausprobieren for (int xxx = 0; xxx < 1000; xxx++) { //Multithreading --> bei gleichzeitigem Zugriff = Absturz lock (this) { aktuell.Text = string_key_array[cd_code[0]] + string_key_array[cd_code[1]] + string_key_array[cd_code[2]] + string_key_array[cd_code[3]] + "-" + string_key_array[cd_code[4]] + string_key_array[cd_code[5]] + string_key_array[cd_code[6]] + string_key_array[cd_code[7]] + "-" + string_key_array[cd_code[8]] + string_key_array[cd_code[9]] + string_key_array[cd_code[10]] + string_key_array[cd_code[11]] + "-" + string_key_array[cd_code[12]] + string_key_array[cd_code[13]] + string_key_array[cd_code[14]] + string_key_array[cd_code[15]]; } //PART1 Int64 prufcounter = 0x1; Int64 prufsumme = 0x0; for (int i = 0; i < 8; i++) { int buffer = cd_code[i * 2] * 24 + cd_code[i * 2 + 1]; if (buffer >= 256) { prufsumme = prufsumme | prufcounter; buffer = buffer - 256; } cd_hash[i * 2] = (buffer / 2 / 2 / 2 / 2) % 0x10; cd_hash[i * 2 + 1] = buffer % 0x10; prufcounter = prufcounter * 2; } //PART2 Int64 pruffert = 0x03; Int64 pruffert2 = 0x0; for (int i = 0; i < cd_code.Length; i++) { pruffert2 = pruffert * 0x2; pruffert2 = pruffert2 ^ cd_hash[i]; pruffert = pruffert + pruffert2; } //vergeleich pruffert = pruffert & 0xff; if ((pruffert) == (prufsumme)) { //tauschcrypto for (int i = 0xF; i >= 0; i--) { int buffer_c = (i * 0x11) + 0x7; int stelle = buffer_c % 0x10; int buff = cd_hash[stelle]; cd_hash[stelle] = cd_hash[i]; cd_hash[i] = buff; } int under = 0x13AC9741; for (int i = 15; i >=0; i--) { if ( cd_hash[i] <= 7) { int xorer = under & 0x7; cd_hash[i] = xorer ^ cd_hash[i]; under = under / 2 / 2 / 2; } else if (cd_hash[i] > 9) { }else{ int xorer = i & 0x1; cd_hash[i] = xorer ^ cd_hash[i]; } } //if (cd_hash[0] == 0 && (cd_hash[1] == 6 || cd_hash[1] == 7))//Diablo II if (cd_hash[0] == 0 && (cd_hash[1] == 10 || cd_hash[1] == 12))//Diablo II - Expansion { //Q&D string key = string_key_array[cd_code[0]] + string_key_array[cd_code[1]] + string_key_array[cd_code[2]] + string_key_array[cd_code[3]] + "-" + string_key_array[cd_code[4]] + string_key_array[cd_code[5]] + string_key_array[cd_code[6]] + string_key_array[cd_code[7]] + "-" + string_key_array[cd_code[8]] + string_key_array[cd_code[9]] + string_key_array[cd_code[10]] + string_key_array[cd_code[11]] + "-" + string_key_array[cd_code[12]] + string_key_array[cd_code[13]] + string_key_array[cd_code[14]] + string_key_array[cd_code[15]] + "\n"; //Ausgabe auf der GUI txt_brute.Text = key + txt_brute.Text; number++; lbl_key_number.Text = number.ToString(); //Ressource für andere Threads blocken lock (this) { StreamWriter myFile = new StreamWriter("./d2_serial.txt", true); myFile.Write(key); myFile.Close(); } xxx = 0; } } //hochzählen des keys for (int i = 15; i > 0; i--) { if (i == 15) { cd_code[i]++; } if (cd_code[i] >= 24) { cd_code[i - 1]++; cd_code[i] = 0; } } } } }
Fazit
Für die genaue Funktionsweise des Serial verweise ich an das Diablo II – Let’s CrackIt. Mich ärgert es immer noch, das Blizzard sich nicht mehr einfallen lassen hat und nur 2 Zahlen geändert hat. Egal! Falls ihr Anregungen oder Ideen für zukünftige Programme, Spiele etc. habt, schreibt es in die Kommentare. Bis zum nächsten CrackIt, was hoffentlich nicht so ein Armutszeugnis wird ;)
Greetz TheVamp
Diablo II KeyBruteforcer - Source (5903 Downloads)