R0 CREW

Решение крякми написанного на Qt

Доброго времени суток.

Само крякми: http://forum.reverse4you.org/attachment.php?attachmentid=100&d=1358541694

Сегодня рассмотрим решение крякмиса, с которого мы сможем вынести информацию о том как класс Qt QString хранит внутри себя данные.

Собственно, посмотрим на то с чем мы будем иметь дело. Видим, что программа написана на С++ с использованием фреймворка Qt. Запустим ее и попробуем ввести что-то в поле Login и нажав кнопку Enter видим, что никаких окошек не всплывает. А жаль… Очень жаль… Но не будем долго печалиться и начнем как-то выкручиваться с этой ситуации.

Для начала посмотрим, что используется в данном приложении. Для этого зайдем в IDA и нажмем View->Open subview->Names window. Немного посмотрев, меня очень сильно заинтересовал класс QlineEdit. Вбив в гугле, нашел интересную функцию у этого класса под названием text(), которая возвращает Qstring, где хранится введенная нами строка. Т.к. Введеная нами строка вероятно где-то должна сравниваться, то попробуем найти в окне Names window класс Qstring и посмотрем, какие операторы его используются. Видим, что используется Qstring::operator==(QString const &). Нажимаем на ней 2 раза и видим, что наша строка сравнивается дважды по адресам 0х00401341 и 0х004013F2. Поставим на этих адресах сразу бряки. Теперь проанализируем, что находится возле этих адресов. Начнем с адреса 0х00401341:

.text:00401329                 cmp     byte ptr [esi+24h], 1
.text:0040132D                 mov     edi, ds:??8QString@@QBE_NABV0@@Z ; QString::operator==(QString const &)
.text:00401333                 jnz     loc_4013E4
.text:00401339                 lea     eax, [esi+20h]
.text:0040133C                 push    eax
.text:0040133D                 lea     ecx, [esp+60h+var_40]
.text:00401341                 call    edi ; QString::operator==(QString const &) ; QString::operator==(QString const &)
.text:00401343                 test    al, al
.text:00401345                 jz      loc_4013E4
.text:0040134B                 push    0FFFFFFFFh
.text:0040134D                 push    offset aYouVeBrokenMe ; "You' ve broken me!"
.text:00401352                 call    ds:?fromAscii_helper@QString@@CAPAUData@1@PBDH@Z ; QString::fromAscii_helper(char const *,int)
.text:00401358                 mov     [esp+64h+var_38], eax
.text:0040135C                 push    0FFFFFFFFh
.text:0040135E                 mov     bl, 9
.text:00401360                 push    offset aCongratulation ; "Congratulation"
.text:00401365                 mov     byte ptr [esp+6Ch+var_4], bl
.text:00401369                 call    ds:?fromAscii_helper@QString@@CAPAUData@1@PBDH@Z ; QString::fromAscii_helper(char const *,int)

Ура, мы видим нужные для нас сообщения вида «You’ ve broken me!» и «Congratulation». Они не могу не радовать, но давайте попробуем понять, что должно произойти, что бы мы туда попали. Исходя из листинга выше, можно сделать вывод, что для этого должны выполниться следующие условия: 1. по адресу esi+24h, мы должны получить значение 1 ( похоже на флажок) и 2. какие-то две строки, можно предположить, что это введенная нами строка и правильная, должны быть равны.
Если мы запустим приложение, введем какое-то значение и нажмем кнопку Enter, то попадем на оператор сравнения, который находится по адресу 0х004013F2. Почему же мы не попали на оператор сравнения описанный выше? Наверно, все из-за того, флажок оказался равным 0. Это мы можем легко проверить, если поставим бряк на строчке и посмотрев, что находится по адресу esi+24h:
[HTML].text:00401329 cmp byte ptr [esi+24h], 1[/HTML]

Т.к. мы попали на 2й оператор сравнения, то давайте проанализируем код, который находится вокруг него:

