Введение в крэкинг с нуля, используя OllyDbg - Глава 29 — Архив WASM.RU

Все статьи

Введение в крэкинг с нуля, используя OllyDbg - Глава 29 — Архив WASM.RU



Файлы к статье

Думаю, что в предыдущих главах мы рассмотрели все основные вещи относительно Visual Basic’а, а те, кто хочет изучить эту тему поглубже, могут прочитать посвящённые VB туториалы с cracklatinos (на испанском – прим. пер.), которые укрепят и дополнят ваши знания.

Среди них встречаются очень хорошие туториалы о Visual Basic’е, написанные COCO, которые достаточно сложны и могут стать хорошей практикой, туториалы ARAMPUMK о волшебных точках Visual Basic’а, а также другие прекрасные туториалы, которые открывают дорогу для дальнейшего углубления в данную область. А мы меж тем перейдём к следующей теме, а именно к P-CODE.

Программы на Visual Basic’е могут быть двух типов: NATIVE, который мы рассматривали ранее, и P-CODE (псевдокод). Это то, что мы будем рассматривать в данной главе.

Основная разница заключается в том, что программы в NATIVE-коде выполняют строки кода в секции кода программы, в то время как если мы откроем программу, где используется P-CODE, в OllyDbg, модифицированном на предмет OEP’ов и VB, и установим BPM ON ACCESS в секции CODE, то увидим, что при выполнении не происходит остановок в секции кода, кроме тех случаев, когда встречается вызов какой-либо API-функции. Это очевидным образом указывает на то, что никакого исполняемого кода в данной секции нет.

Дизассемблирование программы, использующей P-CODE, ничем не поможет, так как там нет исполняемого кода. В ней всегда запускается DLL Visual Basic'а, которая читает значения из секции кода, указывающие ей, что нужно сделать. Например:

1e	делает		Безусловный переход

1e означает условный переход (похоже, Рикардо не смог определиться, какой же переход он имел в виду – условный или безусловный? – прим. пер.). Он выполняется в DLL Visual Basic’а, то есть, то есть она считывает эти значения из секции кода, и они указывают DLL, что нужно сделать. Таким образом, никакого выполнения кода в соответствующей секции не происходит, только считываются из неё значения.

А сейчас, раз мы такие нахальные, то возьмём нож в руки и решительно атакуем то, за что ещё никто не брался: оттрасируем и интерпретируем действия, выполняемые крэкми с помощью псевдокода, и всё это в OllyDbg, опкод за опкодом, хе-хе.

Рассмотрим первый крэкми под названием clave1, в котором необходимо найти серийный номер. Этот крэкми мы позаимствовали у нашего друга JB DUC’а, написавшего очень хорошие статьи по данной тематике.

Очевидно, что большая часть исследования P-CODE проводится с помощью прекрасного отладчика WKT. Если хотите почитать туториалы с его применением, то поищите те, что были написаны JB DUC’ом, а также в «Новом курсе» от CracksLatinos также есть превосходные статьи о P-CODE. Здесь мы будем использовать OllyDbg и EXDEC, который нам поможет видеть имена опкодов, так как Билл Гейтс не предоставил нам их список, хе.

-

Здесь открываем крэкми в обычном непропатченном OllyDbg с установленными плагинами для его сокрытия, которые мы рассматривали в прошлых главах.

Поверхностный осмотр показывает, что как и в NATIVE-крэкми метод 4c может быть применён и здесь, и мы можем найти место, где находятся формы таким же образом, что и в NATIVE-приложениях (4c для снятия наг-окон отлчино работает и в P-CODE, так что можно использовать именно этот метод, когда его возможно применить).

Что мы ещё видим?

Если пойдём вниз от точки входа, то не видим строк с кодом.

-

Только мусор в подобном роде, поэтому не надо пытаться бессмысленно анализировать его, вспомним, что в NATIVE-приложениях на Visual Basic’е мы видели примерно то же самое, если спускали вниз от точки входа.

-

Появляется мусор, но если продолжим дальше, то:

-

Встречаем кусок кода довольно большого размера, который идёт до самого конца секции и представляющий собой исполняемый код вместо P-CODE, за исключением некоторых строк, которые, учитывая близлежащие байты, OllyDbg интерпретирует как инструкции, хотя на самом деле это тоже мусор.

