Comment on page
Глава 14
Прежде всего я собираюсь объяснить, как решается крэкми, над которым мы начали работать в главе 13.

Это mielecrackme [ссылка], загруженный в OllyDbg, и его точка входа. Посмотрим, какие функции API используются с помощью SEARCH FOR-NAME (LABEL) IN CURRENT MODULE.

Вот список найденных api-функций.

Среди них указаны самые важные: GetWindowTextA, использующаяся при вводе серийного номера, lstrcmpA, которая встречалась в предыдущей главе и используется для сравнения строк, и MessageBoxA, отображающая сообщение о том, верен ли серийный номер или нет.
Мы можем установить BPX на эти функции, чтобы произошла остановка, когда будет введён неверный серийный номер, но в данном случае, который весьма про ст, можно сделать всё гораздо быстрее, если посмотреть СТРОКИ, используемые программой.

Выполнение SEARCH FOR – ALL REFERENCED TEXT STRINGS даёт нам список строк, которые использует крэкми.

Здесь видим как строку, отображающуюся как в успешном случае, так и строку, показывающуюся в случае неудачи. Если сделаем на какой-нибудь из них двойной клик мышью, то окажемся в окрестностях MessageBoxA. Попробуем сделать двойной клик на "YOU ENTERED THE RIGHT PASSWORD" ("Вы ввели правильный пароль").

Оказались в соответствующем месте.
Сначала идёт GetWindowTextA, считывающая серийный номер, который мы напечатали, затем lstrcmpA, сравнивающая считанный номер с правильным, а затем, если они верны, MessageBoxA показывает "YOU ENTERED THE RIGHT PASSWORD", а если нет, то происходит переход к другому MessageBoxA, отображающему "YOU SHOULD TRY AGAIN, IT'S SO EASY" ("Попробуй ещё раз, это же так просто").
Установим BPX на вызове lstrcmpA, чтобы посмотреть, что она сравнивает.

Запустим программу с помощью F9.

Выскочило окно ввода серийного номера. Введём в него что-нибудь вроде 989898.

Нажмём кнопку CHECK, чтобы сработал установленный нами BPX.

Видим, что OLLY показывает и параметры функции, которые являются двумя сравниваемыми строками, в данном случае сравниваются строки "989898" и "cannabis".
Чтобы выполнить вызов этой API-функции, нажмём F8.

В EAX помещается результат, равный FFFFFFFF или -1, а это означает, что строки не одинаковые.

В результате этого сравнения флаг Z неактивен и инструкция JNZ выполняет переход, т.к. флаг Z равен нулю (вспомним, что JNZ совершает переход, если флаг Z равен нулю, а JZ, наоборот, если флаг Z активен, т.к. равен 1).

Раз мы делаем переход, то получаем сообщение с ошибкой, но зато теперь мы знаем, что сравнение происходит со словом "cannabis", то есть это и есть верный серийный номер. Снова продолжим выполнение программы.

Нажав на "ОК", снова вернёмся в окно ввода серийного номера и напечатаем правильный – "cannabis".

Нажмём кнопку CHECK, и срабатыва ет BPX.

Видим, что будут сравниваться две одинаковые строки. Жмём F8, чтобы выполнить вызов API-функции..

Обе строки были равны, и в EAX был сохранён результат функции – ноль, что активирует флаг Z.

И поэтому JNZ не будет совершать переход.

А раз так, то как только мы продолжим выполнение программы, отобразится окошко, сообщающее о том, что мы ввели верный серийный номер.

Это и есть решение крэкми из главы 13. Хе-хе, серийным номером является слово "cannabis".
Продолжим дальше и рассмотрим более сложные случаи с жёстко заданным серийным номером, чем те, что мы рассматривали ранее.
В следующем случае уже не производится прямого сравнения серийного номера с введённой строкой. Откроем крэкми crackmeeasy [ссылка] в OllyDbg.

Мы уже знаем, как посмотреть используемые API-функции. В списке также есть GetDlgItemTextA, на который мы и установим BPX.

В commandbar’е напечатаем:

Теперь запустим программу с помощью F9, и откроется окно ввода серийного номера.

Напечатаем неверный серийный номер.

Кликнем по кнопке "Check" и попадём на вызов нужной нам функции.

Посмотрим, какие параметры в стеке.

Здесь видим буфер, в котором сохраняется неправильный серийный номер, отметим эту строку, затем правый клик мышью и FOLLOW IN DUMP.

Буфер пуст, так как функция ещё не запустилась, поэтому выберем DEBUG-EXECUTE TILL RETURN.

Это приведёт к выполнению функции, которое остановится на инструкции RET.

Нажмём F7, чтобы вернуться в программу.

И видим, что в буфере окажется введенный нами неверный серийный номер.

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

Здесь в EAX помещается число 401222, которое является адресом, указывающем на строку, содержащую число-константу.


На следующей строке, где EAX равен 401222:
MOV EDX,DWORD PTR DS:[EAX]
Это, на самом деле, всё равно, что:
MOV EDX,DWORD PTR DS:[401222]

То есть содержимое памяти по адресу 401222 перемещается в EDX.
Олли показывает, что это первые 4 байта числа 10445678951.

Выполнение строки с помощью F7 перемещает их в EDX (они всегда перемещаются в регистр в порядке, обратном тому, как они располагались в памяти).

На следующей строке байты, находящиеся в EDX, перемещаются в
[EBP-30]
.
Из пояснений Олли видно, что
[EBP-30]
на моей машине – это 240f9e4. Поищем это значение в DUMP’е.

F7 копирует байты, находящиеся в EDX.

После чего

в EDX перемещаются следующие 4 байта числа-константы.

Пояснения Олли показывают, что
[eax+4]
содержат 401226, и выполнение инструкции с помощью F7 перемещает следующие 4 байта в EDX.
И копируется продолжение того, что копировалось ранее.

В действительности происходит вот что: этот номер копируется по 4 байта в другую часть памяти.

И наконец копируются последние 4 байта.

Наконец-то номер скопирован полностью.
Видим, что чуть ниже находится вызов функции memset. Посмотрим её параметры с помощью OllyDbg.

Здесь три значение (n, c и s).
- s – начальный адрес
- n – количество байтов, которых нужно заполнить требуемым значением
- c – значение, которым будет заполняться указанная область памяти

В стеке находятся вышеуказанные параметры: область памяти по адресу 240f9f0 продолжительностью в 8 байт должна быть заполнена нулями.

Выполнив эту функцию с помощью F8, мы видим, что указанная нами область памяти действительно была заполнена нулями.
Ещё ниже находится вызов lstrlen, которая считает длину строки, адрес которой задаётся непосредственно до вызова самой функции.

В стеке находятся следующие параметры:

То есть будет вычислена длина строки, которая начинается по адресу 240f9E4, что является адресом уже известного нам числа, хех.
F8, и происходит выполнение strlen, а в EAX возвращается длина строки.

Видим, что размер равен 0B, то есть 11 в десятиричной системе, что является длиной нашего номера.

Здесь LEA перемещает в EDX значение EAX минус 1, то есть 0A.
На следующей строке происходит сравнение 0A с содержимым
[EBX-10]
, то есть с нулём.