Diablo II – Expansion: Let’s CrackIt

Heute wird es Zeit für den nächsten Artikel und diesmal nehmen wir uns die Erweiterung von Diablo II vor. Es wird sich wieder um den Serial drehen. Ich denke das es ein leichtes wird, da sich wahrscheinlich die Installationsroutine ähnlich wie in Diablo II verhält. Einzig der Algorithmus wird wohl angepasst sein und das ein anderes ASCII-Memory-Sieb verwendet wird. Also auf geht es in eine neue Runde Let’s CrackIt!!!

Vorbereitung

Wieder einmal heißt es eine ISO von der CD erstellen, damit das CD-Laufwerk nicht die ganze Zeit rum rattert. Nach dem die ISO fertig erstellt wurde, wird sich wieder die Dateien auf der CD angeschaut und es befindet sich eine SETUP.EXE und eine INSTALL.EXE auf der CD. Da die INSTALL.EXE größer ist und die SETUP.EXE wahrscheinlich nur auf die INSTALL.EXE verweist, widme ich mich gleich der INSTALL.EXE. Als nächstes wird der PE-Header der Datei analysiert und wir bekommen folgendes Resultat: „Microsoft Visual C++ 6.0“. Sehr gut, somit ist die Datei nicht gepackt und wir können die INSTALL.EXE in OllyDbg öffnen. Ich benutze hierfür OllyDbg 2.01d, da die neue OllyDbg Version schon einige PlugIns mit übernommen hat, die man sonst für OllyDbg 1.10 installieren müsste.

Alt Bekanntes

Ich fange wieder einmal Blind an, ich habe Extra nicht die alten BreakPoints und Kommentare geladen. Hätte ich dies gemacht, würde man sofort alles sehen, wo sich die gesuchten Funktionen befinden (:D). Also werden zum Anfang wieder die Intermodularen Calls durchsucht und schnell findet man den Einstiegspunkt. Die erste Funktion die mir dann entgegenspringt, ist die Konkatinierungsfunktion der einzelnen Felder für die Serialnummer. Sie ist genauso wie in Diablo II aufgebaut.

CPU Disasm
 Address   Hex dump          Command                                  Comments
 0041EBEB  |. /74 15         JE SHORT 0041EC02
 0041EBED  |. |6A 01         PUSH 1
 0041EBEF  |. |6A 11         PUSH 11
 0041EBF1  |. |68 B0464601   PUSH OFFSET 014646B0                     ; ASCII "4444444444444444"
 0041EBF6  |. |8B4D 08       MOV ECX,DWORD PTR SS:[ARG.1]
 0041EBF9  |. |51            PUSH ECX
 0041EBFA  |. |E8 E1050000   CALL 0041F1E0                            ; Konkatinierungsfunktion fuer die Serialnummer
 0041EBFF  |. |83C4 10       ADD ESP,10
 0041EC02  |> \833D 84464601 CMP DWORD PTR DS:[1464684],0

Weiter unten im Code stoße ich dann schon auf die Überprüfungsroutine. Hier zeige ich kurz den Einstiegspunkt:

CPU Disasm
 Address   Hex dump          Command                                  Comments
 0041EC49  |.  6A 01         PUSH 1
 0041EC4B  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
 0041EC4E  |.  50            PUSH EAX
 0041EC4F  |.  68 B0464601   PUSH OFFSET 014646B0                     ; ASCII "4444444444444444"
 0041EC54  |.  E8 E7010000   CALL 0041EE40                            ; Ueberpruefung der Serial
 0041EC59  |.  83C4 0C       ADD ESP,0C

Auf die Länge kommt es an

Was ist das wichtigste am Serial. Genau die Länge ;)

CPU Disasm
 Address   Hex dump          Command                                  Comments
 0041EE72  |> \8B55 08       MOV EDX,DWORD PTR SS:[ARG.1]             ; ASCII "4444444444444444"
 0041EE75  |.  52            PUSH EDX                                 ; /String => [ARG.1]
 0041EE76  |.  FF15 88D14400 CALL DWORD PTR DS:[<&KERNEL32.lstrlenA>] ; \KERNEL32.lstrlenA
 0041EE7C  |.  83F8 10       CMP EAX,10                               ; Serial muss 0x10 (16 dezimal) lang sein
 0041EE7F  |.  74 1D         JE SHORT 0041EE9E                        ;
 0041EE81  |.  8B45 0C       MOV EAX,DWORD PTR SS:[ARG.2]             ; Fehlermeldungsfunktion
 0041EE84  |.  50            PUSH EAX                                 ; /Arg3 => [ARG.2]
 0041EE85  |.  68 5A020000   PUSH 25A                                 ; |Arg2 = 25A
 0041EE8A  |.  68 58020000   PUSH 258                                 ; |Arg1 = 258
 0041EE8F  |.  E8 DC85FEFF   CALL 00407470                            ; \INSTALL.00407470
 0041EE94  |.  83C4 0C       ADD ESP,0C
 0041EE97  |.  33C0          XOR EAX,EAX
 0041EE99  |.  E9 A1010000   JMP 0041F03F

