📜 ⬆️ ⬇️

Limbo game defense research. Keygen



Hello. Many people know about this great game - LIMBO ! You probably even bought it in Steam , or downloaded it from torrents ...
I also bought it once ( which I advise you! ), And passed). But, as always, it was not enough for me, and I, out of sports interest, decided to study its protection. So there was a keygen for the game LIMBO .

In this article I will tell and show you how I did it.

Before you begin, remember: you perform all actions at your own peril and risk. Respect the work of gamedevelopers.

')
Stage One: Patient's Surface Examination

The full game installer can be downloaded here . Having established the game, first of all, as usual, we find out what the main executable file is written on. I will use ExeInfo PE for this.



See: Visual Studio 2008 . IDA Pro perfectly copes with it, so we will send it there. I will use the IDA Pro + HexRays bundle , i.e. with decompiler - to speed up the work.

Stage two: what are we looking for?

First of all, let's give Ide to analyze limbo.exe - the main executable file of the game.

Next, you need to determine what exactly we actually want to find here. Run the game:



We see the magic inscription " UNLOCK FULL GAME ". On it and click. Next, we are waiting for the unexpected (at least when I first chose this menu item, I expected to see the input field on the graphic engine of the game, or something like that, but it turned out to be much easier ...):



Yes Yes! It is the usual window! It's easier for us. Let's try to enter something, and click Unlock . Something like this:



Well, let's look for the text in IDA , then to make a start from it, and find the place of the check. And then I expected a bummer ...
In the text of the message in the error box, Ida found nothing for me! The same thing said to me and search for content through Total Commander . The message may be encrypted. You can try to find a window call from the call to MessageBoxA / W. But, I went another way, which, for some reason, few places in articles describe.

Stage Three: Click Me

We will proceed as follows. Open any resource editor that is convenient for you, dragging an ehe-shnik into it, find the dialog box for entering the key, and in it - the Unlock button. No sooner said than done:



On the screen I highlighted the ID of our button. According to it, we will look for exactly where the click is processed. Let's open Go , press Alt + I ( Search -> immediate value ...), enter the number 203 (no 0x , because decimal), and see what exists. And there was this:



You see those lines that Ida marked as ; nIDDlgItem ? Let's start with them. Double click on the first of these results:



With a green arrow, I marked the place indicated by Ida , and just below ( habit: scroll above / below the desired place ), the arrow indicates the place where one interesting API function was called : GetDlgItemTextA . Judging by the name on MSDN , this function receives the text of the specified window element into the buffer.

Why did I not immediately search for the input field ID ? You can, of course, and so it was done. But, you never know what actions take place after pressing the button, even before the text is read from the field.

So, let's see where the resulting serial goes. Scroll the listing to see the entire place to call the API function:



My “ soaped up ” look tells me that the received buffer ( Ida designated it as var_134 ) is passed straight to the function following the GetDlgItemTextA call, which returns a zero or non-zero value to al (similar to the result of a key check). Let's check a guess ...

Stage Four: Decompilation

Go to the function. We see there jump to another address - go through it. We see the normal code, so feel free to click there F5 (call HexRays Decompiler ).

