Keygenning diablo2oo2's crackme 10

Overview

The following tutorial will document how to program a keygenerator for diablo2oo2’s tenth crackme.

Functions, some addresses and variables are labeled to make the tutorial easier to follow.

The crackme can be found here.

Reverse engineering the crackme.

Load up the executable in x32dbg. Place breakpoints at the following code:

1.png

The “verify_serial” function takes the username, and entered serial and then returns a value depending if its valid or not. Tracing further into the “verify_serial” function yields the following:

1.png

The serial check routine is as follows:

  • It checks if a valid username is entered.
  • MD5 constants for a MD5 check routine are initialized:

md5consts.png

  • The username has the string “-diablo2oo2” concatenated to it.
  • This “username-diablo2oo2” string is then MD5-hashed. The hash isn’t modified in any way.
  • A buffer (I’m gonna name it “buf1”) is generated. It is first generated by the following pseudocode
for (int i = 0; i < 16; i++) buf1[i] = i;
  • “buf1” is then further modified using data from the resulting MD5 hash from the previous step. The MD5 hash is also modified.

1.png

for (int k = 0; k < 16; k++)
	{
		for (int i = 0; i < 16; i++)
		{
			Register EAX, EDX, EBX;
			EAX.ex = md5_hash[i];
			EDX = EAX;
			EAX.b.lo &= 0xF;
			EDX.b.lo >>= 4;
			EBX.b.lo = buf1[EAX.ex];
			buf1[EAX.ex] = buf1[EDX.ex];
			buf1[EDX.ex] = EBX.b.lo;
			md5_hash[i] += EBX.b.lo;
			md5_hash[i] ^= 0x17;
		}
	}
  • The entered serial is decoded from base64-format to its original byte format

1.png

  • “buf2” is then made from “buf1”

1.png

for (int i = 0, k = 0; i < 8; i++, k += 2)
	{
		int j = buf1[k] << 4;
		int l = buf1[k + 1] & 0x0F;
		buf2[i] = j+l;
	}

  • the decoded base64 buffer is then XORed using MMX assembly, using “buf2” as a mask.

1.png

int64_t* base64_ptr = b64encstr;
	__m128i buf2_mask_mmx = _mm_loadl_epi64(buf2);
	for (int i = 0; i < 16; i++)
	{
	__m128i var1_mmx = _mm_loadl_epi64(base64_ptr);
	var1_mmx= _mm_xor_si128(buf2_mask_mmx, var1_mmx);
	_mm_storeu_si64(base64_ptr, var1_mmx);
	base64_ptr++;
	}
  • a third buffer is made using the XORed data buffer

1.png

  • This third buffer has the diagonal portions of it checked against “buf1”. This means that the main buffer to be checked is a 16x16 matrix.

1.png

  • The buffer is checked so each line in the matrix matches a hardcoded MD5. This is very similar to how a soduku puzzle works, in that each line must match have no number repeating twice. Only this time, the match checks are done using hardcoded MD5 hashes.

1.png

Thus to solve the crackme:

1) Concatenate a name string with “-diablo2oo2”

2) Hash that string with MD5 (unmodified)

3) Modify the resulting MD5 hash exactly how the crackme does.

4) Create “buf1” using info from the previous step

5) Build “buf2” using buf1, exactly how the crackme does.

6) Create a 16x16 matrix, filling the diagonal from 0x0, to 16x16, with info from buf1

7) Using a soduku solver algorithm, solve the incomplete matrix to form a complete soduku puzzle.

8) Make a buffer using the following:

	BYTE b64encstr[128] = { 0 };
		unsigned char* in = soduku;
		unsigned char* out = b64encstr;
		for (int i = 0,  k=0; i < sizeof(b64encstr); i++,k+=2)
		{
			int j = in[k ] << 4;
			int l = in[k +1] & 0x0F;
			out[i] = j + l;
		}

9) XOR that buffer using buf2. This can be done using the following MMX intrinsics:

int64_t* base64_ptr = b64encstr;
	__m128i buf2_mask_mmx = _mm_loadl_epi64(buf2);
	for (int i = 0; i < 16; i++)
	{
	__m128i var1_mmx = _mm_loadl_epi64(base64_ptr);
	var1_mmx= _mm_xor_si128(buf2_mask_mmx, var1_mmx);
	_mm_storeu_si64(base64_ptr, var1_mmx);
	base64_ptr++;
	}

10) Base64-encode the resulting data.

Keygen source code.

Source code to the keygen is here.

MSVC2019 is used to compile. It should compile out of the box.

Feel free to use the template for your own crackme keygens.

Written on December 11, 2019