Статья Расшифровываем пароль в CrackMe (x86) с x64 кодом

  • 28
  • 3
  • 42
Всем привет. Сегодня будет разбор довольно занятного CrackMe из этой темы - Вопрос - Один вопрос.
Заинтересовал он меня тем, что человек додумался перенести проверку ключа в x32 программе в x64 код, алгоритм шифрования тоже весёлый, так что давайте разберём по подробнее.

В main программы автор протектит память размером 0x914 с привилегиями выполнения кода:

ida_pPBmbaUqdC.png


Мы видим что для успешной проверки ключа функция sub_4015E0 должна вернуть результат != 0.
Переходим в неё и вот что в ней видим:

x32dbg_doMGaQDpLO.png

По инструкции "ret far" и странному асм листингу можно догадаться что остальной код проверки написан для x64 среды.
Не долго думая я понял какую функцию вызывает этот код, её память протектилась в main, и вот как раз в ней и происходит шифрование введённого пароля и сравнение с константой.

Что же делать? Как отладить этот код?
Всё просто, копируем все эти 0x914 байт, открываем любой файл в x64 отладчике, выделяем память (для удобства) и вставляем всё это в неё, на выходе мы получаем оригинальный асм листинг проверки ключа:

7304


Теперь можно выделять память для записи своего ключа и начинать трассировку. Сразу хочу заметить, что код рассчитан на то, что входной параметр будет иметь x32 адрес, потому что до этого момента код выполнялся в x32 контексте, по этому для удобства я заменил в прологе ECX на RCX, в итоге мы ставим RIP на самую первую инструкцию и в RCX суём адрес строки нашего пароля.

Из начала кода мы понимаем, что длинна ключа должна состоять из 16 символов (cmp ecx, 10 на скрине).
Листаем в самый низ и видим как в конце сравниваются 2 QWORD'a (16 байт) с константами, и если всё совпадает нам возвращает результат 5, который не равен 0, как и требуется в процедуре main.

x64dbg_ig1RNwsOoj.png


Если у вас нет квантового компьютера для брутфорса 16-ти символов, среди которых есть и цифры и буквы - придётся изучать алгоритм шифрования :(

Я не могу вам сказать что я полностью разобрал этот алгоритм, поскольку:
1) В моей процедуре декрипта есть костыль (на мой взгляд)
2) Конечный результат моего рашифрованного ключа имел 1 не совсем правильно расшифрованный символ.
Но крякми я решил, так что перфекционисты идите на*** :roflan-ebalo:

Вся шифровка состоит из нескольких этапов:
1) Символы в буфере с введённым паролем меняются в определённом порядке местами и некоторые char'ы инкрементируются (в конечном итоге меняется и сам символ)
2) Далее всё реверсируется (пишется задом наперёд)
3) Полученный результат шифруется по примерно такому алгоритму:
C++:
// псевдокод
int key = 0;

for ( int i = 0; i < 16; i++ )
{
    buf[i] = encrypt_byte( buf[i], key );
    key++;
    if ( key == 3 )
        key = 0;
}

В encrypt_byte учавствуют 2 ключевые инструкции шифрования, а именно:
1) sar eax, cl
2) bts eax, 7

Мой костыльный код для расшифровки char получился таким:
C++:
.CODE

    decrypt_byte PROC PUBLIC
        push r10
        push r13
        mov r13, 1
        xor rax, rax
        mov al, cl
        mov r10d, ecx
        and r10d, 80000001h
        jge skip
        dec r10d
        or r10d, -2
        inc r10d
    skip:
        cmp r10d, r13d
        jne skip_2
        btr eax, 7
    skip_2:
        mov cl, dl
        sal eax, cl
        cmp eax, 100h
        jb skip_3
        inc eax
    skip_3:
        pop r13
        pop r10
        ret
    decrypt_byte ENDP

END

Костыль заключается в проверке cmp eax, 100h, потому что на 1 символ она не сработала и мне пришлось инкрементировать 1 байт вручную.
Теперь сам код расшифровки пароля:
C++:
int main()
{

    //
    // Я ЗНАЮ ЧТО МОЖНО БЫЛО ПРОСТО СОЗДАТЬ МАССИВ unsigned char, НЕ НАДО ОБ ЭТОМ ПИСАТЬ, ТАК НАДО БЫЛО ДЛЯ ТЕСТОВ...
    //

    DWORD_PTR hash_1 = 0xA8B39226E116A432; // Первая часть зашифрованного пароля
    DWORD_PTR hash_2 = 0x6C14A6C399A8C78C; // Вторая часть зашифрованного пароля

    char* pByte1 = (char*)&hash_1;
    char* pByte2 = (char*)&hash_2;

    char* enc = (char*)malloc(0x1000);
    ZeroMemory( enc, 0x1000 );
    memcpy( enc, (void*)&hash_1, 8 );
    memcpy( enc + 8, (void*)&hash_2, 8 );

    //
    // Цикл расшифровки
    //

    int key = 0;

    for ( int i = 0; i < 16; i++ )
    {
        enc[i] = decrypt_byte( enc[i], key );
        key++;
        if ( key == 3 )
            key = 0;
    }

    ReverseQword( enc ); // Реверс QWORD'a

    //
    // Выделяем память для записи конечного результата
    //

    char* dec = (char*)malloc( 0x1000 );
    ZeroMemory( dec, 0x1000 );

    //
    // Возвращаем на место все символы
    //

    dec[0] = enc[14];
    dec[1] = enc[9];
    dec[2] = enc[5];
    dec[3] = enc[13];
    dec[4] = enc[1];
    dec[5] = enc[8];
    dec[6] = enc[3];
    dec[7] = enc[6];
    dec[8] = enc[10];
    dec[9] = enc[12];
    dec[10] = enc[0];
    dec[11] = enc[15];
    dec[12] = enc[11];
    dec[13] = enc[4];
    dec[14] = enc[2];
    dec[15] = enc[7];

    std::cout << dec << std::endl; // PROFIT?

    _getch();
    return 0;
}

Позиции символов до реверса я понял достаточно просто, я передал в качестве пароля 0123456789ABCDEF и посмотрел что с этим делом произошло после первого этапа шифрования.
На выходе мы получаем следующий результат: IdIGXM12QPQa3ClL
Этот пароль крякмис какого то хрена не принял, и я пошёл смотреть в отладчике что же я сделал не так, и как оказалось моя костыль-функция-декрипта не инкрементировала второй символ, который на самом деле является e

И так, пароль от этого пиздеца: IeIGXM12QPQa3ClL
vmware_DFOlOXX8lu.png


Всем спасибо кто дочитал это месево до конца, до скорого!

Для тех кто хочет потыкать сие чудо: Crackme.exe
Для параноиков: VirusTotal
 
Последнее редактирование:
  • 112
  • 92
Контакты для связи отсутствуют.
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.
Всем привет. Сегодня будет разбор довольно занятного CrackMe из этой темы - Вопрос - Один вопрос.
Заинтересовал он меня тем, что человек додумался перенести проверку ключа в x32 программе в x64 код, алгоритм шифрования тоже весёлый, так что давайте разберём по подробнее.

В main программы автор протектит память размером 0x914 с привилегиями выполнения кода:

Посмотреть вложение 7336

Мы видим что для успешной проверки ключа функция sub_4015E0 должна вернуть результат != 0.
Переходим в неё и вот что в ней видим:

Посмотреть вложение 7337
По инструкции "ret far" и странному асм листингу можно догадаться что остальной код проверки написан для x64 среды.
Не долго думая я понял какую функцию вызывает этот код, её память протектилась в main, и вот как раз в ней и происходит шифрование введённого пароля и сравнение с константой.

Что же делать? Как отладить этот код?
Всё просто, копируем все эти 0x914 байт, открываем любой файл в x64 отладчике, выделяем память (для удобства) и вставляем всё это в неё, на выходе мы получаем оригинальный асм листинг проверки ключа:

7304


Теперь можно выделять память для записи своего ключа и начинать трассировку. Сразу хочу заметить, что код рассчитан на то, что входной параметр будет иметь x32 адрес, потому что до этого момента код выполнялся в x32 контексте, по этому для удобства я заменил в прологе ECX на RCX, в итоге мы ставим RIP на самую первую инструкцию и в RCX суём адрес строки нашего пароля.

Из начала кода мы понимаем, что длинна ключа должна состоять из 16 символов (cmp ecx, 10 на скрине).
Листаем в самый низ и видим как в конце сравниваются 2 QWORD'a (16 байт) с константами, и если всё совпадает нам возвращает результат 5, который не равен 0, как и требуется в процедуре main.

Посмотреть вложение 7339

Если у вас нет квантового компьютера для брутфорса 16-ти символов, среди которых есть и цифры и буквы - придётся изучать алгоритм шифрования :(

Я не могу вам сказать что я полностью разобрал этот алгоритм, поскольку:
1) В моей процедуре декрипта есть костыль (на мой взгляд)
2) Конечный результат моего рашифрованного ключа имел 1 не совсем правильно расшифрованный символ.
Но крякми я решил, так что перфекционисты идите на*** :roflan-ebalo:

Вся шифровка состоит из нескольких этапов:
1) Символы в буфере с введённым паролем меняются в определённом порядке местами и некоторые char'ы инкрементируются (в конечном итоге меняется и сам символ)
2) Далее всё реверсируется (пишется задом наперёд)
3) Полученный результат шифруется по примерно такому алгоритму:
C++:
// псевдокод
int key = 0;

for ( int i = 0; i < 16; i++ )
{
    buf[i] = encrypt_byte( buf[i], key );
    key++;
    if ( key == 3 )
        key = 0;
}

В encrypt_byte учавствуют 2 ключевые инструкции шифрования, а именно:
1) sar eax, cl
2) bts eax, 7

Мой костыльный код для расшифровки char получился таким:
C++:
.CODE

    decrypt_byte PROC PUBLIC
        push r10
        push r13
        mov r13, 1
        xor rax, rax
        mov al, cl
        mov r10d, ecx
        and r10d, 80000001h
        jge skip
        dec r10d
        or r10d, -2
        inc r10d
    skip:
        cmp r10d, r13d
        jne skip_2
        btr eax, 7
    skip_2:
        mov cl, dl
        sal eax, cl
        cmp eax, 100h
        jb skip_3
        inc eax
    skip_3:
        pop r13
        pop r10
        ret
    decrypt_byte ENDP

END

Костыль заключается в проверке cmp eax, 100h, потому что на 1 символ она не сработала и мне пришлось инкрементировать 1 байт вручную.
Теперь сам код расшифровки пароля:
C++:
int main()
{

    //
    // Я ЗНАЮ ЧТО МОЖНО БЫЛО ПРОСТО СОЗДАТЬ МАССИВ unsigned char, НЕ НАДО ОБ ЭТОМ ПИСАТЬ, ТАК НАДО БЫЛО ДЛЯ ТЕСТОВ...
    //

    DWORD_PTR hash_1 = 0xA8B39226E116A432; // Первая часть зашифрованного пароля
    DWORD_PTR hash_2 = 0x6C14A6C399A8C78C; // Вторая часть зашифрованного пароля

    char* pByte1 = (char*)&hash_1;
    char* pByte2 = (char*)&hash_2;

    char* enc = (char*)malloc(0x1000);
    ZeroMemory( enc, 0x1000 );
    memcpy( enc, (void*)&hash_1, 8 );
    memcpy( enc + 8, (void*)&hash_2, 8 );

    //
    // Цикл расшифровки
    //

    int key = 0;

    for ( int i = 0; i < 16; i++ )
    {
        enc[i] = decrypt_byte( enc[i], key );
        key++;
        if ( key == 3 )
            key = 0;
    }

    ReverseQword( enc ); // Реверс QWORD'a

    //
    // Выделяем память для записи конечного результата
    //

    char* dec = (char*)malloc( 0x1000 );
    ZeroMemory( dec, 0x1000 );

    //
    // Возвращаем на место все символы
    //

    dec[0] = enc[14];
    dec[1] = enc[9];
    dec[2] = enc[5];
    dec[3] = enc[13];
    dec[4] = enc[1];
    dec[5] = enc[8];
    dec[6] = enc[3];
    dec[7] = enc[6];
    dec[8] = enc[10];
    dec[9] = enc[12];
    dec[10] = enc[0];
    dec[11] = enc[15];
    dec[12] = enc[11];
    dec[13] = enc[4];
    dec[14] = enc[2];
    dec[15] = enc[7];

    std::cout << dec << std::endl; // PROFIT?

    _getch();
    return 0;
}

Позиции символов до реверса я понял достаточно просто, я передал в качестве пароля 0123456789ABCDEF и посмотрел что с этим делом произошло после первого этапа шифрования.
На выходе мы получаем следующий результат: IdIGXM12QPQa3ClL
Этот пароль крякмис какого то хрена не принял, и я пошёл смотреть в отладчике что же я сделал не так, и как оказалось моя костыль-функция-декрипта не инкрементировала второй символ, который на самом деле является e

И так, пароль от этого пиздеца: IeIGXM12QPQa3ClL
Посмотреть вложение 7341


Всем спасибо кто дочитал это месево до конца, до скорого!

Для тех кто хочет потыкать сие чудо: Crackme.exe
Для параноиков: VirusTotal

Получилось интересно. Спасибо за помощь.
Автор этого крякми просил оценить его. Насколько оценишь?
 
Активность
Пока что здесь никого нет
Сверху Снизу