Возвращаемся к крэкми с псевдокодом.

-

Другая особенность – это API-функция под названием MethCallEngine, которую мы встречаем в крэкми, использующих псевдокод, так что теперь, когда нам нужно сделать первый шаг – определить, использует ли программа псевдокод или нет, то уже знаем, что нужно делать: надо посмотреть, есть ли исполняемый код в секции CODE или встречается ли там упомянутая нами функция.

Первое, что нам стоит сделать – это посмотреть, найдём ли мы какие-нибудь строки.

-

-

Это не слишком нам поможет, хотя, возможно, поможет что-нибудь другое.

Хорошо, установим BP прямо на JMP, который является прямым переходом к функции MethCallEngine,

-

Ищем сверху от точки входа и быстро встречаем JMP на функцию MethCallEngine и, находясь прямо на этой строке, делаем правый клик мышью и выбираем FOLLOW, что приведёт нас прямо к данной функции, где и устанавливаем BP.

-

-

Теперь делаем RUN.

-

Видим, что окно для ввода серийного номера появилось до остановки на вышеуказанной API-функции, что логично, так как создание окна и всё, что с этим связано, происходит таким же образом, как и в VB-приложениях, использующих «настоящий» код.

Теперь вводим ложный серийный номер.

-

И нажимаем «REGISTRAR».

-

Останавливаемся на JMP, который начинает истинную часть P-CODE.

-

Здесь входим в API-функцию, посмотрим, что она будет делать.

-

Здесь начинается.

Теперь, если откроем то же крэкми с помощью exdec, являющегося дизассемблером P-CODE, чтобы немного помочь себе, то увидим следующее:

-

То есть первый байт, который будет прочтён – это 04, находящийся по адресу 401BD0. Он находится не очень далеко отсюда, так что установим на него BPM ON ACCESS.

-

-

-

Этот первый байт, который будет прочтён. Когда остановимся, то окажемся в начале, и таким образом, мы можем оказаться там без помощи EXDEC. Как только остановимся на BP, установленном на API-функции MethCallEngine, мы можем поместить BPM ON ACCESS на секцию кода.

-

И делаем RUN. Видим, что останов происходит несколько раз.

-

Но только в одном из них происходит чтение содержимого ESI, в котором находится адрес, указывающий на вышеуказанный байт. Это место мы быстро находим после нескольких остановок (на моей машине их было точно 10).

-

В первый раз, когда происходит останов и байт из [ESI] считывается и перемещается в AL – это то место, где начинается чтение первого опкода P-CODE. Данным образом можно найти первый байт, не используя EXDEC.

Как видим, последующие опкоды, которые отображаются EXDEC’ом идут вслед за предыдущим.

-

-

Как видим, порядок опкодов на обоих картинках соответствует друг другу. Между ними располагаются параметры, которые необходимы опкоду для выполнения.

-

Как видим, здесь читается первый байт.

-

На который, как видим, указывает ESI. На следующей строке никаких действий по выполнению опкода не предпринимается. Значение ESI увеличивается на 1, чтобы затем прочитать параметры опкода.

-

Затем наконец доходим до косвенного JMP, который отправляет нас к строкам, выполняющим опкода, в данном случае это 04, как видим в EXDEC’е.

401BD0: 04 FLdRfVar                local_008C

Видим, что такого загадочного делает этого опкод.

-

Здесь видим выполнение опкода 04 FLdRfVar, это несколько маленьких строк кода, ничего такого, чтобы испугаться, хе-хе, и в конце видим, что заканчивается XOR EAX, EAX – это подготовка к чтению следующего опкода.

Первое, что делается – это берутся параметры опкода, которыми являются два байта, следующих за ним.

-

Хорошо, они помещаются в EAX с помощью инструкции MOVSX, и значение FF74 является отрицательным (мы рассматривали это в главах, посвящённым ассемблеру). Продолжаем трассировать.

-

Это значение в EAX равно -8c, и если кликнем по нему два раза:

-

В окне показывается, что это значение равно -140 в десятеричной системе счисления или -8c в шестнадцатеричной. В EXDEX нам показывается 8c.

401BD0: 04 FLdRfVar                local_008C

На следующей строке значение, считанное из параметров опкода, суммируется с EBP.

-

