Here’s my solution to a nice crackme, not so hard but enjoyable. The crackme rules are simple:
1. No patching!
2. A valid solution has a keygen and a tutorial!
Extra info: just a very simple algo with some anti-analysis tricks

360 Mobile Vision - 360mobilevision.com North & South Carolina Security products and Systems Installations for Commercial and Residential - $55 Hourly Rate. ACCESS CONTROL, INTRUSION ALARM, ACCESS CONTROLLED GATES, INTERCOMS AND CCTV INSTALL OR REPAIR 360 Mobile Vision - 360mobilevision.com is committed to excellence in every aspect of our business. We uphold a standard of integrity bound by fairness, honesty and personal responsibility. Our distinction is the quality of service we bring to our customers. Accurate knowledge of our trade combined with ability is what makes us true professionals. Above all, we are watchful of our customers interests, and make their concerns the basis of our business.

The crackme is not packed/protected, I decided to start looking at the disasm output.
GetDlgItemTextA function is used to read both name and serial. Once the checks over name and serial are passed (strlen(name) > 4, strlen(serial) == 8) you’ll face the next snippet:

403ED0 nop
403ED1 mov ecx, dword_40ADB0
403ED7 call 4033A0
403EDC test eax, eax
403EDE jz short loc_403ED0
403EE0 mov ecx, dword_40ADB0
403EE6 cmp dword ptr [ecx+4], 0FFh
403EED mov esi, ecx
403EEF jnz short loc_403F19
...
Show congratulation box
...
403F16 retn 10h
403F19
...
Show error notification box
...
403F3E retn 10h

It’s obvious, first of all I have to understand what’s the content of the procedure at 0x4033A0 address is. The procedure’s code is really long but a trained eye understands immediately what’s behind everything. Why? Mainly because there’s a big switch, and there are a lot of snippets with common things like “esi+0C”, “esi+438”, “add ecx, value”. When I face this kind of algorithm I always start trying to verify if it’s an implementation of a Virtual Machine or not. I could be wrong but most of the time VM protections are easily identificable. To verify the correctness of my assumption is quite easy, a glance to the snippets used to implement VM instructions is enough.

(0x00: nop):
403A45 nop
403A46 add [esi+438h], ebx   <-- update eip by 1
403A4C pop edi
403A4D pop esi
403A4E xor eax, eax
403A50 pop ebx
403A51 retn

This is the first snippet I checked, it only adds one to the dword pointed by esi+0x438. That’s a nop, almost every VM has a nop instruction.
Let’s take a look to another snippet:

(0x61: mov reg_i, dword_val):
4036F4 mov ecx, esi
4036F6 call loc_403360
4036FB test al, al
4036FD jz loc_40342F
403703 mov eax, [esi+438h]   <-- eip
403709 movzx edx, byte ptr [eax+1]   <-- register index
40370D mov eax, [eax+2]   <-- dword value
403710 mov [esi+edx*4+0Ch], eax   <-- save the value inside register
403714 add dword ptr [esi+438h], 6   <--- update eip
40371B pop edi
40371C pop esi
40371D xor eax, eax
40371F pop ebx
403720 retn

The snippet is used to move a dword value inside a register. The instruction has two parameters, the register index and the value to move.
As you can see it’s really easy to understand every single snippet with a possible VM implementation in mind.

The VM implemented inside the crackme is simple:
eip is stored at [esi+0x438]
flag is stored at [esi+0x42]
there are 8 dword registers stored from [esi+0xc] to [esi+0x28]
starting from [esi+0x2c] there’s a private memory buffer

It’s all quite easy except the memory buffer that needs some more details. The memory buffer used by the VM contains data that are not stored in a sequential way. What does it mean? Think of a memory buffer like a dword sequence where each bytes inside a dword has a specific meaning:
1° byte: is used to store the username’s chars
2° byte: is used to store the right_serial’s chars
3° byte: is used to store the fake serial’s chars
4° byte: you can live without knowing its meaning
So, an hypotetical piece of memory could be:

[esi+0x2c]: 5A 59 31 00   61 69 32 00   69 59 33 00 ...

