CRACKL @ B Contest 2010. Analysis of the first task

  • Tutorial

2010 was ending, global reforms were underway on the resource. These were troubled times. And in this harsh time, the idea of ​​creating a local tournament is emerging. This idea was very joyfully received by the local community. After some time, 3 tasks were created (although 5 were planned), the jury members and the rating system were selected. And so, it has begun.

Foreword

The first assignment was KeyGenMe by PE_Kill. The task was to write KeyGen, which was to generate many keys for one name. Here is the original description from the author:
Here is KeyGenMe. The name speaks for itself - you need to write a key generator
that will successfully pass the test in KeyGenMe. For any name, the generator must
produce many unique serial numbers. KeyGenMe is packed with a simple homemade
packer.

I would like to warn. KeyGenMe is not as complicated as it may seem, but it is not trivial
and requires a non-standard approach. A small plus will be the provided unpacked
version of KeyGenMe.

Good luck


Analysis

As you can see from the description, keygenmy is covered with a homemade packer, which causes some antiviruses to issue a false positive. Well, I didn’t bother with it and removed it with the help of QU . Then I opened the unpacked file in OllyDbg and saw by the code on the EP (Entry point) that the keygen was written in Delphi. Knowing this, I decided to stick keygen in IDR ( Interactive Delphi Reconstructor ), which would recognize me VCL and Delphi run-time functions. This is only necessary for two things:
  • creating a .map file for importing it into OllyDbg;
  • finding the function of processing the click of the check button.

After the complete analysis of the file is completed, create a .map file.


And we find the address of the button click handler.


To use the .map file in OllyDbg, you need to install the mapimp plugin . By default, the .map file is saved in the same folder from which the program was opened and with the same name as the program, if everything is done correctly and the plug-in is installed, then when you open keygen in Olk, a message should appear asking you to download the .map file, click “Yes” and everything is fine. If something is wrong, then you can download it manually through the main menu.
All preparations are made. Go to OllyDbg to the address of the processing function for clicking the check button, which you need to remember / write / copy with IDR. And we see how in the beginning the name and serial are obtained (it is not so important), but the most interesting part goes on.

Namely TKeyEngine.Create, an instance of the TKyeEngine class is created according to the logic of things, this class is the heart of the entire circuit. Two functions follow it; the result of the second is involved in one of the conditions following immediately after its call. Here they are all concluded. So, a little more detail about each of them.

In the first function, we meet yet another inconsequential thing, which, as it were, should complicate the analysis - this is a little obfuscation, which is easily removed by the mask on the script.

It will also be further in the child functions. Therefore, I just found the address of the first meeting by searching the mask and this address became the starting point. In the script, we will search from here until the dawn of the end, until all these sections are flooded (it really looks for a long time, a few minutes, well, there was no calculation for optimizing a single operation, incl.).
MOV addr, 0046BED9
@loop:
FIND addr, #EB033BC?7?#
CMP $RESULT, 0
JE @exit
MOV addr, $RESULT
FILL addr, 5, 90
ADD addr, 5
JMP @loop
@exit:
RET

Well, before meeting this obfuscation, there were a couple of actions - this is that an array of 16 bytes is made with the name for the subsequent “simple” manipulations, people with a weak mind who did not see so much code that needed to be analyzed were in horror. But "the devil is not so terrible as he is painted." A more detailed analysis revealed that it does not carry a deep semantic load. I’m talking about this big loop with a switch in 10001 * 16 iterations.

