We solve crackme from Kaspersky Lab

    One fine day, different channels in the telegram began to throw a link to the 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:

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

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

    Let's start with stage1

    At 0x4033f4 is the function that I called crypt_64bit_up (later you will understand why), it is called from the cycle somewhere inside stage1

    And a little crooked result of decompilation

    At first I tried to rewrite the same algorithm on a python, killed it a few hours and got something this is (what get_dword does and byteswap should be clear from the titles)

    def_add(x1, x2):return (x1+x2) & 0xFFFFFFFFdefget_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
    defcrypt_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)

    defcrypt_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)
    defstage1_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: first, an array of unique chars 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, each time you start the program this array will be the same, dump it after initialization and we can unpack the stage_2 simple function

    defstage2_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 = open('stage2.state.bin', 'rb')
    stage2 = f.read()
    f = open('rprotected.dat', 'rb')
    packed = f.read()
    unpacked_from_2 = stage2_unpack(packed, stage2)
    f = open('unpacked_from_2', 'wb')
    unpacked_from_1 = stage1_unpack(unpacked_from_2, stage1)
    f = open('unpacked_from_1', 'wb')

    And we get the result

    Also popular now: