Задание: bkp-cutie-keygen.zip

Как следует из названия это будет что-то написанное на Qt, и да, мы получаем ехе-шник с кучей библиотек и прочего шлака. Ну, хорошо что не статически собранный файл, так хоть можно все Qt-шные функции видеть. Обилием которых можно “насладится” с самых первых секунд анализа. Функция main пестрит ими, а в добавок видим, что тут еще Qml используется, становится еще “веселее”.

Среди этого всего есть только одна интересная функция sub_D01BF0, которая инициализирует какие-то данные.

Сам crackme представляет с себя окошко с избыточными эффектами, которое принимает ключ в 16 символов.

Решил посмотреть что же написано в kickass.qml. Немного поискав нашел утилиту, которая может доставать ресурсы Qt приложений. Она была написана под .NET, кажется на F# и она не отработала, т.к. мне не знаком был тот синтаксис на котором оно было написано, я не смог разобраться, что же не работает и как пофиксить. И решил прибегнуть к радикальным методам, а именно к binwalk :) и он справился, он достал мне qml файл. В нем не нашел ничего интересного, да я и не знал, что же там может быть, никогда до этого с ним не сталкивался.

Решено было изучить само приложение лучше и была найдена многообещающая функция с выводом отладочных сообщений. По сообщениям было просто найти функцию, которая отвечала за проверку ключа. Разбирать это все в статике было больно, декомпилятор позволял понять общий ход, какая функция что принимает и что и куда отдает дальше, но сами алгоритмы не особо то и понимались. Забегая наперед можно сказать, что это все из-за того что была работа с 64 битной арифметикой в 32 битном приложении. Потому отладка могла разъяснить многие моменты.

.text:00D01DB3     call    ds:QString::QString(QString const &) ; password
.text:00D01DB9     mov     ecx, ebx
.text:00D01DBB     call    xor_passwd
.text:00D01DC0     push    eax
.text:00D01DC1     lea     ecx, [esp+128h+arg_0]
.text:00D01DC8     call    ds:QString::operator=(QString const &)
.text:00D01DCE     push    ecx
.text:00D01DCF     lea     eax, [esp+12Ch]
.text:00D01DD6     mov     ecx, esp
.text:00D01DD8     push    eax
.text:00D01DD9     call    ds:QString::QString(QString const &)
.text:00D01DDF     mov     ecx, ebx
.text:00D01DE1     call    encrypt_passwd              ; work with xored password

Поначалу все просто, ключ ксорится с данными, которые инициализировались еще в самом начале, о чем я выше писал:

.text:00D0221B     mov     [esp+0B4h+var_24], 70BC90DFh
.text:00D02226     mov     [esp+0B4h+var_20], 5A96EF57h
.text:00D02231     mov     [esp+0B4h+var_1C], 5509CFEEh
.text:00D0223C     mov     [esp+0B4h+var_18], 0D2080CEh
.text:00D02247     mov     [esp+0B4h+var_14], 70EE14Fh
.text:00D02252     mov     [esp+0B4h+var_10], 2FC6A446h
.text:00D0225D     mov     [esp+0B4h+var_C], 5355ECF0h
.text:00D02268     mov     [esp+0B4h+var_8], 6457782Bh

Далее идет функция, которая шифрует поксореный ключ. Сперва она пересохраняет элементы в big endian, для того что бы работать с ключом не посимвольно, а как с единым блоком данных. Сам алгоритм шифрования блочный, он работает с парой блоков по 64 бита, также использует пару ключей по 64 бит каждый. Эти же ключи также инициализируются в само начале как и тот что для ксора:

.text:00D022B8     mov     dword ptr [edi+18h], 880F0E3Ah
.text:00D022BF     mov     dword ptr [edi+1Ch], 16D856AFh
.text:00D022C6     mov     dword ptr [edi+20h], 58FF310h
.text:00D022CD     mov     dword ptr [edi+24h], 0D8E8367Ch

Вот алгоритм шифрования пожалуй самое интересное. Т.к. он работает с данными по 64 бита, а 32 битное приложение не может оперировать такими блоками, оно обрабатывает их по очереди по 32 бита по этому можно почти половину инструкций игнорить, если понимать что это продолжение предыдущей операции.