И к этому значению применяется PUSH.

-

То есть это эквивалент PUSH EBP-8c – выделение в стеке места для локальной переменной. На моей машине EBP равен 12f4e0, если отнимем 8c, то получится 12f454, то есть значение, которое останется в EAX и будет передано инструкции PUSH.

-

-

Ничего хорошего. Продолжаем.

-

Видим, что далее в EAX помещается ноль, что означает завершение проводимых операций с данным опкодом и инициализацию регистров для чтения следующего. На следующей строке, действительно, читается следующий опкод.

-

Второй опкод – это 21.

Видим его тут.

-

-

Как обычно, он помещается в AL.

-

А теперь к значению в ESI прибавляется 3, чтобы регистр указывал на параметры опкода. Затем управление переходит к косвенному JMP, который ведёт на опкод 21.

Посмотрим, что нам скажет гугл.

'21, FLdPrThis 
(Загрузить ссылочный указатель в указатель на элемент данных.)

Хорошо, у нас здесь несколько указателей, про которые мы не знаем, для чего они служат, но примерно дело состоит в том, что берётся ссылочный указатель и загружается в указатель на элемент данных. В ассемблере это было бы чтение указателя из стека и загрузка его в другое место стека же.

Если продолжим трассировать:

-

Видим, что читается содержимое EBP+8 (ссылочный указатель) и сохраняется в в EBP-4c (указатель на элемент данных).

Хорошо, прочитанное значение на моей машине равно 15b000. Если посмотрим в DUMP’е:

-

-

Видим, что здесь находится указатель на 4022e8, а если посмотрим в DUMP, то увидим:

-

Видим, что здесь начинается таблица, так что 15b000 – это ссылочный указатель, указывающий на что-то, что находится в таблице, хотя это не очень поможет нам для крэкинга, всегда хорошо постараться расшифровать, что происходит в программе.

В остальном, это команда без параметров, поэтому никаких вариаций у неё нет. При исполнении она всегда читает ссылочный указатель и сохраняет его в указатель на элемент данных.

Хорошо, продолжаем.

-

Далее обнуляется EAX, а на следующей строке читается третий опкод.

-

Смотрим в EXDEC, что такое 0F.

-

VcallAd

-

Ок, здесь видим, в чём заключается смысл опкода 0F, видим, что у него один параметр из двух байтов.

-

Параметр в моём случае – это 0300, и он указывает смещение в таблице дескрипторов элементов данных. Хм, видим, что доходим до косвенного JMP, посмотрим, совпадёт ли это с тем, что говорится.

-

Здесь читается содержимое EBP-4c и помещается в EBX.

-

Здесь располагается указанное значение 15b000. Оно помещается в стек.

-

-

Затем читаем параметры. В моём случае, это 300.

-

-

Суть дела состоит в том, что содержимое EBX, равное 15b000, это начало таблички, которую мы ищем, то есть таблица дескрипторов элементов данных. Адрес – 4022e8.

-

И к этом прибавляем 300, то есть смещение до адреса, где начинается искомое значение.

-

Таким образом, прибавив к началу таблицы 300, получим в EAX значение 4025e8.

-

Это значение указывает сюда:

-

То есть опкод 0f, используя предоставленный параметр, получает сохранённое в таблице значение.

-

Дальше у нас вызов по адресу, где читается данная таблица, и как видим, содержимое EAX равно:

-

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

Продолжаем трассировать, проходим CALL с F8.

-

-

То есть после всего этого в стеке остаётся значение, и нам необходимо выяснить, для чего оно служит. Пока что мы знаем, что это значение читается из таблицы элементов данных, параметр равен 0300, и результат кладётся в стек.

-

Следующий опкод равен 19, он работает с локальной переменной 88, которая становится источником его параметра.

-

Здесь выходим из его выполнения.

-

Что у нас тут?

-

С помощью MOVSX считываются параметры, отрицательные значения дополняются FF.

-

Как уже предсказывалось ранее, это значение -88 в шестнадцатеричной системе счисления, о чём нам говорит EXDEC.

-

-136 в десятеричной равной -88 в шестнадцатеричной.

-

Здесь увеличиваем указатель ESI на два и складываем EBP с -88, и результат этой операции помещается в EAX.

-

-

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

-

