Intro

The “4N006135” by borismilner contains a total of four x86 binaries, each of them with an increasing level of difficulty. Level-0 and Level-1 are pretty straightforward, while Level-2 and Level-3 took me a bit more time and the Intel Software Developer’s Manual at hand.
In this post I’ll focus on (some aspects of) Level-2 and how I built the corresponding keygen in Python.

Solution process

Let’s jump into it, and start with the following disassembled section in .text:

.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 this means 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 the value of 2 or 4 bytes respectively.

In Python this would look something like this:

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.

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

Our keygen will ask for this value:

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

Moving on:

.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 came up before, but what is it? 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.
A quick recap so far:
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”.

Let’s add this to 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, it changes the second value of the key to “*”

In 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 have performed “inc EDI” twice so far). 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 analyse 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 the 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 in the loop does is:

  • shift right the value of User ID by 1 bit
  • take the remainder of the division between User ID and 1Ah (User ID mod 26)
  • 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 user to insert the key, and the value entered is compared with the one just computed in memory.

If they are equal… we have found the right key!

Solution

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 )