.text:00D029C0 loc_D029C0:                             ; CODE XREF: encrypt_passwd+409
.text:00D029C0     xor     eax, eax
.text:00D029C2     mov     ecx, edx
.text:00D029C4     shrd    edx, ebp, 8
.text:00D029C8     shl     ecx, 18h
.text:00D029CB     shr     ebp, 8
.text:00D029CE     or      edx, eax
.text:00D029D0     or      ebp, ecx                    ; q0 = ror(q0, 8)
.text:00D029D2     add     edx, ebx
.text:00D029D4     mov     ecx, esi
.text:00D029D6     adc     ebp, esi                    ; q0 = q0 + q1
.text:00D029D8     shr     ecx, 1Dh
.text:00D029DB     xor     ebp, [esp+144h+x01]
.text:00D029DF     xor     edx, edi                    ; q0 = q0 ^ x0
.text:00D029E1     shld    esi, ebx, 3
.text:00D029E5     mov     [esp+144h+d00], ebp
.text:00D029E9     or      esi, eax
.text:00D029EB     mov     [esp+144h+d00_], ebp
.text:00D029EF     xor     esi, ebp
.text:00D029F1     shl     ebx, 3
.text:00D029F4     mov     ebp, [esp+144h+x10]
.text:00D029F8     or      ebx, ecx                    ; q1 = rol(q1, 3)
.text:00D029FA     mov     ecx, [esp+144h+x11]
.text:00D029FE     xor     ebx, edx                    ; q1 = q1 ^ q0
.text:00D02A00     shrd    [esp+144h+x11], ebp, 8
.text:00D02A06     or      eax, [esp+144h+x11]
.text:00D02A0A     shl     ecx, 18h
.text:00D02A0D     shr     ebp, 8
.text:00D02A10     or      ecx, ebp                    ; x1 = ror(x1, 8)
.text:00D02A12     mov     [esp+144h+d01], edx
.text:00D02A16     mov     ebp, [esp+144h+x01]
.text:00D02A1A     add     eax, edi
.text:00D02A1C     mov     [esp+144h+d10], esi
.text:00D02A20     adc     ecx, ebp                    ; x1 = x1 + x0
.text:00D02A22     xor     eax, dword ptr [esp+144h+i]
.text:00D02A26     xor     ecx, [esp+144h+i_]          ; x1 = x1 ^ i
.text:00D02A2A     mov     [esp+144h+x11], eax
.text:00D02A2E     xor     eax, eax
.text:00D02A30     mov     [esp+144h+x10], ecx
.text:00D02A34     mov     ecx, ebp
.text:00D02A36     shld    ebp, edi, 3
.text:00D02A3A     shr     ecx, 1Dh
.text:00D02A3D     or      eax, ebp
.text:00D02A3F     shl     edi, 3
.text:00D02A42     xor     eax, [esp+144h+x10]
.text:00D02A46     or      edi, ecx                    ; x0 = rol(x0, 3)
.text:00D02A48     xor     edi, [esp+144h+x11]         ; x0 = x0 ^ x1
.text:00D02A4C     mov     ebp, [esp+144h+d00]
.text:00D02A50     mov     [esp+144h+x01], eax
.text:00D02A54     mov     eax, dword ptr [esp+144h+i]
.text:00D02A58     add     eax, 1
.text:00D02A5B     mov     dword ptr [esp+144h+i], eax
.text:00D02A5F     adc     [esp+144h+i_], 0            ; i = i + 1
.text:00D02A64     jnz     short loc_D02A6F
.text:00D02A66     cmp     eax, 20h
.text:00D02A69     jb      loc_D029C0

Вот так обрабатывается первый блок в 16 байт, далее идет такой же кусок кода, который обрабатывает последние 16 байт поксореного ключа. После чего идет обратное преобразование элементов в little endian. Все это можно представить в python как:

def encrypt_block(data):
    q0, q1 = data
    x0 = 0xD8E8367C058FF310
    x1 = 0x16D856AF880F0E3A
    for i in xrange(32):
        q0 = add(ror(q0, 8), q1) ^ x0
        x1 = add(ror(x1, 8), x0) ^ i
        q1 = rol(q1, 3) ^ q0
        x0 = rol(x0, 3) ^ x1
    return q0, q1

def encrypt(data):
    res = []
    for i in xrange(0, len(data), 2):
        res.extend(encrypt_block(data[i:i + 2]))
    return res

def encrypt_passwd(passwd):
    l = unpack('>4Q', pack('>16H', *passwd))
    l = encrypt(l)
    l = unpack('>16H', pack('>4Q', *l))
    return l

Ну и выходим на финишную прямую, дальше осталась еще одна функция, которая на основе зашифрованного ключа и другого массива создаст нам финальный, который дальше и будет сравниваться с эталонным.

.text:00D01E11     push    dword ptr [ebx+8]
.text:00D01E14     lea     eax, [esp+128h+var_110]
.text:00D01E18     push    eax
.text:00D01E19     lea     eax, [esp+12Ch+var_90]
.text:00D01E20     push    eax
.text:00D01E21     call    mul_matrix
.text:00D01E26     mov     esi, eax
.text:00D01E28     mov     ecx, 20h
.text:00D01E2D     rep movsd
.text:00D01E2F     add     esp, 0Ch
.text:00D01E32     lea     edi, [esp+124h+var_110]
.text:00D01E36     mov     esi, eax
.text:00D01E38     mov     ecx, 20h
.text:00D01E3D     rep movsd
.text:00D01E3F     push    dword ptr [ebx+0Ch]
.text:00D01E42     lea     ecx, [esp+128h+var_110]
.text:00D01E46     call    check

Как видно с названия функции она перемножает матрицы, наш массив в 16 элементов можно представить как матрицу 4x4, которая умножается на другую матрицу инициализированную в начале как и все остальные константные данные:

.text:00D01FBE     mov     [esp+0B4h+var_A4], 1380h
.text:00D01FC6     mov     [esp+0B4h+var_A0], 0
.text:00D01FCE     mov     [esp+0B4h+var_9C], 4E4h
.text:00D01FD6     mov     [esp+0B4h+var_98], 0
.text:00D01FDE     mov     [esp+0B4h+var_94], 2709h
.text:00D01FE6     mov     [esp+0B4h+var_90], 0
.text:00D01FEE     mov     [esp+0B4h+var_8C], 2035h
.text:00D01FF6     mov     [esp+0B4h+var_88], 0
.text:00D01FFE     mov     [esp+0B4h+var_84], 25FAh
.text:00D02006     mov     [esp+0B4h+var_80], 0
.text:00D0200E     mov     [esp+0B4h+var_7C], 56DAh
.text:00D02016     mov     [esp+0B4h+var_78], 0
.text:00D0201E     mov     [esp+0B4h+var_74], 103h
.text:00D02026     mov     [esp+0B4h+var_70], 0
.text:00D0202E     mov     [esp+0B4h+var_6C], 1531h
.text:00D02036     mov     [esp+0B4h+var_68], 0
.text:00D0203E     mov     [esp+0B4h+var_64], 0CAAh
.text:00D02046     mov     [esp+0B4h+var_60], 0
.text:00D0204E     mov     [esp+0B4h+var_5C], 1A61h
.text:00D02056     mov     [esp+0B4h+var_58], 0
.text:00D0205E     mov     [esp+0B4h+var_54], 0E07h
.text:00D02066     mov     [esp+0B4h+var_50], 0
.text:00D0206E     mov     [esp+0B4h+var_4C], 20h
.text:00D02076     mov     [esp+0B4h+var_48], 0
.text:00D0207E     mov     [esp+0B4h+var_44], 0E2h
.text:00D02086     mov     [esp+0B4h+var_40], 0
.text:00D0208E     mov     [esp+0B4h+var_3C], 123Fh
.text:00D02096     mov     [esp+0B4h+var_38], 0
.text:00D0209E     mov     [esp+0B4h+var_34], 0C0h
.text:00D020A9     mov     [esp+0B4h+var_30], 0
.text:00D020B4     mov     [esp+0B4h+var_2C], 0DC7h
.text:00D020BF     mov     [esp+0B4h+var_28], 0

