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

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

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



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


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

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



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

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



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

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

Вся шифровка состоит из нескольких этапов:
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


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

Для тех кто хочет потыкать сие чудо: Crackme.exe
Для параноиков: VirusTotal
 
Последнее редактирование:
  • 112
  • 92
Контакты для связи отсутствуют.
Обратите внимание, пользователь заблокирован на форуме. Не рекомендуется проводить сделки.

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