Введение в крэкинг с нуля, используя OllyDbg - Глава 30 — Архив WASM.RU
Ок, после того, как попрактиковались, и если вы ещё живы, продолжаем с PCODE.
У нас есть ещё кое-какие опкоды, собранные из туториалов 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.
Код (Text):
Proc: 401e90 401CC0: 04 FLdRfVar local_008CПомним, что 04 – это просто PUSH, в данном случае его аргументом является EBP-8C, как это видно из пояснения рядом с опкодом
Код (Text):
04 567B 0B8E 2 1 2 Push arg
![]()
Из этого нам становится понятно, что каждая цифра, в нашем случае 0B8E, является RVA опкода, 2 – это количество байтов, которые в общей сложности занимают параметры, 1 нам говорит, что аргумент только один, и последнее значение (2) указывает нам размер, занимаемый каждым аргументом в отдельности.
Хорошо, вот PUSH EBP-8C, продолжаем изучать исходник, пока без трассировки шаг за шагом. Смотрим.
Код (Text):
401D4C: 0d VCallHresult get__ipropTEXTEDIT 401DFB: 0d VCallHresult get__ipropTEXTEDITВидим, что используется два вызова для считывания введённого в окне регистрации, вероятно, что в первый раз читает имя, а во второй – неправильный серийник, использованный нами. Устанавливаем BPM ON ACCESS на первом вызове, то есть на 401d4C.
![]()
Allí esta el opcode y le coloco el BPM y doy RUN.
Вот опкод, устанавливаем на него BPM и делаем RUN.
![]()
Bueno ahora traceemos hasta el siguiente opcode, a ver si ingresa el nombre.
Ок, теперь трассируем до следующего опкода, чтобы посмотреть, что будет происходить с именем.
![]()
Дошли до следующего опкода, смотрим стек, чтобы узнать, что там находится
Код (Text):
401D4C: 0d VCallHresult get__ipropTEXTEDIT 401D51: 3e FLdZeroAd local_008CКак видим, продолжается работа с локальной переменной 8c (или EBP-8C). Проверим, что там сохранено. На моей машине в ebp-8c содержит 12f454.
![]()
Busco en el stack esa dirección a ver si esta allí mi nombre
Ищем этот адрес в стеке и видим, что там находится моё имя.
![]()
Конечно же, оно находится здесь, так что всё идёт хорошо. Раз здесь находится моё имя, то логично предположить, что в следующем будет идти работа с серийником, поэтому устанавливаем на второй опкод BPM ON ACCESS.
Код (Text):
401DFB: 0d VCallHresult get__ipropTEXTEDIT
![]()
Готово, теперь жмём RUN, чтобы оказаться рядом с этим опкодом.
![]()
Как и в предыдущем случае, трассируем до следующего опкода.
![]()
И смотрим, сохранён ли в используемой переменной неправильный серийный номер.
Код (Text):
401DFB: 0d VCallHresult get__ipropTEXTEDIT 401E00: 3e FLdZeroAd local_008CКак и в прошлый раз используется EBP-8C.
![]()
Хорошо, вот мы и дошли до места, куда помещён наш неправильный серийник. Видим, что зная опкоды, не обязательно трассировать всю программу, можем просто установить BPM и оказаться в нужной части программы. В прошлой главе мы трассировали всё подряд, чтобы понять механизмы, согласно которым работает PCODE, но как правильно, нам не нужно делать это. Как мы увидим в следующей главе, когда исследуем большую программу, будем локализовывать интересующую нас часть программы и работать именно с ней.
Код (Text):
401E0F: Lead0/ef ConcatVar 401E13: Lead0/40 NeVarBool 401E15: 1a FFree1Ad local_0088 401E18: 36 FFreeVar local_00CC local_00AC 401E1F: 1c BranchF: 401E59Далее видим, что здесь есть все признаки сравнения, потом освобождение локальных переменных с помощью FREE, а затем условный переход, который ведёт в 401e59, где вызывается rtcMsgBox с сообщением о правильном серийном номере, а если перехода не происходит, то отображается сообщение о том, что серийник неверен.
![]()
Тут всё совершенно понятно. Вероятное сравнение и условный переход, в зависимости от которого отображается окошко с соответствующим сообщением, так что устанавливаем сюда BPM ON ACCESS.
Код (Text):
401E0F: Lead0/ef ConcatVar 401E13: Lead0/40 NeVarBool
![]()
Ок, делаем RUN.
![]()
Останавливаемся тут, в первом опкоде и трассируем до второго, который производит некую операцию.
![]()
Второй опкод равен EF.
Смотрим в списке опкодов.
Код (Text):
FB EF 6BAB 25AD 2 FB F0 6B99 259B 0 vbaStrCat FB F1 C423 B981 0 Push [FC0D134]Что такое FB EF нам ещё не совсем понятно, EXDEC говорит нам что-то о конкатенации переменных, смотрим.
Код (Text):
401E07: 3a LitVarStr: ( local_009C ) CRK 401E0C: 04 FLdRfVar local_018C 401E0F: Lead0/ef ConcatVar 401E13: Lead0/40 NeVarBoolВидим, что прямо до этого опкода идёт работа с двумя локальными переменными, в одной из которых находится строка CRK, а другая является EBP-018C. Выясним, что находится в каждой из вышеупомянутых переменных.
![]()
Первая переменная – это EBP-9C, которая на моей машине равна 12f444 и находится там следующее:
![]()
Как мы знаем, в случае с переменными сначала идёт байт, указывающий тип, в данном случае это 3, а ещё выше находится сама переменная, которая равна 2EA.
![]()
Входим в опкод.
![]()
Здесь читаются параметры.
![]()
После сложения с EBP получается 12f434.
![]()
Доходим до API-функции vbaVarCat, у которой три аргумента в стеке.
![]()
Смотрим, что находится в каждом из них. Отметим, что когда идёт работа с переменной, то отображается маленькая структура, в которой первый байт задаёт её тип.
![]()
Это первый аргумент.
![]()
Во втором сначала идёт 3, а потом 02EA, задающее значение.
![]()
А в третьем в начале 8, означающее, что 4017648 – это указатель на строку CRK.
![]()
Ок, у нас тут мешанина из переменных, посмотрим, что останется. Если войдём в функцию vbaVarCat, до окажемся у внутренней API-функции, которая ясно покажет нам, что будет объединяться.
![]()
![]()
То есть предстоит объединение CRK с 746. А теперь выясним, что значит параметр 02EA.
![]()
Lo transforma a string ya que 02EA es
Он конвертируется в строку. 02EA – это:
![]()
То есть vbaVarCat в данном случае получила строку и численную переменную, которая конвертируется в строку, после чего обе объединяются.
Продолжаем трассировать с помощью F8.
Дойдя до RET’а из функции, видим:
![]()
Обе переменных объединились в одну строку.
![]()
И, как и раньше, до начала следующего опкода это значение сохраняется в качестве первого аргумента.
![]()
Видим, что первый аргумент сейчас имеет тип 8, то есть строку, и указывает на 15d88c, то есть на сконкатенированную строку.
![]()
Ок, теперь снова должно произойти сравнение, быстро доходим до следующего опкода.
![]()
Код (Text):
401E13: Lead0/40 NeVarBoolЭто FB40, и он двойной. Трассируем, пока не дойдём до считывания второго опкода.
![]()
![]()
Конечно, список опкодов от Микрософта ничего нам о нём не говорит, так что трассируем опкод, чтобы узнать, что он делает.
![]()
Видим, что в опкоде всего один вызов, после которого сразу следует завершение. Смотрим параметры вызова.
![]()
Первый равен нулю, а второй 12f434. Смотрим, что там находится.
![]()
Ок, 08 говорит нам, что это строка, теперь осталось посмотрим, на что указывает 15d88c.
![]()
Вот строка, смотрим следующий аргумент.
![]()
В данном случае 15ca94 указывает на строку с нашим неправильным серийным номером.
![]()
Похоже, что это сравнение двух строк.
![]()
Чтобы рассеять сомнения, устанавливаем BP, минуем вызов с помощью f8 и идём к следующему опкоду.
Видим, что в стеке осталось значение FFFFFFFF, вероятно, означающее, что строки не равны. Запишем возможный серийный номер, и попробуем его использовать.
![]()
Нажимаем на кнопку “Registrar”.
![]()
Снова оказались у этого вызова, проходим его с помощью F8 и идём к следующему опкоду, как в прошлый раз.
![]()
Видим, что в данном случае результатом является ноль, так как обе строки одинаковы.
![]()
Как мы увидели, не обязательно трассировать всё подряд. Нужно найти подозрительную область и трассировать только неизвестные опкоды. Этого оказалось достаточным, чтобы найти серийный номер исследуемой программы.
Для заинтересованных – более детализированный список, где изложены сведения, которые нам удалось узнать.
Код (Text):
401CC0: 04 FLdRfVar local_008C 401CC3: 21 FLdPrThis ; Load reference pointer into item pointer. Загрузка ссылочного указателя в указатель на элемент 401CC4: 0f VCallAd text ; Доступ к методу ITEM DESCRIPTOR TABLE 401CC7: 19 FStAdFunc local_0088 401CCA: 08 FLdPr local_0088 401CCD: 0d VCallHresult get__ipropTEXTEDIT ; Читаем содержимое из текстового поля 401CD2: 6c ILdRf local_008C ; Имя 401CD5: 1b LitStr: & ; Поместить строку в стек 401CD8: Lead0/30 EqStr ; Сравнить две строки 401CDA: 2f FFree1Str local_008C 401CDD: 1a FFree1Ad local_0088 401CE0: 1c BranchF: 401CE6 ; Переход, если сравнение не удалось 401CE3: 1e Branch: 401e8c ; Безусловный переход 401CE6: 04 FLdRfVar local_008C 401CE9: 21 FLdPrThis 401CEA: 0f VCallAd text ; Доступ к методу ITEM DESCRIPTOR TABLE 401CED: 19 FStAdFunc local_0088 401CF0: 08 FLdPr local_0088 401CF3: 0d VCallHresult get__ipropTEXTEDIT ; Читаем содержимое текстового поля 401CF8: 6c ILdRf local_008C ; Имя 401CFB: 4a FnLenStr 401CFC: f5 LitI4: 0x6 6 (....) ; Передаём элемент из 4-ёх байт 401D01: d1 LtI4 ; Сравнение меньше чем (?) 401D02: 2f FFree1Str local_008C 401D05: 1a FFree1Ad local_0088 401D08: 1c BranchF: 401D3F ; Переход если ложноSalta si falso (>= 6) 401D0B: 27 LitVar_Missing 401D0E: 27 LitVar_Missing 401D11: 3a LitVarStr: ( local_00BC ) P-Code 401D16: 4e FStVarCopyObj local_00CC 401D19: 04 FLdRfVar local_00CC 401D1C: f5 LitI4: 0x40 64 (...@) 401D21: 3a LitVarStr: ( local_009C ) Минимум 6 символов 401D26: 4e FStVarCopyObj local_00AC 401D29: 04 FLdRfVar local_00AC 401D2C: 0a ImpAdCallFPR4: _rtcMsgBox 401D31: 36 FFreeVar local_00AC local_00CC local_00EC local_010C 401D3C: 1e Branch: 401e8c ;Если < 6 символов в строке 401D3F: 04 FLdRfVar local_008C 401D42: 21 FLdPrThis 401D43: 0f VCallAd text 401D46: 19 FStAdFunc local_0088 401D49: 08 FLdPr local_0088 401D4C: 0d VCallHresult get__ipropTEXTEDIT ; Читаем содержимое текстового поля 401D51: 3e FLdZeroAd local_008C ; Имя 401D54: 46 CVarStr local_00AC 401D57: 04 FLdRfVar local_00CC 401D5A: 0a ImpAdCallFPR4: _rtcLowerCaseVar ; Конвертируем в строчные символы 401D5F: 04 FLdRfVar local_00CC 401D62: 04 FLdRfVar local_00EC 401D65: 0a ImpAdCallFPR4: _rtcTrimVar 401D6A: 04 FLdRfVar local_00EC 401D6D: Lead1/f6 FStVar local_011C 401D71: 1a FFree1Ad local_0088 401D74: 36 FFreeVar local_00AC local_00CC 401D7B: 04 FLdRfVar local_011C 401D7E: Lead0/eb FnLenVar 401D82: Lead1/f6 FStVar local_012C 401D86: 28 LitVarI2: ( local_00BC ) 0x1 (1) 401D8B: 04 FLdRfVar local_013C 401D8E: 04 FLdRfVar local_012C 401D91: Lead3/68 ForVar: (when done) 401DE0 , Начало цикла for next 401D97: 28 LitVarI2: ( local_00AC ) 0x1 (1) 401D9C: 04 FLdRfVar local_013C 401D9F: Lead1/22 CI4Var 401DA1: 04 FLdRfVar local_011C 401DA4: 04 FLdRfVar local_00CC 401DA7: 0a ImpAdCallFPR4: _rtcMidCharVar ; Загрузка символов из... 401DAC: 04 FLdRfVar local_00CC ; ... имени 401DAF: Lead2/fe CStrVarVal local_008C 401DB3: 0b ImpAdCallI2 _rtcAnsiValueBstr ; конвертируем значения символов в hexadec 401DB8: 44 CVarI2 local_00BC 401DBB: Lead1/f6 FstVar local_016C 401DBF: 2f FFree1Str local_008C 401DC2: 36 FFreeVar local_00AC local_00CC 401DC9: 04 FLdRfVar local_017C 401DCC: 04 FLdRfVar local_016C 401DCF: Lead0/94 AddVar local_00AC 401DD3: Lead1/f6 FStVar local_017C 401DD7: 04 FLdRfVar local_013C 401DDA: Lead3/7e NextStepVar: (continue) 401D97 ; Конец цикла for-next? 401DE0: 04 FLdRfVar local_017C 401DE3: 04 FLdRfVar local_012C 401DE6: Lead0/94 AddVar local_00AC 401DEA: Lead1/f6 FStVar local_018C 401DEE: 04 FLdRfVar local_008C 401DF1: 21 FLdPrThis 401DF2: 0f VCallAd text 401DF5: 19 FStAdFunc local_0088 401DF8: 08 FLdPr local_0088 401DFB: 0d VCallHresult get__ipropTEXTEDIT ; Читаем содержимое текстового поля 401E00: 3e FLdZeroAd local_008C ; Серийный номер 401E03: 46 CVarStr local_00CC 401E06: 5d HardType 401E07: 3a LitVarStr: ( local_009C ) CRK 401E0C: 04 FLdRfVar local_018C 401E0F: Lead0/ef ConcatVar 401E13: Lead0/40 NeVarBool 401E15: 1a FFree1Ad local_0088 401E18: 36 FFreeVar local_00CC local_00AC 401E1F: 1c BranchF: 401E59 401E22: 27 LitVar_Missing 401E25: 27 LitVar_Missing 401E28: 3a LitVarStr: ( local_00BC ) P-Code 401E2D: 4e FStVarCopyObj local_00CC 401E30: 04 FLdRfVar local_00CC 401E33: f5 LitI4: 0x10 16 (....) 401E38: 3a LitVarStr: ( local_009C ) Серийный номер неверен! 401E3D: 4e FStVarCopyObj local_00AC 401E40: 04 FLdRfVar local_00AC 401E43: 0a ImpAdCallFPR4: _rtcMsgBox 401E48: 36 FFreeVar local_00AC local_00CC local_00EC local_010C 401E53: 1e Branch: 401e8c 401E56: 1e Branch: 401e8c 401E59: 27 LitVar_Missing 401E5C: 27 LitVar_Missing 401E5F: 3a LitVarStr: ( local_00BC ) P-Code 401E64: 4e FStVarCopyObj local_00CC 401E67: 04 FLdRfVar local_00CC 401E6A: f5 LitI4: 0x30 48 (...0) 401E6F: 3a LitVarStr: ( local_009C ) Серийный номер верен!! 401E74: 4e FStVarCopyObj local_00AC 401E77: 04 FLdRfVar local_00AC 401E7A: 0a ImpAdCallFPR4: _rtcMsgBox 401E7F: 36 FFreeVar local_00AC local_00CC local_00EC local_010C 401E8A: Lead1/c8 End 401E8C: 13 ExitProcHresultОтсюда ясно, как работает крэкми.
Полезно знать, как работать с P-CODE в OllyDbg, так как есть программы, которые защищены от WKT и EXDEC, но в случае с OllyDbg мы можем использовать плагины, которые спрячут её практически ото всех, кроме очень редких исключений.
Загружаем прилагающийся крэкми nags1, который просит нас убрать первоначальное наг-окно. Смотрим его листинг в EXDEC.
Код (Text):
Proc: 401a40 401A14: 27 LitVar_Missing 401A17: 27 LitVar_Missing 401A1A: 27 LitVar_Missing 401A1D: f5 LitI4: 0x0 0 (....) 401A22: 3a LitVarStr: ( local_0094 ) NAG 401A27: 4e FStVarCopyObj local_00A4 401A2A: 04 FLdRfVar local_00A4 401A2D: 0a ImpAdCallFPR4: _rtcMsgBox 401A32: 36 FFreeVar local_00A4 local_00C4 local_00E4 local_0104 401A3D: 13 ExitProcHresult Proc: 401958 401954: Lead1/c8 End 401956: 13 ExitProcHresultВидим, что знаменитый NAG – это просто rtcMsgBox. В P-Code нет NOP’ов, хе-хе, поэтому нам нужно забить эту функцию опкодами, которые не изменят хода выполнения программы.
![]()
Устанавливаем BPM на опкоде, вызывающем rtcMsgBox, и запускаем программу.
Код (Text):
401A2D: 0a ImpAdCallFPR4: _rtcMsgBox
![]()
![]()
Останавливаемся, когда считывается опкод.
![]()
Видим, что в стеке находится 12f9e8. Идём к следующему опкоду. Сначала, конечно, выскочит наг-окошко, поэтому надо будет нажать «Aceptar».
![]()
![]()
Здесь мы доходим до следующего опкода после CALL EAX, который вызывал API-функцию rtcMsgBox.
В стеке находится 12f9fc.
![]()
То есть, для того, чтобы оставить равные, нужно сделать различные POP’ы. Сюда мы не вмешиваемся, можем попробовать использовать PUSH с помощью F5.
Код (Text):
F5 5CBE 1377 4 1 4 Push imm#4У нас 4 параметра размером равные 0A. Столько нам нужно заменить.
Код (Text):
0A 664E 1F30 4 2 2 2Пробуем изменить 0A на F5 и делаем все параметры равными нулю.
![]()
Следующий опкод – это 36, как показывает нам EXDEC. Мы всегда должны быть уверенными, что замещающий опкод имеет такое же количество параметров, как и у замещаемого, чтобы не было проблем. Сохраняем изменения.
![]()
![]()
![]()
![]()
Доходим досюда без появления наг-окна. В другой раз, вероятно, можно использовать иной опкод для патчинга. В качестве домашнего упражнения вы можете попробовать решить крэкми nags2, прилагающийся к данной статье.
31 часть будет последней посвящённой P-CODE, и в ней будет рассмотрена коммерческая программа.
© Рикардо Нарваха, пер. Aquila
Введение в крэкинг с нуля, используя OllyDbg - Глава 30
Дата публикации 9 июл 2008