📜 ⬆️ ⬇️

We solve crackme from Kaspersky Lab

One fine day, different channels in the telegram began to throw a link to a crack mic from the LC. Those who successfully completed the task will be invited for an interview! . After such a loud statement, I wondered how difficult the reverse would be. How I solved this task can be read under the cut (many pictures).

When I got home, I carefully read the assignment again, downloaded the archive, and began to watch what was inside. And inside was this:



Run x64dbg, dump after unpacking, see what's inside actually:
')




We take the file name from the command line arguments -> open, read -> encrypt with the first step -> encrypt with the second step -> write to a new file.

It's simple, it's time to look at encryption.

Let's start with stage1


At the address 0x4033f4 is a function, which I called crypt_64bit_up (later you will understand why), it is called from a loop somewhere inside stage1



And a little crooked decompilation result



At first I tried to rewrite the same algorithm on the python, killed it for a few hours and it turned out something like that (which makes get_dword and byteswap should be clear from the names)

def _add(x1, x2): return (x1+x2) & 0xFFFFFFFF def get_buf_val(t, buffer): t_0 = t & 0xFF t_1 = (t >> 8) & 0xFF t_2 = (t >> 16) & 0xFF t_3 = (t >> 24) & 0xFF res = _add(get_dword(buffer, t_0 + 0x312), (get_dword(buffer, t_1 + 0x212) ^ _add(get_dword(buffer, t_2+0x112), get_dword(buffer, t_3+0x12)))) # print('Got buf val: 0x%X' % res) return res def crypt_64bit_up(initials, buffer): steps = [] steps.append(get_dword(buffer, 0) ^ byteswap(initials[0])) # = z steps.append(get_buf_val(steps[-1], buffer) ^ byteswap(initials[1]) ^ get_dword(buffer, 1)) for i in range(2, 17): steps.append(get_buf_val(steps[-1], buffer) ^ get_dword(buffer, i) ^ steps[i-2]) res_0 = steps[15] ^ get_dword(buffer, 17) res_1 = steps[16] print('Res[0]=0x%X, res[1]=0x%X' % (res_0, res_1)) 

But then I decided to pay attention to the constants 0x12, 0x112, 0x212, 0x312 (without hex, 18, 274, 536 ... not very similar to something unusual). We try to google them and find a whole repository (hint: NTR) with the implementation of encryption and decryption functions, this is good luck. We try to encrypt a test file with random content in the source program, dump it and encrypt the same file with a python script, everything should work and the results should be the same. After that we try to decrypt it (I decided not to go into details and just copy the decryption function from the sources)

 def crypt_64bit_down(initials, keybuf): x = initials[0] y = initials[1] for i in range(0x11, 1, -1): z = get_dword(keybuf, i) ^ x x = get_buf_val(z, keybuf) x = y ^ x y = z res_0 = x ^ get_dword(keybuf, 0x01) # x - step[i], y - step[i-1] res_1 = y ^ get_dword(keybuf, 0x0) return (res_1, res_0) def stage1_unpack(packed_data, state): res = bytearray() for i in range(0, len(packed_data), 8): ciphered = struct.unpack('>II', packed_data[i:i+8]) res += struct.pack('>II', *crypt_64bit_down(ciphered, state)) return res 

Important note: the key in the repository is different from the key in the program (which is quite logical). Therefore, after initializing the key, I just dumped it into a file, this is buffer / keybuf

Go to the second part


Everything is much simpler here: first, an array of unique chars with a size of 0x55 bytes in the range (33, 118) (printable chars) is created, then the 32-bit value is packed into 5 printable chars from the array created earlier.





Since there is no randomization when creating the array mentioned above, every time you start the program this array will be the same, dump it after initialization and we can unpack the stage_2 simple function

 def stage2_unpack(packed_data, state): # checked! res = bytearray() for j in range(0, len(packed_data), 5): mapped = [state.index(packed_data[j+i]) for i in range(5)] res += struct.pack('>I', sum([mapped[4-i]*0x55**i for i in range(5)])) return res 

We do something like this:

 f = open('stage1.state.bin', 'rb') stage1 = f.read() f.close() f = open('stage2.state.bin', 'rb') stage2 = f.read() f.close() f = open('rprotected.dat', 'rb') packed = f.read() f.close() unpacked_from_2 = stage2_unpack(packed, stage2) f = open('unpacked_from_2', 'wb') f.write(unpacked_from_2) f.close() unpacked_from_1 = stage1_unpack(unpacked_from_2, stage1) f = open('unpacked_from_1', 'wb') f.write(unpacked_from_1) f.close() 

And we get the result

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


All Articles