Gut die Längenüberprüfung ist nun nicht so spannend.

Das Sieb und die Löcher!

Nach der Überprüfung geht es nun darum dass überprüft werden muss, ob auch alle gültigen Zeichen verwendet werden.

CPU Disasm
 Address   Hex dump          Command                         Comments
 0041EEC1  |.  8D4D E0       LEA ECX,[LOCAL.8]
 0041EEC4  |.  51            PUSH ECX
 0041EEC5  |.  E8 B6010000   CALL 0041F080                   ; Memory Sieb
 0041EECA  |.  83C4 04       ADD ESP,4
 0041EECD  |.  85C0          TEST EAX,EAX
 0041EECF  |.  75 1D         JNE SHORT 0041EEEE
 0041EED1  |.  8B55 0C       MOV EDX,DWORD PTR SS:[ARG.2]    ;Fehlermeldung
 0041EED4  |.  52            PUSH EDX                        ; /Arg3 => [ARG.2]
 0041EED5  |.  68 59020000   PUSH 259                        ; |Arg2 = 259
 0041EEDA  |.  68 58020000   PUSH 258                        ; |Arg1 = 258
 0041EEDF  |.  E8 8C85FEFF   CALL 00407470                   ; \INSTALL.00407470
 0041EEE4  |.  83C4 0C       ADD ESP,0C
 0041EEE7  |.  33C0          XOR EAX,EAX
 0041EEE9  |.  E9 51010000   JMP 0041F03F                    ;Sprung zum Funktionsende

 

CPU Disasm
 Address   Hex dump          Command                               Comments
 0041F080  /$  55            PUSH EBP                              ; Memory Sieb
 0041F081  |.  8BEC          MOV EBP,ESP
 0041F083  |.  51            PUSH ECX
 0041F084  |>  8B45 08       /MOV EAX,DWORD PTR SS:[EBP+8]
 0041F087  |.  0FBE08        |MOVSX ECX,BYTE PTR DS:[EAX]
 0041F08A  |.  85C9          |TEST ECX,ECX
 0041F08C  |.  74 28         |JE SHORT 0041F0B6
 0041F08E  |.  8B55 08       |MOV EDX,DWORD PTR SS:[EBP+8]
 0041F091  |.  8A02          |MOV AL,BYTE PTR DS:[EDX]
 0041F093  |.  8845 FF       |MOV BYTE PTR SS:[EBP-1],AL
 0041F096  |.  8A4D FF       |MOV CL,BYTE PTR SS:[EBP-1]
 0041F099  |.  51            |PUSH ECX                             ; /Arg1
 0041F09A  |.  8B55 08       |MOV EDX,DWORD PTR SS:[EBP+8]         ; |
 0041F09D  |.  83C2 01       |ADD EDX,1                            ; |
 0041F0A0  |.  8955 08       |MOV DWORD PTR SS:[EBP+8],EDX         ; |
 0041F0A3  |.  E8 18000000   |CALL 0041F0C0                        ; \get_number
 0041F0A8  |.  83C4 04       |ADD ESP,4
 0041F0AB  |.  83F8 17       |CMP EAX,17
 0041F0AE  |.^ 76 04         |JBE SHORT 0041F0B4
 0041F0B0  |.  33C0          |XOR EAX,EAX
 0041F0B2  |.  EB 07         |JMP SHORT 0041F0BB
 0041F0B4  |>^ EB CE         \JMP SHORT 0041F084
 0041F0B6  |>  B8 01000000   MOV EAX,1
 0041F0BB  |>  8BE5          MOV ESP,EBP
 0041F0BD  |.  5D            POP EBP
 0041F0BE  \.  C3            RETN
 0041F0BF      CC            INT3
 0041F0C0  /$  55            PUSH EBP                              ; get_number(guessed Arg1)
 0041F0C1  |.  8BEC          MOV EBP,ESP
 0041F0C3  |.  8B45 08       MOV EAX,DWORD PTR SS:[ARG.1]
 0041F0C6  |.  25 FF000000   AND EAX,000000FF
 0041F0CB  |.  0FBE80 98D744 MOVSX EAX,BYTE PTR DS:[EAX+44D798]    ; an der Speicheradresse 0x44D798 befindet sich das Memorysieb
 0041F0D2  |.  5D            POP EBP
 0041F0D3  \.  C3            RETN

 

