We solve the crackme from Kaspersky Lab

One day, different channels in Telegram started throwing link on crack poker from LC, 3r3-3101. Successfully completed the task will be invited for an interview! 3r3102. . 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). 3r3202.  
3r3202.  
3r3202.  
When I got home, I carefully read the assignment again, downloaded the archive, and began to watch what was inside. And inside was this:
 
3r3202.  
We solve the crackme from Kaspersky Lab 3r3202.  
3r3202.  
Run x64dbg, dump after unpacking, see what actually is inside:
 
3r3202.  
3r3331. 3r3202.  
3r3202.  
3r3202.  
3r3202.  
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. 3r3202.  
It's simple, it's time to look at encryption 3r3202.  
3r3202.  
3r3138. Let's start with stage1
3r3202.  
At the address 0x4033f4 there is a function, which I called crypt_64bit_up (later you will understand why), it is called from a loop somewhere inside stage1
 
3r3202.  
3r3355. 3r3202.  
3r3202.  
And a little crooked decompilation result
 
3r362. 3r3202.  
3r3202.  
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) 3r3202.  
3r3202.  
3r3176. 3r33177. def _add (x? 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 (? 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_? res_1))
3r3196. 3r3197. 3r3202.  
3r3202.  
But then I decided to pay attention to the constants 0x1? 0x11? 0x21? 0x312 (without hex, 1? 27? 536 not very similar to something unusual). We try to google them and find a whole repository (hint: NTR) with the implementation of encryption functions and 3r3-300. decryption [/u] that's 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 decipher it (I decided not to go into details and just copy the decryption function from the sources)
 
3r3202.  
3r3176. 3r33177. def crypt_64bit_down (initials, keybuf):
x = initials[0]
y = initials[1]
for i in range (0x1? ? -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_? res_0)
def stage1_unpack (packed_data, state):
res = bytearray ()
for i in range (? len (packed_data), 8):
ciphered = struct.unpack ('> II', packed_data[i:i+8])
res + = struct.pack ('> II', * crypt_64bit_down (ciphered, state))
return res
3r3196. 3r3197. 3r3202.  
3r3202.  
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
 
3r3202.  
3r3138. Moving on to the second part,
3r3202.  
Everything is much simpler here: first, an array of unique chars with a size of 0x55 bytes in the range (3? 118) (printable chars) is created, then the 32-bit value is packed into 5 printable chars from the array created earlier. 3r3202.  
3r3202.  
3r3146. 3r3202.  
3r3202.  
3r3151. 3r3202.  
3r3202.  
Since there is no randomization during the creation of the array mentioned above, each time the program is started this array will be the same, dump it after initialization and we can unpack the simple stage_2
 
3r3202.  
3r3176. 3r33177. def stage2_unpack (packed_data, state): # checked!
res = bytearray ()
for j in range (? len (packed_data), 5):
mapped =[state.index(packed_data[j+i]) for i in range (5)]
res + = struct.pack ('> I', sum (w2w216. * 0x55 ** i for i in range (5)]))
return res
3r3196. 3r3197. 3r3202.  
3r3202.  
We do something like this:
 
3r3176. 3r33177.
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_? stage1)
f = open ('unpacked_from_1', 'wb')
f.write (unpacked_from_1)
f.close ()
3r3196. 3r3197. 3r3202.  
3r3202.  
And we get the result 3r3202.  
3r3204.
+ 0 -

Add comment