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 resultbool __cdecl sub_48D410(int a1) { int v1;
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;
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;
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:
- The game needs a key of 32 characters without hyphens ( 37 - with hyphens ).
- 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) ;
- Each such sum is converted to a lower-case hex string ( 5 characters ) and glued to the existing one (total 40 characters );
- 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;
- If the lines do not match - our key is incorrect.
Reverse thinking:
- Write a function that converts a 40- character hash back to the key;
- Generate a 32- character hash;
- Calculate from it 8 - character CRC32;
- Glue the lines obtained in steps ( 2 ) and ( 3 );
- Pass to function ( 1 ) - we get the desired key.
Thoughts on the transformative function:
- Since the input hash was obtained from eight 5- character hash pieces, we will process it in the same way, using the " five ";
- Each " five " was derived from four key symbols ;
- 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 ;
- 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 ”;
- 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';
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 .