Пожалуйста,
Вход
или
Регистрация
для просмотра содержимого URL-адресов!
Думаю, что в предыдущих главах мы рассмотрели все основные вещи относительно 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”, используя тот же метод. Мне бы хотелось, чтобы вы попробовали самостоятельно его сделать, но если не получится, то в следующей части вы сможете прочитать решение этого крэкми.