where you can clearly see part of the username (“Zai”), part of the right serial (“YiY”) and part of the fake serial (“123”).
How does the crackme read a specific byte from the username, or the serial? The memory is addressed by a dword at [esi+0x42c], each byte of the dword is used as an index position of a specific byte of the username/right serial/fake serial.
i.e.: [esi+0x42c] = 02 00 01 00 means that the next operation over the:
– username will be done with the 1° byte of the 3° dword of the memory buffer (char ‘i’ of “Zai”)
– right serial will be done with the 2° byte of the 1° dword of the memory buffer (char ‘Y’)
– serial will be done with the 3° byte of the 2° dword of the memory buffer (char ‘2’)

Having said that, here is a description of the 0x62 VM opcode used to move a byte from memory buffer to a register.

mov reg_i, memory_buffer_j:
00403750 mov ecx, esi ; case 0x62
00403752 call loc_403360
00403757 test al, al
00403759 jz loc_40342F
0040375F mov eax, [esi+438h]   <-- eip
00403765 movzx ecx, byte ptr [eax+2]   <-- 2° operand
00403769 movzx edx, byte ptr [ecx+esi+42Ch]   <-- memory index position (refer to name, fake serial or right serial position)
00403771 movzx eax, byte ptr [eax+1]   <-- 1° operand
00403775 lea ecx, [ecx+edx*4+2Ch]   <-- position inside memory
00403779 movzx edx, byte ptr [ecx+esi]   <-- get byte from memory buffer
0040377D mov [esi+eax*4+0Ch], edx   <-- store byte into register
00403781 mov ecx, [esi+438h]
00403787 movzx edx, byte ptr [ecx+2]
0040378B add [edx+esi+42Ch], bl   <-- add 1 to the memory index position
00403792 add dword ptr [esi+438h], 3   <-- eip update
00403799 lea eax, [edx+esi+42Ch]
004037A0 pop edi
004037A1 pop esi
004037A2 xor eax, eax
004037A4 pop ebx
004037A5 retn

Every VM memory operation has the ability to increment the index memory pointer value, memory is read/write in sequential order.

Just another thing and then I’ll show you the complete algorithm with a simple keygen. Do you remember when I said “flag is stored at [esi+0x42]”? The VM has some conditional jump instructions and the only way to decide to jump or not depends on the value of a specific flag. This VM has one single flag, it takes 3 possible values: 1, 2 or 3 according to the result of a VM instruction like this one:

(0x81: cmp reg_i_val, dword_val):
40397C mov ecx, esi
40397E call loc_403360
403983 test al, al
403985 jz loc_40342F
40398B mov edx, [esi+438h]   <-- eip
403991 movzx ecx, byte ptr [edx+1]   <-- 1° parameter: reg index
403995 mov eax, [esi+ecx*4+0Ch]   <-- get dword value from reg
403999 mov ecx, [edx+2]   <-- 2° parameter: dword value
40399C cmp eax, ecx   <-- compare the two dword values
40399E jnz short loc_4039B2
4039A0 add edx, 6
4039A3 pop edi
4039A4 mov [esi+8], bl   <-- values are equal: flag = 1
4039A7 mov [esi+438h], edx
..
4039B8 mov byte ptr [esi+8], 3   <-- flag value 3
...
4039CB mov byte ptr [esi+8], 2   <-- flag value 2

There’s a compare between two values, one from a register and the other from a dword value. The flag at [esi+8] is updated depending on the compare result.
You can now imagine how a conditional jump instruction looks like:

4038C3 cmp byte ptr [esi+8], 2   <-- check flag value
4038C7 jnz loc_4035F9
4038CD sub ecx, [ecx+1]   <-- get the jump offset value
4038D0 pop edi
4038D1 mov [esi+438h], ecx   <-- jump!
...
4035F9 add ecx, 5   <-- add length conditional jump instruction
4035FC pop edi
4035FD mov [esi+438h], ecx   <-- update eip

The VM instructions are taken starting from 0x40A000, here is the initial byte sequence:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 61 01 00 00 00 00 61 02 00 00 00 00...

I showed you few instructions but the VM instruction set is simple, there are basically almost all the instructions of an x86 instruction set: mov, cmp, conditional jump and direct jump. Parameters are stored using little-endian system. The VM doesn’t have call instructions, and so it doesn’t need a real stack implementation.

The interesting part of the VM algorithm is:

0:   00                  nop 
     00                  nop
     00                  nop            
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop  
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop 
     00                  nop
     61 01 00 00 00 00   mov reg_1, 0x00000000 
     61 02 00 00 00 00   mov reg_2, 0x00000000 
     61 03 00 00 00 00   mov reg_3, 0x00000000
     61 04 00 00 00 00   mov reg_4, 0x00000000
     61 06 00 00 00 00   mov reg_6, 0x00000000
     61 07 00 00 00 00   mov reg_7, 0x00000000
     61 00 00 00 00 00   mov reg_0, 0x00000000
     20 00 00            mov m_0, 0x00
     00                  nop 
     20 00 01            mov m_0, 1
