GenoCide Crackme 10 [Gandalf]

by

{Cronos}

Intro

GenoCide seem to like packing their crackmes with UPX it appears, I can't think why personally. Why do they want to hide the code ? So you just unpack it..... Anyway, it's a Delphi program swollen beyond all proportions :) and 90% of the code that is there is ignorable. It is full of bloat for class type initialisation and type and error checking and it's easy to get lost in a debugger tracing through seemingly endless calls to a library stringlength type function. After a while cracking Delphi apps you become familiar and learn to ignore it for the best part. Delphi however does leave some nice remnants in memory which enable you to start cracking Delphi apps faster :).

Target

The crackme asks for a name, valid serial, unlock code and I'll write a keygen for it too. Once you're used to Delphi apps it is pretty simple to crack this one.

Analysis

OK, we are presented with a dialog box with three text boxes into which we can type a name and a serial, and an unlock code. In the disassembly of the program I have just shown the mainly relevant parts of the code, in a more relevant order for me to talk about.
1000:0042d630 c60511f7420000       mov    byte ptr [flag2], 00h
1000:0042d637 c60512f7420000       mov    byte ptr [flag3], 00h
1000:0042d63e e8adfdffff           call   check_str_lengths
1000:0042d643 803d10f7420000       cmp    byte ptr [flag1], 00h
1000:0042d64a 752f                 jnz    42d67bh
1000:0042d64c e867feffff           call   check_str3
1000:0042d651 803d11f7420001       cmp    byte ptr [flag2], 01h
1000:0042d658 7521                 jnz    42d67bh
1000:0042d65a e8f5feffff           call   final_check
1000:0042d65f 803d12f7420001       cmp    byte ptr [flag3], 01h
1000:0042d666 7513                 jnz    42d67bh
1000:0042d668 6a00                 push   00h
1000:0042d66a 687cd64200           push   offset s_Success_
1000:0042d66f 6888d64200           push   offset s_Good_work_cracker_
1000:0042d674 6a00                 push   00h
1000:0042d676 e8817ffdff           call   _MessageBoxA
;                                                 XREFS First: 1000:0042d64a Number : 3
1000:0042d67b c3                   ret    
s_Success_:
;                                                 XREFS First: 1000:0042d66a Number : 1
1000:0042d67c 537563636573732100   ds     "Success!"
1000:0042d685 00                   db     00
1000:0042d686 00                   db     00
1000:0042d687 00                   db     00
s_Good_work_cracker_:
;                                                 XREFS First: 1000:0042d66f Number : 1
1000:0042d688 476f6f6420776f726b.. ds     "Good work cracker!"
If I really need to explain the code above then you are seriously out of of your depth here :). Basically the program calls three routines and there are three corresponding conditional jumps which can stop us from getting to our goal (the success message). You'll see that I have given them names which will become relevant as we look at each in turn.
check_str_lengths:
;                                                 XREFS First: 1000:0042d63e Number : 1
1000:0042d3f0 55                   push   ebp
1000:0042d3f1 8bec                 mov    ebp, esp
1000:0042d3f3 6a00                 push   00h
1000:0042d3f5 6a00                 push   00h
1000:0042d3f7 33c0                 xor    eax, eax
1000:0042d3f9 55                   push   ebp
1000:0042d3fa 68abd44200           push   offset 42d4abh
1000:0042d3ff 64ff30               push   dword ptr fs:[eax]
1000:0042d402 648920               mov    fs:[eax], esp
;*******************************************************************
; entry code to here.........boring
;*******************************************************************
1000:0042d405 8d55fc               lea    edx, [ebp-04h]
1000:0042d408 a10cf74200           mov    eax, [str_ptr_ptr]
1000:0042d40d 8b80e8010000         mov    eax, [eax+1e8h]
1000:0042d413 e864cafeff           call   str_copier
1000:0042d418 837dfc00             cmp    dword ptr [ebp-04h], 00h
1000:0042d41c 7420                 jz     42d43eh
1000:0042d41e 8d55f8               lea    edx, [ebp-08h]
1000:0042d421 a10cf74200           mov    eax, [str_ptr_ptr]
check_str1_length_gte_5:
1000:0042d426 8b80e8010000         mov    eax, [eax+1e8h]
1000:0042d42c e84bcafeff           call   str_copier
;*******************************************************************
; delphi stuff to here......boring
;*******************************************************************
1000:0042d431 8b45f8               mov    eax, [ebp-08h]
1000:0042d434 e88763fdff           call   get_length
; ******a moment of excitement, name must be 5 or more chars!
1000:0042d439 83f805               cmp    eax, 05h	
1000:0042d43c 7d09                 jge    42d447h
;                                                 XREFS First: 1000:0042d41c Number : 1
1000:0042d43e c60510f7420001       mov    byte ptr [flag1], 01h
1000:0042d445 eb07                 jmp    42d44eh
;                                                 XREFS First: 1000:0042d43c Number : 1
1000:0042d447 c60510f7420000       mov    byte ptr [flag1], 00h
;*******************************************************************
; end of interesting stuff, now we have set the flag.....
;*******************************************************************
;                                                 XREFS First: 1000:0042d445 Number : 1
1000:0042d44e 8d55fc               lea    edx, [ebp-04h]
1000:0042d451 a10cf74200           mov    eax, [str_ptr_ptr]
check_str2_length_gt0:
1000:0042d456 8b80ec010000         mov    eax, [eax+1ech]
1000:0042d45c e81bcafeff           call   str_copier
; ******another moment of excitement, serial must be 1 or more chars!
1000:0042d461 837dfc00             cmp    dword ptr [ebp-04h], 00h
1000:0042d465 7419                 jz     42d480h
1000:0042d467 8d55f8               lea    edx, [ebp-08h]
1000:0042d46a a10cf74200           mov    eax, [str_ptr_ptr]
check_str3_length_gt0:
1000:0042d46f 8b80f0010000         mov    eax, [eax+1f0h]
1000:0042d475 e802cafeff           call   str_copier
; ******another moment of excitement, unlock code must be 1 or more chars!
1000:0042d47a 837df800             cmp    dword ptr [ebp-08h], 00h
1000:0042d47e 7509                 jnz    42d489h
;                                                 XREFS First: 1000:0042d465 Number : 1
1000:0042d480 c60510f7420001       mov    byte ptr [flag1], 01h
1000:0042d487 eb07                 jmp    42d490h
;                                                 XREFS First: 1000:0042d47e Number : 1
1000:0042d489 c60510f7420000       mov    byte ptr [flag1], 00h
;*******************************************************************
; and everything from here is really.............boring
;*******************************************************************
;                                                 XREFS First: 1000:0042d487 Number : 1
1000:0042d490 33c0                 xor    eax, eax
1000:0042d492 5a                   pop    edx
1000:0042d493 59                   pop    ecx
1000:0042d494 59                   pop    ecx
1000:0042d495 648910               mov    fs:[eax], edx
1000:0042d498 68b2d44200           push   offset 42d4b2h
;                                                 XREFS First: 1000:0042d4b0 Number : 1
1000:0042d49d 8d45f8               lea    eax, [ebp-08h]
1000:0042d4a0 ba02000000           mov    edx, 02h
1000:0042d4a5 e8be60fdff           call   403568h
1000:0042d4aa c3                   ret    
;                                                 XREFS First: 1000:0042d3fa Number : 1
1000:0042d4ab e9385bfdff           jmp    402fe8h
1000:0042d4b0 ebeb                 jmp    42d49dh
;                                                 XREFS First: 1000:0042d498 Number : 1
1000:0042d4b2 59                   pop    ecx
1000:0042d4b3 59                   pop    ecx
1000:0042d4b4 5d                   pop    ebp
1000:0042d4b5 c3                   ret    
This is probably the most complete routine I'll regurgitate here. All that to check three stringlengths are greater than or equal to 5,1 and 1 characters respectively, and set a couple of flags to be checked in the main routine. Now there were two other routines to get through yet, so dont start getting too excited ;)
check_str3:
;                                                 XREFS First: 1000:0042d64c Number : 1
1000:0042d4b8 55                   push   ebp
;********************************************************************
; gap..................................................
;********************************************************************
1000:0042d4df 8b45fc               mov    eax, [ebp-04h]
1000:0042d4e2 e85991fdff           call   uses_ascii_to_num
1000:0042d4e7 8bd8                 mov    ebx, eax
;********************************************************************
; gap..................................................
;********************************************************************
1000:0042d4ff e8bc62fdff           call   get_length
1000:0042d504 84c0                 test   al, al
1000:0042d506 7610                 jbe    42d518h
;                                                 XREFS First: 1000:0042d516 Number : 1
1000:0042d508 81f3865e0100         xor    ebx, 15e86h
1000:0042d50e 81e3f01e0100         and    ebx, 11ef0h
1000:0042d514 fec8                 dec    al
1000:0042d516 75f0                 jnz    42d508h
;                                                 XREFS First: 1000:0042d506 Number : 1
1000:0042d518 81fbe00a0100         cmp    ebx, 10ae0h
1000:0042d51e 7509                 jnz    42d529h
1000:0042d520 c60511f7420001       mov    byte ptr [flag2], 01h
1000:0042d527 eb07                 jmp    42d530h
;                                                 XREFS First: 1000:0042d51e Number : 1
1000:0042d529 c60511f7420000       mov    byte ptr [flag2], 00h
;********************************************************************
; big gap..................................................
;********************************************************************
You can see the relevant parts of the above checking routine. This is all on the unlock code (string3 I called it at the time). It is obviously converted into a number and saved in ebx. We then get the length of it, loop around doing some maths and check it against an expected value before setting a flag.