Decompilation result
bool __cdecl sub_48D410(int a1) { int v1; // esi@7 char *v2; // ebp@7 unsigned int v3; // edi@7 int v4; // ST28_4@9 int v5; // edx@9 int v6; // eax@12 bool result; // al@12 char v8; // [sp+4h] [bp-44h]@8 char v9; // [sp+Ch] [bp-3Ch]@12 char v10; // [sp+1Ch] [bp-2Ch]@7 char v11; // [sp+3Ch] [bp-Ch]@12 if ( strlen((const char *)a1) != 37 || *(_BYTE *)(a1 + 5) != 45 || *(_BYTE *)(a1 + 11) != 45 || *(_BYTE *)(a1 + 17) != 45 || *(_BYTE *)(a1 + 23) != 45 || *(_BYTE *)(a1 + 30) != 45 ) { result = 0; } else { v1 = 0; v2 = &v10; v3 = 0; do { v8 = *(_BYTE *)(v3 + a1); if ( v8 != 45 ) { v4 = (char)sub_412EBD(v8); v1 += v4 << 5 * (3 - v5); if ( v5 == 3 ) { v2 += sprintf(v2, "%05lx", v1); v1 = 0; } } ++v3; } while ( v3 < 0x25 ); v6 = sub_40C48C(&v10, 32); sprintf(&v9, "%08x", v6); result = strcmp(&v9, &v11) == 0; } return result; } 


Now you can try to bring this code to a more adequate.

First of all, we note that the input parameter is of type int , which is not entirely true. Denote it as " char * ". To do this, we become the name of the function and click there Y ( Set item type ). We fix the type and name of the input parameter (I called it as key ).

Next ... See the line:

  if ( strlen(key) != 37 || key[5] != 45 || key[11] != 45 || key[17] != 45 || key[23] != 45 || key[30] != 45 ) 

Since our input parameter is a string, let's in those places where the key symbols are compared with the numbers, correct for comparison with the symbols. To do this, on each of these numbers, press R ( Char ). Already better:

  if ( strlen(key) != 37 || key[5] != '-' || key[11] != '-' || key[17] != '-' || key[23] != '-' || key[30] != '-' ) 

Now the cycle:

Cycle number 1
  v1 = 0; v2 = &v10; v3 = 0; do { v8 = key[v3]; if ( v8 != 45 ) { v4 = (char)sub_412EBD(v8); v1 += v4 << 5 * (3 - v5); if ( v5 == 3 ) { v2 += sprintf(v2, "%05lx", v1); v1 = 0; } } ++v3; } while ( v3 < 0x25 ); 


For clarity, we give v3 the name i , since It seems that it is used as an iterator. Rename it by clicking on the name of the N ( Name ) key.

We notice that in the cycle, each character is taken from the key, and transferred to an unknown function. I propose to find out what this function is. Double click to go into it. We see there a call to another function, go there. And here it is - processing a single character! ( There is a lot of work here for the R key, but I will just show the result of the processing immediately ).

Convert_char function
 char __cdecl convert_char(char C) { char _C; // al@1 _C = C; if ( C < '0' ) return -1; if ( C <= '9' ) return C - '0'; if ( C < 'A' ) return -1; if ( C <= 'Z' ) { if ( C != 'I' && C != 'L' && C != 'O' && C != 'U' ) { if ( C >= 'U' ) _C = C - 1; if ( _C >= 'O' ) --_C; if ( _C >= 'L' ) --_C; if ( _C >= 'I' ) --_C; return _C - '7'; } return -1; } if ( C < 'a' || C > 'z' || C == 'i' || C == 'l' || C == 'o' || C == 'u' ) return -1; if ( C >= 'u' ) _C = C - 1; if ( _C >= 'o' ) --_C; if ( _C >= 'l' ) --_C; if ( _C >= 'i' ) --_C; return _C - 'W'; } 


Perfectly! Now go back to the main function with the Esc key . We note that IDA itself redefined the result type returned by the function for processing the character for us. Name further, denote types, and get the following loop code:

Cycle number 2
  sum = 0; x5buf = v10; i = 0; do { C = key[i]; if ( C != '-' ) { new_c = j_convert_char(C); sum += new_c << 5 * (3 - itr); if ( itr == 3 ) { x5buf += sprintf(x5buf, "%05lx", sum); sum = 0; } } ++i; } while ( i < 0x25 ); 


If you noticed, then there is one interesting bug decompiler. We see that the variable designated by it as itr is not incremented at all. To find out what is actually happening, click PKM -> Copy to assembly , and look where our itr is used . We find out: it is incremented directly in this cycle (which was to be expected), and before the cycle it is zeroed. We take this into account when writing keygens.

Now the second part of the key verification function ... We still have one unexplored function, which, by the way, is very similar to the CRC32 counting function. The result of processing (albeit in a hurry, but readable):

crc32
 int __cdecl calc_crc32(char *my_key, int len) { int i; // ebp@1 unsigned int _xor; // ecx@1 char *_my_key; // edi@2 char C; // al@3 signed int mask; // edx@3 int B; // esi@3 bool bit; // al@4 int crc32; // eax@10 signed int _len; // edx@10 i = len; _xor = 0xFFFFFFFF; if ( len ) { _my_key = my_key; do { C = *_my_key; --i; ++_my_key; mask = 1; B = (unsigned __int8)C; do { bit = (_xor & 0x80000000) == 0x80000000; _xor *= 2; if ( B & mask ) bit = bit == 0; if ( bit ) _xor ^= 0x4C11DB7u; mask *= 2; } while ( (_BYTE)mask ); } while ( i ); } crc32 = _xor & 1; _len = 0x1F; do { _xor >>= 1; crc32 = _xor & 1 | 2 * crc32; --_len; } while ( _len ); return ~crc32; } 


Remaining piece (converted):
  crc32 = j_calc_crc32(my_key, 32); sprintf(crc32_, "%08x", crc32); result = strcmp(crc32_, &my_key[32]) == 0; 


Stage Five: Keygen Writing

Task: determine what exactly happened with the key to write the inverse function. I will write, contrary to common sense and the issuance of HexRays , in Delphi , and you can write in the language that is easier for you.

By debugging we find out what happened:
  1. The game needs a key of 32 characters without hyphens ( 37 - with hyphens ).
  2. Four characters are taken from the key (hyphens are not counted). Each of them is passed through the function convert_char and summed by the formula: sum + = new_c << 5 * (3 - itr) ;
  3. Each such sum is converted to a lower-case hex string ( 5 characters ) and glued to the existing one (total 40 characters );
  4. The CRC32 is taken from the first 32 characters of the resulting string and compared with the remaining eight characters of the string obtained in the previous paragraph;
  5. If the lines do not match - our key is incorrect.


Reverse thinking:
  1. Write a function that converts a 40- character hash back to the key;
  2. Generate a 32- character hash;
  3. Calculate from it 8 - character CRC32;
  4. Glue the lines obtained in steps ( 2 ) and ( 3 );
  5. Pass to function ( 1 ) - we get the desired key.


Thoughts on the transformative function:
  1. Since the input hash was obtained from eight 5- character hash pieces, we will process it in the same way, using the " five ";
  2. Each " five " was derived from four key symbols ;
  3. Since at each calculation of the " five ", it shifted 5 bits to the left , it turns out that for each key symbol there are 5 bits ;
  4. Careful consideration of the convert_char function code leads us to believe that the key character set is limited to the characters of the character set “ 0123456789ABCDEFGHJKMNPQRSTVWXYZ ”;
  5. Total: 32 hash symbols are generated from " fives ". 32% 5 = 24 whole characters and 2 in the remainder - i.e. two characters we have to simply generate.

The final version of the function that generates a hash (Delphi)
 function GenHash(len: byte): string; var i, k: byte; sum: integer; begin Randomize; Result := ''; sum := 0; k := 0; for i := 1 to len do begin sum := sum + (Random(Length(alphabet)) shl ((3 - k) * 5)); Inc(k); if k = 4 then begin Result := Result + AnsiLowerCase(Int2Hex(sum, 5)); sum := 0; k := 0; end; end; Result := Result + 'a0'; //   ,       end; 


Next, consider the CRC32 from the hash:
 var key, hash, crc32: string; begin hash := GenHash(24); crc32 := Crc32b(hash); //    ,   - 

The code of the converter function:
Hash to code function
 function Hash2Code(const Hash: string): string; var s: string; five: integer; begin Result := ''; s := Hash; while Length(s) > 0 do begin five := Hex2Int(Copy(s, 1, 5)); Delete(s, 1, 5); Result := Result + alphabet[(five and $F8000 shr 15) + 1] + alphabet[(five and $07C00 shr 10) + 1] + alphabet[(five and $003E0 shr 05) + 1] + alphabet[(five and $0001F shr 00) + 1]; end; end; 



And finally, the resulting license key acquisition:

  key := Hash2Code(hash + crc32); lic_code := Format('%s-%s-%s-%s-%s-%s', [Copy(key, 1, 5), Copy(key, 6, 5), Copy(key, 11, 5), Copy(key, 16, 5), Copy(key, 21, 6), Copy(key, 27, 6) ]); 

We check, and ... Entering the generated key activated the game, the activation point disappeared!

Results

The main thing when writing keygen is to be able to think back. Those. be able to write such an algorithm that will be the opposite of what you have. This is not an easy task, but, and it can be solved in most cases.

PS

Perhaps the article was too messy, I do not know. The main idea I wanted to convey: keygen is not such a complicated thing, if there are brains, and desire along with assiduity.

Pps

In the next article I will describe how I wrote keygen to another game - Unepic .

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


All Articles