А результат сравнивается с

.text:00D020EE     mov     [esp+0B4h+var_A4], 146FC26Ah
.text:00D020F6     mov     [esp+0B4h+var_A0], 0
.text:00D020FE     mov     [esp+0B4h+var_9C], 2434019Ah
.text:00D02106     mov     [esp+0B4h+var_98], 0
.text:00D0210E     mov     [esp+0B4h+var_94], 16B2964Eh
.text:00D02116     mov     [esp+0B4h+var_90], 0
.text:00D0211E     mov     [esp+0B4h+var_8C], 1DFCC164h
.text:00D02126     mov     [esp+0B4h+var_88], 0
.text:00D0212E     mov     [esp+0B4h+var_84], 10766B04h
.text:00D02136     mov     [esp+0B4h+var_80], 0
.text:00D0213E     mov     [esp+0B4h+var_7C], 1F67E99Dh
.text:00D02146     mov     [esp+0B4h+var_78], 0
.text:00D0214E     mov     [esp+0B4h+var_74], 13905802h
.text:00D02156     mov     [esp+0B4h+var_70], 0
.text:00D0215E     mov     [esp+0B4h+var_6C], 14A99DA3h
.text:00D02166     mov     [esp+0B4h+var_68], 0
.text:00D0216E     mov     [esp+0B4h+var_64], 2AE5CE6Ch
.text:00D02176     mov     [esp+0B4h+var_60], 0
.text:00D0217E     mov     [esp+0B4h+var_5C], 4048AA7Fh
.text:00D02186     mov     [esp+0B4h+var_58], 0
.text:00D0218E     mov     [esp+0B4h+var_54], 33CF9B5Fh
.text:00D02196     mov     [esp+0B4h+var_50], 0
.text:00D0219E     mov     [esp+0B4h+var_4C], 2C101662h
.text:00D021A6     mov     [esp+0B4h+var_48], 0
.text:00D021AE     mov     [esp+0B4h+var_44], 2DF5FCE4h
.text:00D021B6     mov     [esp+0B4h+var_40], 0
.text:00D021BE     mov     [esp+0B4h+var_3C], 4C26C74Ch
.text:00D021C6     mov     [esp+0B4h+var_38], 0
.text:00D021CE     mov     [esp+0B4h+var_34], 2CD5980Fh
.text:00D021D9     mov     [esp+0B4h+var_30], 0
.text:00D021E4     mov     [esp+0B4h+var_2C], 2BA9DEDBh
.text:00D021EF     mov     [esp+0B4h+var_28], 0

И если они равны, то ключ верен.

Зная все это можно построить схему как с это всего получить верный ключ. Сперва зная 2 матрицы нужно найти 3-ью. Для этого я использовал z3 solver

def solve():
    B = []
    for i in xrange(16):
        B.append(Int(i))

    s = Solver()
    for i in B:
        s.add(And(i >= 0, i <= 0xFFFF))

    for i in xrange(4):
        for j in xrange(4):
            s.add(
                B[i + 0 * 4] * A[j + 0 * 4] +
                B[i + 1 * 4] * A[j + 1 * 4] +
                B[i + 2 * 4] * A[j + 2 * 4] +
                B[i + 3 * 4] * A[j + 3 * 4] == R[i * 4 + j]
            )
    r = []
    if s.check() == sat:
        r = []
        model = s.model()
        for i in xrange(16):
            r.append(model[B[i]].as_long())
    else:
        print 'Oops'

    return r

Результат этой функции, нужно расшифровать. Зная алгоритм шифрования, который не такой уж и сложный можно написать функцию дешифровки. Кроме изменения порядка операций нужно еще посчитать ключи для расшифровки, для этого достаточно сдампить их по окончанию шифрования блока. И получим такое

