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 and serial. If you look through the code and used purely a Soft-Ice approach to the program then you could fall into a nice trap set by the programmer which involves an entirely false piece of 'serial checking code' which even generates a string in memory that you might think is the right serial.
Analysis
The approach I took, as usual with these crackmes was to disassemble and look back through the program, find some interesting messages and some interesting routines and track back from there, occasionally using Soft-Ice to verify conjectures and check things and to gain a broader understanding of subroutines. Anyway, I have shown the main routine in the disassembly, which includes our success messages at the bottom. The routine is actually difficult to follow in that the serial itself is used in checking the serial. This can lead to names for which there is no serial, and I will look at a shorter analysis method and some shortcomings of the serial routine after the main attack.
;**************************************************************** ; all the standard Delphi entry code is here......... ;**************************************************************** ; XREFS First: 1000:00429b75 Number : 1 1000:004298c8 55 push ebp 1000:004298c9 8bec mov ebp, esp 1000:004298cb 81c4ecfeffff add esp, fffffeech 1000:004298d1 53 push ebx 1000:004298d2 56 push esi 1000:004298d3 57 push edi 1000:004298d4 33c0 xor eax, eax 1000:004298d6 8985ecfeffff mov [ebp-114h], eax 1000:004298dc 8945f0 mov [ebp-10h], eax 1000:004298df 33c0 xor eax, eax 1000:004298e1 55 push ebp 1000:004298e2 68f99a4200 push 429af9h 1000:004298e7 64ff30 push dword ptr fs:[eax] 1000:004298ea 648920 mov fs:[eax], esp 1000:004298ed 33db xor ebx, ebx 1000:004298ef c645f500 mov byte ptr [ebp-0bh], 00h 1000:004298f3 a130b74200 mov eax, [42b730h] 1000:004298f8 8b80f8010000 mov eax, [eax+1f8h] 1000:004298fe 33d2 xor edx, edx 1000:00429900 e8d3f8ffff call 4291d8h 1000:00429905 a130b74200 mov eax, [42b730h] 1000:0042990a 8b80f8010000 mov eax, [eax+1f8h] 1000:00429910 e8dbf6ffff call 428ff0h 1000:00429915 8845ff mov [ebp-01h], al 1000:00429918 8d55f0 lea edx, [ebp-10h] 1000:0042991b a130b74200 mov eax, [42b730h] 1000:00429920 8b80e0010000 mov eax, [eax+1e0h] 1000:00429926 e8adc4feff call 415dd8h 1000:0042992b 8b55f0 mov edx, [ebp-10h] 1000:0042992e a130b74200 mov eax, [42b730h] 1000:00429933 8b08 mov ecx, [eax] 1000:00429935 ff5118 call dword ptr [ecx+18h] ; these three instructions are standard Delphi 'access string pointer' code 1000:00429938 8d55f0 lea edx, [ebp-10h] 1000:0042993b a130b74200 mov eax, [42b730h] 1000:00429940 8b80e8010000 mov eax, [eax+1e8h] 1000:00429946 e88dc4feff call 415dd8h 1000:0042994b 8b55f0 mov edx, [ebp-10h] 1000:0042994e 8d85f0feffff lea eax, [ebp-110h] 1000:00429954 b9ff000000 mov ecx, ffh 1000:00429959 e8269efdff call 403784h 1000:0042995e 8d95f0feffff lea edx, [ebp-110h] 1000:00429964 8d45f6 lea eax, [ebp-0ah] 1000:00429967 b108 mov cl, 08h 1000:00429969 e8928dfdff call 402700h 1000:0042996e 8a45ff mov al, [ebp-01h] 1000:00429971 84c0 test al, al 1000:00429973 0f8234010000 jc 429aadh ;**************************************************************** ; here we start to get serious....... ;**************************************************************** 1000:00429979 40 inc eax 1000:0042997a 8845f4 mov [ebp-0ch], al ; set up loop ; XREFS First: 1000:00429aa7 Number : 1 1000:0042997d 33c0 xor eax, eax 1000:0042997f 8ac3 mov al, bl ; bl is an index into our serial ;**************************************************************** ; next follows some code which uses one char and generates four numbers based on :- ; key[1]=char xor 3d7; ; key[3]=char xor 144; ; key[2]=char xor 2a3; ; key[0]=char xor ae; ;**************************************************************** 1000:00429981 8b1530b74200 mov edx, [42b730h] 1000:00429987 8b5208 mov edx, [edx+08h] 1000:0042998a 8a5402ff mov dl, [edx-01h+eax] 1000:0042998e 33c0 xor eax, eax 1000:00429990 8ac2 mov al, dl 1000:00429992 8bc8 mov ecx, eax 1000:00429994 6681f1d703 xor cx, 3d7h 1000:00429999 0fb7c9 movzx ecx, cx 1000:0042999c 890d38b74200 mov [42b738h], ecx ; key[1] done 1000:004299a2 33c9 xor ecx, ecx 1000:004299a4 8acb mov cl, bl 1000:004299a6 8b3530b74200 mov esi, [42b730h] 1000:004299ac 8b7608 mov esi, [esi+08h] 1000:004299af 8bc8 mov ecx, eax 1000:004299b1 6681f14401 xor cx, 144h 1000:004299b6 0fb7c9 movzx ecx, cx 1000:004299b9 890d40b74200 mov [42b740h], ecx ; key[3] done 1000:004299bf 33c9 xor ecx, ecx 1000:004299c1 8acb mov cl, bl 1000:004299c3 8b3530b74200 mov esi, [42b730h] 1000:004299c9 8b7608 mov esi, [esi+08h] 1000:004299cc 6635a302 xor ax, 2a3h 1000:004299d0 0fb7c0 movzx eax, ax 1000:004299d3 a33cb74200 mov [42b73ch], eax ; key[2] done 1000:004299d8 33c0 xor eax, eax 1000:004299da 8ac3 mov al, bl 1000:004299dc 8b0d30b74200 mov ecx, [42b730h] 1000:004299e2 8b4908 mov ecx, [ecx+08h] 1000:004299e5 80f2ae xor dl, aeh 1000:004299e8 33c0 xor eax, eax 1000:004299ea 8ac2 mov al, dl 1000:004299ec a334b74200 mov [42b734h], eax ; key[0] done ;**************************************************************** ; now we divide each by 10 continually until they are all ; less than 10. ;**************************************************************** 1000:004299f1 b301 mov bl, 01h 1000:004299f3 bf34b74200 mov edi, offset 42b734h ; XREFS First: 1000:00429a1f Number : 2 1000:004299f8 8b07 mov eax, [edi] 1000:004299fa b90a000000 mov ecx, 0ah 1000:004299ff 99 cdq 1000:00429a00 f7f9 idiv ecx 1000:00429a02 8bf0 mov esi, eax 1000:00429a04 8937 mov [edi], esi 1000:00429a06 8d95ecfeffff lea edx, [ebp-114h] 1000:00429a0c 8bc6 mov eax, esi 1000:00429a0e e8d1cafdff call 4064e4h 1000:00429a13 8b85ecfeffff mov eax, [ebp-114h] 1000:00429a19 e88a9dfdff call 4037a8h 1000:00429a1e 48 dec eax 1000:00429a1f 75d7 jnz 4299f8h 1000:00429a21 43 inc ebx 1000:00429a22 83c704 add edi, 04h 1000:00429a25 80fb05 cmp bl, 05h 1000:00429a28 75ce jnz 4299f8h ;**************************************************************** ; now just check the serial is in an appropriate form for comparison ;**************************************************************** 1000:00429a2a 8a5df6 mov bl, [ebp-0ah] 1000:00429a2d 84db test bl, bl 1000:00429a2f 762a jbe 429a5bh 1000:00429a31 8d7df7 lea edi, [ebp-09h] 1000:00429a34 be54b74200 mov esi, 42b754h ; XREFS First: 1000:00429a59 Number : 1 1000:00429a39 8d85ecfeffff lea eax, [ebp-114h] 1000:00429a3f 8a17 mov dl, [edi] 1000:00429a41 e88a9cfdff call 4036d0h 1000:00429a46 8b85ecfeffff mov eax, [ebp-114h] 1000:00429a4c e8c3cafdff call 406514h 1000:00429a51 8906 mov [esi], eax 1000:00429a53 83c604 add esi, 04h 1000:00429a56 47 inc edi 1000:00429a57 fecb dec bl 1000:00429a59 75de jnz 429a39h ; XREFS First: 1000:00429a2f Number : 1 ;**************************************************************** ; finally compare the two digit by digit ;**************************************************************** 1000:00429a5b b301 mov bl, 01h 1000:00429a5d b834b74200 mov eax, offset 42b734h 1000:00429a62 ba54b74200 mov edx, offset 42b754h ; XREFS First: 1000:00429a81 Number : 1 1000:00429a67 8b08 mov ecx, [eax] 1000:00429a69 3b0a cmp ecx, [edx] 1000:00429a6b 7406 jz 429a73h ;**************************************************************** ; note that if the comparison fails then we leave the loop early ; the point at which it failed is used as an index for the next ; char for generating a new number to check.......and so on and so on ; for a total of loop times (which is quite high......) ;**************************************************************** 1000:00429a6d c645f500 mov byte ptr [ebp-0bh], 00h 1000:00429a71 eb10 jmp 429a83h ; XREFS First: 1000:00429a6b Number : 1 1000:00429a73 c645f501 mov byte ptr [ebp-0bh], 01h 1000:00429a77 43 inc ebx 1000:00429a78 83c204 add edx, 04h 1000:00429a7b 83c004 add eax, 04h 1000:00429a7e 80fb05 cmp bl, 05h 1000:00429a81 75e4 jnz 429a67h ; XREFS First: 1000:00429a71 Number : 1 1000:00429a83 a130b74200 mov eax, [42b730h] 1000:00429a88 8bb0f8010000 mov esi, [eax+1f8h] 1000:00429a8e 8bc6 mov eax, esi 1000:00429a90 e893f5ffff call 429028h 1000:00429a95 8bd0 mov edx, eax 1000:00429a97 42 inc edx 1000:00429a98 8bc6 mov eax, esi 1000:00429a9a e839f7ffff call 4291d8h 1000:00429a9f e86cfbffff call 429610h ;**************************************************************** ; finally it continues for quite a while ;**************************************************************** 1000:00429aa4 fe4df4 dec byte ptr [ebp-0ch] 1000:00429aa7 0f85d0feffff jnz 42997dh ;**************************************************************** ; until we are fed up and decide it was the last time ;**************************************************************** ; XREFS First: 1000:00429973 Number : 1 1000:00429aad 807df501 cmp byte ptr [ebp-0bh], 01h 1000:00429ab1 7513 jnz 429ac6h 1000:00429ab3 6a00 push 00h 1000:00429ab5 68089b4200 push offset s_Success 1000:00429aba 68109b4200 push offset s_You_ve_found_right_serial_ 1000:00429abf 6a00 push 00h 1000:00429ac1 e83abafdff call _MessageBoxA ; XREFS First: 1000:00429ab1 Number : 1 ;**************************************************************** ; and the rest is boring Delphi exit code ;) ;**************************************************************** 1000:00429ac6 a130b74200 mov eax, [42b730h] 1000:00429acb 8b80f8010000 mov eax, [eax+1f8h] 1000:00429ad1 33d2 xor edx, edx 1000:00429ad3 e800f7ffff call 4291d8h 1000:00429ad8 33c0 xor eax, eax 1000:00429ada 5a pop edx 1000:00429adb 59 pop ecx 1000:00429adc 59 pop ecx 1000:00429add 648910 mov fs:[eax], edx 1000:00429ae0 68009b4200 push 429b00h ; XREFS First: 1000:00429afe Number : 1 1000:00429ae5 8d85ecfeffff lea eax, [ebp-114h] 1000:00429aeb e83c9afdff call 40352ch 1000:00429af0 8d45f0 lea eax, [ebp-10h] 1000:00429af3 e8349afdff call 40352ch 1000:00429af8 c3 ret 1000:00429af9 e9d294fdff jmp 402fd0h 1000:00429afe ebe5 jmp 429ae5h 1000:00429b00 5f pop edi 1000:00429b01 5e pop esi 1000:00429b02 5b pop ebx 1000:00429b03 8be5 mov esp, ebp 1000:00429b05 5d pop ebp 1000:00429b06 c3 ret 1000:00429b07 00 db 00 s_Success: ; XREFS First: 1000:00429ab5 Number : 1 1000:00429b08 5375636365737300 ds "Success" s_You_ve_found_right_serial_: ; XREFS First: 1000:00429aba Number : 1 1000:00429b10 596f7527766520666f.. ds "You've found right serial!" |
I have indicated in the comments the main algorithm in use. Basically we start at the beginning of the name string and generate a number from the char we are looking at. We then compare this to the serial and the point at which it fails determines the char for the next generation. After a number of loops it all ends and we take the last check as final....... |
Now its difficult to see how you can generate a serial for a given name from this, but I will give three ways, one the first method I used and the other two methods I thought of later. Method 1 involved breakpointing with SoftIce as the serial was generated. I entered 'Cronos' as the name and '1234' as the code. First loop, 1963 was generated. So I entered that as the code, but now later in the loop '1972' was generated and so I entered that as the code and started again. This soon popped the message up 'You've found the right serial!'. So that was little work with Soft-Ice. Method 2 is to take the first five characters of your nick and generate the code for each. Then try them all....... Method 3 is based on an analysis of the formulae. You will see that the formulae are quite limited and key[1] for example is between 300h and 3ffh. This means the character is either 7,8,9 or 1. In fact the possibilities are 0-2 for key[0], 5-7 for key[2] and 2-5 for key[3]. Now we can limit this further by checking only lowercase possibilities. Since the loop must end on one of the characters 1-5 of your name, if we input a lowercase name only then we have reduced the possibilities to 1 or 2 for key[0], 8 or 9 for key[1], 7 for key[2] and 2 or 3 for key[3]. Hence there are only 8 possible serial numbers for a lowercase name. These are 1872, 2872, 1972, 2972, 1873, 2873, 1973 and 2973. It doesnt take long to check them all.
Now there are some other points to note in the serial routine. Firstly if we do succeed then we are likely to reach a stable point on character 5. With 'Cronos' we end up at character 5 since we match the serial at one point, and so the next character used for the serial generation in the loop is character 4+1=character 5..... and so we do not move from there. Other possibilities include moving back and forth between two characters, and this can lead to failure of the serial generation (for example using character 2 and matching 3 chars so using character 4 and matching 1, so using character 2........etc etc). This can mean that although you can generate 5 possible codes from the names characters none will work because the last one generated is different in each case........
And so there you have it, name:Cronos, serial:1972.
Conclusions
This crackme was quite hard, although the hardest part for most people will probably be understanding how Delphi compiles programs. The serial checking routine was interesting although limited in the number of valid codes and although some names may not lead to valid serials there are many ways to analyse the routine.
{Cronos}