There's actually an easy way to solve this problem and here is how. We loop based on the length of the number (call this unlock code X for now), and then check it against a 5 digit number, so let X have 5 digits, so we do 5 loops, ok. Now the problem is ((X xor 15e86) and 11ef0) etc etc = 10ae0. What do you notice about this ? Well for a start one digit in X has no effect on any others. Take the last digit of X. It is anded with 0 right ? On each iteration, so it will always be 0 at the end. So the last digit of X can be anything, we'll call it 0. You can actually solve the whole problem digit by digit. Or you could solve it bit by bit.....so this isnt so hard. The final code ? I used 1460h, or 5216 decimal. Ah but that has only 4 digits.......ok, input 05216......happy now ?

Now for the final check.

final_check:
;                                                 XREFS First: 1000:0042d65a Number : 1
1000:0042d554 55                   push   ebp
;********************************************************************
; gap..................................................
;********************************************************************
1000:0042d572 bf1b990800           mov    edi, 8991bh
;********************************************************************
; gap..................................................
;********************************************************************
1000:0042d5aa 8b45f8               mov    eax, [ebp-08h]
1000:0042d5ad e88e90fdff           call   uses_ascii_to_num
1000:0042d5b2 8945fc               mov    [ebp-04h], eax
1000:0042d5b5 eb20                 jmp    42d5d7h
;                                                 XREFS First: 1000:0042d5ee Number : 1
1000:0042d5b7 a10cf74200           mov    eax, [str_ptr_ptr]
1000:0042d5bc 8b4008               mov    eax, [eax+08h]
1000:0042d5bf 0fb64430ff           movzx  eax, [eax-01h+esi]
1000:0042d5c4 b905000000           mov    ecx, 05h
1000:0042d5c9 99                   cdq    
1000:0042d5ca f7f9                 idiv   ecx
1000:0042d5cc 69c25ffd0e00         imul   eax, edx, 982367
1000:0042d5d2 33f8                 xor    edi, eax
1000:0042d5d4 80c302               add    bl, 02h
;                                                 XREFS First: 1000:0042d5b5 Number : 1
1000:0042d5d7 a10cf74200           mov    eax, [str_ptr_ptr]
1000:0042d5dc 8b4008               mov    eax, [eax+08h]
1000:0042d5df e8dc61fdff           call   get_length
1000:0042d5e4 8bf3                 mov    esi, ebx
1000:0042d5e6 81e6ff000000         and    esi, ffh
1000:0042d5ec 3bc6                 cmp    eax, esi
1000:0042d5ee 7dc7                 jge    42d5b7h
1000:0042d5f0 8b45fc               mov    eax, [ebp-04h]
1000:0042d5f3 351b990800           xor    eax, 8991bh
1000:0042d5f8 3bf8                 cmp    edi, eax
1000:0042d5fa 7509                 jnz    42d605h
1000:0042d5fc c60512f7420001       mov    byte ptr [flag3], 01h
1000:0042d603 eb07                 jmp    42d60ch
;                                                 XREFS First: 1000:0042d5fa Number : 1
1000:0042d605 c60512f7420000       mov    byte ptr [flag3], 00h
;                                                 XREFS First: 1000:0042d603 Number : 1
This is the final check, there's actually not much to it all with all the irrelevance cut out. As is to be expected this involves the serial number and the name (given that the unlock code was fixed). The serial is converted to a number (which is xored with 8991bh later) and compared in the end to some number which is made up from the name. Notice the constants in use, like 5 and 982367, you will find them in my keygen and it will all become a bit more obvious below.