.text:004013EA                 lea     eax, [esi+1Ch]
.text:004013ED                 push    eax
.text:004013EE                 lea     ecx, [esp+60h+var_40]
.text:004013F2                 call    edi ; QString::operator==(QString const &) ; QString::operator==(QString const &)
.text:004013F4                 test    al, al
.text:004013F6                 jz      short loc_401448
.text:004013F8                 push    0FFFFFFFFh
.text:004013FA                 push    offset aPassword ; "Password:"
.text:004013FF                 call    ds:?fromAscii_helper@QString@@CAPAUData@1@PBDH@Z ; QString::fromAscii_helper(char const *,int)
.text:00401405                 add     esp, 8
.text:00401408                 mov     [esp+5Ch+var_38], eax
.text:0040140C                 lea     ecx, [esp+5Ch+var_38]
.text:00401410                 push    ecx
.text:00401411                 mov     ecx, [esi+18h]
.text:00401414                 mov     byte ptr [esp+60h+var_4], 0Bh
.text:00401419                 call    ds:?setText@QLabel@@QAEXABVQString@@@Z ; QLabel::setText(QString const &)
.text:0040141F                 mov     edx, [esp+5Ch+var_38]
.text:00401423                 mov     byte ptr [esp+5Ch+var_4], 6
.text:00401428                 or      eax, 0FFFFFFFFh
.text:0040142B                 lock xadd [edx], eax
.text:0040142F                 jnz     short loc_40143B
.text:00401431                 mov     ecx, [esp+5Ch+var_38]
.text:00401435                 push    ecx
.text:00401436                 call    ebx ; QString::free(QString::Data *) ; QString::free(QString::Data *)
.text:00401438                 add     esp, 4
.text:0040143B
.text:0040143B loc_40143B:                             ; CODE XREF: sub_401140+2EF#j
.text:0040143B                 mov     ecx, [esi+14h]
.text:0040143E                 call    ds:?clear@QLineEdit@@QAEXXZ ; QLineEdit::clear(void)
.text:00401444                 mov     byte ptr [esi+24h], 1

В этом листинге можем видеть, что если 2 строки равны, то Qlabel с текущим текстом «Login» поменяется на Passoword и флажок, к-ый мы упоминали выше, установится в 1! Исходя из всего можем сделать вывод, что по адресу 0x004013F2 у нас проверяется правильность логина, а по адресу 00401341 — правильность пароля. И для того, что бы это проверить нам надо разобраться, как QString хранит строки. Посмотрим какой код находится внутри функции Qstring::operator==(QStting const &):

QtCore4.dll:6705BFD0 qtcore4_??8QString@@QBE_NABV0@@Z:
QtCore4.dll:6705BFD0 mov     eax, [esp+4]
QtCore4.dll:6705BFD4 mov     edx, [ecx]
QtCore4.dll:6705BFD6 mov     eax, [eax]
QtCore4.dll:6705BFD8 mov     ecx, [edx+8]
QtCore4.dll:6705BFDB cmp     ecx, [eax+8]
QtCore4.dll:6705BFDE jz      short loc_6705BFE5
QtCore4.dll:6705BFE0 xor     al, al
QtCore4.dll:6705BFE2 retn    4
QtCore4.dll:6705BFE5 ; ---------------------------------------------------------------------------
QtCore4.dll:6705BFE5
QtCore4.dll:6705BFE5 loc_6705BFE5:                           ; CODE XREF: QtCore4.dll:qtcore4_??8QString@@QBE_NABV0@@Z+E#j
QtCore4.dll:6705BFE5 push    ecx
QtCore4.dll:6705BFE6 mov     ecx, [eax+0Ch]
QtCore4.dll:6705BFE9 mov     eax, [edx+0Ch]
QtCore4.dll:6705BFEC call    near ptr unk_6705BB20
QtCore4.dll:6705BFF1 add     esp, 4
QtCore4.dll:6705BFF4 retn    4

С этого листинга, можно заключить, что [esp+4] содержит указатель на объект Qstring, a [ecx] — this. Далее проверяется не равны ли указатели объектов и если не равны, то получаем указатели сравниваемых строк: [*QString+0Ch] и [*this + 0Сh]
. Соответственно, если к тому что передается в функцию сравнения прибавить 0Ch, то увидим указатель на введенную и верную строки. Проверим это.