Первым является значение, которое было сохранено предыдущим опкодом, а два других значения в моём случае равны 12f458 (локальная переменная ebp-88) и -1 – третий параметр. Нажимаем F8, чтобы не заходить внутрь вызова.

После его выполнения видим, что в ebp-88 сохранилось значение, найденное предыдущим опкодом.

-

Всё остальное осталось таким же, не считая изменений в стеке и того, что ECX возвратил 0, вероятно для того, чтобы отметить, что процесс был завершён.

То есть то, что было помещено в EBP-88 – это значение, полученное предыдущим опкодом.

-

Доходим до другого опкода, равного 08, которая также пытается работать с той же локальной переменной 88, то есть ebp-88.

-

Входим в опкод.

-

В куске кода, видим “XOR EAX, EAX”, завершающий выполнение опкода, а чуть выше – условный переход. Посмотрим, что он делает.

Сначала EAX’у передаются параметры опкода.

-

Как и в прошлый раз, значение FF78, перемещённое с помощью MOVSX, имеет FFы в своём составе, то есть это отрицательное число, равное -88 в шестнадцатеричной системе.

-

-

На это строке напрямую задаётся сумма EAX+EBP, то есть EBP-88, и перемещается значение, содержащееся по данному адресу. Очевидно, что мы берём его из таблицы элементов данных.

-

-

Тестируем, равно ли нулю, если бы так и было, то переход был бы совершён, но так как сейчас нуля нет, то продолжаем.

-

Сохраняем это значение в EBP-4C.

Здесь нам нужно вспомнить то, о чём говорилось в самом начале.

То, что мы видим – это чтение содержимого EBP + 8 (ссылочный указатель) и сохранение оного в EBP.

Так как ebp-4с – это указатель на элемент данных, и мы уже убедились, что он содержит корректное значение и в нём нет нуля, то поэтому эта переменная ebp-4c называется «указатель на элемент данных», так как это значение связано с таблицей элементов данных.

-

Доходим до следующего опкода.

-

-

В пресвятом Гугле смотрим, для чего служит этот опкод.

0d VCallHresult #получает текст из поля ввода текста (textbox)

То есть будет прочитан неправильный серийный номер, который мы ввели в текстовое поле. Трассируем и смотрим, так ли это.

-

Видим опкод, завершающийся “XOR EAX, EAX” как обычно.

Первое, что здесь происходит – это считывается содержимое из EBP-4c (а это, как мы говорили, указатель на элемент данных) и помещается в EAX. Это архизнакомое нам значение, считывающееся из таблицы элементов данных.

-

Дальше это значение помещается в стек.

-

Потом считывается параметр опкода.

-

-

-

И помещается в EDI.

Затем читается содержимое EAX, являющееся началом другой таблицы.

-

-

И к этому значению прибавляется параметр 00a0, чтобы получить окончательный адрес в её пределах.

-

-

И затем совершается CALL по адресу в этой таблице. Конечно, не будет трассировать этот вызов, посмотрим на то, какие значения останутся в стеке от выполнившихся опкодов.

-

Выполняем CALL с помощью F8.

Затем помещаем в EDX значение из EBP-44.

-

-

И после сравнения пары значений доходим до следующего опкода, но вы можете спросит, читает ли он то, что мы ввели, то есть наш неправильный опкод? Смотрим ответ на этот вопрос в EXDEC.

-

Видим, что продолжается работа с локальной переменной 8c, которую мы также знаем как EBP-8C.

Если поищем значение EBP-8c:

-

Оно равно 12f454.

-

И это указатель на введенный нами неправильный серийный номер, который находится в 15d3bc.

-

Уф, пришлось попотеть, но мы, наконец, добрались до того места, где считывается неправильный серийник.

Следующий опкод – это 6c IldRf

-

-

-

Здесь говорится, что этот опкод занимается загрузкой ссылочного значения. Заходим в него.

-

Что у нас здесь?

-

Сначала осуществляется перемещение параметра с помощью MOVSX, который является отрицательным (FF).

-

Смотрим, что у нас оказывается в EAX.

-

Это -8c в шестнадцатеричной системе.

-

Следующая инструкция суммирует его с EBP, что в результате даёт EBP-8c. Содержимое по этому адресу кладётся в стек.

-

