Intro
Now I won't mess about with this tutorial. This is a high-rated crackme and this isnt a tutorial for newbies. I'll bring in the level straight away so as not to get bogged down.
Target
Our target is [yAtEs] crackme v1.0. The most interesting thing about this is that it includes PE-PRO protected by the crackme. The idea is to find a valid serial number for the program to run.
Analysis
OK, we note it has a PE section (marked 'data') which contains code and everything else looks like nonsense (actually encrypted). What follows is a dump of the main code, interspersed with some comments.
start: 1000:0049709d 6a00 push 00h 1000:0049709f 6880000000 push 80h 1000:004970a4 6a03 push 03h 1000:004970a6 6a00 push 00h 1000:004970a8 6a00 push 00h 1000:004970aa 6800000080 push 80000000h 1000:004970af 6853704900 push offset filename 1000:004970b4 e88b010000 call CreateFileA |
This first bit just opens a file called 'technics.dat' which will contain the serial number. |
1000:004970b9 83f8ff cmp eax, -01h 1000:004970bc 0f8437010000 jz 4971f9h 1000:004970c2 a37c704900 mov [49707ch], eax 1000:004970c7 6a00 push 00h 1000:004970c9 6890704900 push offset buff_length 1000:004970ce 6a0c push 0ch 1000:004970d0 6880704900 push offset var_a 1000:004970d5 ff357c704900 push dword ptr [49707ch] 1000:004970db e852010000 call _ReadFile |
Now it reads in 12 bytes from that file into a buffer at offset var_a. So the length of the serial number is 12 and we can create a 12 byte file called technics.dat. |
1000:004970e0 a190704900 mov eax, [buff_length] 1000:004970e5 a184704900 mov eax, [var_b] 1000:004970ea a378704900 mov [res_3], eax 1000:004970ef b870704900 mov eax, offset res_1 1000:004970f4 e89f000000 call 497198h 1000:004970f9 833d7070490000 cmp dword ptr [res_1], 00h 1000:00497100 0f85f3000000 jnz 4971f9h 1000:00497106 833d7470490000 cmp dword ptr [res_2], 00h 1000:0049710d 0f85e6000000 jnz 4971f9h |
Now here we have part of the serial number (the centre of it) being put into res_3 and we call a subroutine at 497198h. The jumps to 4971f9h are jumps to a messagebox routine telling us the serial is incorrect. We note that we need both res_1 and res_2 to be zero to get past this. In fact 497178 is a decryption routine which we will need to crack. |
1000:00497113 a16c704900 mov eax, [49706ch] 1000:00497118 8b18 mov ebx, [eax] 1000:0049711a 891d80704900 mov [var_a], ebx 1000:00497120 a16c704900 mov eax, [49706ch] 1000:00497125 8b5804 mov ebx, [eax+04h] 1000:00497128 891d84704900 mov [var_b], ebx 1000:0049712e a180704900 mov eax, [var_a] 1000:00497133 8b1d84704900 mov ebx, [var_b] 1000:00497139 a370704900 mov [res_1], eax 1000:0049713e 891d74704900 mov [res_2], ebx 1000:00497144 e84f000000 call 497198h 1000:00497149 a170704900 mov eax, [res_1] 1000:0049714e 8b1d74704900 mov ebx, [res_2] 1000:00497154 8b0d6c704900 mov ecx, [49706ch] 1000:0049715a 8901 mov [ecx], eax 1000:0049715c 8b0d6c704900 mov ecx, [49706ch] 1000:00497162 83c104 add ecx, 04h 1000:00497165 8919 mov [ecx], ebx 1000:00497167 83056c70490008 add dword ptr [49706ch], 08h 1000:0049716e ff0594704900 inc dword ptr [497094h] 1000:00497174 a194704900 mov eax, [497094h] 1000:00497179 3d80d70000 cmp eax, d780h 1000:0049717e 7593 jnz 497113h 1000:00497180 6a00 push 00h 1000:00497182 6800704900 push offset PE_Pro_message 1000:00497187 6860704900 push offset CorrectKey_message 1000:0049718c 6a00 push 00h 1000:0049718e e887000000 call _MessageBoxA 1000:00497193 e9d859fdff jmp 46cb70h |
Now this longer piece of code is actually decrypting the PE_Pro program itself (which is just the PE section containing the file) and then prints a message saying 'correct key'. We won't actually be concerned with this part any further |
1000:00497198 60 pushad 1000:00497199 bd20000000 mov ebp, 20h 1000:0049719e b82037efc6 mov eax, c6ef3720h 1000:004971a3 8b1570704900 mov edx, [res_1] 1000:004971a9 8b0d74704900 mov ecx, [res_2] 1000:004971af 8b3578704900 mov esi, [res_3] 1000:004971b5 8bfa mov edi, edx 1000:004971b7 c1ef05 shr edi, 05h 1000:004971ba 03fe add edi, esi 1000:004971bc 8bda mov ebx, edx 1000:004971be c1e304 shl ebx, 04h 1000:004971c1 03de add ebx, esi 1000:004971c3 33fb xor edi, ebx 1000:004971c5 8d1c10 lea ebx, [eax+edx] 1000:004971c8 33fb xor edi, ebx 1000:004971ca 2bcf sub ecx, edi 1000:004971cc 8bf9 mov edi, ecx 1000:004971ce c1ef05 shr edi, 05h 1000:004971d1 03fe add edi, esi 1000:004971d3 8bd9 mov ebx, ecx 1000:004971d5 c1e304 shl ebx, 04h 1000:004971d8 03de add ebx, esi 1000:004971da 33fb xor edi, ebx 1000:004971dc 8d1c08 lea ebx, [eax+ecx] 1000:004971df 33fb xor edi, ebx 1000:004971e1 2bd7 sub edx, edi 1000:004971e3 2db979379e sub eax, 9e3779b9h 1000:004971e8 4d dec ebp 1000:004971e9 75ca jnz 4971b5h 1000:004971eb 891570704900 mov [res_1], edx 1000:004971f1 890d74704900 mov [res_2], ecx 1000:004971f7 61 popad 1000:004971f8 c3 ret |
This is the most important part of the program, the decryption routine which we will analyse in detail. Note specifically the starting values for res_1, and res_2, which are 9184ea05h and 26440640h respectively. |
1000:004971f9 6a00 push 00h 1000:004971fb 6800704900 push offset PE_Pro_message 1000:00497200 6817704900 push offset Sorry_Message 1000:00497205 6a00 push 00h 1000:00497207 e80e000000 call _MessageBoxA _ExitProcess: 1000:0049720c ff258c014700 jmp dword ptr ExitProcess |
And the final part is just the sorry message code. |
So the next stage is the analysis of the decryption routine in detail, in fact I prefer to rewrite it as pseudo-code. Here it is in pseudo code, which will be comparable to the real code above. I have included some code just before the call and the code just after.
read var_a,var_b,var_c from file res_1=9184ea05 res_2=26440640 res_3=var_b eax=c6ef3720 edx=res_1 ecx=res_2 esi=res_3 do { ebp=32 edi=edx edi=edi>>5+esi ebx=edx ebx=ebx<<4+esi edi=edi^ebx ebx=eax+edx edi=edi^ebx ecx-=edi edi=ecx edi=edi>>5+esi ebx=ecx ebx=ebx<<4+esi edi=edi^ebx ebx=eax+acx edi=edi^ebx edx-=edi eax-=9e3779b9 dec ebp } while ebp!=0 res_1=edx res_2=ecx if res_1 != 0 or res_2 !=0, invalid key |
Now my next task is to start simplifying this and rewriting it, so I take things like edi=edx, edi=edi>>5+esi, ebx=edx, ebx=ebx<<4+esi, edi=edi^ebx and start combining some items. The result is below, my reverse engineered decryption routine.
read var_a,var_b,var_c from file res_1=9184ea05 res_2=26440640 res_3=var_b eax=c6ef3720 edx=res_1 ecx=res_2 esi=res_3 // Tea algorithm with a simplified key for(i=0;i<32;i++) { ecx-=(edx>>5+esi)^(edx<<4+esi)^(eax+edx) edx-=(ecx>>5+esi)^(ecx<<4+esi)^(eax+ecx) eax-=9e3779b9 } res_1=edx res_2=ecx if res_1 != 0 or res_2 !=0, invalid key |
Now, before we go any further this is actually recognisable as the TEA encryption algorithm, which is a pretty strong algorithm. Luckily [yAtEs] has made this quite a bit easier for us by using only a 32-bit key though. Nevertheless we can't reverse it to re-encrypt the final part, we need to recover the key of tha algorithm and the only way to do that is by brute force. So, I take the above, and change it around a bit and write a brute force decoder in C. Here it is:
/* modified from an original Tea encryption program I had, in order to conform to the algorithm which [yAtEs] is using. Just a simple brute forcer for part I. I compiled as a win32 console proggie. It takes about an hour and twenty minutes to try all keys. In fact it finds the key 5ef3184a in about 20 minutes or so. */ /* The Tiny Encryption Algorithm, or TEA, is a Feistel cipher invented by David Wheeler. It is intended for use in applications where code size is at a premium, or where it is necessary for someone to remember the algorithm and code it on an arbitrary machine at a later time. Since its round function is relatively weak, with nonlinearity coming only from the carry propagation, TEA has 64 rounds. However, its simplicity means that it runs more quickly in software than many other algorithms with fewer, more complex, rounds. */ #include <stdio.h> void code(long* v, long k) { unsigned long y=v[0],z=v[1],sum=0xc6ef3720, /* set up */ delta=0x9e3779b9, n=32 ; /* key schedule constant*/ while (n-->0) { /* basic cycle start*/ z -= ((y<<4)+k) ^ (y+sum) ^ ((y>>5)+k) ; /* end cycle */ y -= ((z<<4)+k) ^ (z+sum) ^ ((z>>5)+k) ; sum -= delta ; } v[0]=y ; v[1]=z ; } void main(void) { long k, v[2]; k=0; do { k++; if((k&0xffff)==0) printf("%x\n",k); v[0]=0x9184ea05; v[1]=0x26440640; code(v,k); }while(v[0]||v[1]); printf("%x",k); } |
The code here should be fairly self explanatory, it merely checks all possible keys until the right one is found. It finds the key 5ef3184a in around 20 minutes and we can then remake the keyfile:
00 00 00 00 4a 18 f3 5e 00 00 00 00
The key works and we have finished our objective.
Conclusions
I suspect that [yAtEs] first made this with a longer key and realising it was going to be too hard he must've reduced the key length. A longer key would just make it a bit lame, the current key length is sufficiently interesting. The alternative would be to make us reverse the algorithm by hard coding the key and saying decrypt(serial,key)=0 which would allow us to use encrypt(0,key) to find the serial. In my mind this would be better because you could still use a long key and it would take a greater leap in imagination to reverse the algorithm itself. (unless you recognise TEA of course). Still it was worth doing for the experience and I would rate it 6/10 for difficulty.
{Cronos}