📜 ⬆️ ⬇️

Study protection Artmoney. Part one

Greetings ArtMoney itself was zaigengen me a long time ago. This is not the first time I've been trying to start writing an article about how this program was cached, but it has always stopped somewhere. This time, I decided to finish everything to the end! Plus, this article can be considered a continuation of a series of articles on cracking for beginners.

So, in this article you will learn how I wrote the keygen to ArtMoney (version 7.45.1 will be described here ).

Stage One: Analysis of the executable file


I installed the English version of the program so that IDA and other utilities look for the text normally.

First of all, you need to find out what AM is written / packed with. Open it (am745.exe file ) in my favorite ExeInfo PE :
')


We are told that this is Aspack v2.24 - 2.34 . Well, seemingly not a complicated packer. I will remove it by the first automatic unpacker (for the article is not about unpacking).

Stage Two: Analyzes Again


We look again at ExeInfo PE , and we see that the program is written in Borland Delphi . Perfectly! Let's use the super program for analyzing Delphi programs: IDR ( Interactive Delphi Reconstructor ) . By the way, she also has open source . The IDE version is there and defined.

Download all available databases and put in the directory with the IDR . We are dragging our unpacked test subject into the reconstructor, waiting for the end of the analysis.

I will do the whole research process in IDA Pro (plus Hex Rays ), so let's generate an IDC script that tells her about all the names of forms, methods, classes, etc. Click ToolsIDC Generator in the IDR menu. We are waiting for the script to be created.

Next, open the IDA , and also shove our program into it. Let's wait for the end of the analysis. Then apply the generated IDC script: in IDA Pro, click FileScript File ... , select the script. We are waiting for the application.

In order for IDA to properly decompile Delphi code, it needs to say that we are dealing with a Delphi compiler, and __fastcall calls. Unfortunately, the generated IDC , like the IDA itself, doesn’t say / know about it.

Go to the settings of the IDA compiler: OptionsCompiler ... :


Next, click to re-analyze the program: OptionsGeneralAnalysisReanalyze program and OK .

Not everything ... :) The IDC- script for some reason did not mark the boundaries of library functions. Because of what the process of disassembling and decompiling does not become easier. We'll have to mark up and specify the types ( Y key). We take call on the known function and we look at its borders. If the function does not start where the call leads, correct it. Go to the instruction that is above the beginning of the function (most often, it is retn ), and press E (specify the address of the end of the function) there. That's better.

Now you need to specify the type of function. Take for example @LStrClr . The caring IDR pointed out in the comments that this function takes one argument - the address of the string, which means we denote the function's prototype as (remember that in Delphi the fastcall call convention ):

void __fastcall LStrClr(char*) 

I hope it is clear why. Well, void because the procedure .

And so we repeat with many and many functions ... When specifying prototypes, we use these simple rules:


Well, now you can start searching for the registration procedure ...

Stage three: Where are you, my beloved, where?


We turn to IDR to the forms, and (I’ll say right away, open the Form28 form), switch to the visual viewing. We see the registration window. Scroll through the window and find the three outlines of the buttons. The left one is the OK button, the registration application:


Stage four: Analyze onclick (). Surface


Right-click on it and go to the OKClick handler. In IDA, go to the address of the beginning of this procedure (button G ).

First, we point out that this is a bp-based function (i.e., addressing local variables goes through the EBP register minus offset), otherwise local variables are not recognized. We press Alt + P (or PKM by function, Edit function ... ), and set the BP-based frame .

Let's try to decompile ... If everything went well, we will see a terrible pseudocode decompiler, otherwise fix the prototypes:


We press Y on each call to the library / self-writing function, and make sure that the prototypes are with __fastcall , and the arguments are correctly specified.

The first cycle, judging by the IDR , is checking the key for alphabetic belonging of characters, and gluing them into a global variable. Call it g_LicKey .

Next comes the call to an unknown function so far, and, apparently, this is actually the key verification function itself ...

Stage Five: Key Verification (Side View)


This function takes two arguments: the first is the out parameter, it will contain some error code, and the second is the letter (' A ' is in the English version, or ' B ' is in Russian).

Decompiling ...