Он содержимое ebp-8c – это указатель на неправильный серийник, то есть сейчас этот указатель находится у нас в стеке.

-

Если посмотрим через DUMP, то ясно увидим, что он указывает на неправильный серийный номер. Таким образом, это и является действием опкода загрузки ссылочного значения, который кладёт в стек значение из локальной переменной.

Следующий опкод – это:

1b LitStr , что расшифровывается как “Literal String” (символьная строка)

Смотрим, что он делает.

-

Входим в опкод.

-

Сначала читается параметр, который здесь равен 0001 (положительный, т.к. нет FF). Он помещается в EAX.

-

-

Видим, что на следующей строке в EDX помещается значение 4017E4. Для чего оно, мы не знаем.

-

И для чего может служить помещение в стек 4016f8?

-

Видим, что в пояснении EXDEC’а показываются только две одинарные кавычки, то есть значение, помещаемое в стек, является пустой строкой.

-

Конечно же, смотрим через DUMP, что указывает на пустую строку, заключённую в кавычки, то есть то, с чем будет работать следующий опкод, проверяющий, напечатали мы что-нибудь или оставили поле ввода пустым и нажали “Register”.

-

Что есть:

-

Ок, “LEAD 0” – это операция, а “30 EqStr” – это вторая часть опкода. Посмотрим, что это такое в пресвятом Гугле.

Lead0/30 EqStr – сравнение двух строк.

То есть здесь будут сравниваться эти две строки. Это двойной опкод, то есть сначала считывается то, что в дальнейшем будет использоваться для косвенного перехода.

-

И здесь первый опкод завершается “XOR EAX, EAX”, где ничего не делается, и читается второй опкод.

-

-

Здесь читается второй опкод 30, но разница в случае с двойным опкодом заключается в считывании параметров, так как в таком опкоде первый завершается с помощью «XOR EAX, EAX» и считывается второй, но параметры первого опкода остаются доступными для чтения.

-

Здесь находится второй опкод, который делает PUSH 0.

-

Когда доходим до CALL’а, у нас есть три аргумента. Минуем CALL с помощью F8 и смотрим, что изменилось.

Видим, что стек сместился, но значения остались без изменений, только находятся теперь чуть выше.

Следующая строка «CMP AL, 0» сообщает, что здесь в AL сохраняется результат. В моём случае это:

-

AL=01

Потому что строки не равны.

-

После сравнения в EAX помещается значение ноль, а в конечном итоге это значение помещается в стек, так как это результат опкода. Ноль, если не равны, а если бы были равно, то вместо ноля было бы FFFFFF. Можете проверить, хе-хе.

-

Доходим до следующего опкода.

-

Пресвятой Гугл сообщает нам, что это примерно то же, что и SysFreeString, которая освобждает память, занятую неиспользуемой строкой. Видим, что в данном случае освободится ebp-8c.

-

Видим, что в EDI перемещается значение 1, затем в EBX – FF74 (в шестнадцатеричной системе это -8c). Как обычно, используется MOVSX, чтобы перемещать вместе с FF’ами, если значение отрицательно.

-

-

EBX+EBP равно EBP-8c, так что в стек помещается указатель на неправильный серийный номер.

-

-

Видим, что идёт вызов API-функции SysFreeString, а по возвращению из неё:

-

Указатель на наш неправильный серийный номер был заменён на нули.

-

Сам неправильный серийник не был стёр, он как был, так и остаётся по адресу 15d3bc. Был стёрт указатель на него, находившийся в EBP-8c.

-

Доходим до следующего опкода.

-

Это сотрёт содёржимое локальной переменной ebp-88.

А что там находится?

-

-

Ах, это значение, которое мы искали в таблице элементов данных.

-

Здесь смотрим параметры опкода:

-

FF78, который является шестнадцатеричным значением -88.

-

Далее в EAX помещается содержимое EBP-88, проверяется, равно ли оно нулю, так как оно не равно, то продолжаем дальше.

-

И доходим до вызова, который, как и раньше, освобождает это значение, и стирает содержимое ebp-88.

-

Здесь помещаем в EAX ноль.

-

Следующий опкод – это:

-

Это условный переход, так как все BRANCH’и – это переходы.

-

То есть это переход, срабатывающий, если результат равен «лжи», а затем идёт JMP, то есть этот переход, если мы что-то вводим в поле ввода, переходит на проверку серийного номера, а если нет, то процесс будет повторён, используя JMP.

Видим, что это так, если будет переход, то следующим опкодом станет:

- Так как это условный переход, избегаем JMP в 401bf3.

-

Здесь опкод завершается и читается следующий.

-

Как можно предположить из FE в 401bfg6, условный переход срабатывает и минут JMP, хе-хе.

Lead3/c1 LitVarI4

Это двойной опкод. Посмотрим, что он делает.

-

Здесь завершается первый опкод и читается второй.

-

Который начинается здесь. Терпеливо трассируем его.

-

Считываются параметры опкода.

-

Уже знаем, что они дополяются FF, если содержат отрицательное значение, как в данном случае.

-

Параметры считываются дальше, в данном случае это целый DWORD, который помещается в EAX.

-

-

И это значение сохраняется в локальную переменную.

-

EBP+ FFFFFF54 + 8, то есть сумма первого параметра и 8 даёт 12f43c, а здесь это значение сохранено.

-

-

JMP помещает нас в:

-

Где 12f434 помещается в стек.

-

Это указатель на структуру, начинающуюся с 3, которое уже было сохранено, а чуть пониже – сохранённое значение.

-

Продолжаем.

В случае, если серийный номер здесь жёстко задано, можем попробовать передать это значение в десятеричном виде и посмотреть, не является ли он правильным серийным номером. Откроем другой экземпляр крэкми не из-под OllyDbg.

-

Посмотрим в OllyDbg, чему равно это число в десятеричной системе.

-

Оно равно 246810. Введём его в крэкми.

-

-

Хе-хе, я подозревал это, но так легко нам не отделаться. Доходим до сравнения.

-

Следующий опкод также двойной.

Это FC и немедленно он завершается и начинается второй.

-

-

Это F6, бесстрашно заходим в него, EXDEC нам говорит, что будем работать с локальной переменной 9c (то есть ebp-9c).

-

Здесь читаются параметры, которые дополнены FF.

-

Конечно, это значение -9c.

-

Прибавляем к EBP, чтобы получилось EBP-9c или 12f444, которое пока что остаётся пустым.

-

-

-

Затем проводится проверка, равно ли это значение 8, если да, то делаем переход.

-

Далее снова переход, но не будет вдаваться в детаил. Доходим до последних строк опкода.

-

-

Помещаем значение 3, находящееся в EAX в переменную EBP-9c

И стираем 3 из структуры, встреченной нами ранее.

-

При выполнении:

-

Затем последующие строки копируют всё, что у нас здесь есть в новое местоположение, то есть в EBP-9c.

-

Здесь видим число, которое является правильным серийным номером и находящееся в структуре, начинающейся в EBP-9c.

-

Доходим до следующего опкода.

401C02: 04 FLdRfVar                local_008C

Видим, что повторяется всё, что мы уже видели в начале.

401C02: 04 FLdRfVar                local_008C
401C05: 21 FLdPrThis              
401C06: 0f VCallAd                 text
401C09: 19 FStAdFunc               local_0088
401C0C: 08 FLdPr                   local_0088
401C0F: 0d VCallHresult            get__ipropTEXTEDIT
401C14: 6c ILdRf                   local_008C

Всё похоже на то, как было в начале. Следующий опкод:

-

Поэтому, чтобы перепрыгнуть через всё, что уже было, устанавливаем BPM ON ACCESS на 401c17, чтобы остановиться на считывании опкода.

-

Останавливаемся здесь и читаем опкод 0A. Смотрим, что это такое в EXDEC’е.

Это ImpAdCallFPR4 – вызов API-функции. EXDEC показывает какой именно.

Например:

-

В данном примере произойдёт вызов API-функции rtcMsgBox. В нашем же случае это вызов функции:

401C17: 0a ImpAdCallFPR4: _rtcR8ValFromBstr

-

Читаются параметры опкода.

-

-

Они помещаются в ECX.

-

Затем в EAX помещается значение 401000 и тестируется, не равно ли оно нулю.

-

Далее читается второй параметр.

-

-

-

И доходим до “CALL EAX”, где EAX равен 401000. Смотрим, куда это ведёт – а ведёт это к упомянутой выше API-функции.

-

Параметр, передающийся через стек API-фукнкции:

-

Мой неправильный серийный номер:

-

Который был загружен в St0, являющаяся верхним элементом стека плавающей запятой. Мы не говорили об этом раньше, но ничего. Находятся эти элементы под регистрами, если у вас они не отображаются, то нужно использовать специальную опцию. Нажимаем правую кнопку мыши.

-

Здесь загружается мой неправильный серийный номер.

-

Доходим до следующего опкода.

401C1C: Lead2/6b CVarR8

Итак, находимся здесь, заходим в опкод.

-

Так как опкод двойной, то завершается первый и грузится второй.

-

Ок, здесь несколько инструкций плавающей запятой, которые мы не рассматривали.

Но видим, что вначале загружаются параметры.

- В данном случае:

-

-

Суммируются с EBP, и в EAX остаётся:

-

-

FSTP сохраняет первое первое значение из стека плавающей запятой, то есть из ST(0), в указанную ячейку памяти (в данном случае [EAX+8], то есть 12f43c) и делает следующий элемент стека верхним. Ок, мы рассмотрим это позже.

-

После выполнения:

-

Я уже знаю, что у вас могут зародиться кое-какие подозрения о том, что это наш серийный номер, сконвертированный в 64-х битное число. Посмотрим, так ли это, кликнув на правую кнопку мыши.

-

-

Видим, что действительно это неправильный серийный номер, но только в другом представлении – 8 байтов, то есть 64-битном. Логично, раз 4 байта – это 32 бита, то двойное слово состоит из 64-х бит и выглядит по другому.

-

Включаем обратно обычный режим представления.

-

Эта инструкция сохраняет значение регистра SR (регистр состояния FPU) в AX. Выполняем:

-

-

Здесь опкод завершается.

401C20: 5d HardType

Ок, у меня нет ни малейшей идеи, что это такое, но попытаемся это выяснить.

-

Ок, всего лишь две строки, которые грузят содержимое ESP в EAX.

-

-

Видим, что там находится число, находящееся чуть выше моего трансформированного фальшивого серийного номера. Возможно, что это число указывает, в каком формате он хранится.

-

Следующий опкод.

401C21: 04 FLdRfVar                local_009C

То есть PUSH ebp-9c, в данном случае:

-

-

Далее идёт двойной опкод.

401C24: Lead0/40 NeVarBool

-

Здесь читается второй опкод.

-

Доходим до вызова, а параметры в стеке следующие:

-

Один – это указатель на правильный серийный номер, другой – на трансформированный неправильный. Будет их сравнение?

-

-

-

Устанавливаем BP сюда.

-

Видим, что после выхода из call’а EAX содержит 1.

-

И поскольку делается PUSH значения FFFFFFFF в стек, то можно практически безошибочно предположить, что именно здесь проводится сравнение, так что рестартуем крэкми, установив BP, и вводим правильный серийный номер, который был получен из этого сравнения.

-

03c41a в десятеричной системе – это 246810. Вводим его и снова идём к сравнению.

-

Остановившись, видим, что происходит сравнение:

-

246810 со прежним шестнадцатеричным значением. Дело в том, что они в разных форматах, но сверху каждого из них есть число, указывающее, в каком именно хранится значение, поэтому возможно, что внутри каждого call’а они будут трансформированы и сравнены. Смотрим, что у нас получится в результате.

-

Видим, что теперь у нас EAX = 0 (раньше было 1).

-

И в стек также уходит ноль.

Это означает, что это и есть сравнение, к тому же прямо внизу видим несколько Branch’ей, которые совершают переход в зависимости от того, окошко с каким сообщением надо показать, а потом идёт освобождение строк и использованных значений.

-

Вы можете сказать, что было слишком много работы, но это был первый раз, поэтому мы подробно рассматривали каждый опкод, и это необязательно повторять в будущем. Отладчик WKT может показать вам имена опкодов. Вы не знаете, что делает каждый из них, но с помощью OllyDbg можете определить, для чего они служат, и получить представление о том, как работает программа.

В следующей части мы рассмотрим крэкми “clave 2”, используя тот же метод. Мне бы хотелось, чтобы вы попробовали самостоятельно его сделать, но если не получится, то в следующей части вы сможете прочитать решение этого крэкми.

2002-2013 (c) wasm.ru