It's time to do some manual decompiling, and adding a short interface for a proper keygen isnt too difficult so I have done this at the same time (in fact its very similar to other keygens of mine.......). I give this as documentation of the algorithm above, which is really very simple.
#include <windows.h>
#include <stdio.h>
#include <string.h>

int adjust(int c)
{	return (c%5);
}

int checkit(char *string1)
{	int l,t,r;
	l=0;
	t=0x8991b;
	l=l+2;
	while(l<=strlen(string1))
	{	t=((int)(adjust(string1[l-1]))*982367)^t;
   		l=l+2;
	}
	t=t^0x8991b;
	return t;
}

void main(int argc, char *argv[])
{	char str1[10];
	int i,u,j,r;
	printf("Keygen for GenoCide crackme 10 by Cronos\n");
	printf("Usage: keygen name (name>4 chars)\n");
	if(argc<2) return;
	strcpy(str1,argv[1]);
	strupr(str1);
	j=checkit(str1);
	r=0x01460;
	printf("Name: %s Serial:%lu Unlock Code:%05lu\n",str1,j,r);
}
I actually created a function called adjust, which arose out of a mistake on my part in interpreting imul eax,edx,982367 but no harm done and I couldnt be bothered to get rid of it, so it stands as one of the shortest functions in any keygen ;)

Running this give the following:
D:\uncracked crackmes\gncrk10>keygen cronos
Keygen for GenoCide crackme 10 by Cronos
Usage: keygen name (name>4 chars)
Name: CRONOS Serial:1964734 Unlock Code:05216

And so there you have it, name:CRONOS, serial:1964734 Unlock Code:05216.

Conclusions

This crackme wasnt really that hard, the hardest part for most people will probably be understanding how Delphi compiles programs.

{Cronos}