Heute geht es weiter in der Diablo-Reihe! Heute geht es um den CD-Code beziehungsweise Serial, der bei der Installation benötigt wird. Ziel ist es zu verstehen, wie der CD-Code geprüft wird und auf diesen Erkenntnissen ein Key-Genrator zu programmieren. Man sollte auch erwähnen, dass Blizzard zwei verschiedene Varianten hat, um Diablo II zu installieren. Zum einem kann man sich das Spiel über das Battle.net herunterladen und dann von der Festplatte aus installieren. Zum anderen gibt es die Version, die man im Geschäft kaufen kann. Beide Versionen benutzen verschiedene Algorithmen für die Überprüfung des Serial. Dies sieht man daran, dass die verschiedenen Versionen andere Muster benutzen, um den CD-Code zu überprüfen. Die Download Variante benutzt das Muster XXXXXX-XXXX-XXXXXX-XXXX-XXXXXX und die alte CD-Installation benutzt das Muster XXXX-XXXX-XXXX-XXXX. Also auf zum fröhlichen Cracken und analysieren!
Vorbereitungen
Ja, ein wenig Vorbereitung gehört auch dazu. Zunächst müssen alle Diablo II CD’s als Image auf die Festplatte gebannt werden. So erspare ich mir das ewige gedröhne des CD-Laufwerkes. Hierfür benutze ich ImgBurn, mit dem man die CD’s im Iso-Format erstellen kann. Im Anschluss habe ich dann die Installations-CD in ein virtuelles Laufwerk gemountet und geschaut was sich alles auf der CD befindet. Die interessantesten Dateien sind die INSTALL.EXE (360 KB) und die SETUP.EXE (32 KB). Ich vermute mal dass die SETUP.EXE nur auf die INSTALL.EXE verweist beziehungsweise das Programm einfach die INSTALL.EXE statrtet. Deswegen werde ich die SETUP.EXE gleich links liegen lassen und anfangen die INSTALL.EXE mit OllyDbg zu debuggen.
Die ersten Versuche
Also kommen wir auch schon zu den ersten Versuchen. Als aller erstes sollte man den PE-Header untersuchen, um herauszufinden ob das Programm irgendwie gepackt ist und somit das Debuggen verhindert werden kann. Ein Blick mit PEid auf die INSTALL.EXE verrät mir, dass es sich um „Microsoft Visual C++ 6.0“ handelt. Wie man sieht ist das Programm nicht gepackt, also können wir auch gleich mit dem debuggen anfangen und schauen wie sich das Programm im inneren Verhält. Doch zunächst wird das Programm erst einmal so gestartet und ausgetestet wie sich das Programm im Normalfall verhält. Außerdem schaut man noch nach auffälligen Schlüsselwörtern wie: CD-Code, Name, ungültiger CD-Code oder irgendwelche Fehlermeldungen, die uns eventuell helfen könnten die richtige Stelle nach her im Code zu finden.
Nach dem wir nun wissen wie sich das Programm verhält, versuchen wir nun über den Debugger die richtige Stelle zu finden, wo die Abfrage des Schlüssels beginnt. Also durchsuchen wir in OllyDbg nach allen referenzierten Strings und schauen nach, ob wir irgendwo einen Hinweis finden. Jedoch konnte ich auf die schnelle nichts relevantes finden. Deswegen greifen wir nun zur zweiten Such-Methode: Wir suchen nach „allen intermodularen Calls“. Intermodulare Calls sind alle Aufrufe (CALL’s), die auf externe Bibliotheken zugreifen. Die Liste sortiere ich noch einmal speziell nach Funktionsnamen, denn so kann man einfach nach speziellen Befehlen suchen. Ich suche nun speziell nach Get-Methoden, die uns hoffentlich zu der Stelle bringen, wo der Serial aus der Textbox geholt wird.
Durch ein wenig herumprobieren und schauen ob man durch die gesetzten BreakPoints Erfolg hat, bin ich nun an folgende Stelle angekommen, die den Namen abfängt:
CPU Disasm Address Hex dump Command Comments 0041B013 |> \68 00100000 PUSH 1000 ; /MaxCount = 4096. 0041B018 |. 8D8D ECEFFFFF LEA ECX,[LOCAL.1029] ; | 0041B01E |. 51 PUSH ECX ; |String => OFFSET LOCAL.1029 0041B01F |. 68 DA070000 PUSH 7DA ; |ItemID = 2010. 0041B024 |. 8B55 08 MOV EDX,DWORD PTR SS:[ARG.1] ; | 0041B027 |. 52 PUSH EDX ; |hDialog => [ARG.1] 0041B028 |. FF15 34734400 CALL DWORD PTR DS:[<&USER32.GetDlgIt ; \USER32.GetDlgItemTextA 0041B02E |. 85C0 TEST EAX,EAX 0041B030 |. 75 07 JNE SHORT 0041B039 0041B032 |. C685 ECEFFFFF MOV BYTE PTR SS:[LOCAL.1029],0 0041B039 |> 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] ; holt den Namen 0041B03C |. 50 PUSH EAX ; /Arg2 => [ARG.1] 0041B03D |. 8D8D ECEFFFFF LEA ECX,[LOCAL.1029] ; | 0041B043 |. 51 PUSH ECX ; |Arg1 => OFFSET LOCAL.1029 //hier steht dann der Name, wenn der Pointer hier angelangt ist 0041B044 |. E8 F3000000 CALL 0041B13C ; \INSTALL.0041B13C
Nun haben wir den ersten Ansatz und können uns weiter durch das Programm debuggen. In den Nachfolgenden Funktionen wird dann auch schon der Serial abgefragt. Da der Serial sowieso falsch ist kommt man früher oder später zu einer Fehlermeldung, dass der CD-Code falsch ist. Wenn man nun den zweiten Versuch startet, wird man feststellen das im Programm nun mehr steht. Das Programm löscht nach der Überprüfung nicht die Variablen, dadurch sehen wir welcher Wert vorher in der Variable stand. Hier ein Code Ausschnitt aus dem zweiten Anlauf:
CPU Disasm Address Hex dump Command Comments 0041B039 |> \8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] ; holt den Namen 0041B03C |. 50 PUSH EAX ; /Arg2 => [ARG.1] 0041B03D |. 8D8D ECEFFFFF LEA ECX,[LOCAL.1029] ; | 0041B043 |. 51 PUSH ECX ; |Arg1 => OFFSET LOCAL.1029 0041B044 |. E8 F3000000 CALL 0041B13C ; \INSTALL.0041B13C 0041B049 |. 83C4 08 ADD ESP,8 0041B04C |. 85C0 TEST EAX,EAX 0041B04E |. 75 0A JNE SHORT 0041B05A 0041B050 |. B8 01000000 MOV EAX,1 0041B055 |. E9 DC000000 JMP 0041B136 0041B05A |> 8D95 ECEFFFFF LEA EDX,[LOCAL.1029] 0041B060 |. 52 PUSH EDX ; /Src => OFFSET LOCAL.1029 0041B061 |. 68 A0A04501 PUSH OFFSET 0145A0A0 ; |Dest = "Vamp" 0041B066 |. FF15 74714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrcpy 0041B06C |. 6A 11 PUSH 11 ; /Arg3 = 11 0041B06E |. 68 8CA04501 PUSH OFFSET 0145A08C ; |Arg2 = ASCII "AAAAAAAAAAAAAAAA" 0041B073 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] ; | 0041B076 |. 50 PUSH EAX ; |Arg1 => [ARG.1] 0041B077 |. E8 D1040000 CALL 0041B54D ; \INSTALL.0041B54D 0041B07C |. 83C4 0C ADD ESP,0C 0041B07F |. 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] 0041B082 |. 51 PUSH ECX ; /Arg2 => [ARG.1] 0041B083 |. 68 8CA04501 PUSH OFFSET 0145A08C ; |Arg1 = ASCII "AAAAAAAAAAAAAAAA" 0041B088 |. E8 5E010000 CALL 0041B1EB ; \INSTALL.0041B1EB 0041B08D |. 83C4 08 ADD ESP,8 0041B090 |. 85C0 TEST EAX,EAX
Man kann ganz klar erkennen, wo unser Serial nun übergeben wird. Es sind zwei Funktionen die etwas mit dem Serial machen. Gehen wir mal in die erste Funktion (INSTALL.0041B54D) und schauen uns diese genauer an:
CPU Disasm Address Hex dump Command Comments 0041B54D /$ 55 PUSH EBP ; Funktion: Kopiert den Key zusammen 0041B54E |. 8BEC MOV EBP,ESP 0041B550 |. B8 00400000 MOV EAX,4000 0041B555 |. E8 16F50100 CALL 0043AA70 ; Allocates 16384. bytes on stack 0041B55A |. 56 PUSH ESI 0041B55B |. 68 00100000 PUSH 1000 ; /MaxCount = 4096. //liest Feld #1 aus 0041B560 |. 8D85 00F0FFFF LEA EAX,[LOCAL.1024] ; | 0041B566 |. 50 PUSH EAX ; |String => OFFSET LOCAL.1024 0041B567 |. 68 D6070000 PUSH 7D6 ; |ItemID = 2006. 0041B56C |. 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] ; | 0041B56F |. 51 PUSH ECX ; |hDialog => [ARG.1] 0041B570 |. FF15 34734400 CALL DWORD PTR DS:[] ; \USER32.GetDlgItemTextA 0041B576 |. 85C0 TEST EAX,EAX 0041B578 |. 75 07 JNE SHORT 0041B581 0041B57A |. C685 00F0FFFF MOV BYTE PTR SS:[LOCAL.1024],0 0041B581 |> 68 00100000 PUSH 1000 ; /MaxCount = 4096. //liest Feld #2 aus 0041B586 |. 8D95 00E0FFFF LEA EDX,[LOCAL.2048] ; | 0041B58C |. 52 PUSH EDX ; |String => OFFSET LOCAL.2048 0041B58D |. 68 D7070000 PUSH 7D7 ; |ItemID = 2007. 0041B592 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] ; | 0041B595 |. 50 PUSH EAX ; |hDialog => [ARG.1] 0041B596 |. FF15 34734400 CALL DWORD PTR DS:[] ; \USER32.GetDlgItemTextA 0041B59C |. 85C0 TEST EAX,EAX 0041B59E |. 75 07 JNE SHORT 0041B5A7 0041B5A0 |. C685 00E0FFFF MOV BYTE PTR SS:[LOCAL.2048],0 0041B5A7 |> 68 00100000 PUSH 1000 ; /MaxCount = 4096. // liest Feld #3 aus 0041B5AC |. 8D8D 00D0FFFF LEA ECX,[LOCAL.3072] ; | 0041B5B2 |. 51 PUSH ECX ; |String => OFFSET LOCAL.3072 0041B5B3 |. 68 D9070000 PUSH 7D9 ; |ItemID = 2009. 0041B5B8 |. 8B55 08 MOV EDX,DWORD PTR SS:[ARG.1] ; | 0041B5BB |. 52 PUSH EDX ; |hDialog => [ARG.1] 0041B5BC |. FF15 34734400 CALL DWORD PTR DS:[] ; \USER32.GetDlgItemTextA 0041B5C2 |. 85C0 TEST EAX,EAX 0041B5C4 |. 75 07 JNE SHORT 0041B5CD 0041B5C6 |. C685 00D0FFFF MOV BYTE PTR SS:[LOCAL.3072],0 0041B5CD |> 68 00100000 PUSH 1000 ; /MaxCount = 4096. //liest Feld #4 aus 0041B5D2 |. 8D85 00C0FFFF LEA EAX,[LOCAL.4096] ; | 0041B5D8 |. 50 PUSH EAX ; |String => OFFSET LOCAL.4096 0041B5D9 |. 68 DC070000 PUSH 7DC ; |ItemID = 2012. 0041B5DE |. 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] ; | 0041B5E1 |. 51 PUSH ECX ; |hDialog => [ARG.1] 0041B5E2 |. FF15 34734400 CALL DWORD PTR DS:[] ; \USER32.GetDlgItemTextA 0041B5E8 |. 85C0 TEST EAX,EAX 0041B5EA |. 75 07 JNE SHORT 0041B5F3 0041B5EC |. C685 00C0FFFF MOV BYTE PTR SS:[LOCAL.4096],0 0041B5F3 |> 8D95 00F0FFFF LEA EDX,[LOCAL.1024] ; Alle Felder werden auf Länge überprüft 0041B5F9 |. 52 PUSH EDX ; /String => OFFSET LOCAL.1024 0041B5FA |. FF15 70714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrlenA 0041B600 |. 8BF0 MOV ESI,EAX 0041B602 |. 8D85 00E0FFFF LEA EAX,[LOCAL.2048] 0041B608 |. 50 PUSH EAX ; /String => OFFSET LOCAL.2048 0041B609 |. FF15 70714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrlenA 0041B60F |. 03F0 ADD ESI,EAX 0041B611 |. 8D8D 00D0FFFF LEA ECX,[LOCAL.3072] 0041B617 |. 51 PUSH ECX ; /String => OFFSET LOCAL.3072 0041B618 |. FF15 70714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrlenA 0041B61E |. 03F0 ADD ESI,EAX 0041B620 |. 8D95 00C0FFFF LEA EDX,[LOCAL.4096] 0041B626 |. 52 PUSH EDX ; /String => OFFSET LOCAL.4096 0041B627 |. FF15 70714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrlenA 0041B62D |. 03F0 ADD ESI,EAX 0041B62F |. 3B75 10 CMP ESI,DWORD PTR SS:[ARG.3] 0041B632 |. 76 04 JBE SHORT 0041B638 0041B634 |. 33C0 XOR EAX,EAX 0041B636 |. EB 47 JMP SHORT 0041B67F 0041B638 |> 8D85 00F0FFFF LEA EAX,[LOCAL.1024] ; Hier werden dann die Felder in ein zusammenhängenden String konkatiniert 0041B63E |. 50 PUSH EAX ; /Src => OFFSET LOCAL.1024 0041B63F |. 8B4D 0C MOV ECX,DWORD PTR SS:[ARG.2] ; | 0041B642 |. 51 PUSH ECX ; |Dest => [ARG.2] 0041B643 |. FF15 74714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrcpy 0041B649 |. 8D95 00E0FFFF LEA EDX,[LOCAL.2048] 0041B64F |. 52 PUSH EDX ; /Src => OFFSET LOCAL.2048 0041B650 |. 8B45 0C MOV EAX,DWORD PTR SS:[ARG.2] ; | 0041B653 |. 50 PUSH EAX ; |Dest => [ARG.2] 0041B654 |. FF15 78714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrcat 0041B65A |. 8D8D 00D0FFFF LEA ECX,[LOCAL.3072] 0041B660 |. 51 PUSH ECX ; /Src => OFFSET LOCAL.3072 0041B661 |. 8B55 0C MOV EDX,DWORD PTR SS:[ARG.2] ; | 0041B664 |. 52 PUSH EDX ; |Dest => [ARG.2] 0041B665 |. FF15 78714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrcat 0041B66B |. 8D85 00C0FFFF LEA EAX,[LOCAL.4096] 0041B671 |. 50 PUSH EAX ; /Src => OFFSET LOCAL.4096 0041B672 |. 8B4D 0C MOV ECX,DWORD PTR SS:[ARG.2] ; | 0041B675 |. 51 PUSH ECX ; |Dest => [ARG.2] 0041B676 |. FF15 78714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrcat 0041B67C |. 8B45 0C MOV EAX,DWORD PTR SS:[ARG.2] 0041B67F |> 5E POP ESI 0041B680 |. 8BE5 MOV ESP,EBP 0041B682 |. 5D POP EBP 0041B683 \. C3 RETN
Wie wir sehen ist dies ja noch ziemlich einfach und verständlich. Selbst die Befehle aus C++ sind deutlich erkennbar, was das verstehen des ASM Code enorm erleichtert Man sieht wie zuerst alle 4 Felder ausgelesen werden und zum Schluss in einen String gefasst werden. Nun ja, so spannend war die erste Funktion nun auch wieder nicht, deswegen gehen wir nun zur 2. Funktion. Diese Funktion ist enorm lang, deswegen werde ich nur auf die wichtigsten Stellen und Funktionen eingehen, aber eines kann ich schon vor wegnehmen: Hier wird die Serial Überprüfung stattfinden.
Die Serial Prüfung – Sieben, sieben, sieben und Gold finden
In dem ersten Teil der Überprüfung geht es natürlich um die Länge:
CPU Disasm Address Hex dump Command Comments 0041B21F |. 52 PUSH EDX ; /String = "AAAAAAAAAAAAAAAA" 0041B220 |. FF15 70714400 CALL DWORD PTR DS:[] ; \KERNEL32.lstrlenA 0041B226 |. 83F8 10 CMP EAX,10 ; prüft ob String eine Länge von 0x10 hat 0041B229 |. 74 1D JE SHORT 0041B248 0041B22B |. 8B45 0C MOV EAX,DWORD PTR SS:[ARG.2] ; Fehlermeldung das Serial zu kurz ist 0041B22E |. 50 PUSH EAX ; /Arg3 => [ARG.2] 0041B22F |. 68 5A020000 PUSH 25A ; |Arg2 = 25A 0041B234 |. 68 58020000 PUSH 258 ; |Arg1 = 258 0041B239 |. E8 40B7FEFF CALL 0040697E ; \INSTALL.0040697E 0041B23E |. 83C4 0C ADD ESP,0C 0041B241 |. 33C0 XOR EAX,EAX 0041B243 |. E9 8B010000 JMP 0041B3D3 0041B248 |> 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1]
Nun ja die richtige Länge haben wir ohnehin. Beim weiteren debuggen stoßen wir dann auf die erste Stelle, wo ein nicht weiter definierter CALL aufgerufen wird und unser Serial wird mit in diesen CALL übergeben. Diesen Call sollten wir uns aufjedenfall anschauen:
CPU Disasm Address Hex dump Command Comments 0041B255 |. 83C4 08 ADD ESP,8 0041B258 |. 8D45 E0 LEA EAX,[LOCAL.8] 0041B25B |. 50 PUSH EAX ; ASCII "AAAAAAAAAAAAAAAA" 0041B25C |. E8 A5010000 CALL 0041B406 ;nicht definierter CALL 0041B261 |. 83C4 04 ADD ESP,4 0041B264 |. 85C0 TEST EAX,EAX
CPU Disasm - nicht definierter CALL Address Hex dump Command Comments 0041B406 /$ 55 PUSH EBP ; Anfang des CALL 0041B406 0041B407 |. 8BEC MOV EBP,ESP 0041B409 |. 51 PUSH ECX 0041B40A |> 8B45 08 /MOV EAX,DWORD PTR SS:[EBP+8] ; ASCII "AAAAAAAAAAAAAAAA" 0041B40D |. 0FBE08 |MOVSX ECX,BYTE PTR DS:[EAX] 0041B410 |. 85C9 |TEST ECX,ECX 0041B412 |. 74 28 |JE SHORT 0041B43C ;springt raus wenn das Ende des String erreicht ist 0041B414 |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP+8] 0041B417 |. 8A02 |MOV AL,BYTE PTR DS:[EDX] 0041B419 |. 8845 FF |MOV BYTE PTR SS:[EBP-1],AL 0041B41C |. 8A4D FF |MOV CL,BYTE PTR SS:[EBP-1] 0041B41F |. 51 |PUSH ECX ; /Arg1 = 41 0041B420 |. 8B55 08 |MOV EDX,DWORD PTR SS:[EBP+8] ; |ASCII "AAAAAAAAAAAAAAAA" 0041B423 |. 83C2 01 |ADD EDX,1 ; | 0041B426 |. 8955 08 |MOV DWORD PTR SS:[EBP+8],EDX ; |ASCII "AAAAAAAAAAAAAAA" 0041B429 |. E8 17000000 |CALL 0041B445 ; \INSTALL.0041B445 0041B42E |. 83C4 04 |ADD ESP,4 0041B431 |. 83F8 17 |CMP EAX,17 ;vergleiche EAX mit 17 0041B434 |.^ 76 04 |JBE SHORT 0041B43A ; ist eax ^ EB CE \JMP SHORT 0041B40A 0041B43C |> B8 01000000 MOV EAX,1 0041B441 |> 8BE5 MOV ESP,EBP 0041B443 |. 5D POP EBP 0041B444 \. C3 RETN
CPU Disasm Address Hex dump Command Comments 0041B445 /$ 55 PUSH EBP ; INSTALL.0041B445(guessed Arg1) 0041B446 |. 8BEC MOV EBP,ESP 0041B448 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041B44B |. 25 FF000000 AND EAX,000000FF 0041B450 |. 0FBE80 607744 MOVSX EAX,BYTE PTR DS:[EAX+447760] ;interessant 0041B457 |. 5D POP EBP 0041B458 \. C3 RETN
Der Call bringt uns zu einer Funktion, welche eine Schleife (0041B40A – 0041B43A) beinhaltet. Diese nimmt jeden Buchstaben unseres Serial und leitet ihn an eine Funktion weiter. Wie wir sehen wird im (siehe mittlerer ASM-Code) ersten Argument (Arg1) 0x41 übergeben. 0x41 steht im dezimalem für 65 und dies ist der ASCII Code für ‚A‘. Wenn wir nun in die Funktion gehen (letzter ASM-Code) ist die folgende Stelle ziemlich Interessant: MOVSX EAX,BYTE PTR DS:[EAX+447760]! Der folgende Befehl liest an der Stelle 0x447760 + 0x41, also die Stelle 0x4477A1, den Wert aus dem Arbeitsspeicher aus und gibt diesen Wert dann wieder zurück. Schauen wir uns mal die Stelle im Arbeitsspeicher an:
;Memory Dump ;Address Hex dump ASCII 00447760 FF FF FF FF|FF FF FF FF|FF FF FF FF|FF FF FF FF| ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 00447770 FF FF FF FF|FF FF FF FF|FF FF FF FF|FF FF FF FF| ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 00447780 FF FF FF FF|FF FF FF FF|FF FF FF FF|FF FF FF FF| ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ 00447790 FF FF 00 FF|01 FF 02 03|04 05 FF FF|FF FF FF FF| ÿÿ#ÿ#ÿ####ÿÿÿÿÿÿ 004477A0 FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ÿA#######ÿ##ÿ##ÿ 004477B0 11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| #ÿ#ÿ#ÿ###ÿ#ÿÿÿÿÿ 004477C0 FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ÿÿ#######ÿ##ÿ##ÿ 004477D0 11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| #ÿ#ÿ#ÿ###ÿ#ÿÿÿÿÿ
Sehr interessant! Wir sehen überall 0xFF im HexDump, außer an ein paar Stellen. Diese sind sehr interessant! Ich habe den ASCII Block so editiert, dass wir einige Rauten sehen und an einer Stelle steht unser A (Zeile 0x4477A0). Genau an dieser Stelle steht im HexDump 0xFF. Sehen wir uns nun die mittlere Funktion noch einmal an. An einer Stelle habe ich im Kommentar vermerkt, dass eax kleiner oder gleich 0x17 sein muss, damit er in der Schleife bleibt. Wenn das Programm vorzeitig aus der Schleife herausspringt, werden die Werte so gesetzt, dass das Programm auf eine Fehlermeldung trifft. Diese Fehlermeldung sagt uns, dass der CD-Code falsch ist. Also darf die letzte Funktion auf keinenfall 0xFF zurückgeben. Wir haben es hier mit einer Art digitalen Sieb zu tun. Dieses digitale Sieb liegt im Arbeitsspeicher und lässt nur bestimmte Zahlen und Buchstaben durch. Hierfür habe ich per Hand eine kleine Liste geschrieben, die verdeutlicht, welche Buchstaben und Zahlen erlaubt sind und welche nicht:
ASCII-Memory-Sieb
0 = nope | 8 = 4 | G = 11 | O = nope | W = 21 | e = 9 | m = 15 | u = nope |
1 = nope | 9 = 5 | H = 12 | P = 17 | X = 22 | f = 10 | n = 16 | v = 20 |
2 = 0 | A = nope | I = nope | Q = nope | Y = nope | g = 11 | o = nope | w = 21 |
3 = nope | B = 6 | J = 13 | R = 18 | Z = 23 | h = 12 | p = 17 | x = 22 |
4 = 1 | C = 7 | K = 14 | S = nope | a = nope | i = nope | q = nope | y = nope |
5 = nope | D = 8 | L = nope | T = 19 | b = 6 | j = 13 | r = 18 | z = 23 |
6 = 2 | E = 9 | M = 15 | U = nope | c = 7 | k = 14 | s = nope | |
7 = 3 | F = 10 | N = 16 | V = 20 | d = 8 | l = nope | t = 19 |
Es ist eine sehr coole Sache, den Serial so auf bestimmte Zeichen einzugrenzen. Die Schwierigkeit hierbei war es, zu erkennen was die Funktion macht und dann das Sieb im Arbeitsspeicher ausfindig zu machen. Nun gut, wir wissen nun welche Buchstaben und Zahlen für unseren Serial erlaubt sind. Weiterhin ist es sehr interessant, dass die Buchstaben und Zahlen verschiedene Werte bekommen haben. Dies könnte vielleicht eine Rolle in der Überprüfung des Serials spielen. Weiter geht es mit dem debuggen:
CPU Disasm Address Hex dump Command Comments 0041B25B |. 50 PUSH EAX 0041B25C |. E8 A5010000 CALL 0041B406 ;das Buchstabensieb (wenn alles korrekt EAX=1) 0041B261 |. 83C4 04 ADD ESP,4 0041B264 |. 85C0 TEST EAX,EAX ;ist EAX vorhanden 0041B266 |. 75 1D JNE SHORT 0041B285 ;wenn EAX 0 ist dann wird die Fehlermeldung ausgelöst 0041B268 |. 8B4D 0C MOV ECX,DWORD PTR SS:[ARG.2] 0041B26B |. 51 PUSH ECX ; /Arg3 => [ARG.2] 0041B26C |. 68 59020000 PUSH 259 ; |Arg2 = 259 0041B271 |. 68 58020000 PUSH 258 ; |Arg1 = 258 0041B276 |. E8 03B7FEFF CALL 0040697E ; \INSTALL.0040697E 0041B27B |. 83C4 0C ADD ESP,0C 0041B27E |. 33C0 XOR EAX,EAX 0041B280 |. E9 4E010000 JMP 0041B3D3 0041B285 |> 8D55 F4 LEA EDX,[LOCAL.3] ;hier gehts weiter wenn alles richtig ist 0041B288 |. 52 PUSH EDX 0041B289 |. 8D45 E0 LEA EAX,[LOCAL.8] 0041B28C |. 50 PUSH EAX 0041B28D |. E8 C7010000 CALL 0041B459 ; und die nächste unbekannte Funktion 0041B292 |. 83C4 08 ADD ESP,8 0041B295 |. C745 F8 03000 MOV DWORD PTR SS:[LOCAL.2],3
Kurz nach dem Buchstabensieb folgt nun die nächste unbekannte Funktion, welcher wir uns im nächsten Kapitel widmen werden.
Die Serial Prüfung – Hashing???
Da wir nun Wissen, welche Buchstaben und Zahlen erlaubt sind, wird es Zeit die nächste Funktion der Serial-Überprüfung unter die Lupe zu nehmen. Doch jetzt wird es ziemlich verrückt! Ich habe selber ziemlich lange gebraucht, um diese Funktion zu verstehen und versuche einmal mit entsprechenden Kommentaren deutlich zu machen, wie sie so ungefähr funktioniert:
CPU Disasm Address Hex dump Command Comments 0041B463 |. C700 00000000 MOV DWORD PTR DS:[EAX],0 0041B469 |. C745 F8 01000 MOV DWORD PTR SS:[LOCAL.2],1 ; setzt Variable-2 auf 1 0041B470 |. C745 FC 00000 MOV DWORD PTR SS:[LOCAL.1],0 ; setzt Variable-1 auf 0 (Zähler Variable i) 0041B477 |. EB 11 JMP SHORT 0041B48A ; springt in die Schleife 0041B479 |> 8B4D FC /MOV ECX,DWORD PTR SS:[LOCAL.1] 0041B47C |. 83C1 02 |ADD ECX,2 ; i = i + 2; 0041B47F |. 894D FC |MOV DWORD PTR SS:[LOCAL.1],ECX 0041B482 |. 8B55 F8 |MOV EDX,DWORD PTR SS:[LOCAL.2] 0041B485 |. D1E2 |SHL EDX,1 ; Verdopple 1mal Variable 2 0041B487 |. 8955 F8 |MOV DWORD PTR SS:[LOCAL.2],EDX 0041B48A |> 837D FC 10 |CMP DWORD PTR SS:[LOCAL.1],10 ; Anfang und Ende der Schleife - wenn Variable-1 0x10 erreicht springe raus 0041B48E |. 0F83 89000000 |JNB 0041B51D 0041B494 |. 8B45 08 |MOV EAX,DWORD PTR SS:[ARG.1] 0041B497 |. 0345 FC |ADD EAX,DWORD PTR SS:[LOCAL.1] 0041B49A |. 8A08 |MOV CL,BYTE PTR DS:[EAX] ; holt den ersten Buchstaben vom Serial 0041B49C |. 51 |PUSH ECX ; /Arg1 0041B49D |. E8 A3FFFFFF |CALL 0041B445 ; \INSTALL.0041B445 0041B4A2 |. 83C4 04 |ADD ESP,4 0041B4A5 |. 8BF0 |MOV ESI,EAX 0041B4A7 |. 6BF6 18 |IMUL ESI,ESI,18 ; ESI = Serial[i] * 18 0041B4AA |. 8B55 08 |MOV EDX,DWORD PTR SS:[ARG.1] 0041B4AD |. 0355 FC |ADD EDX,DWORD PTR SS:[LOCAL.1] 0041B4B0 |. 8A42 01 |MOV AL,BYTE PTR DS:[EDX+1] ; holt den zweiten Buchstaben vom Serial (Serial[i+1]) 0041B4B3 |. 50 |PUSH EAX ; /Arg1 0041B4B4 |. E8 8CFFFFFF |CALL 0041B445 ; \INSTALL.0041B445 0041B4B9 |. 83C4 04 |ADD ESP,4 0041B4BC |. 03F0 |ADD ESI,EAX ; ESI += Serial[i+1] 0041B4BE |. 8975 F4 |MOV DWORD PTR SS:[LOCAL.3],ESI ; Variable-3 ist nun ESI (v3 = Serial[i] * 18 + Serial[i+1]) 0041B4C1 |. 817D F4 00010 |CMP DWORD PTR SS:[LOCAL.3],100 ; Wenn ESI > 0x100 weitermachen 0041B4C8 |. 72 19 |JB SHORT 0041B4E3 0041B4CA |. 8B4D F4 |MOV ECX,DWORD PTR SS:[LOCAL.3] 0041B4CD |. 81E9 00010000 |SUB ECX,100 ; Variable-3 um 0x100 verringern 0041B4D3 |. 894D F4 |MOV DWORD PTR SS:[LOCAL.3],ECX 0041B4D6 |. 8B55 0C |MOV EDX,DWORD PTR SS:[ARG.2] 0041B4D9 |. 8B02 |MOV EAX,DWORD PTR DS:[EDX] ; irgendwas wird vom Stack geladen 0041B4DB |. 0B45 F8 |OR EAX,DWORD PTR SS:[LOCAL.2] ; und mit unser Variable 2 geodert (logische OR-Verknüpfung) 0041B4DE |. 8B4D 0C |MOV ECX,DWORD PTR SS:[ARG.2] 0041B4E1 |. 8901 |MOV DWORD PTR DS:[ECX],EAX ; hier wird die Variable wieder zurück auf den Stack gepackt 0041B4E3 |> 8B55 F4 |MOV EDX,DWORD PTR SS:[LOCAL.3] ; wenn ESI nicht über 0x100 war, gehts hier weiter 0041B4E6 |. C1EA 04 |SHR EDX,4 ; Variable-3 wird viermal durch 2 geteilt & dieses Ergebnis wird in nachfolgender Funktion übergeben 0041B4E9 |. 52 |PUSH EDX ; /Arg1 0041B4EA |. E8 33000000 |CALL 0041B522 ; \INSTALL.0041B522 0041B4EF |. 83C4 04 |ADD ESP,4 0041B4F2 |. 8B4D 08 |MOV ECX,DWORD PTR SS:[ARG.1] 0041B4F5 |. 034D FC |ADD ECX,DWORD PTR SS:[LOCAL.1] 0041B4F8 |. 8801 |MOV BYTE PTR DS:[ECX],AL 0041B4FA |. 8B45 F4 |MOV EAX,DWORD PTR SS:[LOCAL.3] 0041B4FD |. 33D2 |XOR EDX,EDX 0041B4FF |. B9 10000000 |MOV ECX,10 0041B504 |. F7F1 |DIV ECX ; Variable 3 wird durch 0x10 geteilt und in nachfolgende funktion übergeben 0041B506 |. 52 |PUSH EDX ; /Arg1 0041B507 |. E8 16000000 |CALL 0041B522 ; \INSTALL.0041B522 0041B50C |. 83C4 04 |ADD ESP,4 0041B50F |. 8B55 08 |MOV EDX,DWORD PTR SS:[ARG.1] 0041B512 |. 0355 FC |ADD EDX,DWORD PTR SS:[LOCAL.1] 0041B515 |. 8842 01 |MOV BYTE PTR DS:[EDX+1],AL 0041B518 |.^ E9 5CFFFFFF \JMP 0041B479 0041B51D |> 5E POP ESI 0041B51E |. 8BE5 MOV ESP,EBP 0041B520 |. 5D POP EBP 0041B521 \. C3 RETN
Ich versuche noch einmal in Worten zu erklären, was diese Funktion mit dem Serial anstellt. Zum Anfang werden 2 Variablen erstellt. Eine dient zum Zählen, an welcher Stelle wir im Serial sind und die andere Variable ist ebenfalls ein Zähler (ich nenne ihn mal Prüfzähler). Diese dient jedoch nur zur Veränderung der Variable auf dem Stack, aber dazu später mehr.
In der Schleife werden zunächst die ersten beiden Zeichen des CD-Code eingelesen. Diese werden dann in Zahlen umgewandelt, welche wir aus dem Sieb bekommen. Beispielsweise wenn unser erster Buchstabe aus dem Serial ein P ist, wird dieses P in eine 17 umgewandelt. Die beiden Zahlen die wir aus diesem Prozess erhalten, werden dann in einer Funktion berechnet: Ergebnis-1 = Zahl-1 * 18 + Zahl-2. Dieses Ergebnis wird uns dann helfen, unseren CD-Code, in eine Art neuen Code zu verwandeln.
Das erste Zeichen des neuen Codes wird folgendermaßen berechnet: Ergebnis-1 / 2 / 2 / 2 / 2 (also viermal durch 2, wie im ASM Code :) ) und dieses Ergebnis wird durch 16 geteilt und der Rest aus dieser Division wird unser erstes Zeichen sein. Mathematisch in eine Formel gefasst: ( Ergebnis-1 / 2 / 2 / 2 / 2 ) mod 16 = neuer_code[i].
Kommen wir nun zum nächsten Zeichen. Hierfür wird das Ergebnis-1 durch 16 geteilt und aus dieser Division wird wieder der Rest als neues Zeichen verwendet. Also Mathematisch: Ergebnis-1 mod 16 = neuer_code[i+1].
Wir müssen immer noch daran denken, dass wir uns in einer Schleife befinden, somit wird der neue Code nur Schrittweise aufgebaut. Kommen wir nun zum spannenderen Teil dieser Schleife. In der Mitte der Schleife befindet sich eine Abfrage, welche besagt wenn unser Ergebnis-1 größer als 256 ist, dann soll unser Ergebnis-1 zum einen um 256 verringert werden und zum anderen wird eine Prüfziffer vom Stack abgerufen und mit unserem Prüfzähler (siehe oben) geodert (logische OR-Verknüpfung). Nach dem die ersten 2 Zeichen fertig errechnet sind, wird zum Schluss der Prüfzähler verdoppelt und die Schleife nimmt sich nun die nächsten zwei Buchstaben des CD-Code/Serial vor.
Nun gut nach dem die Schleife also durchgelaufen ist, haben wir nun einen neuen Code erhalten. Ich nenne ihn an dieser Stelle einfach mal Hash-Code. Außerdem hat diese Funktion uns noch eine Prüfziffer errechnet, welche bei einer späteren Überprüfung gebraucht wird.
Die Serial Prüfung – Prüfsumme
Also kleine Zusammenfassung. Unser CD-Code darf nur bestimmte Buchstaben benutzen und wird nach korrekter Überprüfung in einen Hash verwandelt. Außerdem wurde noch nebenbei eine Prüfziffer erstellt. Nun kommen wir zum Abgleich der Prüfziffern.
CPU Disasm Address Hex dump Command Comments 0041B28C |. 50 PUSH EAX 0041B28D |. E8 C7010000 CALL 0041B459 ;Hash Generator für den Serial 0041B292 |. 83C4 08 ADD ESP,8 0041B295 |. C745 F8 03000 MOV DWORD PTR SS:[LOCAL.2],3 ;Variable 2 (v2) wird auf 3 gesetzt 0041B29C |. C745 D4 00000 MOV DWORD PTR SS:[LOCAL.11],0 ;Variable 11 (v11) wird auf 0 gesetzt (Zähler i) 0041B2A3 |. EB 09 JMP SHORT 0041B2AE ;ab in die schleife 0041B2A5 |> 8B4D D4 /MOV ECX,DWORD PTR SS:[LOCAL.11] 0041B2A8 |. 83C1 01 |ADD ECX,1 0041B2AB |. 894D D4 |MOV DWORD PTR SS:[LOCAL.11],ECX 0041B2AE |> 837D D4 10 |CMP DWORD PTR SS:[LOCAL.11],10 ; start der Schleife (wenn zähler >= 0x10 ende) 0041B2B2 |. 0F83 8F000000 |JNB 0041B347 0041B2B8 |. 8B55 D4 |MOV EDX,DWORD PTR SS:[LOCAL.11] ;OK ab hier wird nur überprüft ob unser hash wirklich nur aus zahlen und buchstaben besteht 0041B2BB |. 0FBE4415 E0 |MOVSX EAX,BYTE PTR SS:[EDX+EBP-20] 0041B2C0 |. 83F8 30 |CMP EAX,30 0041B2C3 |. 7C 0D |JL SHORT 0041B2D2 0041B2C5 |. 8B4D D4 |MOV ECX,DWORD PTR SS:[LOCAL.11] 0041B2C8 |. 0FBE540D E0 |MOVSX EDX,BYTE PTR SS:[ECX+EBP-20] 0041B2CD |. 83FA 39 |CMP EDX,39 0041B2D0 |. 7E 51 |JLE SHORT 0041B323 0041B2D2 |> 8B45 D4 |MOV EAX,DWORD PTR SS:[LOCAL.11] 0041B2D5 |. 0FBE4C05 E0 |MOVSX ECX,BYTE PTR SS:[EAX+EBP-20] 0041B2DA |. 83F9 41 |CMP ECX,41 0041B2DD |. 7C 0D |JL SHORT 0041B2EC 0041B2DF |. 8B55 D4 |MOV EDX,DWORD PTR SS:[LOCAL.11] 0041B2E2 |. 0FBE4415 E0 |MOVSX EAX,BYTE PTR SS:[EDX+EBP-20] 0041B2E7 |. 83F8 5A |CMP EAX,5A 0041B2EA |. 7E 37 |JLE SHORT 0041B323 0041B2EC |> 8B4D D4 |MOV ECX,DWORD PTR SS:[LOCAL.11] 0041B2EF |. 0FBE540D E0 |MOVSX EDX,BYTE PTR SS:[ECX+EBP-20] 0041B2F4 |. 83FA 61 |CMP EDX,61 0041B2F7 |. 7C 0D |JL SHORT 0041B306 0041B2F9 |. 8B45 D4 |MOV EAX,DWORD PTR SS:[LOCAL.11] 0041B2FC |. 0FBE4C05 E0 |MOVSX ECX,BYTE PTR SS:[EAX+EBP-20] 0041B301 |. 83F9 7A |CMP ECX,7A 0041B304 |. 7E 1D |JLE SHORT 0041B323 0041B306 |> 8B55 0C |MOV EDX,DWORD PTR SS:[ARG.2] 0041B309 |. 52 |PUSH EDX ; /Arg3 => [ARG.2] 0041B30A |. 68 59020000 |PUSH 259 ; |Arg2 = 259 0041B30F |. 68 58020000 |PUSH 258 ; |Arg1 = 258 0041B314 |. E8 65B6FEFF |CALL 0040697E ; \INSTALL.0040697E 0041B319 |. 83C4 0C |ADD ESP,0C 0041B31C |. 33C0 |XOR EAX,EAX 0041B31E |. E9 B0000000 |JMP 0041B3D3 0041B323 |> 8B75 F8 |MOV ESI,DWORD PTR SS:[LOCAL.2] ;ab hier wird es eigentlich interessant 0041B326 |. D1E6 |SHL ESI,1 ;verdoppele v2 0041B328 |. 8B45 D4 |MOV EAX,DWORD PTR SS:[LOCAL.11] 0041B32B |. 8A4C05 E0 |MOV CL,BYTE PTR SS:[EAX+EBP-20] ;holt hash[i] - also die i. Stelle aus dem Hash-Code 0041B32F |. 51 |PUSH ECX 0041B330 |. E8 A3000000 |CALL 0041B3D8 ;verwandelt String in Hex (übergabe "A"; Ausgabe 0x0A) 0041B335 |. 83C4 04 |ADD ESP,4 0041B338 |. 33F0 |XOR ESI,EAX ; ESI = v2 XOR string_to_hex( hash[i] ) 0041B33A |. 8B55 F8 |MOV EDX,DWORD PTR SS:[LOCAL.2] ; holt die alte summe von v2 und gibt sie in EDX 0041B33D |. 03D6 |ADD EDX,ESI ; neue Summe = ESI + EDX 0041B33F |. 8955 F8 |MOV DWORD PTR SS:[LOCAL.2],EDX ; neue summe in v2 abspeichern 0041B342 |.^ E9 5EFFFFFF \JMP 0041B2A5
An dieser Stelle ist nur der Schluss wichtig. Es wird in dieser Funktion eine Prüfsumme ermittlet. Hierbei wird unser Hash-Code zeichenweise eingelesen und jedes Zeichen mit der Variable-2 gexored. Die Variable-2 verdoppelt sich bei jedem neu eingelesenen Zeichen. Das neue Ergebnis wird dann anschließend wieder auf die alte Summe aufgerechnet, somit erhalten wir nun eine Prüfsumme aus unserem Hash-Code erhalten.
CPU Disasm Address Hex dump Command Comments 0041B33F |. 8955 F8 |MOV DWORD PTR SS:[LOCAL.2],EDX ; 0041B342 |.^ E9 5EFFFFFF \JMP 0041B2A5 ;alte schleife 0041B347 |> 8B45 F8 MOV EAX,DWORD PTR SS:[LOCAL.2] ;holt die Prüfsumme 0041B34A |. 25 FF000000 AND EAX,000000FF ;schneidet die Prüfsumme zu, sodass nur die beiden letzten Stellen übergeben werden Bsp: 0x23424 ==> 0x00024 0041B34F |. 8945 F8 MOV DWORD PTR SS:[LOCAL.2],EAX ;abspeichern der abgeschnittenen prüfsumme 0041B352 |. 8B4D F8 MOV ECX,DWORD PTR SS:[LOCAL.2] ;holt die abgeschnittene prüfsumme 0041B355 |. 3B4D F4 CMP ECX,DWORD PTR SS:[LOCAL.3] ;vergleicht die Prüfsumme mit der Prüfziffer 0041B358 |. 74 1A JE SHORT 0041B374 ;und wenn diese beiden gleich sind gehts weiter 0041B35A |. 8B55 0C MOV EDX,DWORD PTR SS:[ARG.2] 0041B35D |. 52 PUSH EDX ; /Arg3 => [ARG.2] 0041B35E |. 68 59020000 PUSH 259 ; |Arg2 = 259 0041B363 |. 68 58020000 PUSH 258 ; |Arg1 = 258 0041B368 |. E8 11B6FEFF CALL 0040697E ; \INSTALL.0040697E 0041B36D |. 83C4 0C ADD ESP,0C 0041B370 |. 33C0 XOR EAX,EAX 0041B372 |. EB 5F JMP SHORT 0041B3D3 0041B374 |> \8D45 DC LEA EAX,[LOCAL.9] 0041B377 |. 50 PUSH EAX ; /Arg4 => OFFSET LOCAL.9 0041B378 |. 8D4D FC LEA ECX,[LOCAL.1] ; | 0041B37B |. 51 PUSH ECX ; |Arg3 => OFFSET LOCAL.1 0041B37C |. 6A 00 PUSH 0 ; |Arg2 = 0 0041B37E |. 8B55 08 MOV EDX,DWORD PTR SS:[ARG.1] ; | 0041B381 |. 52 PUSH EDX ; |Arg1 => [ARG.1] 0041B382 |. E8 FD020000 CALL 0041B684 ; \INSTALL.0041B684 0041B387 |. 83C4 10 ADD ESP,10
Gut gut! Also wurde nun die Prüfziffer mit der Prüfsumme verglichen und wenn diese gleich sind haben wir einen gültigen CD-Code! … …
Man sollte sich halt nicht zu früh freuen, denn es wird jetzt noch verrückter. Jetzt kommt noch eine weitere Überprüfung des CD-Code. Die nachfolgende Funktion hat es noch einmal in sich. Sie besteht aus drei Teilen, aber kurz gesagt wird der Hash-Code noch einmal in einen neuen Hash umgewandelt.
Die Serial Prüfung – Tauschen, tauschen, tauschen…
OK eigentlich kann man sich den ersten Teil der Funktion sparen, denn er macht noch einmal genau dasselbe was wir gerade hinter uns haben. Er holt sich den Serial bildet daraus einen Hash und eine Prüfziffer. Dann bildet er aus dem Hash eine Prüfsumme und schaut ob die beiden letzten Stellen der Prüfsumme, der Prüfziffer entsprechen. Also auf zum zweiten Teil! Folgende Schleife wird nun eingeleitet:
CPU Disasm Address Hex dump Command Comments 0041B720 |. 8B45 FC MOV EAX,DWORD PTR SS:[LOCAL.1] ;Prüfsumme 0041B723 |. 3B45 F8 CMP EAX,DWORD PTR SS:[LOCAL.2] ;vergleich mit Prüfziffer 0041B726 |. 74 07 JE SHORT 0041B72F 0041B728 |. 33C0 XOR EAX,EAX 0041B72A |. E9 D9000000 JMP 0041B808 0041B72F |> C745 DC 0F000 MOV DWORD PTR SS:[LOCAL.9],0F ;Zähler Variable für die Schleife (die Schleife geht rückwärts 0xF - 0x0) 0041B736 |. EB 09 JMP SHORT 0041B741 0041B738 |> 8B4D DC /MOV ECX,DWORD PTR SS:[LOCAL.9] ; eine art tauschmechanismus 0041B73B |. 83E9 01 |SUB ECX,1 0041B73E |. 894D DC |MOV DWORD PTR SS:[LOCAL.9],ECX 0041B741 |> 837D DC 00 |CMP DWORD PTR SS:[LOCAL.9],0 ;ANFANG der SChleife, wenn Zähler 0 ist raus springen 0041B745 |. 7C 39 |JL SHORT 0041B780 0041B747 |. 8B45 DC |MOV EAX,DWORD PTR SS:[LOCAL.9] ;holt den aktuellen Wert des Zählers 0041B74A |. 6BC0 11 |IMUL EAX,EAX,11 ;multipliziert ihn mit 0x11 0041B74D |. 83C0 07 |ADD EAX,7 ;und addiert dann noch einmal 0x7 dazu 0041B750 |. 33D2 |XOR EDX,EDX 0041B752 |. B9 10000000 |MOV ECX,10 0041B757 |. F7F1 |DIV ECX ;hier wird die oben errechnete Summe durch 0x10 dividiert, dabei gibt er den Rest zurück 0041B759 |. 8955 D8 |MOV DWORD PTR SS:[LOCAL.10],EDX ;die Folgenden Befehle bewirken dass im Hash Stellen ausgetauscht werden (Stelle i mit der Stelle des Rest) 0041B75C |. 8B55 DC |MOV EDX,DWORD PTR SS:[LOCAL.9] 0041B75F |. 8A4415 E4 |MOV AL,BYTE PTR SS:[EDX+EBP-1C] 0041B763 |. 8845 D4 |MOV BYTE PTR SS:[LOCAL.11],AL 0041B766 |. 8B4D DC |MOV ECX,DWORD PTR SS:[LOCAL.9] 0041B769 |. 8B55 D8 |MOV EDX,DWORD PTR SS:[LOCAL.10] 0041B76C |. 8A4415 E4 |MOV AL,BYTE PTR SS:[EDX+EBP-1C] 0041B770 |. 88440D E4 |MOV BYTE PTR SS:[ECX+EBP-1C],AL 0041B774 |. 8B4D D8 |MOV ECX,DWORD PTR SS:[LOCAL.10] 0041B777 |. 8A55 D4 |MOV DL,BYTE PTR SS:[LOCAL.11] 0041B77A |. 88540D E4 |MOV BYTE PTR SS:[ECX+EBP-1C],DL 0041B77E |.^ EB B8 \JMP SHORT 0041B738 0041B780 |> C745 CC 4197A MOV DWORD PTR SS:[LOCAL.13],13AC9741 0041B787 |. C745 D0 0F000 MOV DWORD PTR SS:[LOCAL.12],0F 0041B78E |. EB 09 JMP SHORT 0041B799
OK. Das ist schon ziemlich verrückt. Die Schleife beginnt mit der letzten Stelle im Hash-Code. Im ersten Durchgang wird also die letzte Stelle mit der Stelle des errechneten Restwertes getauscht. Zum Beispiel wenn unser Restwert 6 beträgt tauscht die Funktion die letzte Stelle mit der 6. Stelle aus. Dann geht die Schleife weiter und die Vorletzte Stelle wird mit der Stelle des Restwertes ausgetauscht, dann die 3. letzte und so weiter, bis der komplette Hash abgearbeitet ist. Somit haben wir nun einen durchmischten Hash. Gehen wir nun über zum letzten Teil dieser riesigen Funktion.
Die Serial Prüfung – Crazy XOR
Da wir nun unseren Hash gemischt haben wird es Zeit aus ihm einen neuen Hash zu bilden.
CPU Disasm Address Hex dump Command Comments 0041B780 |> \C745 CC 4197A MOV DWORD PTR SS:[LOCAL.13],13AC9741 ;v13 = 0x13AC9741 0041B787 |. C745 D0 0F000 MOV DWORD PTR SS:[LOCAL.12],0F ;wieder ein Zähler - und wieder Rückwärtsschleife 0041B78E |. EB 09 JMP SHORT 0041B799 ;schleife beginnt 0041B790 |> 8B45 D0 /MOV EAX,DWORD PTR SS:[LOCAL.12] 0041B793 |. 83E8 01 |SUB EAX,1 0041B796 |. 8945 D0 |MOV DWORD PTR SS:[LOCAL.12],EAX 0041B799 |> 837D D0 00 |CMP DWORD PTR SS:[LOCAL.12],0 ;ANFANG der Schleife, wenn Zähler 0 erreicht raus springen 0041B79D |. 7C 1A |JL SHORT 0041B7B9 0041B79F |. 8D4D CC |LEA ECX,[LOCAL.13] 0041B7A2 |. 51 |PUSH ECX 0041B7A3 |. 8B55 D0 |MOV EDX,DWORD PTR SS:[LOCAL.12] 0041B7A6 |. 52 |PUSH EDX 0041B7A7 |. 8B45 D0 |MOV EAX,DWORD PTR SS:[LOCAL.12] 0041B7AA |. 8D4C05 E4 |LEA ECX,[EAX+EBP-1C] 0041B7AE |. 51 |PUSH ECX 0041B7AF |. E8 59000000 |CALL 0041B80D ;verrückte XOR Funktion 0041B7B4 |. 83C4 0C |ADD ESP,0C 0041B7B7 |.^ EB D7 \JMP SHORT 0041B790 0041B7B9 |> 6A 03 PUSH 3
Diese Schleife verarbeitet unseren durchmischten Hash-Code zu einem neuen Hash-Code. Es wird wieder am Ende des Hash-Code angefangen, zeichenweise einzulesen. Jedes Zeichen wird dann durch den Wert ersetzt, der von der XOR Funktion zurückgegeben wird. In dieser Funktion wird zum einen die Variable v13 übergeben und zum anderem der Zähler der Schleife, welcher unsere Stelle im Hash-Code repräsentiert. Schauen wir uns nun diese verrückte XOR Funktion an:
CPU Disasm Address Hex dump Command Comments 0041B80D /$ 55 PUSH EBP 0041B80E |. 8BEC MOV EBP,ESP 0041B810 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041B813 |. 0FBE08 MOVSX ECX,BYTE PTR DS:[EAX] ; Holt das Zeichen aus dem Hashcode (Arg1 ist der übergebene Zähler) 0041B816 |. 51 PUSH ECX ; /Arg1 0041B817 |. E8 09070200 CALL 0043BF25 ; \INSTALL.0043BF25 0041B81C |. 83C4 04 ADD ESP,4 0041B81F |. 8B55 08 MOV EDX,DWORD PTR SS:[ARG.1] 0041B822 |. 8802 MOV BYTE PTR DS:[EDX],AL 0041B824 |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041B827 |. 0FBE08 MOVSX ECX,BYTE PTR DS:[EAX] 0041B82A |. 83F9 37 CMP ECX,37 ; wenn Zeichen Zahl ist und kleiner oder gleich 7, dann kein Sprung 0041B82D |. 7F 23 JG SHORT 0041B852 0041B82F |. 8B55 10 MOV EDX,DWORD PTR SS:[ARG.3] 0041B832 |. 8B02 MOV EAX,DWORD PTR DS:[EDX] ; holt v13 0041B834 |. 83E0 07 AND EAX,00000007 ; v13 undiert 0x7 (AND-Verknuepfung) 0041B837 |. 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] 0041B83A |. 8A11 MOV DL,BYTE PTR DS:[ECX] 0041B83C |. 32D0 XOR DL,AL ; Zahl XOR mit (v13 & 0x07) 0041B83E |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041B841 |. 8810 MOV BYTE PTR DS:[EAX],DL 0041B843 |. 8B4D 10 MOV ECX,DWORD PTR SS:[ARG.3] 0041B846 |. 8B11 MOV EDX,DWORD PTR DS:[ECX] 0041B848 |. C1EA 03 SHR EDX,3 ; v13 drei mal durch 2 ist v13 0041B84B |. 8B45 10 MOV EAX,DWORD PTR SS:[ARG.3] 0041B84E |. 8910 MOV DWORD PTR DS:[EAX],EDX 0041B850 |. EB 1D JMP SHORT 0041B86F ; springe raus 0041B852 |> 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] ; wenn Zeichen > 7 ist, landet es hier 0041B855 |. 0FBE11 MOVSX EDX,BYTE PTR DS:[ECX] 0041B858 |. 83FA 41 CMP EDX,41 ; wenn Zeichen > 9 (Also irgendein Buchstabe, deswegen 0x41) 0041B85B |. 7D 12 JGE SHORT 0041B86F ; > 9 springe raus 0041B85D |. 8B45 0C MOV EAX,DWORD PTR SS:[ARG.2] ; aktuelle Zaehler 0041B860 |. 83E0 01 AND EAX,00000001 ; Zaehler undiert mit 0x1 (zaehler & 0x1) 0041B863 |. 8B4D 08 MOV ECX,DWORD PTR SS:[ARG.1] 0041B866 |. 8A11 MOV DL,BYTE PTR DS:[ECX] ; zahl XOR mit (zahl & 0x1) 0041B868 |. 32D0 XOR DL,AL 0041B86A |. 8B45 08 MOV EAX,DWORD PTR SS:[ARG.1] 0041B86D |. 8810 MOV BYTE PTR DS:[EAX],DL 0041B86F |> 5D POP EBP 0041B870 \. C3 RETN
Wie wir sehen hat die Funktion drei verschiedene Zweige. Der erste Zweig wird ausgelöst, wenn unser Zeichen kleiner oder gleich 7 ist. Der zweite Zweig wird bearbeitet, wenn unser Zeichen irgendein Buchstabe ist und der letzte Zweig wird aktiv, wenn das Zeichen eine 8 oder 9 ist. Gehen wir erst einmal den einfachsten Weg: Wenn das Zeichen ein Buchstabe ist, wird einfach der Buchstabe wieder zurückgegeben.
Nun zum zweiten Weg: Wenn unser Zeichen einer 8 oder 9 entspricht, wird der Zähler (die Stelle im aktuellen Hash-Code) mit 1 undiert und dieses Ergebnis wird dann mit unserem Zeichen (8 oder 9) gexored. Dieses Ergebnis ersetzt dann die alte Stelle im Hash-Code.
Kommen wir nun zum schwierigsten Zweig: Wenn unser Zeichen kleiner als 8 ist. Als erstes wird die Variable v13 geholt, welche den Inhalt 0x13AC9741 hat. Diese Zahl wird dann mit 7 undiert und dieses Ergebnis, aus der UND-Verknüpfung, wird mit dem Zeichen (0-7) gexored. Zum Schluss wird die Variable v13 drei Mal durch 2 geteilt, sodass beim nächsten Durchlauf v13 nur noch 1/8 entsprich. Das Ergebnis des XOR Vorgangs ersetzt natürlich wiederrum unser aktuelles Zeichen im Hash-Code.
Wie wir sehen werden die Zahlen auf verschiedene Weisen gexored. Nachdem die Funktion nun durchgelaufen ist, haben wir einen neuen Hash und dieser wird noch einmal überprüft. Die Überprüfung besteht daraus, dass wir uns die ersten beiden Stellen des neuen Hashes anschauen. Die erste Stelle muss eine 0 sein und die zweite Stelle muss 6 oder 7 entsprechen.
Wenn diese Bedingung erfüllt ist, haben wir einen gültigen Serial!!!11einself
KeyGen – No Way
Genau wie es die Überschrift schon sagt, ich habe keine Ahnung, wie ich anhand dieser vielen Schleifen und Hashes einen ordentlichen KeyGen programmieren soll. Ich müsste das Verfahren jetzt irgendwie umdrehen, aber ich finde einfach keinen Ansatz. Es ist schon ziemlich krank was Blizzard mit dem Serial/CD-Code so alles anstellt. Doch wie kann man nun am besten einen CD-Code herausfinden. Ein Weg wäre das klassische bruteforcing. Einfach jeden Serial durchprobieren. Hierfür muss man nur noch die ganzen Erkenntnisse in Code umwandeln. Ich habe hierfür die Überprüfung in C# implementiert:
public void brute_serial() { //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) { //Die Serial Prüfung - Hashing 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; } //Die Serial Prüfung - Prüfsumme 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; } //vergleich pruffert = pruffert & 0xff; if ((pruffert) == (prufsumme)) { //tauschen tauschen tauschen 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; } //crazy XOR int under = 0x13AC9741; for (int i = 15; i >=0; i--) { 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)) { //umwandeln in lesbaren serial 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"; //abspeichern StreamWriter myFile = new StreamWriter("./d2_serial.txt", true); myFile.Write(key); myFile.Close(); } } //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; } } //ausstieg wenn ZZZZ-ZZZZ-ZZZZ-ZZZZ if (cd_code[0] == 23 && cd_code[1] == 23 &&cd_code[2] == 23 &&cd_code[3] == 23 &&cd_code[4] == 23 &&cd_code[5] == 23 &&cd_code[6] == 23 &&cd_code[7] == 23 &&cd_code[8] == 23 &&cd_code[9] == 23 &&cd_code[10] == 23 &&cd_code[11] == 23 &&cd_code[12] == 23 &&cd_code[13] == 23 &&cd_code[14] == 23 &&cd_code[15] == 23){ x=false; } }
Fazit – Crazy Shit
Also, was können wir festhalten: Wir wandeln den CD-Code in einen Hash und eine Prüfziffer um, dann wird aus dem Hash eine Prüfsumme ermittelt, wobei die beiden letzten Stellen der Prüfsumme, der Prüfziffer entsprechen. Danach wird der Hash durchgemixt und durch ein paar verrückte XOR-Funktionen gejagt, nur damit ein neuer Hash entsteht. Dieser neue Hash muss am Anfang „06“ oder „07“ sein, damit der CD-Code als gültig gilt. Wenn man einmal den ersten Serial gefunden hat, dann werden in Minuten tausende von Serials generiert. Meiner Meinung nach ist dies ein schlechter Weg um an die Serials zu kommen. Da mir dieser Weg überhaupt nicht gefällt, wollte ich doch einmal wissen, wie andere den KeyGen programmiert haben. Deswegen habe ich mir einen KeyGen heruntergeladen und habe diesen ebenfalls analysiert. PEid gab mir an, dass dieser KeyGen in Delphi gecodet wurde und dank eines Decompiler konnte man sofort sehen, wie es funktionierte. Es waren alle Serials vordefiniert in einem Array! Das finde ich dann doch eher arm. Effektiv, aber arm. Da finde ich mein Lösungansatz doch schon eleganter und brutaler zugleich :D
Ich hoffe es hat euch auch dieses Mal gefallen und ihr habt den Ansatz verstanden, wie Diablo 2 den CD-Code überprüft. Ich habe die Version von BestSeller Series gehabt. Wenn euch das Spiel gefällt dann kauft es euch ;)
Wollen wir mal schauen welches Programm als nächstes gegen die virtuelle Wand geworfen wird, um dann seine Innereien zu durchforsten!
Bis zum nächsten Mal,
TheVamp
Hallo,
zum Thema Keygen no way- ich habe 2009 in delphi pascal einen keygen geschrieben. Ich habe den Keygenerator Schritt für Schritt erstellt. Serial eingegeben in der Setup.exe und im Speicher mit ollydbg und trace / step geschaut was die setup.exe mit der serial anstellt.
Meine LookupTable:
lookuptable: array [0..255] of byte =
(
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$00,$FF,$01,$FF,$02,$03,$04,$05,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$06,$07,$08,$09,$0A,$0B,$0C,$FF,$0D,$0E,$FF,$0F,$10,$FF,
$11,$FF,$12,$FF,$13,$FF,$14,$15,$16,$FF,$17,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$06,$07,$08,$09,$0A,$0B,$0C,$FF,$0D,$0E,$FF,$0F,$10,$FF,
$11,$FF,$12,$FF,$13,$FF,$14,$15,$16,$FF,$17,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,
$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
);
Valid Chars in serial :
ValidChars: array [0..23] of char =
(
‚B‘,’C‘,’D‘,’E‘,’F‘,’G‘,
‚H‘,’J‘,’K‘,’M‘,’N‘,’P‘,
‚R‘,’T‘,’V‘,’W‘,’X‘,’Z‘,
‚2‘,’4′,’6′,’7′,’8′,’9′
);
Key test funktion:
function Blizzardkey(key: tserial): bool;
var
eax, ebx, ebx2 : byte;
temp, ldpkey: array [0..15] of byte;
i, esi, edi, imagicnumber :integer;
begin
imagicnumber:= $13AC9741;
i := 1;
EBX := 1;
EBX2 := 0;
EDI := 3;
Inc(tryedBkeys);
Form1.Memo2.Clear;
while (i= $0100 then
begin
ESI := ESI – 256;
EBX2 := EBX2 or EBX;
end;
EAX := ESI shr 4;
EAX := EAX and 15;
If EAX >= 10 then EAX := EAX + 55 else EAX := EAX + 48;
ESI := ESI and 15;
Temp[i-1]:= EAX;
EAX:= ESI;
EAX := EAX and 15;
IF EAX < 10 then EAX := EAX + 48 else EAX := EAX + 55;
Temp[i]:=EAX;
EBX:= EBX shl 1;
//
inc( i, 2);
end;
// Ende Schleife 1
Move(Temp[0], ldpkey[0], SizeOf(Temp));
// Begin Schleife 2
for i:=0 to 15 do
begin
IF Temp[i] <= 57 then Temp[i]:= Temp[i] – 48 else Temp[i]:= Temp[i] – 55;
Temp[i] := Temp[i] xor ( EDI + EDI);
EDI := EDI + Temp[i];
end;
// Ende Schleife 2
// Code check
EDI := EDI and 255;
IF EDI EBX2 then Result:= False else
begin
Inc(tryedD2keys);
Form1.Edit2.Text:=Copy(string(key),1,16);
Form1.Label3.Caption:= IntToStr(tryedBkeys)+‘ keys tryed, valid Blizzard key found.‘;
//Result:=True;
for i:= 15 downto 0 do
begin
EDI:=ldpkey[i];
if i< 9 then EAX:=ldpkey[i+7] else EAX:=ldpkey[i-9];
ldpkey[i]:= eax;
if i< 9 then ldpkey[i+7]:=edi else ldpkey[i-9]:= edi;
end;
for i:= 15 downto 0 do
begin
//if ldpkey[i] $37 then
begin
if ldpkey[i] >= $41 then // never used
else ldpkey[i] := (i and $01) xor ldpkey[i];
end
else
begin
edi:= lo(imagicnumber);
edi:= edi and $07;
edi:= edi xor ldpkey[i];
imagicnumber:= imagicnumber shr 3;
ldpkey[i] := edi;
end;
end;
IF
(ldpkey[0] = $30) and (ldpkey[1] =$36)
then
begin
Result:=true;
Form1.Edit4.Text:=’0′;
for i:=0 to 15 do Form1.Edit4.Text:=IntToStr(StrToInt(Form1.Edit4.Text) +ord(ldpkey[i]));
Form1.Edit3.Text:=Copy(string(key),1,16);
Form1.Label4.Caption:= IntToStr(tryedD2keys)+‘ subkeys tryed, valid Diablo2 key found.‘;
// Anzeige original
Form1.Memo2.Lines[0]:=’0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15’+#13#10;
Form1.Memo2.Lines[1]:=’^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^’+#13#10;
for i:=0 to 15 do Form1.Memo2.Lines[2]:=Form1.Memo2.Lines[2]+Char(key[i])+‘ ‚;
Form1.Memo2.Lines[2]:= Form1.Memo2.Lines[2]+#13#10;
for i:=0 to 15 do Form1.Memo2.Lines[3]:=Form1.Memo2.Lines[3]+InttoHex(ldpkey[i],1)+‘ ‚;
Form1.Memo2.Lines[3]:= Form1.Memo2.Lines[3]+#13#10;
// Anzeige verdreht 1:1
Form1.Memo2.Lines[4]:=‘———————————————–‚+#13#10;
Form1.Memo2.Lines[5]:=’Gedreht 1:1’+#13#10;
Form1.Memo2.Lines[6]:=’0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15’+#13#10;
Form1.Memo2.Lines[7]:=’^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^’+#13#10;
for i:=0 to 15 do Form1.Memo2.Lines[8]:=Form1.Memo2.Lines[8]+Char(key[i])+‘ ‚;
Form1.Memo2.Lines[8]:= Form1.Memo2.Lines[8]+#13#10;
for i:=2 to 6 do Form1.Memo2.Lines[9]:= Form1.Memo2.Lines[9]+IntToHex(ldpkey[i],1)+‘ ‚;
for i:=0 to 1 do Form1.Memo2.Lines[9]:= Form1.Memo2.Lines[9]+IntToHex(ldpkey[i],1)+‘ ‚;
for i:=14 to 15 do Form1.Memo2.Lines[9]:= Form1.Memo2.Lines[9]+IntToHex(ldpkey[i],1)+‘ ‚;
for i:=7 to 13 do Form1.Memo2.Lines[9]:= Form1.Memo2.Lines[9]+IntTohex(ldpkey[i],1)+‘ ‚;
end else
begin
Form1.Label4.Caption:= IntToStr(tryedD2keys)+‘ subkeys tryed…‘;
Form1.Refresh;
Form1.Edit3.Text:=Copy(string(key),1,16);
Result:= False;
end;
end;
end;
Sieht ein wenig durcheinander aus, funktioniert aber. Wer interesse hat, dem schick ich das. Die keys funktionieren auch in der setup.exe, auf battle.net aber nicht. Entweder hat blizzard extra listen in der die serials stehen, nachdem sie die funktion durchlaufen haben, oder die serial wird noch einer weiteren überprüfung unterzogen.
Ich glaube aber eher ersteres. 20.000 Serials werden durch die funktion erstellt, in einer datei abgespeichert bei blizzard, und gehen dann auf den aufkleber in der cd hülle.
Ganz interessant finde ich ja auch den Downloader, der eigentlich nur ein glorifizierter Torrent-Client ist, also auch komplett ohne Authentifikation von Blizzard funktioniert.Theoretisch, denn wenn man zu lang wartet nach dem Download, tut er einfach gar nichts mehr: http://i.imgur.com/pGBwXcG.png
Laesst sich das irgendwie rauspatchen um den Download trotzdem zu starten?
Lesen darf ich ja. Bei deinem c# Quelltext kann man bei der Berechnung manche Sachen anders machen. Alleine die Zeile mit
cd_hash[2i]
Ist laut den obigen asm Quelltext der ganzzahlige Anteil bei der Division durch 16. Der erste Hash ist nichts weiteres als die Lösung einfacher Kongruenzgleichungen in einem Array.
Kann mir nicht vorstellen, da man einen serial algo schreibt der nur durch bruteforce erzeugte Serials annimmt ;-)
Ich benutze OllyDbg 2.01d, also die Alpha Version ;)
C#-Code ist bei mir wie immer, Quick & Dirty :D
Wow, Du hast meinen vollen Respekt! :D Ich hätte bereits nach dem ersten Hashing aufgegeben… Welche OllyDGB Version hast Du? Ist diese irgendwie modifiziert (mit LocalsXY und so)?
Achja, und über den C#-Code verliere ich jetzt kein Wort :D Allein die Zeile wie der Key „lesbar“ gemacht wird… (SCHLEIFE :D)