czCrackme09 [czDrillard][GenoCide][

by

{Cronos}

Intro

I've been busy recently :) and was looking for a challenge, so I took this crackme on since it appeared to have been packed by UPX (messages in the exe) but procdump wouldnt unpack it. Ah, I thought it is time to do some manual unpacking but woe betide, I disassembled it and found it wasnt packed. Hehe, but then it's a bit lame to use someones packer unless you have made modifications to it, or it is your own packer :). Still, nice trick to put people off anyway.

Target

The crackme asks for a name, valid serial and keygen. The crackme appears to be pretty straightforward although the algorithm is uses doesnt look reversible to me, so we will take a brute force approach to serial generation for it in the keygen. The dialog box it presents looks a bit funny on my screen, which must either due to my screen size, or some of its properties. One of the buttons is obscured by a bitmap but it's still usable.

Analysis

OK, we are presented with a dialog box with two text boxes into which we can type a name and a serial, and a checkit button. So we need to put in two things and get the correct registration message. The disassembly of the program shows some entry code, easily traced through and a standard dialog box routine. So I'm just going to start within this dialog routine, at the point where the text inputs are read into strings, and take it from there. Following is the disassembly of the relevant part of the program.
;****************************************************************************************
; This is the main part of the dialog which deals with processing the two input strings
; and checking whether they make a valid serial-key pair or not.
;****************************************************************************************
1000:00401340 6a40                 push   40h
1000:00401342 6830314000           push   offset string1
1000:00401347 ff35c3304000         push   dword ptr [4030c3h]
1000:0040134d e8a6030000           call   _GetWindowTextA		; first get both strings
1000:00401352 83f804               cmp    eax, 04h			; string1=name
1000:00401355 0f8ee9000000         jle    failed
1000:0040135b 6a40                 push   40h
1000:0040135d 6870314000           push   offset string2
1000:00401362 68b90b0000           push   bb9h
1000:00401367 ff7508               push   dword ptr [ebp+08h]
1000:0040136a e877030000           call   _GetDlgItemTextA		; from the dialog box
1000:0040136f 83f804               cmp    eax, 04h			; string2=serial
1000:00401372 0f8ecc000000         jle    failed
1000:00401378 a3bf304000           mov    [string2_length], eax		; save string2 length
1000:0040137d ff35c3304000         push   dword ptr [4030c3h]
1000:00401383 e8ac030000           call   _SetFocus
1000:00401388 bf30314000           mov    edi, offset string1
1000:0040138d be30314000           mov    esi, offset string1
;                                                 XREFS First: 1000:0040139a Number : 1
1000:00401392 ac                   lodsb  				; turn string1
1000:00401393 0c00                 or     al, 00h			; into lowercase
1000:00401395 7405                 jz     40139ch
1000:00401397 0c20                 or     al, 20h
1000:00401399 aa                   stosb  
1000:0040139a ebf6                 jmp    401392h
;                                                 XREFS First: 1000:00401395 Number : 1
1000:0040139c bf70314000           mov    edi, offset string2
1000:004013a1 be70314000           mov    esi, offset string2
1000:004013a6 8d1d30314000         lea    ebx, [string1]
;                                                 XREFS First: 1000:004013b9 Number : 1
1000:004013ac ac                   lodsb  
1000:004013ad 0c00                 or     al, 00h			; now 'subtract'
1000:004013af 740a                 jz     4013bbh			; string1 from string2
1000:004013b1 8a13                 mov    dl, [ebx]
1000:004013b3 2ad0                 sub    dl, al
1000:004013b5 8ac2                 mov    al, dl
1000:004013b7 aa                   stosb  				; now string2=string1-string2
1000:004013b8 43                   inc    ebx
1000:004013b9 ebf1                 jmp    4013ach
;                                                 XREFS First: 1000:004013af Number : 1
1000:004013bb 8b0dbf304000         mov    ecx, [string2_length]		; init counter
;                                                 XREFS First: 1000:00401424 Number : 1
1000:004013c1 80c900               or     cl, 00h
1000:004013c4 7460                 jz     401426h
1000:004013c6 51                   push   ecx
1000:004013c7 6870314000           push   offset string2
1000:004013cc e883020000           call   401654h			; converts ascii to number
1000:004013d1 f7e1                 mul    ecx				; times counter
1000:004013d3 68b0314000           push   offset temp_string
1000:004013d8 50                   push   eax
1000:004013d9 e8be020000           call   40169ch			; convert it back to ascii
1000:004013de bfb0314000           mov    edi, offset temp_string
1000:004013e3 beb0314000           mov    esi, offset temp_string
;                                                 XREFS First: 1000:004013f1 Number : 1
1000:004013e8 ac                   lodsb  
1000:004013e9 0c00                 or     al, 00h
1000:004013eb 7406                 jz     4013f3h
1000:004013ed 83e00f               and    eax, 0fh			; bottom bits only of ascii digits
1000:004013f0 aa                   stosb  
1000:004013f1 ebf5                 jmp    4013e8h
;                                                 XREFS First: 1000:004013eb Number : 1
1000:004013f3 8b0dbf304000         mov    ecx, [string2_length]		; divides string in 2
1000:004013f9 d1e9                 shr    ecx, 1h
1000:004013fb bff0304000           mov    edi, offset enc_string
1000:00401400 beb0314000           mov    esi, offset temp_string
1000:00401405 8d99b0314000         lea    ebx, [ecx+temp_string]
;                                                 XREFS First: 1000:00401420 Number : 1
1000:0040140b 80c900               or     cl, 00h
1000:0040140e 7412                 jz     401422h
1000:00401410 ac                   lodsb  				; calculate
1000:00401411 33d2                 xor    edx, edx			; enc_string+=
1000:00401413 8a13                 mov    dl, [ebx]			; temp_string (first half)
1000:00401415 02c2                 add    al, dl			; +temp_string (second half)
1000:00401417 8a17                 mov    dl, [edi]
1000:00401419 02c2                 add    al, dl
1000:0040141b 240f                 and    al, 0fh			; mod 16
1000:0040141d aa                   stosb  
1000:0040141e 49                   dec    ecx
1000:0040141f 43                   inc    ebx
1000:00401420 ebe9                 jmp    40140bh
;                                                 XREFS First: 1000:0040140e Number : 1
1000:00401422 59                   pop    ecx				; loop over counter
1000:00401423 49                   dec    ecx
1000:00401424 eb9b                 jmp    4013c1h
;                                                 XREFS First: 1000:004013c4 Number : 1
1000:00401426 bef0304000           mov    esi, offset enc_string
1000:0040142b 8b1dbf304000         mov    ebx, [string2_length]	; final string must start with string2 length/2 as chars
1000:00401431 8bcb                 mov    ecx, ebx			; eg length 6 becomes '333'
1000:00401433 d1e9                 shr    ecx, 1h			; (hex, not ascii)
;                                                 XREFS First: 1000:00401442 Number : 1
1000:00401435 8a06                 mov    al, [esi]
1000:00401437 80c900               or     cl, 00h
1000:0040143a 7439                 jz     passed
1000:0040143c 38d8                 cmp    al, bl
1000:0040143e 7504                 jnz    failed
1000:00401440 46                   inc    esi
1000:00401441 49                   dec    ecx
1000:00401442 ebf1                 jmp    401435h
failed:
;                                                 XREFS First: 1000:00401355 Number : 3
1000:00401444 6800200000           push   2000h
1000:00401449 6818304000           push   offset s________________Error
1000:0040144e 6841304000           push   offset s____Sorry_Cracker__wrong_
1000:00401453 ff7508               push   dword ptr [ebp+08h]
1000:00401456 e8bb020000           call   _MessageBoxA
1000:0040145b 6a40                 push   40h
1000:0040145d 68f0304000           push   offset enc_string
1000:00401462 e80f030000           call   _RtlZeroMemory
1000:00401467 6a40                 push   40h
1000:00401469 68b0314000           push   offset temp_string
1000:0040146e e803030000           call   _RtlZeroMemory
1000:00401473 eb2f                 jmp    4014a4h
passed:
;                                                 XREFS First: 1000:0040143a Number : 1
1000:00401475 6800200000           push   2000h
1000:0040147a 682d304000           push   offset s_________Registered_
1000:0040147f 685a304000           push   offset s_____________You_did_it_
1000:00401484 ff7508               push   dword ptr [ebp+08h]
1000:00401487 e88a020000           call   _MessageBoxA
1000:0040148c 6a40                 push   40h
1000:0040148e 68f0304000           push   offset enc_string
1000:00401493 e8de020000           call   _RtlZeroMemory
1000:00401498 6a40                 push   40h
1000:0040149a 68b0314000           push   offset temp_string
1000:0040149f e8d2020000           call   _RtlZeroMemory
;                                                 XREFS First: 1000:00401473 Number : 1
1000:004014a4 eb0e                 jmp    4014b4h
;****************************************************************************************
; end of our checking routine
;****************************************************************************************
Now thats quite a large chunk of code to get our heads around. Some of the string processing is straightforward, ie check the lengths are greater than or equal to 4. At the end we have the message printing parts, the well done you passed, or the bad luck you failed type messages. In the middle there are several loops over various strings, including simple 'convert to lowercase loops' and more complex loops with additions of various strings. Since we will need to write a keygen the next step is to convert this into some kind of easier to read format. I chose to decompile it by hand, into C.