Перезапускаем программу, вводим логин, нажимает кнопку Enter. И смотрим, что передается функции operator==. Посмотрим, какая строка передается оператору сравнения:

debug035:00A16E58 db    1
debug035:00A16E59 db    0
debug035:00A16E5A db    0
debug035:00A16E5B db    0
debug035:00A16E5C db  0Ah
debug035:00A16E5D db    0
debug035:00A16E5E db    0
debug035:00A16E5F db    0
debug035:00A16E60 db    8
debug035:00A16E61 db    0
debug035:00A16E62 db    0
debug035:00A16E63 db    0
debug035:00A16E64 db  6Ah ; j
debug035:00A16E65 db  6Eh ; n
debug035:00A16E66 db 0A1h ; �
debug035:00A16E67 db    0
debug035:00A16E68 db    0
debug035:00A16E69 db 0F0h ; �
debug035:00A16E6A db  2Eh ; .
debug035:00A16E6B db    0
debug035:00A16E6C db  29h ; )
debug035:00A16E6D db    0
debug035:00A16E6E db  2Eh ; .
debug035:00A16E6F db    0
debug035:00A16E70 db  34h ; 4
debug035:00A16E71 db    0
debug035:00A16E72 db  2Eh ; .
debug035:00A16E73 db    0
debug035:00A16E74 db  23h ; #
debug035:00A16E75 db    0
debug035:00A16E76 db  22h ; "
debug035:00A16E77 db    0
debug035:00A16E78 db  35h ; 5
debug035:00A16E79 db    0
debug035:00A16E7A db    0

Это строка 2Eh 29h 2eh 34h 2eh 34h 2eh 23h 22h 35h. Переводим это все в ASCII пробуем ввести в логин нажимает Enter и все равно видим, что наша программа не приняла логин, соответственно, что тут не так и по ходу логин тем или иным образом шифруется. Что бы это проверить давайте проанализируем функцию, в которой находится наши операторы сравнения. Посмотрим на ее начало ( что бы долго не капашиться, посмотрим на код в Hex-rays):

v1 = this;
  v25 = this;
  enteredString = QLineEdit::text(*(_DWORD *)(this + 20), &v28);
  v32 = 0;
  v3 = QString::toAscii(enteredString, &v27);
  LOBYTE(v32) = 1;
  QString::QString(&v21, v3);
  LOBYTE(v32) = 3;
  if ( !_InterlockedExchangeAdd((signed __int32 *)v27, 0xFFFFFFFFu) )
    qFree(v27);
  v4 = QString::free;
  LOBYTE(v32) = 4;
  if ( !_InterlockedExchangeAdd(v28, 0xFFFFFFFFu) )
    QString::free(v28);
  v22 = (signed __int32 *)QString::shared_null;
  _InterlockedExchangeAdd((signed __int32 *)QString::shared_null, 1u);
  LOBYTE(v32) = 5;
  v26 = QString::fromAscii_helper("g", -1);
  v5 = *(_DWORD *)(v21 + 8) == 0;
  v6 = *(_DWORD *)(v21 + 8) < 0;
  LOBYTE(v32) = 6;
  v23 = 0;
  if ( !(v6 | v5) )
  {
    do
    {
      v7 = QString::toAscii(&v26, &v29);
      LOBYTE(v32) = 7;
      v8 = QString::toAscii(&v21, &v24);
      LOBYTE(v32) = 8;
      v9 = QByteArray::operator__(v7, &v30, 0);
      v10 = **(_DWORD **)v9;
      v11 = *(_DWORD *)(v9 + 4);
      if ( v11 >= *(_DWORD *)(v10 + 8) )
        v12 = 0;
      else
        v12 = *(_BYTE *)(v11 + *(_DWORD *)(v10 + 12));
      v13 = QByteArray::operator__(v8, &v31, v23);
      v14 = **(_DWORD **)v13;
      v15 = *(_DWORD *)(v13 + 4);
      if ( v15 >= *(_DWORD *)(v14 + 8) )
        v16 = 0;
      else
        v16 = *(_BYTE *)(v15 + *(_DWORD *)(v14 + 12));
      LOBYTE(v32) = 7;
      v17 = (v12 ^ v16) % 127;
      if ( !_InterlockedExchangeAdd((signed __int32 *)v24, 0xFFFFFFFFu) )
        qFree((void *)v24);
      LOBYTE(v32) = 6;
      if ( !_InterlockedExchangeAdd((signed __int32 *)v29, 0xFFFFFFFFu) )
        qFree(v29);
      if ( v17 < 32 )
        v17 += 32;
      QString::operator__(&v22, v17);
      v18 = __OFSUB__(v23 + 1, *(_DWORD *)(v21 + 8));
      v6 = v23++ + 1 - *(_DWORD *)(v21 + 8) < 0;
    }
    while ( v6 ^ v18 );
    v1 = v25;
    v4 = QString::free;
  }