3F:  40 02               jmp $+2
41:  00                  nop 
42:  05 04               inc reg_4
     00                  nop 
     62 01 00            mov reg_1, m_0   <-- get current char from name
     00                  nop 
     81 01 00 00 00 00   cmp reg_1, 0x00000000
     00                  nop
     01 01               add reg_5, reg_0, reg_1
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
57:  54 15 00 00 00      ja $-0x00000015
     00                  nop 
     06 04               dec reg_4
     00                  nop
     60 00 03            mov reg_3, reg_0
     13 AC DC 00 00      mul reg_5, reg_0, 0x0000DCAC
     61 07 4A 00 00 00   mov reg_7, 0x0000004A
6E:  40 02               jmp $+2
     00                  nop 
     63 07 03            mov m_3, reg_7
74:  46 06               loop $-0x06
     00                  nop 
     00                  nop  
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
     17 88 77 66 55      xor reg_5, reg_0, 0x55667788
     00                  nop 
     60 05 00            mov reg_0, reg_5 
     00                  nop 
     14 15 00 00 00      div reg_5, reg_0, 0x00000015
     00                  nop 
     60 05 00            mov reg_0, reg_5
     00                  nop 
     03 06               mul reg_5, reg_0, reg_6
     60 05 00            mov reg_0, reg_5
     00                  nop 
     03 04               mul reg_5, reg_0, reg_4
     60 05 00            mov reg_0, reg_5 
     60 00 01            mov reg_0, reg_1 
     61 07 08 00 00 00   mov reg_7, 0x00000008 
A5:  14 3C 00 00 00      div reg_5, reg_0, 0x0000003C
     60 06 00            mov reg_0, reg_6 
     11 41 00 00 00      add reg_5, reg_0, 0x00000041
     60 05 00            mov reg_0, reg_5 
     63 00 01            mov m_1, reg_0           <-- save right serial char
     03 01               mul reg_5, reg_0, reg_1
     60 05 00            mov reg_0, reg_5 
     07 01               xor reg_5, reg_0, reg_1
     60 05 00            mov reg_0, reg_5 
     03 04               mul reg_5, reg_0, reg_4
     60 05 00            mov reg_0, reg_5 
     07 06               xor reg_5, reg_0, reg_6  
     60 05 00            mov reg_0, reg_5 
     17 00 BB 00 DD      xor reg_5, reg_0, 0xDD00BB00
     60 05 01            mov reg_1, reg_5 
D4:  45 2F               jnz_7 $-0x2F
     61 07 07 00 00 00   mov reg_7, 0x00000007 
     20 00 02            mov m_pos_2, 0x00       <-- my fake serial 
     20 00 01            mov m_pos_1, 0x00       <-- right serial
E2:  62 01 02            mov reg_1, memory_buffer_2
     62 02 01            mov reg_2, memory_buffer_1
     85 01 02            cmp reg_1, reg_2
     43 10 00 00 00      jb $+0x00000010
     44 0B 00 00 00      ja $+0x0000000B
F5:  45 13               jnz_7 $-0x13
     00                  nop  
     00                  nop 
     00                  nop 
     FF                  VM_SUCCESS

The implemented algorithm is quite easy, once you have the instructions sequence on a paper you can easily write a keygen. Here is a quick and dirty implementation of the keygen:

   sum = 0;
   for(int i=0;i< strlen(name);i++) 
      sum += (__int8)name[i];
   sum = (sum * 0xDCAC) ^ 0x55667788;
   q = sum / 0x15;
   r = sum % 0x15;
   len = strlen(name);
   tmp1 = (q * r) * len;	
   tmp2 = tmp1;
   for(int j=0;j<8;j++) {
      r = tmp2 % 0x3c;
      serial[j] = (char)(r + 0x41);
      tmp2 = ((((tmp1 * (__int32)serial[j]) ^ tmp1) * len) ^ r);
      tmp1 = tmp2 ^ 0xDD00BB00;
   }

Valid combinations are:
ZaiRoN / YiYaiQMm
crackmes.de / nmUQuME]
reversing / emAMy]Qy

That’s all folks!

By admin