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}