Видим, что вначале мы получаем исходную строку, и потом в цикле с каждым символом делаем операции: взять ASCII очередного введенного символа, сделать с ним операцию xor, резудьтат взять по модулю 127 и далее если получившиеся значение меньше 32 (20h), то добавить еще к нему 20h.
Соответственно, что бы получить строку, к-ую мы должны ввести, нам надо поставить бряку на строчку, где xor’ся значения и получить ключ с которым происходит эта операция. Для этого анализируем ассемблерный код и ставим бряку в этой строчке:

004012A0 xor     eax, edx

Запускаем программу, попадаем на эту точку и смотрим чему равно значение edx — это и есть наш ключ. Он у нас получается равным 67h.

Т.к. У нас есть строка, к-ую мы должны были бы получить, то делаем обратные операции и получаем строку, к-ую мы должны ввести:

Искомый символ = (Символ полученной строки) xor 0x67.

Получаем строку, к-ую должны получить, как было описано выше:

debug025:003DB040 db    1
debug025:003DB041 db    0
debug025:003DB042 db    0
debug025:003DB043 db    0
debug025:003DB044 db  0Dh
debug025:003DB045 db    0
debug025:003DB046 db    0
debug025:003DB047 db    0
debug025:003DB048 db  0Dh
debug025:003DB049 db    0
debug025:003DB04A db    0
debug025:003DB04B db    0
debug025:003DB04C db  52h ; R
debug025:003DB04D db 0B0h ; -
debug025:003DB04E db  3Dh ; =
debug025:003DB04F db    0
debug025:003DB050 db    0
debug025:003DB051 db 0F0h ; �
debug025:003DB052 db  37h ; 7
debug025:003DB053 db    0
debug025:003DB054 db  26h ; &
debug025:003DB055 db    0
debug025:003DB056 db  35h ; 5
debug025:003DB057 db    0
debug025:003DB058 db  26h ; &
debug025:003DB059 db    0
debug025:003DB05A db  23h ; #
debug025:003DB05B db    0
debug025:003DB05C db  2Eh ; .
debug025:003DB05D db    0
debug025:003DB05E db  34h ; 4
debug025:003DB05F db    0
debug025:003DB060 db  22h ; "
debug025:003DB061 db    0
debug025:003DB062 db  47h ; G
debug025:003DB063 db    0
debug025:003DB064 db  2Bh ; +
debug025:003DB065 db    0
debug025:003DB066 db  28h ; (
debug025:003DB067 db    0
debug025:003DB068 db  34h ; 4
debug025:003DB069 db    0
debug025:003DB06A db  33h ; 3
debug025:003DB06B db    0

И начинаем восстанавливать исходную строку:
X1 = ( 37h — 20h ) xor 67h = 70h = P
X2 = (26h — 20h ) xor 67h = 61h = A

Проделывая для всех символов — paradise lost. Проверяем этот логин, и смотрим о чудо, он подходит. Мы проходим проверку, которая находится сразу за оператором сравнения, находящийся по адресу 004013F2. Видим, что надпись Login поменяется на Password и наш флажок установится в 1.

Теперь если мы введем какой либо пароль, то мы попадем на оператор сравнения, находящийся по адресу 00401341. Где скорее всего понятно, что мы не правильно ввели строку ;). Сразу смотрим чему должна быть равна правильная строка:

debug024:003DB088 db    1
debug024:003DB089 db    0
debug024:003DB08A db    0
debug024:003DB08B db    0
debug024:003DB08C db  0Dh
debug024:003DB08D db    0
debug024:003DB08E db    0
debug024:003DB08F db    0
debug024:003DB090 db  0Dh
debug024:003DB091 db    0
debug024:003DB092 db    0
debug024:003DB093 db    0
debug024:003DB094 db  9Ah ; �
debug024:003DB095 db 0B0h ; -
debug024:003DB096 db  3Dh ; =
debug024:003DB097 db    0
debug024:003DB098 db    0
debug024:003DB099 db 0F0h ; �
debug024:003DB09A db  42h ; B
debug024:003DB09B db    0
debug024:003DB09C db  52h ; R
debug024:003DB09D db    0
debug024:003DB09E db  4Fh ; O
debug024:003DB09F db    0
debug024:003DB0A0 db  4Bh ; K
debug024:003DB0A1 db    0
debug024:003DB0A2 db  45h ; E
debug024:003DB0A3 db    0
debug024:003DB0A4 db  4Eh ; N
debug024:003DB0A5 db    0
debug024:003DB0A6 db  20h
debug024:003DB0A7 db    0
debug024:003DB0A8 db  44h ; D
debug024:003DB0A9 db    0
debug024:003DB0AA db  52h ; R
debug024:003DB0AB db    0
debug024:003DB0AC db  45h ; E
debug024:003DB0AD db    0
debug024:003DB0AE db  41h ; A
debug024:003DB0AF db    0
debug024:003DB0B0 db  4Dh ; M
debug024:003DB0B1 db    0
debug024:003DB0B2 db  53h ; S
debug024:003DB0B3 db    0
debug024:003DB0B4 db    0

Теперь по тому же принципу, что и с логином получаем правильный пароль. Он оказывается равным - %5(,")G#5"&*4

Вот, собственно и все.
Все, что хочется вынести с этого крякми, как это то, что если внимательно посмотреть на тот как хранятся строки в Qstring, то можно сделать вывод (испольую то, что писал root, очень хорошо написал :wink: ):

[0xaaaabbbb] = 0x12345670

0x12345670 struct {
                      0x0 reserved   <= *QString
                      0x8 pSize      <= *QString + 8 
                      0xC pString    <= *QString + C
}

QString = this(QString) = 0xaaaabbbb

*QString = [QString]

pSize = *QString + 8
Size = [pSize]

pString = *QString + C
String = [pString]

И также, хочется отметить, что каждый символы в QString хранится формате unicode.

Вроде как все. Всем спасибо за внимание и всем кто помогал мне разбираться.

Жду гневных отзывов, комментариев и критики по написанному.

П.С.: может заняться исследованием типичных конструкций для ООП на языке С++ и как это все выглядит в дизасме? Кого-то такое заинтересует?

Советую пользоваться IDA Pro только по прямому на значению, так как отлаживать в ней кране не удобно. ИМХО.

Я это сказал, не смотря в отладчик, а чисто исходя из логики, на самом деле там проверяются “размеры строк”, а не идентичность объектов. Позже, на своем скриншоте, я это уточнил как size(this->text).

Сам же крякми решается намного быстрее, без всяких операций XOR. Нужно:

  1. Поставить бряк на operator == (адрес 0x6705BFD0)

  2. Подсмотреть сравниваемые строки, адреса 0x6705BFE6 и 0x6705BFE9. Понять, где твоя строка, а где Логин.

  3. Ввести найденый Логин. Увидеть, что он превратился в строку PARADISE LOST.

  4. Ввести полученную строку (PARADISE LOST) в качестве Логина.

  5. Тем же макаром, что и с Логином - подсмотреть строки для Пароля. Понять, где твоя строка, а где Пароль.

  6. Ввести найденый Пароль. Увидеть, что он превратился в строку %5(,")G#5"&*4.

  7. Ввести полученный строку (%5(,")G#5"&*4) в качестве Пароля.