CPU Dump
 Address   Hex dump                                         ASCII
 0044D7C8  FF FF 00 FF|01 FF 02 03|04 05 FF FF|FF FF FF FF| ¦¦2¦4¦6789¦¦¦¦¦¦
 0044D7D8  FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ¦¦BCDEFGH¦JK¦MN¦
 0044D7E8  11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| P¦R¦T¦VWX¦Z¦¦¦¦¦
 0044D7F8  FF FF 06 07|08 09 0A 0B|0C FF 0D 0E|FF 0F 10 FF| ¦¦bcdefgh¦jk¦mn¦
 0044D808  11 FF 12 FF|13 FF 14 15|16 FF 17 FF|FF FF FF FF| p¦r¦t¦vwx¦z¦¦¦¦¦

Das Sieb wurde gar nicht angepasst, hoffentlich werden die Hashing-Routinen interessanter. Noch einmal kurz zur Erläuterung des Memory-Sieb: Die Funktion gibt den ASCII-Wert an die Funktion get_number(arg1) weiter und schaut dann im Arbeitsspeicher an eine bestimmte Stelle (0x44D798 + ASCII-Wert). Steht im Arbeitsspeicher an dieser Stelle der Wert 0xFF wird eine Fehlermeldung ausgelöst.

Same Hashing all the Time

An dieser Stelle breche ich die weitere Ausführung ab, weil es vergeudete Zeit ist. Gut was hab ich erwartet, Blizzard will das Rad nicht neu erfinden. Ich habe mir nun alle Hashing Routinen angeschaut und mit dem aus Diablo II verglichen! Es ist genauso wie ich gedacht hab, es funktioniert genauso. Blizzard hat sich kaum die Mühe gemacht, das Memory-Sieb ein wenig anzupassen oder vielleicht das Hashing ein wenig abzuändern. Die einzige Änderung die Ich gesehen habe ist, dass bei Diablo II die Crazy-XOR-Funktion aus 2 Funktionen bestand. Diese wurden nun in eine Funktion implementiert. Der zweite Unterschied ist das beim letzten genrierten Hash, der Hash nicht mehr mit 06 oder 07 anfangen muss, sondern nun mit 0A oder 0C beginnen muss. Somit können wir auch den alten Bruteforce-Code nehmen und einfach die eine Abfrage abändern und fertig.

Bruteforcing the World

Mir ist bereits ein kleiner Einfall gekommen, um das Bruteforcing von Serialcodes zu beschleunigen. Da es viel zu aufwendig ist, von Anfang bis zum Ende alles durchzuprobieren, schlagen wir den Serial mit seinen eigenen Waffen: dem Zufall. Als erstes wird ein zufälliger Serial generiert und ab diesem Serial werden dann die nächsten 1000 Stellen durchprobiert. Kommt unter diesen 1000 Serials kein gültiger Serial, wird einfach ein neuer zufälliger Serial erzeugt und wieder die nächsten 1000 Stellen weiter ausprobiert. So bekommt man im Worst-Case-Szenario in 30 Min an einen einen Serial. Random Bruteforce hat doch schon was lustigen an sich ^.^