Converting code like the above into C isnt really that difficult if you take it a small chunk at a time. Sometimes I will take a single instruction and produce some C code for it, other times I will take a whole algorithm. The most difficult part is ensuring that you don't lose something in translation. The asm code above is best read by printing the table below with my reverse engineered C algorithm and referring between the two to see exactly what I mean, and see how it all works.......
	// simple length checks
	if(strlen(string1)<=4)
	{	return 0;
	}
	if(strlen(string2)<=4)
	{	return 0;
	}
	string2_length=strlen(string2);
	i=0;
	// convert string1 to lowercase
	while(string1[i])
	{	string1[i]|=0x20;
		i++;
	}
	i=0;
	// compute differences
	while(string2[i])
	{	string2[i]=string1[i]-string2[i];
		i++;
	}
	count=string2_length;
	// main crypt routine
	while(count)
	{	r=atolnew(string2)*count;	// ascii to long
		wsprintf(temp_string,"%lu",r);
		i=0;
		while(temp_string[i])
		{	temp_string[i]&=0x0f;
			i++;
		}
		r=string2_length/2;
		i=r;
		l=0;
		while(r)
		{	enc_string[l]+=(temp_string[l]+temp_string[i]);
			enc_string[l]&=0x0f;
			l++;
			i++;
			r--;
		}
		count--;
	}
	i=0;
	r=string2_length/2;
	// finally check the answer
	while(r)
	{	if(enc_string[i]!=string2_length) return 0;
		i++;
		r--;
	}
	return 1;
