/ crackmes

Crackmes: 4N006135 level-2

I found this crackme really interesting and I've learned a couple of stuff that will come useful within my long way into the reverse engineering learning process. I thought it would be a good idea to save somewhere a few notes that I took during the static analysis of these executables.

The "4N006135" by borismilner, is zip file that contains a total of 4 x86 binaries, each of those with an increasing level of difficulty. level-0 an level-1 are pretty straight forward while level-2 and level-3 took me some time and the Intel Software Developer's Manual constantly at hand.
In this blog post I'll focus my attention on (some aspects of) level-2 and how I built the relative keygen in Python.

Let's start

.text:004013E0                 mov     edi, 40D020h
.text:004013E5                 mov     ecx, 20h
.text:004013EA                 mov     al, 4Fh; 4Fh is the letter "O"
.text:004013EC                 rep stosb

REP executes an instruction (STOS) and decrease the value of ECX until ECX is 0. The value of ECX is 20h so 32 times.
STOS(B) copies the value (1 byte) contained in AL to [EDI] and increments/decrements (depending on the Direction Flag) EDI by 1. B can be replaced by W or D to copy value of 2 or 4 bytes respectively.

In Python:

s = bytearray(chr(0x4f) * 32)

I'm using bytearray instead of string because we'll need to modify part of this value (strings are immutable in Python).

Skip a few lines of useless code until:

.text:00401401                 rdtsc
.text:00401403                 mov     ebx, eax
.text:00401405                 push    ebx
.text:00401406                 push    40903Dh
.text:0040140B                 call    _printf

From the Intel manual:

The RDPMC (read performance-monitoring counter) and RDTSC (read time-stamp counter) instructions allow application programs to read the processor’s performance-monitoring and time-stamp counters, respectively.

[...]

The RDTSC instruction loads the current count of the time-stamp counter into the EDX:EAX registers.

Basically in this case the instruction is used as a pseudorandom number generator. The EAX part of the result is used as an User ID and stored in EBX for later usage.

Our keygen will ask this value:

uid = int(raw_input("Enter User ID: "))

What's next...

.text:00401413                 mov     edi, 40D020h
.text:00401418                 bt      ebx, 0
.text:0040141C                 jnb     short zero_bit_set
.text:0040141E                 mov     byte ptr [edi], 2Ah
.text:00401421 zero_bit_set: 
[...]                          

The value 40D020h already came up, but what it is? By observing all the remaining instructions of the program we can deduct that this is the memory location that will hold the final key (that we have to guess calculate) computed using a custom algorithm and the user ID random value.
So, quick recap:
EDI - pointer to the memory location where the key is stored. This at the moment contains a string of 32 "O"s
EBX - the random User ID

The BT instruction takes the bit 0 of EBX and stores the result in the Carry Flag (CF). JNB jumps if CF is 0.
We are basically checking if the user ID number is even or not. If the number is odd the first value of the key will be 2Ah (symbol "*") otherwise remains "O".

Check if User ID number is even

Let's add this into our python script:

if ((uid % 2) != 0):
 s[0] = chr(0x2a)

Then the program checks if the number of user ID is bigger than 0B16B00B5h :))

; zero_bit_set:
.text:00401421                 inc     edi
.text:00401422                 cmp     ebx, 0B16B00B5h
.text:00401428                 ja      short above_b16b00b5h
.text:0040142A                 mov     byte ptr [edi], 2Ah
.text:0040142D above_b16b00b5h:    
[...]                    

if not, change the second value of the key to "*"

Python:

if (uid <= 0x0b16b00b5):
 s[1] = chr(0x2a)

Then the Parity Flag (PF) is evaluated. This is, however, done over the value of EDI, which now will be 40D022h (we "inc EDI" twice since now). 404D022h in binary is "10000001101000000100000" and so the PF will always be 0.

; above_b16b00b5h:  
.text:0040142D                 inc     edi
.text:0040142E                 jnp     short no_parity
.text:00401430                 mov     byte ptr [edi], 2Ah
.text:00401433 no_parity:                              [...]

the jump won't be taken and the third value of the key will always be "*".

Same for the 4th as shown in the next two lines:

; no_parity:
.text:00401433                 inc     edi
.text:00401434                 mov     byte ptr [edi], 2Ah

Let's implement this in Python:

s[2] = chr(0x2a)
s[3] = chr(0x2a)

and analyze the next instructions:

.text:00401437                 mov     ecx, 1Ch
.text:0040143C get_byte:                               
.text:0040143C                 shr     ebx, 1
.text:0040143E                 mov     edx, 0
.text:00401443                 mov     eax, ebx
.text:00401445                 mov     esi, 1Ah
.text:0040144A                 div     esi
.text:0040144C                 test    ecx, 1
.text:00401452                 jz      short add_97
.text:00401454                 add     edx, 41h
.text:00401457                 mov     [edi], dl
.text:00401459                 inc     edi
.text:0040145A                 loop    get_byte
.text:0040145C                 jmp     short go_on
.text:0040145E add_97:                                
.text:0040145E                 add     edx, 61h
.text:00401461                 mov     [edi], dl
.text:00401463                 inc     edi
.text:00401464                 loop    get_byte
.text:00401466
.text:00401466 go_on:                                  
[...]

This is obviously a loop as we can see from instruction at address 00401464.
LOOP decreases ECX and jumps to the specified address until ECX is 0. This is done 28 times (ECX is 1Ch).

PS.: remember that our EDI register still points to the third value of our key, since EDI was not increased after the last mov instruction at 00401434

What every cycle does is:

  • shift right the value of User ID by 1 bit
  • take the remainder of the division between User ID and 1Ah
  • if the value of ECX is even, add 61h to the remainder; else, add 41h to the remainder
  • save the remainder value to EDI (our key, remember?)
  • increase EDI
  • decrease ECX
for x in range(28,0,-1):
 uid = uid >> 1
 z = uid % 26
 if ((x % 2) == 0):
  z += 0x61
 else:
  z += 0x41
 s[31-x] = chr(z) # starts from s[3] 

At the end we'll have the final key value.
The remaining instructions of the program ask the operator to insert the key, and the value entered is compared with the one just computed in memory.

If they are equal... WIN!

Our final python script:

s = bytearray(chr(0x4f)*32)

uid = int(raw_input("Enter User ID: "))

if ((uid % 2) != 0):
 s[0] = chr(0x2a)

if (uid <= 0x0b16b00b5):
 s[1] = chr(0x2a)

s[2] = chr(0x2a)
s[3] = chr(0x2a)

for x in range(28,0,-1):
 uid = uid >> 1
 z = uid % 26
 if ((x % 2) == 0):
  z += 0x61
 else:
  z += 0x41
 s[31-x] = chr(z) # starts from s[3] 
 
print ("Password: %s" % s )

Win