REVERSING: Study and programming of a KEYGEN.

1. Introduction and Objectives

In this article, we are going to superficially explain how certain software licenses are generated. To do this, we will study a particular code, with the aim of subsequently programming a key generator that can generate a correct key from any text string.

For this, we will need to have at least basic knowledge in assembler programming (ASM), and in reverse engineering. As tools, we will use the OllyDbg debugger and the DevCpp compiler to program the key generator (Keygen) in C/C++.

If you are interested in conducting a cybersecurity audit of your applications, you can take a look at our services for application audits and black box, white box, and gray box processes.

2. Context and Data Obtained

After statically disassembling a binary responsible for the activation of a software license, the following piece of code has been obtained. This piece of code is responsible for checking that a specific user has a correct license for the use of a particular software.

After executing this part of the code, a text corresponding to the credential for validating the software license is displayed. This credential is generated from a text string stored in the binary itself. Our task will be to understand how this credential (password) is generated from any text string (username), in order to subsequently program a key generator (keygen).


3. Static Analysis: Basic Blocks

The first thing we must do through the static analysis of the obtained code is to divide it into basic blocks in order to have a better understanding, as we can see in the following image.

4. Static Analysis: Execution Flow

Once we have obtained the basic blocks and interpreted what each block does, we proceed to create a flowchart to better understand how the execution of the piece of code is carried out at the process level.

As we can see, there is a control structure in the following blocks, which will guide the main flow of execution.

5. Dynamic Analysis of the Code

From this point, we are going to conduct a more in-depth study of the code obtained. To do this, we must insert the code into our debugger “OllyDbg,” with the goal of being able to execute it step by step and see in more detail what the program does during its execution.

To be able to insert the code into the debugger, we have disassembled a random 32-bit binary with OllyDbg, and then edited and replaced any function in that binary with our assembly code, with some minimal modification that we will see next to make it work correctly.

Once we have edited and replaced it, we proceed to set the new “entrypoint” at the first instruction of our code so that the execution of the program begins at that instruction.

Once the code execution is launched, we can see that space is reserved to introduce the string of characters entered by argument. This string of characters is entered into [EBX – 19A0], with EBX = 2BBA9C, leading us to the conclusion that the string is going to be entered at 2BBA9C-19A0 = 2BA0FC.

That is why we proceed to copy the string of characters provided in the original code, “3jd9cjfk98hnd,” into that memory area.

Continuing with the code execution, in the next screenshot we can see how the string of characters is transferred to the EAX register, and how EAX (the string) is saved on the stack at [EBP-14].

In the following screenshot, we can see the string of characters contained in the EAX register, now saved on the stack at [EBP-14], which means at B1FF80 – 14 = B1FF6C.

In the next screenshot, we can see how the ‘strlen’ function is called to calculate the length (the number of characters) of the string, and the result is stored in the EAX register, with a value of ‘D’ in hexadecimal, equivalent to ’13’ in decimal. As we can verify, indeed the string ‘3jd9cjfk98hnd’ has 13 characters.

Continuing with the execution of the code, in the next capture, we can see that a jump (jmp) is performed to a comparison that jumps to another memory position (<main+71>, i.e., 47C33C), if the comparison between a variable that is initially given the value of “0” is less than “D” (“13” in decimal). We have just entered a loop in charge of going through each and every character in the string to operate on them.

In each iteration of the loop, it takes characters one by one and multiplies them one by one by the total length of the string, that is, by “D.” The results of each operation are summed and stored in an area of the stack that will be shown in upcoming captures.

We can see the comments in the code for greater clarity.

In the next capture, we can see how the first character of the string, the number “3”, equivalent to “33” in hexadecimal, is taken in the EAX register.

Subsequently, the hexadecimal value of the first character (“33h”) is multiplied by the total number of characters in the string (“Dh”), giving a result of 297h, which is then stored in the stack. We can also observe how the EDX register is incremented by “1” so that in the next iteration it processes the next character in the string.

Continuing with the execution of the code, in the next capture, we can see how the multiplication of the second character (“j”), equivalent to “6A”, by “D” (length of the string), gives a result of 562h.

The same operation is performed with the third character. Here, we can also see how the results of the multiplications are being added together and stored in the stack. Up until now, with a value of “7F9”.

Afterward, we can observe how the sum of the results from the different multiplications is stored in the stack. This continuous summation of the products of individual characters and the length of the string plays a crucial role in the given algorithm.

Once it has gone through the entire character string, meaning it has performed the 13 iterations, it will check through the instruction “CMP EAX, DWORD PTR SS:[EBP-18]” that EAX is no longer less than “D” (“13” in decimal) and therefore will exit the loop to continue with the program’s execution. At that moment, we can observe what has been the total result of the operations performed (“3AA7”).

Afterwards, the final result is displayed on the screen by calling the “printf” function. Once the call to the “printf” function contained in the system library “msvcrt” is completed, the parameters saved at the beginning of the code in the stack are restored, and finally, control of the program flow is returned using the “RETN” instruction.

6. Programming the Key Generator (Keygen)

Having reached this point, we can now proceed to program our “keygen”.

As we have mentioned in previous paragraphs, this type of code can be found in certain software where registration is required to use the program. In this case, a specific ‘password’ is generated from a given ‘name.’

Below, we can see the programmed code along with its corresponding comments.

After compiling the code, we can observe the successful execution of the binary by passing the character string contained in the original code as an argument. We can verify that the obtained results are correct, just as they were presented during debugging with OllyDbg. This ultimately yields ‘3AA7h’ as the final result, which is equivalent to ‘15015’ in decimal base.

The programmed code has been designed to accept any string input via arguments (*argv[]) from the user itself, making it adaptable without further modifications.

Thus, for the username ‘Congratulations!’, we can observe that the validation credential is ‘26080,’ as shown in the result provided by the binary in the following image.

7. Conclusions

In this article, we have analyzed the basic code of a key generator and programmed our own keygen through the study conducted via static and dynamic analysis of the original code.

Through this process, we have observed how certain validation credentials are generated from different text strings input as a username.

It goes without saying how important it is for commercial software to test the various licenses they present, in order to prevent different cybercriminal groups from cracking their licenses and taking advantage of the software’s functionalities.

If you are interested in conducting a cybersecurity audit of your applications, you can take a look at our services for black-box, white-box, and grey-box application audits.

For those interested in gaining knowledge in ethical hacking, you can enroll in our basic ethical hacking course. Alternatively, if you prefer to acquire more advanced knowledge in offensive cybersecurity, you can opt for our advanced course.


No puedes copiar el contenido