The code is quite large, I agree, but if you approach it correctly, slowly, and without shying away from the abundance of code, you can successfully figure out what is happening here.

Very often you can come across similar code that issued a decompiler (variable names, of course, may differ):

  v2 = g_LicKey; if ( g_LicKey ) v2 = (char *)*((_DWORD *)g_LicKey - 1); 

Here it is worth saying that Delphi has its own special string type, and in the form of a structure it can be described as follows:

 d_str struc ; (sizeof=0x8, mappedto_243, variable size) _top dd ? length dd ? string db 0 dup(?) ; string(C) delphi_string ends 

The first dvd is always 0xFFFFFFFF , the second is the length of the string, and then the string itself goes. Therefore, similar code constructs only get the length of the string.

Another important note: Delphi row indices because of this come with 1 , so you will often see subtraction 1 from the index in the decompiler / disassembler (to match the actual arrangement of characters in memory).

Next comes the length check: > = 70 and <= 500 .

Further, we see the verification of the first key symbol for equality to the second argument of the function, i.e. ' A ' or ' B ', and setting the flag depending on the character. I called this flag rus_ver .

Agree, it looks cumbersome:

 LStrFromChar((char *)&v193, (char *)(unsigned __int8)g_LicKey[2]); gvar_006F58C0 = Pos(v193, *(char **)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_); gvar_006F58C8 = *(_BYTE *)(*(_DWORD *)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_ + (unsigned __int8)gvar_006F58C0 - 3); gvar_006F58C9 = *(_BYTE *)(*(_DWORD *)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_ + (unsigned __int8)gvar_006F58C0 - 4); LStrFromChar((char *)&v192, (char *)(unsigned __int8)g_LicKey[1]); v242 = Pos(v192, *(char **)_abcdefghijklmnopqrstuvwxyzABCDEFGHIJ1234567890KLMNOPQRSTUVWXYZ_); 

And so much better:

 LStrFromChar((char *)&lic_key, g_LicKey[2]); g_PosChar2 = Pos(lic_key, str_EngAlpha); g_AlphaChar1 = str_EngAlpha[g_PosChar2 - 3]; g_AlphaChar2 = str_EngAlpha[g_PosChar2 - 4]; LStrFromChar((char *)&key_char_1, g_LicKey[1]); key_char1_pos = Pos(key_char_1, str_EngAlpha); 

Next - check key_char1_pos for equality 12 .

Now there is a summation of the key symbols, except for the last two, and, as long as the sum is greater than 0xFF , division by 2, and increment by 1:

 idx = key_len_minus_2 - 2; if ( key_len_minus_2 - 2 > 0 ) { key_idx = 1; do { key_sum += (unsigned __int8)g_LicKey[key_idx++ - 1]; --idx; } while ( idx ); } for ( ; key_sum > 0xFF; key_sum = (key_sum >> 1) + 1 ) ; 

We convert the resulting amount to a hex-string, and then to lowercase .

Now we get the last two characters of the key, transfer each of them to some function, and at the output we get by the transformed character. Let's take this function ...

Stage Six: Character Conversion


In general, the character conversion function looks like this:

 LStrFromChar((char *)&inChar_2, inChar); v3 = Pos(inChar_2, str_EngAlpha); if ( v3 > 0 ) { if ( g_PosChar2 > 0xAu ) { for ( i = 1; i < g_PosChar2 - 5; i += 2 ) { if ( i == v3 ) { v3 = i + 1; } else if ( v3 == i + 1 ) { v3 = i; } } } for ( j = g_PosChar2 + 1; j < 60; j += 2 ) { if ( j == v3 ) { v3 = j + 1; } else if ( v3 == j + 1 ) { v3 = j; } } v6 = v3 - g_PosChar2 + ((char)(v3 - g_PosChar2) < 0 ? 0x3E : 0); if ( flag_1 ) v2 = str_RusAlpha1[v6 - 1]; else v2 = str_EngAlpha1[v6 - 1]; } return v2; 

It seems simple. I have to say, we have to invert it. Let's call it DecodeChar .

As a result, the last two characters of the key are converted by this function, and compared with the previously obtained hex-sum of all the other key characters.