Notice that I have changed the two calls into named routines. We will see that the atol routine isnt quite standard and so I have called it atolnew, and wsprintf is just a standard call although the program uses a subroutine to do this. Now its difficult to actually see how this could be reversed and so all I am going to do is use the above code in a keygen and bruteforce a serial number (I will use just a number rather than any letters).

OK, adding some code to the above we arrive at the following keygen:
#include <string.h>
#include <windows.h>
#include <stdio.h>

// slightly different atol, to take into account the
// program version which allows non-digit characters
long atolnew(unsigned char *str1)
{	long t;
	int i;
	unsigned char e;
	t=0;
	i=0;
	while(str1[i])
	{	e=str1[i]-48;
		t=t*10+e;
		i++;
	}
	return t;
}

// returns 0 for fail, 1 for success
int checkit(char *string1,char *string2)
{	int string2_length,i,count,l;
	char temp_string[10],enc_string[10];
	long r;
	i=0;
	// added zeroing of string for keygen - Cronos
	while(i<10)
	{	temp_string[i]=0;
		enc_string[i]=0;
		i++;
	}
	// simple length checks
	if(strlen(string1)<=4)
	{	return 0;
	}
	if(strlen(string2)<=4)
	{	return 0;
	}
	string2_length=strlen(string2);
	i=0;
	// convert string1 to lowercase
	while(string1[i])
	{	string1[i]|=0x20;
		i++;
	}
	i=0;
	// compute differences
	while(string2[i])
	{	string2[i]=string1[i]-string2[i];
		i++;
	}
	count=string2_length;
	// main crypt routine
	while(count)
	{	r=atolnew(string2)*count;
		wsprintf(temp_string,"%lu",r);
		i=0;
		while(temp_string[i])
		{	temp_string[i]&=0x0f;
			i++;
		}
		r=string2_length/2;
		i=r;
		l=0;
		while(r)
		{	enc_string[l]+=(temp_string[l]+temp_string[i]);
			enc_string[l]&=0x0f;
			l++;
			i++;
			r--;
		}
		count--;
	}
	i=0;
	r=string2_length/2;
	// finally check the answer
	while(r)
	{	if(enc_string[i]!=string2_length) return 0;
		i++;
		r--;
	}
	return 1;
}

void main(int argc, char *argv[])
{	char str1[10],str2[10];
	int i,j;
	j=99999;
	printf("Keygen for czcrackme09 by Cronos\n");
	printf("Usage: keygen name (name>4 chars)\n");
	if(argc<2) return;
	do
	{	j++;
		strcpy(str1,argv[1]);
		i=0;
		while(i<10) str2[i++]=0;
		wsprintf(str2,"%lu",j);
		if(!(j&0xffff)) printf("progress: %lu\r",j);
	} while(!checkit(str1,str2));
	printf("\n%lu\n",j);
}

You will see in the above that I wrote a custom atol routine which allows non-digits in the computation, which is echoed elsewhere in the crackme if you want to look more closely. Running this give the following:
D:\uncracked crackmes\czcrackme09>keygen Cronos
Keygen for czcrackme09 by Cronos
Usage: keygen name (name>4 chars)

101045

And so there you have it, name:Cronos, serial:101045. Took less than a second.

Conclusions

Another interesting crackme bites the dust, although I wasnt able to reverse the algorithm. It was definitely worth doing for the experience and I would rate it 3/10 for difficulty, although I think some people might have difficulty with this.

{Cronos}