Глава 30
(для запуска крэкми понадобиться MSVBVM50.dll [ссылка])
Ок, после того, как попрактиковались, и если вы ещё живы, продолжаем с P-CODE.
У нас есть ещё кое-какие опкоды, собранные из туториалов JBDUC’а.
6c -> ILdRf Поместить адрес в стек
1b -> LitStr5 Поместить строку в стек
fb -> Lead0 Сравнить две строки (хе-хе, для чего это могло служить ?)
30 -> EqStr Сравнить две строки(хе-хе, для чего это могло служить ?)
2f -> FFree1Str Освободить используемую память
1a -> FFree1Ad Освободить используемую память
0f -> VCallAd Выполнить опкод в виртуальной машине
1c -> BranchF Сделать переход, если предшествующее сравнение дало ложь ( аналог jne/jnz в ассемблере )
1d -> BranchT Сделать переход, если предшествующее сравнение дало истину ( аналог je/jz в ассемблере )
1e -> Безусловный переход ( хе-хе, угадайте, для чего её можно использовать )
fc -> Lead1 Прерывает выполнение программы (хе-хе, прекрасно...)
c8 -> End Прерывает выполнение программы (хе-хе, прекрасно...)
f3 -> LitI2 Сохраняет заданное число типа Integer в стек
f4 -> LitI2_Byte Конвертирует Byte-значение в Integer и помещает его в стек
70 -> FStI2 Сохраняет последний Integer, помещённый в стек, в заданную глобальную переменную
6b -> FLdI2 Загружает в стек Integer из заданной локальной переменной
a9 -> AddI2 Складывает два последних Integer'а, помещённых в стек, на вершину которого помещает результат
ad -> SubI2 Использует для вычитания два последних Integer’а, помещённых в стек, и кладёт в последний результат
b1 -> MulI2 Умножает два последних Integer’а, помещённых в стек, и кладёт в последний результат. Думаю, что если переполнение игнорируется.
Ок, есть ещё опкоды, которые мы сейчас рассматривать не будем. К статье прилагает файл под названием "P-CODE OPCODES" [ссылка], который, предположительно, распространялся Микрософтом. Он может немного помочь, так как в нём перечислены опкоды и что они делают (но не все, хе-хе), так что, если встретим незнакомый опкод, то можем посмотреть, есть ли там описание.
Как я и обещал вам, сначала рассмотрим крэкми "clave 2" [ссылка], который остался у нас в качестве домашнего упражнения с прошлого раза. Посмотрим, можем ли мы получить листинг с помощью EXDEC’а [ссылка].
Вот он, и видим, что начинается с 401cc0. Не будем слепо верить EXDEC’у, так как бывали случаи, когда он обманывал. Посмотрим, сможем ли мы найти первый опкод вручную, как мы это делали в предыдущей части.
Смотрим, находится ли переход на API-функцию MethCallEngine за EP.
Вот он. Становимся на него, делаем FOLLOW, чтобы перейти к API-функции и также устанавливаем там BP.
Готово, теперь делаем RUN, чтобы остановиться на одном из установленных BP.
Окно появляется до того, как сработает останов, дело в том, что окно появляется до того, как начинается P-CODE, хотя нужно пояснить, что такое бывает не всегда, может сначала сработать останов, а потом появиться окно, но в данном случае оно сделало это первым. Вводим имя и неправильный серийный номер.
После нажатия на "REGISTRAR" срабатывает останов на JMP. Идём в первую секцию (помните, что не надо использовать пропатченный OllyDbg для OEP’ов [ссылка]) и устанавливаем "BPM ON ACCESS" на секцию кода.
Теперь жмём RUN, пока не встретится знакомая инструкция, где читается опкод.
Вот тут, помним, что ESI всегда должен указывать на опкод (который сам перемещается в AL).
Как видим, EXDEC не ошибся, и первый опкод – это 04 и начинается с 401cc0.
Помним, что 04 – это просто PUSH, в данном случае его аргументом является EBP-8C, как это видно из пояснения рядом с опкодом
04 567B 0B8E 2 1 2 Push arg
Из этого нам становится понятно, что каждая цифра, в нашем случае 0B8E, является RVA опкода, 2 – это количество байтов, которые в общей сложности занимают параметры, 1 нам говорит, что аргумент только один, и последнее значение (2) указывает нам размер, занимаемый каждым аргументом в отдельности.
Хорошо, вот PUSH EBP-8C, продолжаем изучать исходник, пока без трассировки шаг за шагом. Смотрим.
Видим, что используется два вызова для считывания введённого в окне регистрации, вероятно, что в первый раз читает имя, а во второй – неправильный серийник, использованный нами. Устанавливаем BPM ON ACCESS на первом вызове, то есть на 401d4C.
Вот опкод, устанавливаем на него BPM и делаем RUN.
Ок, теперь трассируем до следующего опкода, чтобы посмотреть, что будет происходить с именем.
Дошли до следующего опкода, смотрим стек, чтобы узнать, что там находится
Как видим, продолжается работа с локальной переменной 8c (или EBP-8C). Проверим, что там сохранено. На моей машине в ebp-8c содержит 12f454.
Ищем этот адрес в стеке и видим, что там находится моё имя.
Конечно же, оно находится здесь, так что всё идёт хорошо. Раз здесь находится моё имя, то логично предположить, что в следующем будет идти работа с серийником, поэтому устанавливаем на второй опкод BPM ON ACCESS.
401DFB: 0d VCallHresult get__ipropTEXTEDIT
Готово, теперь жмём RUN, чтобы оказаться рядом с этим опкодом.
Как и в предыдущем случае, трассируем до следующего опкода.
И смотрим, сохранён ли в используемой переменной неправильный серийный номер.
Как и в прошлый раз используется EBP-8C.
Хорошо, вот мы и дошли до места, куда помещён наш неправильный серийник. Видим, что зная опкоды, не обязательно трассировать всю программу, можем просто установить BPM и оказаться в нужной части программы. В прошлой главе мы трассировали всё подряд, чтобы понять механизмы, согласно которым работает P-CODE, но как правильно, нам не нужно делать это. Как мы увидим в следующей главе, когда исследуем большую программу, будем локализовывать интересующую нас часть программы и работать именно с ней.
Далее видим, что здесь есть все признаки сравнения, потом освобождение локальных переменных с помощью FREE, а затем условный переход, который ведёт в 401e59, где вызывается rtcMsgBox с сообщением о правильном серийном номере, а если перехода не происходит, то отображается сообщение о том, что серийник неверен.
Тут всё совершенно понятно. Вероятное сравнение и условный переход, в зависимости от которого отображается окошко с соответствующим сообщением, так что устанавливаем сюда BPM ON ACCESS.
Ок, делаем RUN.
Останавливаемся тут, в первом опкоде и трассируем до второго, который производит некую операцию.
Второй опкод равен EF.
Смотрим в списке опкодов.
Что такое FB EF нам ещё не совсем понятно, EXDEC говорит нам что-то о конкатенации переменных, смотрим.
Видим, что прямо до этого опкода идёт работа с двумя локальными переменными, в одной из которых находится строка CRK, а другая является EBP-018C. Выясним, что находится в каждой из вышеупомянутых переменных.
Первая переменная – это EBP-9C, которая на моей машине равна 12f444 и находится там следующее:
Как мы знаем, в случае с переменными сначала идёт байт, указывающий тип, в данном случае это 3, а ещё выше находится сама переменная, которая равна 2EA.
Входим в опкод.
Здесь читаются параметры.
После сложения с EBP получается 12f434.
Доходим до API-функции vbaVarCat, у которой три аргумента в стеке.
Смотрим, что находится в каждом из них. Отметим, что когда идёт работа с переменной, то отображается маленькая структура, в которой первый байт задаёт её тип.
Это первый аргумент.
Во втором сначала идёт 3, а потом 02EA, задающее значение.
А в третьем в начале 8, означающее, что 4017648 – это указатель на строку CRK.
Ок, у нас тут мешанина из переменных, посмотрим, что останется. Если войдём в функцию vbaVarCat, то окажемся у внутренней API-функции, которая ясно покажет нам, что будет объединяться.
То есть предстоит объединение CRK с 746. А теперь выясним, что значит параметр 02EA.
Он конвертируется в строку. 02EA – это:
То есть vbaVarCat, в данном случае, получила строку и численную переменную, которая конвертируется в строку, после чего обе объединяются.
Продолжаем трассировать с помощью F8.
Дойдя до RET’а из функции, видим:
Обе переменных объединились в одну строку.
И, как и раньше, до начала следующего опкода это значение сохраняется в качестве первого аргумента.
Видим, что первый аргумент сейчас имеет тип 8, то есть строку, и указывает на 15d88c, то есть на сконкатенированную строку.
Ок, теперь снова должно произойти сравнение, быстро доходим до следующего опкода.
401E13: Lead0/40 NeVarBool
Это FB40, и он двойной. Трассируем, пока не дойдём до считывания второго опкода.
Конечно, список опкодов от Микрософта ничего нам о нём не говорит, так что трассируем опкод, чтобы узнать, что он делает.
Видим, что в опкоде всего один вызов, после которого сразу следует завершение. Смотрим параметры вызова.
Первый равен нулю, а второй 12f434. Смотрим, что там находится.
Ок, 08 говорит нам, что это строка, теперь осталось посмотреть, на что указывает 15d88c.
Вот строка, смотрим следующий аргумент.
В данном случае 15ca94 указывает на строку с нашим неправильным серийным номером.
Похоже, что это сравнение двух строк.
Чтобы рассеять сомнения, устанавливаем BP, минуем вызов с помощью F8 и идём к следующему опкоду.
Видим, что в стеке осталось значение FFFFFFFF, вероятно, означающее, что строки не равны. Запишем возможный серийный номер, и попробуем его использовать.
Нажимаем на кнопку "Registrar".
Снова оказались у этого вызова, проходим его с помощью F8 и идём к следующему опкоду, как в прошлый раз.
Видим, что в данном случае результатом является ноль, так как обе строки одинаковы.
Как мы увидели, не обязательно трассировать всё подряд. Нужно найти подозрительную область и трассировать только неизвестные опкоды. Этого оказалось достаточным, чтобы найти серийный номер исследуемой программы.
Для заинтересованных – более детализированный список, где изложены сведения, которые нам удалось узнать.
Отсюда ясно, как работает крэкми.
Полезно знать, как работать с P-CODE в OllyDbg, так как есть программы, которые защищены от WKT и EXDEC, но в случае с OllyDbg мы можем использовать плагины, которые спрячут её практически ото всех, кроме очень редких исключений.
Загружаем прилагающийся крэкми nags1 [ссылка], который просит нас убрать первоначальное наг-окно. Смотрим его листинг в EXDEC.
Видим, что знаменитый NAG – это просто rtcMsgBox. В P-Code нет NOP’ов, хе-хе, поэтому нам нужно забить эту функцию опкодами, которые не изменят хода выполнения программы.
Устанавливаем BPM на опкоде, вызывающем rtcMsgBox, и запускаем программу.
401A2D: 0a ImpAdCallFPR4: _rtcMsgBox
Останавливаемся, когда считывается опкод.
Видим, что в стеке находится 12f9e8. Идём к следующему опкоду. Сначала, конечно, выскочит наг-окошко, поэтому надо будет нажать "Aceptar".
Здесь мы доходим до следующего опкода после CALL EAX, который вызывал API-функцию rtcMsgBox.
В стеке находится 12f9fc.
То есть, для того, чтобы оставить равные, нужно сделать различные POP’ы. Сюда мы не вмешиваемся, можем попробовать использовать PUSH с помощью F5.
F5 5CBE 1377 4 1 4 Push imm#4
У нас 4 параметра размером равные 0A. Столько нам нужно заменить.
0A 664E 1F30 4 2 2 2
Пробуем изменить 0A на F5 и делаем все параметры равными нулю.
Следующий опкод – это 36, как показывает нам EXDEC. Мы всегда должны быть уверенными, что замещающий опкод имеет такое же количество параметров, как и у замещаемого, чтобы не было проблем. Сохраняем изменения.
Доходим досюда без появления наг-окна. В другой раз, вероятно, можно использовать иной опкод для патчинга. В качестве домашнего упражнения вы можете попробовать решить крэкми nags2 [ссылка], прилагающийся к данной статье.
31 часть будет последней посвящённой P-CODE, и в ней будет рассмотрена коммерческая программа.
[C] Рикардо Нарваха, пер. Aquila
Last updated