ToRevert (with this word I will mark the important points in the reverse of the key verification function ): At the very end, when the key is ready, we consider the sum of its characters, convert to hex ( 0xXX ), then each nibbl is inverted DecodeChar , and paste to the key.

We see further that the third character of the key is converted, translated from hex to int , and the bits are checked. So let's call it all:

 v202 = meffi_DecodeChar(g_LicKey[3], 0); PStrCpy(&v132, str_Hex); v134 = v202; v133 = 1; PStrNCat(&v132, &v133, 2); LStrFromString((char *)&v129, &v132); key_char_3 = StrToInt(v129); k3_flag1 = (key_char_3 & 1) != 0; k3_flag2 = (key_char_3 & 2) != 0; k3_flag3 = (key_char_3 & 4) != 0; k3_flag4 = (key_char_3 & 8) != 0; 

While we do not know the purpose of the bits, we therefore give them at least some meaningful names.

Again a function that we do not know, and one of the flags is passed to it:

 key_idx = 5; sub_66408C(&key_idx, k3_flag1, &v128); 

The last parameter seems to be the output string, since passed on to Trim () and used later.

Immediately give this function the corresponding prototype:

 void __fastcall sub_66408C(int idx, bool flag, char *output) 

Stage seven: Reading a string from the key


Yes, this is exactly what this function does. That shows debugging, and a quick overview of the code. But first things first.

 while ( g_LicKey[*(_DWORD *)idx_1 - 1] != g_AlphaChar1 && LStrLen(g_LicKey) >= *(_DWORD *)idx_1 ) 

Immediately, we note that the first parameter is used as a pointer to dword (int), so we change its type to int * (in Delphi, this is called var arguments).

After all type conversions, and adjustment of arguments, our function takes the form:

 while ( g_LicKey[*idx_1 - 1] != g_AlphaChar1 && LStrLen(g_LicKey) >= *idx_1 ) { c1 = g_LicKey[*idx_1 - 1]; if ( c1 == g_AlphaChar2 ) { LStrFromChar((char *)&c1_str, c1); c1_str_ = c1_str; LStrFromChar((char *)&c2_str, g_LicKey[*idx_1]); c2_str_ = c2_str; LStrFromChar((char *)&c3_str, g_LicKey[*idx_1 + 1]); LStrCatN(c3_str, c2_str_, c1_str_, gvar_0070DF4C); PStrCpy(&str_hex, str_Hex_0); cc[1] = meffi_DecodeChar(g_LicKey[*idx_1], 0); cc[0] = 1; PStrNCat(&str_hex, cc, 2); PStrCpy(&hexVal, &str_hex); cc[1] = meffi_DecodeChar(g_LicKey[*idx_1 + 1], 0); cc[0] = 1; PStrNCat(&hexVal, cc, 3); LStrFromString((char *)&hexVal_1, &hexVal); value = ValLong(hexVal_1, &outCode); LStrFromChar((char *)&value_1, value); LStrCat((char *)&output_2, value_1); *idx_1 += 2; } else { LStrFromChar((char *)&keyChar, g_LicKey[*idx_1 - 1]); LStrCat((char *)&gvar_0070DF4C, keyChar); c = meffi_DecodeChar(g_LicKey[*idx_1 - 1], flag_1); LStrFromChar((char *)&c_str, c); LStrCat((char *)&output_2, c_str); } ++*idx_1; } ++*idx_1; 

We see that the key characters are being read, until g_AlphaChar1 is encountered . If the symbol is not equal to g_AlphaChar2 , we glue it into a global variable, and glue the symbol converted with DecodeChar () into the output buffer.

If we got the g_AlphaChar2 character, we read the two characters following it, transform them, convert them to a number, and paste them to the output buffer. In the non-converted form, we add the same two symbols together with g_AlphaChar2 to the global variable. Let's call it g_stringFromKey1 .

Apparently, this function can be called DecodeString .

ToRevert : The string that we want to encode into a key will have to be converted using the inverse function for DecodeString () .

PS On this, the first part of the article about the keying of ArtMoney I probably finish. In the second part, we will continue to decompile the key verification code, faced with new difficulties, and stupid-looking code. But, does it stop us?
PPS Give us more interesting articles on reverse!

Source: https://habr.com/ru/post/255767/


All Articles