Keygenme 0x00 - "Keygenme #2" by OldSoft

Next: 0x01 - simpledatas_keygenme_1

"Keygenme #2" by OldSoft and modernized by wolverine2k can be found here. This is classified on the site as a "Very Easy" C/C++ program for the Windows platform.

If we run the program, we can see that the functionality is very simple: first, the program asks for our name. Then, it asks for the serial number. Finally, it provides feedback based on these values we provide, and calls system("pause") before exiting.


If we open this in IDA, IDA starts us at "mainCRTStartup" which initializes the C runtime library - something not necessarily useful for us here. If we click into "__main" via the Functions window, we can't quite find any valuable code here either.

One trick we can employ is to search for the strings used within the program. Pressing Ctrl+12 will bring up the Strings window. Locate where it says "Enter your name:" and double-click it to load its line in the read-only data (rdata) segment. Click the string and press Ctrl+X to load all cross-references to this string. There is only one, which should be the location of the relevant code. Press OK to load this section. If you are not already in graph view, change to it now by right-clicking the screen and selecting the "Graph View" option.

The code here is as simple as it seemed when we ran the software: two printfs provide our name and serial number prompts, and two scanfs (using "%s" as their format modifiers to format as string) place our input into two buffers. Then "_Z14generateSerialPc" is called, right before a strcmp. Presumably, this strcmp compares our inputted serial number against the generated serial number, and adjusts program flow accordingly.


There are at least two ways to bypass this protection. The first method, patching, is the simplest. Through patching, we edit the assembly code directly to achieve a more desirable program flow. For example, we could replace the "test eax, eax" with an instruction that would set the ZF (zero flag) no matter what, thus taking the jz jump (jump if zero flag set) every time. Or, we could replace the jz jump with an unconditional jump, so that we jump every time, or a jnz jump, so that we jump every time the ZF is not set, meaning that the inputted serial value is not correct (very high probability if we just guess values).

As you can see, patching the software to bypass this protection would be very simple, but if we check the crackme post, the author asks us not to patch the software. Instead, the objective is to create a keygen. The goal of a keygen is to replicate the password/secret code/serial number generator function so that a valid serial number can be reliably inputted without any changes to the executable itself. In order to craft a keygen, we will need to reverse-engineer the _Z14generateSerialPc function.

If we enter this function, the first "box" appears to contain some initialization/setup: esp is decremented to make room on the stack, rcx is moved to rbx to be used as the parameter for strlen, edx is set to 1, and the r9 register is cleared.


There are two things to call out here: first, strlen will evidently be used to generate our serial number, and it is likely based on the name field as that is the only other input we provide. This is very significant. The other item of note is that, with edx initialized to 1, we may be preparing for a loop.

Sure enough, the next box moves edx (which is 1) into ecx (which is commonly referred to as "the counter register"), and rcx is compared to rax, which is the return value from the earlier strlen. If the incrementor is less than or equal to the length that strlen outputted into rax, then we take the "jbe" (jump below or equal) jump.

In other words, this seems to be a "for loop:" for(i = 1; i <= strlen(name); i++)


If our assumption about the loop is correct, we'll take the jump to the left box (denoted in IDA by the green line). Let's break this one down slowly:

  • First, rdx-1 is loaded in ecx, but since the two should be the same, this is the same as "ecx -= 1." 
  • Then, r9+rdx+3 is loaded into r9, which is the same as r9 += rdx + 3, which will evaluate to r9 += 4 on the first loop. 
  • Next, 1 is added to edx, which is what will eventually be loaded back into ecx for incrementing. This also has implications for the above r9 += rdx + 3 math (+= 4 on the first loop, then += 5, then +=6...). 
  • Then, ecx is set to [rbx+rcx]. At this point, rcx is zero, and we made the assumption in the first box that rbx might be our name buffer, so this could be iterating over our name buffer to obtain each character's value. 
  • Finally, and perhaps most significantly, r9 and ecx are added together and the result is stored in r9. Since r9 is not cleared throughout the duration of the loop, the sum in r9 could be significant.



Finally, once we iterate through boxes 2 and 3 for the length of the name buffer, we land in the final box:

  • First, it places rax, which is the length of name, into rdx. 
  • Then, rbx is placed in rcx, which comes pre-commented with "Dest" - presumably for the upcoming sprintf call to store the output. 
  • Next, some math is applied to rdx, which contains the length of "name:" first, it is divided by two, then multiplied by 3, then added to the original length of the "name" buffer. The final result is stored in r8.
  • Finally, a format string of "%d-%d" is loaded into rdx before the aforementioned call to sprintf. So, the serial number takes the format of integer-integer.


At the call to sprintf, we should know the values in r8 and r9, its two format parameters. r8 is based on some math related to the length of the name, and r9 is the sum of the values of each character plus a value which starts at 4 but increments on every loop.

Let's set a breakpoint using F2 on the sprintf call to confirm our thinking. Begin debugging with F9, entering "aaaa" as the name and "bbbb" as the serial number. Once we hit the breakpoint, let's check r8 and r9.

We expect r8 to be (len_name/2) * 3 + len_name. With a length of 4, this should be 10. Sure enough, r8 is 0xA.

We expect r9 to be the value of each letter summed together, plus 4+i (with i initialized to zero) for every character in the name. Since lowercase "a" has an ASCII value of decimal 97, this would be: 97 + 4 + 97 + 5 + 97 + 6 + 97 + 7 = 410, or 0x19A. We can see that this is correct as well.


With the format "%d-%d" and the parameters stored in r8 and r9, we would expect the serial number for "aaaa" to be "10-410". If we enter these values into the program, we can see that they are correct!


We've solved it! We can bypass the serial number generator, but without doing more math by hand, we only know the serial number for the name "aaaa". Let's write a Python script to automate this (Python 2.7):


print "input name:"
name = raw_input();

#calculate the first number of the serial
first_num = (len(name)/2) * 3 + len(name)

#calculate the second number of the serial
second_num = 0
add_num = 4
for letter in name:
    second_num += ord(letter) + add_num
    add_num += 1

#combine the two numbers in a string
serial = str(first_num) + "-" + str(second_num)

print "serial number is: \n" + serial


When we run this, we are asked to provide a name. Once the name is provided, the password is generated:


input name:
alexander
serial number is: 
21-1020


If we test this, it works as well!

Finally, you can take this a step further and automate the name generation process as well. Below is some code which will randomly generate a 20-character lowercase ASCII string:


import random

#generate a random lowercase ascii string of 20 chars
name = ''
for i in range(0,20):
    name += chr(random.randint(97, 122))
print "name is: \n" + name

#calculate the first number of the serial
first_num = (len(name)/2) * 3 + len(name)

#calculate the second number of the serial
second_num = 0
add_num = 4
for letter in name:
    second_num += ord(letter) + add_num
    add_num += 1

#combine the two numbers in a string
serial = str(first_num) + "-" + str(second_num)

print "serial number is: \n" + serial


name is: 
ymejjtvhogzwyhuzuzdf
serial number is: 
50-2511

Next: 0x01 - simpledatas_keygenme_1