It is enough to rip. True, this ripped piece of code will over 12k lines, but after cleansing it from the nodes after deobfuscation a little less than 6k lines will remain. But before you rip it, you can bring it into a more correct form. Because Since the entire circuit works with an instance of the class, this array is somewhere inside with a shift of 5 bytes relative to the pointer to this instance. Therefore, you need to correct all offsets for transferring only an array to our ripped code, without frills. For this, I wrote this script.
MOV addr, 0046BED9
@loop:
OPCODE addr
CMP $RESULT_2, 3
JB @next
MOV temp, addr
ADD temp, $RESULT_2
SUB temp, 2
MOV val, [temp], 1
CMP val, 43, 1
JE @ok
CMP val, 53, 1
JNE @next
@ok:
INC temp
MOV val, [temp], 1
SUB val, 5
MOV [temp], val, 1
@next:
ADD addr, $RESULT_2
CMP addr, 004707A1
JA @exit
JMP @loop
@exit:
RET

Then you can rip the whole thing. It was possible to cram it all with the help of the asm insert, but there were some troubles that I could not solve in an acceptable time, therefore it was decided to put all this into the asm file as a separate function and connect it to the project, which immediately solved all the problems. There are two more stumbles on the ripped code, it uses a local variable of one byte in size and located not at the beginning of the stack, but just in the wrong place and here ebp-5, it would be possible to replace this all, but again I went along the path of the smallest Resistance allocated a stack of 8 bytes for local needs (such as alignment). And the second stumble is that there is still a call to another function, but it is a small 2 teams, rip / rewrite manually (I don’t even know which is faster) and replace the call names with your own. That's all, a quarter of the way behind.

So we finished the analysis of the first function, which worked only with the name, and the second one already works with the serial and what it pulls from it, and data from the first function. Immediately upon entering the second function, there will be a check for the validity of the entered serial and its conversion.

The first one will be a check for the length and location of hyphens in it, from which it will be possible to find out that the serial has the form:
XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXX-XXXXXXXXXXXXXXXX


Next, hyphens are cut from it and the resulting string is decoded. The decoding function is a standard algorithm that I didn’t quickly figure out. The key clue is that the characters in the serial are checked against the alphabet.

Googling this alphabet, it was found out that it is Base32, decoding function. After which we have an array with a size of 25 bytes, and which is then divided into three parts, the first is an array of 16 bytes, the second is a value of 1 byte, and the third is an array of 8 bytes.

typedef struct _KEY {
	unsigned char SomeBytes[16];
	unsigned char SomeCount;
	unsigned int  CipherText[2];
} KEY, *LPKEY;

As you can see the third element of the structure, I have a meaningful name, looking ahead I will say that there should be an encrypted string that is compared with the reference string in the most recent test. So, the verification of the serial and its decomposition into components is completed. When exiting this function, there will be a check that KEY.SomeCount is greater than 32, if so, then we move on. Next is a small cycle in which swap follows WORD'S KEY.SomeBytes and the array received from the name, it serves more to stretch the code, because it does not have any logical meaning, because Next comes work with the same array and their corresponding elements, the first with the first, the second with the second, etc.
And then comes another switch. For those whose first one did not repel all desire, the second one weeded out even more people. By itself, it is simple, for each byte value, a function is called according to the same prototype, but here the functions are different for everyone.

From this place in more detail. So, the array comes back into the game, which was obtained from the name (I will say in confidence that it will participate further in another function). It will serve as a kind of pcode, depending on what value of the byte is and the action will be performed. And the elements of the KEY.SomeBytes array will serve as the input value to these functions. And those functions to which it is transmitted have similar simple logic, the byte that is supplied to it is parsed in bits, depending on whether one or another bit is set, a small series of actions are performed on a single byte.

Each such function has its own actions for each bit, but the most important thing is that if there is one specific bit, the result of all these manipulations is placed in a certain position in KEY.CipherText and a separate counter is incremented, provided that it is more than eight (t .e. 8 replacements were made in KEY.CipherText) terminates the function with an error. There is also another counter that increases with each operation, i.e. if a bit is set, except for the very first, when we get a byte, over which all these gestures occur. When analyzing all this, a vulnerability was revealed that allowed to completely ignore all these manipulations. It lies in the fact that if the bit responsible for writing to KEY.CipherText is not set, then KEY.CipherText will never change, which is very cool, this allows you to leave KEY.

