This article is designed for beginners who are interested in reverse engineering and have a basic understanding of the work of the CPU, the assembly language. This crackme is relatively old and simple, but in solving it the same techniques are used as in solving more complex ones. In the open spaces of the Network, you can find several articles with his analysis such as
this one , and he
is also mentioned
here (crackme with a story), however those solutions are not as detailed as this. At one time, I strongly lacked such a line-by-line parsing, where I could look in when I got confused and do not understand what a particular code section does. If this post will be useful for at least one person, then I tried for nothing. All screenshots (except the first) are clickable. Enjoy your reading.
So, we have a simple crackme, run it and see how it works.
Yeah, it's pretty simple, we have to enter the correct serial. Now open the program in disassembler. As a rule, disassembled listings, even relatively simple programs, are rather voluminous. To determine the part of the code that checks serial input, we will find where in the program's memory a line is stored with the error message βFail, Serial is invalid !!!β and which code refers to this line.

')
In the data section, we see the desired string, and below it, the disassembler forms a link to a section of code that refers to the string, we follow the link.
Loc_140001191 is a label for the transition, below we see the line "
call cs: MessageBoxA ". Suppose a function is called this way that draws a window with the text βFail, Serial is invalid !!!β. It turns out that if we enter the wrong serial, the control is passed exactly by the tag
Loc_140001191 . Ok, let's continue to dig and figure out where else there is an appeal by the same tag, for which we will follow the link to the right of it, which we carefully arranged for us, the disassembler.

This is a very important part of the code, look closely at it. We see the call of the function β
GetDlgItemTextA β, in the title there are the words
get and
text , obviously this function reads the entered serial. After the function has completed, the command β
lea rcx, [rsp + 78h + String] β loads the register
rcx , the address of something from the stack. It then writes the value from
eax to
edx and calls the
sub_140001000 function. Manipulations with registers before the call are not random, thus passing the arguments to the function, and after the function has completed, the command β
test eax, eax β checks the contents of
eax , if it is not
0 , then the addresses of strings reporting that all OK. Then the window drawing is launched with the corresponding notification. If, after work,
sub_140001000 in
ax is
0 , then control is transferred to the previously considered label, where we will receive an error message. We conclude:
sub_140001000 returns the value in the
eax register. Now we can recover the logic of this crackme: the
GetDlgItemTextA function is
called ,
let me remind you that it presumably reads our serial, then the
sub_140001000 function is
called, which as arguments receives the address of something from the stack and the value of the
eax register through the
edx register , if the function returns βnot zero β, then the correct serial number was entered; ifβ zero β, it is not correct. Suppose that it is
sub_140001000 that checks our input, rename the labels and functions in our listing for convenience and clarity. Now our listing looks something like this.

So the floor is done. Now let's see what the
check_func function
gets as arguments, for this we will run the program in the debugger before calling the function and see what is stored in
rcx and
edx .

I entered
12345 . The execution stopped at the call of the check function. We look at the registers, in
rcx is the number
12F660 , as we know this address, let's look at the memory dapm using it in the window below, aha there is our input, now we look at
edx (itβs the younger part of
rdx ) there is the number
5 , and this is our serial. So we figured out which arguments and what the check function returns, now we can assume what its prototype could look like:
int check_func (* char, int) . It's time to study it. Open the check function in disassembler. It is quite large, so we analyze in parts.

Everything is relatively simple here. Let me remind you that the
edx register stores the size of the entered serial number, and
rcx has its address in memory. It can be seen that, first of all, the function
check_func checks the length of the entered number. It should be equal to
19 (
13 in hexadecimal system), if this is the case, then the check continues (go to the
good_size tag), if not, then the control goes to the
bad_serial tag, where the command β
xor eax, eax β
clears the eax register and exit function. We conclude:
19 characters in a valid serial number, and which ones, let's look further. We note that almost immediately the address of the entered number is placed in the register
R8 .
We continue to investigate the verification mechanisms.

We remember that
R8 points to the first element of the serial, which means that the address of the 5th element will be stored at the address
R8 + 4 , it is recorded in
RAX . Further we see a very strange set of commands, which we will meet more than once:
xchg ax, ax db 66h, 66h xchg ax, ax
Now more:
β
Xchg ax, axβ is the equivalent of the β
nop β command on the
x86 platform.
The next line is the so-called prefix, it is used to indicate the bit depth of the next instruction. This code we can ignore. After these instructions there is a check of the following kind: what is located at the
RAX address (and there the address of the
5th element of the serial number) must be equal to the number 2D (in the hexadecimal system). 2D is the ASCII code of the β-β sign, we draw the following conclusions:
1. A function that reads our input reads a string, not a number,
2. In our serial will be several groups of 4 numbers separated by hyphens.
If the 5th character is not β-β, then the serial number is not correct, a transition to the bad_serial label and exit from the function with a value of 0 occur, but if there is a hyphen at the 5th position, then
RAX is increased by 5, now when it points to 10 the element is checked which character is at this position. In total, such a check is carried out 3 times, every 5th character of the serial is β-β, and there are only 19 characters in it, which means it has the form: XXXX-XXXX-XXXX-XXXX. If the input format is appropriate, then verification continues.
The next section of the function is not of particular interest to us.

Go ahead.

Pay attention to the following:
R9 always points to the first element of the block, and
RCX keeps the offset inside the block, so after checking,
R9 increases by 5 to get to the next block, and
RCX is reset to zero after checking the block, remember that there is zero in
RDX , therefore, zeroing occurs on the
zero_to_rcx tag. The line β
add eax, 0FFFFFFD0h β is not as simple as it seems. In fact, it is not addition, but subtraction. Yes, yes, do not believe your eyes,
0FFFFFFD0h for a computer is the number -30h, apparently the fact is that the command β
add eax, 0FFFFFFD0h β is more optimal from the point of view of the compiler than β
sub eax, 30h β. In
Eax, the ASCII code of the element is stored, 30h is subtracted from it and compared with 9. The meaning here is: ASCII codes of digits from 0 to 9 are 30h - 39h, respectively, so if you subtract 30h from the digit code, the result will not exceed 9, but if it exceeds , it means that in eax the code was not a digit, but some other sign, and then we go by the label
wrong_serial . Our serial should consist of numbers and hyphens. Then the codes of all 4 elements of the block are summed, the code of the 4th element is added 3 times and 150h is subtracted. The result is pushed onto the stack, we add up the sum of all the blocks in R10 and the result is divided by 4.

Now the sums are checked for all 4 blocks. The value for each block should be equal to the sum divided by 4, in other words, the sum of the ASCII code of the first character plus the code of the second, third plus 3 times the code of the fourth and minus 150h should be the same for all blocks.
Remained the last stage of verification.

Everything is clear, in the serial should not be the same blocks. Do not forget that
R8 stores the address of our input,
RAX here is used to select the character inside the block, and
R8 to select the block inside the serial number.
Now we know how the check is performed and we can write keygen. I tried to describe the solution in as much detail as possible, questions can be asked in the comments, thank you for reading.