Diablo II – Let’s CrackIt

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

Diablo II KeyBruteforcer - Source (296 Downloads)

5 thoughts on “Diablo II – Let’s CrackIt

  1. 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.

  2. 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?

  3. 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 ;-)

  4. 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)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.