And that is, you need to build a mask table for each byte value in which this bit will not be taken into account. You also need to prevent the bit from being set at which the byte is obtained at the beginning, this is necessary for conveniently counting the bits for the second counter (we need it for one check). In order to avoid this routine of finding bits for each byte value, I wrote a small script that finds the necessary bits, also creates the necessary masks from them and writes it to the table.
MOV addr, 004707FC
ALLOC 100
CMP $RESULT, 0
JE @error
MOV table, $RESULT
PUSHA
MOV c, 0
@loop:
FIND addr, #751190909090900FB643260FB6440326884304#
CMP $RESULT, 0
JE @exit
MOV cl, [$RESULT - 1], 1
MOV addr, $RESULT
ADD addr, 13
FIND addr, #751E90909090900FB643260FB65304885403269090909090FE4326#
CMP $RESULT, 0
JE @exit
MOV bl, [$RESULT - 1], 1
OR bl, cl
NOT bl
MOV [table + c], bl, 1
MOV addr, $RESULT
ADD addr, 20
INC c
CMP c, 100
JE @exit
JMP @loop
@exit:
POPA
DM table, 100, "table.bin"
FREE table, 100
@error:
RET

After this cycle, the bit counter is compared with the value KEY.SomeCount, if they are equal, then we are already at the finish line. Next is the call to the function to which KEY.CipherText is transferred as data and an array with the name as the key, inside there will be a few more entries until we get to the desired function, by the constants in it it was immediately clear that this was something from the TEA family of algorithms , by to where the delta is used, it was determined to be something with XTEA . As it turned out, with only a slight change in the original algorithm.

As can be seen from the screen, I took apart each step to find where the modification was made. It consisted in the fact that the position of the brackets was changed in two places, i.e. priority action.
Original:
void xtea_decipher(unsigned int num_rounds, uint32_t *v, uint32_t const *k) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + k[(sum>>11) & 3]);
        sum -= delta;
        v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + k[sum & 3]);
    }
    v[0]=v0; v[1]=v1;
}

Modified:
void xtea_decipher(unsigned int num_rounds, uint32_t *v, uint32_t const *k) {
    unsigned int i;
    uint32_t v0=v[0], v1=v[1], delta=0x9E3779B9, sum=delta*num_rounds;
    for (i=0; i < num_rounds; i++) {
        v1 -= ((((v0 << 4) ^ (v0 >> 5)) + v0) ^ sum) + k[(sum>>11) & 3];
        sum -= delta;
        v0 -= ((((v1 << 4) ^ (v1 >> 5)) + v1) ^ sum) + k[sum & 3];
    }
    v[0]=v0; v[1]=v1;
}


So, everything is passed, it remains to cross the finish line. In place of which there is a comparison of the data after decryption with the word CRACKLAB, which implies that this word needed to be encrypted.
That's all, write keygen.

Total

To write keygen, we will need: ripped code to create an array with a name, implementation of Base32Encoding, implementation of modified XTEA, a mask table, and add some code to our code. First, we translate the dump of the table into an array that can be inserted into the code, everyone can do it as he sees fit. The serialization generation algorithm has the following form:
  1. Name => name_array [16]
  2. Morf (name_array)
  3. KEY.SomeBytes [i] = random () & table [name_array [i]]
  4. KEY.SomeCount = bitscount (KEY.SomeBytes)
  5. KEY.CipherText = xtea_encode (“CRACKLAB”)
  6. SerialBase = Base32Encode (KEY)
  7. Serial = InsertDefis (SerialBase)


My keygen: github.com/reu-res/CRACKLAB-Contest-2010

In the next article I will analyze the second task, which will be perhaps more interesting than this. Attention SPOILER! There you will find a discussion of VM (well, the author said so), the implementation of which differs from ordinary implementations, which are similar in structure to each other. See you.

Also popular now: