+ Reply to Thread
Results 1 to 6 of 6

Thread: Crackme #02

  1. #1
    AbreC's Avatar

    Default Crackme #02

    Привет всем!

    В конце лета мне удалось-таки ухватить себе немного свободного времени и потратить на любимое дело.
    Итак, сегодня в нашей программе: взлом crackme#02 от [KSDR].

    Не будем дергать кота за хвост - приступим к делу.
    Давайте посмотрим на крэкми:


    На небольшой форме мы видим два текстовых поля и две кнопки.
    В поле 'Name' вводим имя пользователя, а в поле 'Password', соответственно, пароль пользователя.
    Потом жмем кнопку 'CHECK' и проверяем правильный ли пароль мы ввели.
    Я проверил несколько пар имя\пароль (от балды), но мне ничего не выдало...
    Кнопка 'About' выводит следующую информацию:


    Теперь давайте посмотрим на крэкми в отладчике:


    На скрине выше мы видим точку входа (4011A0) программы (да и весь "подготовительный" код). По коду можно легко догадаться о том, что форма создается на основе ресурсов.
    Есть форма - значит, есть код обработки сообщений. Адрес обработчика передается функции DialogBoxParam четвертым параметром:

    Code:
    int DialogBoxParam(
    
        HINSTANCE hInstance,     // handle to application instance
        LPCTSTR lpTemplateName,  // identifies dialog box template
        HWND hWndParent,         // handle to owner window
        DLGPROC lpDialogFunc,    // pointer to dialog box procedure  
        LPARAM dwInitParam       // initialization value
       );
    А в коде передача данного параметра находится по адресу 4011AE и выглядит как PUSH 4011C8.
    Теперь перейдем на код по адресу 4011C8.


    На рисунке Вы можете видеть комментарии в виде кода на С-подобном языке.
    Самое важное, что можно здесь увидеть: при нажатии на кнопку 'CHECK' вызывается код по адресу 401000.
    Я запустил программу, ввел пару AbreC/1165165806 и поставил бряк на 401000.


    Теперь посмотрим на этот участок:


    Итак, содержимое текстового поля 'Name' копируется в память по адресу 403004. Так как целых 64 байта, начиная с адреса 403004, выделены для хранения имени пользователя, давайте назовем эту область памяти (переменную) cName:

    char cName[64]; // &cName[0] = 403004

    Сразу после вызова функции, проверяется содержимое регистра EAX:

    CMP EAX, 4

    В данном случае EAX содержит длину строки, прочитанной из поля 'Name'. Если длина имени пользователя меньше 4 символов - выходим из процедуры. То есть ничего не выполняется.
    А вот если имя пользователя равно 4 и более символам, тогда выполняются два вызова:

    CALL 0040109A
    CALL 00401027


    Я уже исследовал содержимое этих процедур и обозвал их кое-как (смотрите в комментариях).

    Код по адресу 40109A назовем GetCodeHash. И вот почему:


    Берется содержимое поля 'Password', копируется в область памяти по адресу 40302С и длиной в 64 байта. Область назовем cCode:

    char cCode[64]; // &cCode[0] = 40302C

    Далее следует код:
    Code:
    004010B3  |.  BE 2C304000   MOV ESI,crackme#.0040302C
    004010B8  |.  33C0          XOR EAX,EAX
    004010BA  |.  33FF          XOR EDI,EDI
    004010BC  |.  33DB          XOR EBX,EBX
    004010BE  |>  B0 0A         /MOV AL,0A
    004010C0  |.  8A1E          |MOV BL,BYTE PTR DS:[ESI]
    004010C2  |.  84DB          |TEST BL,BL
    004010C4  |.  74 0B         |JE SHORT crackme#.004010D1
    004010C6  |.  80EB 30       |SUB BL,30
    004010C9  |.  0FAFF8        |IMUL EDI,EAX
    004010CC  |.  03FB          |ADD EDI,EBX
    004010CE  |.  46            |INC ESI
    004010CF  |.^ EB ED         \JMP SHORT crackme#.004010BE
    004010D1  |>  5B            POP EBX
    004010D2  |.  58            POP EAX
    004010D3  \.  C3            RETN
    То же самое можно представить в виде кода на С:

    Code:
    void GetCodeHash()
    {
          GetDlgItemText( hWnd, 1002, cCode, 64 );
    
          int nCodeHash = 0,
                      i = 0;
    
          while( cCode[i] )
          {
                nCodeHash *= 0xA;
                nCodeHash += ( cCode[i] - 0x30 );
                i++;
          }
    
          return;
    }
    При выходе из процедуры в регистре EDI будет наш nCodeHash. Хотя на самом деле это никакой не хэш (пока что).
    Выходим из GetCodeHash и влетаем в следующую процедуру - CheckCodeHash. Как и следует из названия, здесь будет проверяться код на валидность (наполовину, т.к. окончательная проверка происходит в процедуре Base64Decode).
    Рассмотрим код процедуры:


    Почти то же самое (в моем видении) выглядело бы так:

    Code:
    void CheckCodeHash()
    {
          nHashCopy = nCodeHash;
    
    	int eax = 0x0;
    	
          eax = cName[3]; eax <<= 8;
          eax += cName[2]; eax <<= 8;
          eax += cName[1]; eax <<= 8;
          eax += cName[0];
    
          int ecx = cName[1];
    
          do
          {
                nCodeHash = nHashCopy;
                nCodeHash <<= ecx;
                nCodeHash ^= eax;
                nCodeHash &= -1;
                ecx -= 0x3;
                eax = nCodeHash;
          }
          while( ecx > 0 );
    
          int edx = 0x2010;
    
          ecx =	0x42;
    
          do
          {
                nHashCopy = edx;
                nHashCopy <<= ecx;
                nHashCopy ^= eax;
                nHashCopy &= -1;
                ecx -= 0x5;
                eax = nHashCopy;
          }
          while( ecx > 0 );
    
          int ebx = eax;
    
          eax <<= 0x10;
    
          if( eax == 0x484E )
          {
                XorCipher;
                Base64Decode;
                MessageBoxA ( hWnd,
                              cMsg,
                              "Attention - a confidential code!",
                              0x40
                             );
          }
          return;	
    }
    Как видите, если после двух циклов в eax (после сдвига) оказывается число 0x484E, то вызываются еще две процедуры XorCipher и Base64Decode, после чего вызывается MessageBox для вывода сообщения.

    Рассмотрим первую процедуру:


    Думаю, я достаточно хорошо прокомментировал код... Ну а если в общих чертах, то проводится операция XOR между зашифрованным сообщением (cCipher) и содержимым регистра BX. Результат (в моем понимании) помещается в cXoredCipher.

    Теперь пара слов о процедуре Base64Decode.
    В самом начале процедуры проверяется содержимое cXoredCipher на наличие двух знаков "равно" в конце (что указывает на Base64). Если они присутствуют, в AL и DL закладываются определенные значения, которые "способствуют" правильной дешифровке конечного сообщения cMsg.


    Далее по коду идет дешифровщик.

    Конечно, одно дело писать статью во время взлома и совсем другое, когда это уже сделано. Поэтому скажу пару слов про то, как я находил пароль.

    Когда впервые видишь какой-то ассемблерный код, не сразу приходит в голову что там к чему.
    Но сразу бросилось в глаза то, что сначала берется текст из поля Name, и только потом Code (и это если имя состоит из четырех или более символов).
    Видно сразу также то, что над Code проводятся какие-то преобразования. И только потом понимаешь, что это.

    Самое сложное в крэкми - это процедура CheckCodeHash. Мне пришлось немало времени ломать над ней голову. Особенно безжалостна эта процедура к тем, кто пытается обратить первый цикл. Хотя вторая такая же по устройству, хоть ее и можно превратить в константу (как подметил BoRoV).

    Наконец, понимаешь, что единственный способ по-быстрому достать ключ - это брутфорс.
    Первый раз я прошил крэкми так, чтобы генерировался такой nCodeHash, при котором после второго цикла и сдвига влево на шестнадцать битов в EAX было бы число 0x484E.
    Да, это у меня получилось, но сообщение я так не прочитал...
    Потом только я уделил больше внимания процедуре XorCipher, где использовалось содержимое BX для XOR'а шифра.
    Но я еще думал, что только брутфорсом можно достать это самое содержимое BX. И продолжал думать до тех пор, пока не заметил проверку в начале процедуры Base64Decode.
    Тут, зная, что XOR имеет одно важное свойство, а именно:

    X xor Key = Y
    Y xor Key = X
    X xor Y = Key


    Я быстро вычислил искомое содержимое BX, на которое надо проXORить cCipher, чтобы в конце cXoredCipher появились знаки "==".

    Мне пришлось подменить только одно сравнение, а именно:

    CMP EAX,484E

    Поменял на:

    CMP EAX,484EE8C3

    и удалил побитовый сдвиг влево (SHR EAX,10).

    И брутфорс заработал правильно =)

    Небольшая оговорка по поводу выводимого сообщения. Сообщение проходит такую "эволюцию":

    cCipher --> cXoredCipher --> cMsg

    На этом статья завершается.

    AbreC © r0 Crew

    Спасибо [KSDR] за занимательный крэкми!

    P.S. Ссылка на исходник кейгена: keygen.txt
    Last edited by AbreC; 31-08-2011 at 10:38.
    You may stop this individual, but you can't stop us all... after all, we're all alike. © The Mentor (Phrack 7, File 3)

  2. 4 пользователя(ей) сказали cпасибо:
    Boolean (31-08-2011) Graxcon (26-02-2014) Rectifier (31-08-2011) root (31-08-2011)
  3. #2
    REU's Avatar

    Default Re: Crackme #02

    Эх, я то думал нормальный кейген, а там тупо брут.

  4. #3
    AbreC's Avatar

    Default Re: Crackme #02

    Quote Originally Posted by BoRoV View Post
    Эх, я то думал нормальный кейген, а там тупо брут.
    Хэш-функции нельзя обратить, поэтому и брутфорс =)

    И еще момент: кейген - это генератор ключей. Имхо, все равно каким образом кейген генерит ключи - хоть брут, хоть вычисление.

    Хотя, конечно, "умный подход" самый правильный.

    Тем более если бы это был кейген на реальное коммерческое ПО, то пользователь мог бы подождать минут 5-10 на генерацию халявного ключа.
    You may stop this individual, but you can't stop us all... after all, we're all alike. © The Mentor (Phrack 7, File 3)

  5. #4
    REU's Avatar

    Default Re: Crackme #02

    Quote Originally Posted by AbreC View Post
    Тем более если бы это был кейген на реальное коммерческое ПО, то пользователь мог бы подождать минут 5-10 на генерацию халявного ключа.
    Ну я как бы на это и намекал.

  6. #5
    Rectifier's Avatar

    Default Re: Crackme #02

    Брут не брут, но за статью спасибо!
    Секрет успеха — в постоянстве цели. (Бенджамин Дизраэли)

    Вы должны воплощать ту перемену, которую хотите произвести в мире. (Махатма Ганди)

    Один опыт я ставлю выше, чем тысячу мнений, рожденных только воображением. (Ломоносов)

  7. #6
    root's Avatar

    Default Re: Crackme #02

    Респект однозначно!

    Спасибо как автору крэкми, без которого небыло бы статьи, так и непосредственно автору мануала!
    Успех – это путь от провала до провала без потери энтузиазма. (В. Черчиль)

    Не бойся идти медленно, бойся остановиться. (Китайская пословица)

    When you lose fun and start doing things only for the payback, you're dead. (c) TCLH (Phrack 65, Intro)

+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 01:36
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org