def decrypt_block(data):
    q0, q1 = data
    x0 = 0x0A728E203850A80E
    x1 = 0x1B8E2679CCAEF6B4
    for i in xrange(32):
        x0 = ror(x0 ^ x1, 3)
        q1 = ror(q1 ^ q0, 3)
        x1 = rol(sub(x1 ^ (31 - i), x0), 8)
        q0 = rol(sub(q0 ^ x0, q1), 8)
    return q0, q1

def decrypt(data):
    res = []
    for i in xrange(0, len(data), 2):
        res.extend(decrypt_block(data[i:i + 2]))
    return res

def decrypt_passwd(passwd):
    l = unpack('>4Q', pack('>16H', *passwd))
    l = decrypt(l)
    l = unpack('>16H', pack('>4Q', *l))
    return l

И после этого остается только проксорить результат расшифровки. А все вместе это выглядит так

#!/usr/bin/env python2
from z3 import *
from struct import pack, unpack


A = [
    0x1380, 0x4E4, 0x2709, 0x2035, 0x25FA, 0x56DA, 0x103, 0x1531,
    0x0CAA, 0x1A61, 0x0E07, 0x20, 0x0E2, 0x123F, 0x0C0, 0x0DC7
]

R = [
    0x146FC26A, 0x2434019A, 0x16B2964E, 0x1DFCC164,
    0x10766B04, 0x1F67E99D, 0x13905802, 0x14A99DA3,
    0x2AE5CE6C, 0x4048AA7F, 0x33CF9B5F, 0x2C101662,
    0x2DF5FCE4, 0x4C26C74C, 0x2CD5980F, 0x2BA9DEDB,
]

xor_key = [
    0x90DF, 0x70BC, 0x0EF57, 0x5A96, 0x0CFEE, 0x5509, 0x80CE, 0x0D20,
    0x0E14F, 0x70E, 0x0A446, 0x2FC6, 0x0ECF0, 0x5355, 0x782B, 0x6457
]

def solve():
    B = []
    for i in xrange(16):
        B.append(Int(i))

    s = Solver()
    for i in B:
        s.add(And(i >= 0, i <= 0xFFFF))

    for i in xrange(4):
        for j in xrange(4):
            s.add(
                B[i + 0 * 4] * A[j + 0 * 4] +
                B[i + 1 * 4] * A[j + 1 * 4] +
                B[i + 2 * 4] * A[j + 2 * 4] +
                B[i + 3 * 4] * A[j + 3 * 4] == R[i * 4 + j]
            )
    r = []
    if s.check() == sat:
        r = []
        model = s.model()
        for i in xrange(16):
            r.append(model[B[i]].as_long())
    else:
        print 'Oops'

    return r

def ror(n, c, bits=64):
    mask = (1 << bits) - 1
    return ((n >> c) | (n << (bits - c))) & mask

def rol(n, c, bits=64):
    return ror(n, bits - c, bits)

def sub(n, c, bits=64):
    mask = (1 << bits) - 1
    return (n - c) & mask

def xor_passwd(passwd):
    l = [0] * 16
    for i in xrange(16):
        l[i] = passwd[i] ^ xor_key[i]
    return l

def decrypt_block(data):
    q0, q1 = data
    x0 = 0x0A728E203850A80E
    x1 = 0x1B8E2679CCAEF6B4
    for i in xrange(32):
        x0 = ror(x0 ^ x1, 3)
        q1 = ror(q1 ^ q0, 3)
        x1 = rol(sub(x1 ^ (31 - i), x0), 8)
        q0 = rol(sub(q0 ^ x0, q1), 8)
    return q0, q1

def decrypt(data):
    res = []
    for i in xrange(0, len(data), 2):
        res.extend(decrypt_block(data[i:i + 2]))
    return res

def decrypt_passwd(passwd):
    l = unpack('>4Q', pack('>16H', *passwd))
    l = decrypt(l)
    l = unpack('>16H', pack('>4Q', *l))
    return l

passwd = solve()
passwd = decrypt_passwd(passwd)
passwd = xor_passwd(passwd)
print(''.join(map(chr, passwd)))
BKP{KYU7EC!PH3R}