public void keygen()
{
	//Startbedingungen und Variablen
	Random r;
	//int[] int_key_array = {      0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23 };
	string[] string_key_array = { "2", "4", "6", "7", "8", "9", "B", "C", "D", "E", "F", "G", "H", "J", "K", "M", "N", "P", "R", "T", "V", "W", "X", "Z" };
	Int64 number = 0;
	int[] cd_code = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	int[] cd_hash = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	int[] cd_hash2 = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
	bool x = true;

	while (x)
	{
		//Zufälligen Key generieren
		r = new Random();
		for (int i = 0; i < cd_code.Length; i++)
		{
			cd_code[i] = r.Next(0, 23);
		}

		//1000 Keys ausprobieren
		for (int xxx = 0; xxx < 1000; xxx++)
		{
			//Multithreading --> bei gleichzeitigem Zugriff = Absturz
			lock (this)
			{
				aktuell.Text = string_key_array[cd_code[0]] + string_key_array[cd_code[1]] + string_key_array[cd_code[2]] + string_key_array[cd_code[3]] + "-" + string_key_array[cd_code[4]] + string_key_array[cd_code[5]] + string_key_array[cd_code[6]] + string_key_array[cd_code[7]] + "-" + string_key_array[cd_code[8]] + string_key_array[cd_code[9]] + string_key_array[cd_code[10]] + string_key_array[cd_code[11]] + "-" + string_key_array[cd_code[12]] + string_key_array[cd_code[13]] + string_key_array[cd_code[14]] + string_key_array[cd_code[15]];
			}

			//PART1
			Int64 prufcounter = 0x1;
			Int64 prufsumme = 0x0;
			for (int i = 0; i < 8; i++)
			{
				int buffer = cd_code[i * 2] * 24 + cd_code[i * 2 + 1];

				if (buffer >= 256)
				{
					prufsumme = prufsumme | prufcounter;
					buffer = buffer - 256;
				}

				cd_hash[i * 2] = (buffer / 2 / 2 / 2 / 2) % 0x10;
				cd_hash[i * 2 + 1] = buffer % 0x10;

				prufcounter = prufcounter * 2;
			}

			//PART2
			Int64 pruffert = 0x03;
			Int64 pruffert2 = 0x0;
			for (int i = 0; i < cd_code.Length; i++)
			{
				pruffert2 = pruffert * 0x2;
				pruffert2 = pruffert2 ^ cd_hash[i];
				pruffert = pruffert + pruffert2;

			}

			//vergeleich
			pruffert = pruffert & 0xff;
			if ((pruffert) == (prufsumme))
			{
				//tauschcrypto
				for (int i = 0xF; i >= 0; i--)
				{
					int buffer_c = (i * 0x11) + 0x7;
					int stelle = buffer_c % 0x10;

					int buff = cd_hash[stelle];
					cd_hash[stelle] = cd_hash[i];
					cd_hash[i] = buff;
				}

				int under = 0x13AC9741;
				for (int i = 15; i >=0; i--)
				{
					if ( cd_hash[i] <=  7)
					{
						int xorer = under & 0x7;
						cd_hash[i] = xorer ^ cd_hash[i];
						under = under / 2 / 2 / 2;
					}
					else if (cd_hash[i] > 9)
					{
					}else{
						int xorer = i & 0x1;
						cd_hash[i] = xorer ^ cd_hash[i];
					}
				}

				//if (cd_hash[0] == 0 && (cd_hash[1] == 6 || cd_hash[1] == 7))//Diablo II
				if (cd_hash[0] == 0 && (cd_hash[1] == 10 || cd_hash[1] == 12))//Diablo II - Expansion
				{
					//Q&D
					string key = string_key_array[cd_code[0]] + string_key_array[cd_code[1]] + string_key_array[cd_code[2]] + string_key_array[cd_code[3]] + "-" +
								 string_key_array[cd_code[4]] + string_key_array[cd_code[5]] + string_key_array[cd_code[6]] + string_key_array[cd_code[7]] + "-" +
								 string_key_array[cd_code[8]] + string_key_array[cd_code[9]] + string_key_array[cd_code[10]] + string_key_array[cd_code[11]] + "-" +
								 string_key_array[cd_code[12]] + string_key_array[cd_code[13]] + string_key_array[cd_code[14]] + string_key_array[cd_code[15]] + "\n";
					//Ausgabe auf der GUI
					txt_brute.Text = key + txt_brute.Text;
					number++;
					lbl_key_number.Text = number.ToString();
					//Ressource für andere Threads blocken
					lock (this)
					{
						StreamWriter myFile = new StreamWriter("./d2_serial.txt", true);
						myFile.Write(key);
						myFile.Close();
					}
					xxx = 0;
				}
			}

			//hochzählen des keys
			for (int i = 15; i > 0; i--)
			{
				if (i == 15)
				{
					cd_code[i]++;
				}
				if (cd_code[i] >= 24)
				{
					cd_code[i - 1]++;
					cd_code[i] = 0;
				}
			}
		}
	}
}

Fazit

Für die genaue Funktionsweise des Serial verweise ich an das Diablo II – Let’s CrackIt. Mich ärgert es immer noch, das Blizzard sich nicht mehr einfallen lassen hat und nur 2 Zahlen geändert hat. Egal! Falls ihr Anregungen oder Ideen für zukünftige Programme, Spiele etc. habt, schreibt es in die Kommentare. Bis zum nächsten CrackIt, was hoffentlich nicht so ein Armutszeugnis wird ;)

Greetz TheVamp

Diablo II KeyBruteforcer - Source (5050 Downloads)

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.