Декомпилируем p-code в уме

Дата публикации 8 сен 2006

Декомпилируем p-code в уме — Архив WASM.RU

Введение

Думаю, что все мы знакомы с программами на пикоде. Даже если реверсер в жизни не видел VB и его компилятор, то все равно хотя бы раз он сталкивался с пикодом. В отличии от стандартного машинного кода, исполняемого напрямую процессором, p-code это набор мнемоник VM, которые исполняются движком msvbvmXX.dll. Olly тут не особый помощник (хотя для кого как), IDA тем более. Тут нужен либо декомпилятор, либо мозги. Надеюсь, что второе есть точно у всех, кто читает эти строки. Именно поэтому листинги из декомпилятора я буду приводить в статье лишь для наглядности, основной же упор будет на декомпилирование, используя в работе только HIEW.

Немного о структурах

Пикод нам еще придется поискать в EXE файле - он так просто не лежит. Для этого придется разобраться хотя бы с частью структур VB, которые зашиты в EXE файл. Начнем с рассмотрения оригинальной точки входа в программу. Чтобы на нее перейти из HEX редактора HIEW нам потребуется лишь загрузить EXE'шник в данный HEX редактор и нажать поочереди Enter, Enter, F8, F5. У кого хиев купленный знают как оптимизировать эту операцию до командной строки, но это так к слову. Нашему взгляду будет представлено примерно следующее:

Код (Text):
  1.  
  2. push 0004042E8 ;'VB5!'
  3. call ThunRTMain ;MSVBVM60 --?2


P-Code Inside :smile3:

Весь смысл этой мегасложной строчки заключается в вызове функции ThunRTMain из MSVBVM60. В параметрах этой функции передается указатель на VB Header. Вот его примерный вид:

Структура VBHeader
ПолеТипОписание
SignatureString * 4Сигнатура "VB5!"
RuntimeBuildIntegerПоказатель рантаймовости
LanguageDLLString * 14Языковая библиотека
BackupLanguageDLLString * 14Не влияет на работу EXE
RuntimeDLLVersionIntegerВерсия рантайм библиотеки
LanguageIDLongЯзык программы
BackupLanguageIDLongИспользуется совместно с LanguageDLL
aSubMainLongMain процедура, запускаемая при старте EXE. Если отсутствует, то при загрузке грузится самая первая форма
aProjectInfoLongУказатель на структуру ProjectInfo
fMDLIntObjsLong
fMDLIntObjs2Long
ThreadFlagsLongФлаги потока
ThreadCountLongЧисло потоков (смысл малопонятен, так как VB не позволяет создавать многопоточные программы)
FormCountIntegerЧисло форм в данном файле
ExternalComponentCountIntegerЧисло внешних OCX компонентов
ThunkCountLong
aGUITableLongУказатель на GUITable
aExternalComponentTableLongУказатель на ExternalComponentTable
aComRegisterDataLongУказатель на ComRegisterData
oProjectExenameLongАдрес строки с именем EXE файла
oProjectTitleLongАдрес строки с заголовком проекта
oHelpFileLongАдрес строки с именем Help файла
oProjectNameLongАдрес строки с именем проекта

ThunRTMain из описанной структуры получает все необходимые данные и адреса структур, необходимые для настройки и запуска EXE. Функция ProcCallEngine, в случае присутствия поля aSubMain, запускает функцию, расположенную по адресу, указанному в этом поле структуры, которая является главной в проекте и не равна 0, если в EXE файле присутствовал модуль с функцией Main. Если данная функция отсутствует, то грузится первая форма и управление передается функции Form_Initialize, а если она отсутствует то Form_Load. Соотвественно, если и этой функции нет, и вообще в форме события не используются, то запускается WindowProc - цикл ожидания событий, как в любой программе (ассемблерщики меня поймут). Нам из этой структуры потребуются лишь aSubMain и структура ProjectInfo. Последняя имеет вид:

Структура ProjectInfo
lTemplateVersionLongВерсия VB совместимости
aObjectTableLongУказатель на aObjectTable
lNull1Long
aStartOfCodeLongНачало кода (нам бесполезно)
aEndOfCodeLongКонец кода (нам бесполезно)
lDataBufferSizeLongРазмер буфера для хранения различных данных
aThreadSpaceLongПространство потока
aVBAExceptionhandlerLongУказатель на функцию обработки ошибок
aNativeCodeLongЕсли не равно нулю, то это Native Code, иначе P-Code
uIncludeID527 Byte
aExternalTableLongУказатель на таблицу API функций
lExternalCountLongЧисло импортируемых API функций

Обозначенная таблица для нас как распутье. Продолжать далее имеет смысл только, если в aNativeCode стоит 0. Это означает, что все функции в программе на пикоде и имеет смысл с ними разобраться. Все полезные нам данные мы получим из aObjectTable - это указатель на структуру объектов (форм модулей и так далее), среди прочих данных в которой можно найти адреса процедур и событий.

Структура ObjectTable
lNull1Long
aExecProjLong
aProjectInfo2Long
lConst1Long
lNull2Long
aProjectObjectLong
uuidObjectTableLong
Flag2Long
Flag3Long
Flag4Long
fCompileTypeInteger
iObjectsCountIntegerЧисло объектов
iCompiledObjectsInteger
iObjectsInUseInteger
aObjectsArrayLongУказатель на массив объектов (единственное что для нас в этой структуре важно помимо iObjectsCount)
lNull3Long
lNull4Long
lNull5Long
aNTSProjectNameLong
lLcID1Long
lLcID2Long
lNull6Long
lTemplateVersionLong

Теперь, когда мы определились с таблицей объектов, давай посмотрим поподробнее на массив этих самых объектов. ObjectsArray - это массив структур TObject. Число элементов массива определяется полем iObjectsCount. Рассмотрим-ка поподробнее структуру TObject и производную от нее TObjectInfo.

Структура TObject
aObjectInfoLongУказатель на структуру ObjectInfo
lConst1Long
aPublicBytesLongУказатель на массив публичных переменных
aStaticBytesLongУказатель на массив статических переменных
aModulePublicLongУказатель на публичные переменных
aModuleStaticLongУказатель на статические переменные
aNTSObjectNameLongУказатель на имя объекта
lMethodCountLongЧисло методов объекта
aMethodNameTableLongУказатель на массив адресов функций
oStaticVarsLongСмещение на переменные из aModuleStatic
lObjectTypeLongТип объекта
lNull2Long

Как видишь эта структура уже поинтереснее. Поле aNTSObjectName означает, что это собственно за модуль. Если это поле содержит адрес на frmRegister, то это уже может нам намекнуть что функции, находящиеся в массиве из следующей структуры отвечают за процесс проверки серийника :smile3:

Структура TObjectInfo
iConst1Integer
iObjectIndexIntegerИндекс объекта
aObjectTableLongУказатель на таблицу объектов (требуется для разбора форм и лежащих на них объектов... нам же в данной статье не потребуется)
lNull1Long
aObjectDescriptorLong
lConst2Long
lNull2Long
aObjectHeaderLongУказатель на ObjectHeader
aObjectDataLongУказатель на ObjectData
следующие члены валидны только если программа скомпилирована в пикод
iMethodCountIntegerЧисло методов
iNull3Integer
aMethodTableLongУказатель на массив указателей на методы
iConstantsCountIntegerЧисло констант
iMaxConstantsIntegerМаксимальновозможное число констант
lNull4Long
lFlag1Long
aConstantTableLongУказатель на массив указателей на константы

Если не брать во внимание события объектов на форме, а только созданные пользователем функции, то для все их адреса можно найти в таблице, на которую указывает aMethodTable. Эта таблица для каждого модуля или формы конечно своя, это логично. Потому, чтобы получить доступ ко всем функциям, необходимо перебрать все формы и модули (а также классмодули, юзерконтролы...). В общем, если у тебя много свободного времени, можешь уже сейчас писать плагин для HIEW, который будет это все автоматизировать, я же лишь рассмотрю что же все-таки находится по адресам из таблицы методов. А находится там собственно не пикод, и не ассемблерные команды, как многие могут подумать. Там очередная структура, коими любит нас потчевать микрософт.

Структура ProcDscInfo
ProcTableLong
field_4Integer
FrameSizeInteger
ProcSizeInteger
...

Я преднамеренно урезал эту структуру, так как нам из нее потребуется только ProcSize и адрес на таблицу ProcTable. Из таблицы ProcTable нам потребуется в свою очередь только адрес на блок данных. Об этом блоке данных думаю надо поговорить поподробнее. Все опкоды пикода, которые так или иначе оперируют с данными (будь то строки, API функции и так далее) ссылаются на них не по абсолютному, а по относительному адресу. Относительный этот адрес именно от начала блока данных. Так что, не зная адреса на блок данных, мы по сути не сможем ничего декомпилировать. Отсюда рассмотрим следующую структуру, расположенную по адресу ProcTable:

Структура ProcTable
SomeTempString*52
DataConstLong

SomeTemp - это просто набор ненужных нам полей, которые я объединил дабы немного сэкономить места в статье. Они нам не потребуются, для нас важен только адрес DataConst. Теперь думаю логично задаться вопросом, а что мы узнали? Только размер пикодовой процедуры и адрес на блок данных. А где же сама пикодовая процедура? Ааа, вот тут билли гатес засюрпризил нам еще одну подлянку. Адрес пикодовой процедуры напрямую нигде не прописан, зато мы его можем получить вычитанием из адреса начала структуры ProcDscInfo поля sProcDscInfo.ProcSize. Как видно, пикод идет прямо перед структурой ProcDscInfo. Что-ж, теперь, когда мы знаем откуда брать методы (про ивенты я умолчу всвязи в ограничением объема статьи), можно приступать исследовать этот самый пикод.

Как исследовать

Узнали мы адрес начала блока пикода, а что дальше? А дальше, что бы хоть как-то понять пикод, нам потребуется таблица назначения опкодов. Ее можно с легкостью найти в гугле. Но для простоты я ее разместил на диске, так что открывай его и начиный изучать :smile3: Заранее предупрежу - таблица эта неполная, так как публичная и имеет кое какие ошибки (их крайне мало), потому доверять ей на все 100 не советую, хотя это всетаки лучшее, что можно добыть в инете на данный момент. Таблица представлена в виде: <опкод>tab<размер>tab<название> Опкод может состоять из одного или двух байт. До FBh опкода невключительно идут однобайтовые. Далее идут двухбайтовые. Так что, если ты встретишь к примеру FEh, то следующий за ним байт также относится к опкоду. <размер> в таблице это число параметров - 1. Из одного ли байта состоит опкод или из двух, все равно -1 :smile3: Не знаю , зачем так сделали составители таблицы, главное, нам это придется учитывать при разборе кода (само собой я сделал для себя иную таблицу, гораздо более продвинутую, но по коммерческим соображениям публиковать ее не буду). Теперь посмотрим на пример пикодового блока:

Код (Text):
  1.  
  2. 00 00 00 00-00 00 00 00-00 00 00 00-F4 00 2B 6E
  3. FF F5 00 00-00 00 F5 00-00 00 00 1B-0C 00 04 70
  4. FF 34 6C 70-FF 1B 0D 00-04 74 FF 34-6C 74 FF F5
  5. 00 00 00 00-59 78 FF 0A-0E 00 18 00-3C 32 04 00
  6. 74 FF 70 FF-13 00 00 00

Пикод в нашем блоке начинается с F4. Посмотрим по таблице, чтоже означает этот опкод. Согласно таблице это LitI2_Byte и имеет один байт в параметрах. Запомни раз и навсегда, Lit это всегда push. I2 - это 2х байтный integer. Вот небольшая табличка всех возможных значений, дабы было дальше проще исследовать:

Типы данных
UI1Byte
BoolBoolean
I2Integer
I4Long
R4Single
R8Double
CyCurrency
VarVariant
StrString

Так как после F4 идет 0, то логично предположить, что эта команда push 0. Продолжим далее. 2B - это PopTmpLdAd2. Читать следует так: Pop from stack to Temp variable and Load Address to stack. Пишу по английски, так как на русском это будет малопонятно. Итак, эта переменная всего лишь резервирует содержимое верхней ячейки стека во временную переменную - нам это не потребуется, так как мы не исполняем код а декомпилируем. Но для понятности расскажу. Следующие 2 байта - это та самая временная переменная FF6E. Если это число из открицательного перевести в положительное, то получится 92. Мой декомилятор в этом случае выведет var_92. Если бы число было положительным, то это была бы не локальная а глобальная переменная. Думаю теперь все ясно :smile3: приступим к следующей команде. F5 - LitI4. Понимаем как push следующие 4 байта, то есть push 0 :smile3: Далее опять идет push 0. Теперь 1B - LitStr. Этот опкод заносит строку в стэк. Не зря я тебе рассказал про адрес на блок данных, именно от него и отсчитываются следующие два байта в параметрах, то есть: адрес строки = ProcTable.DataConst + следующие за командой 2 байта Строка представлена в юникоде и заканчивается двумя нулями. Допустим, что там строка "Test", следовательно имеем push "Test". 04 - FLdRfVar, то есть push переменная. Переменная вычисляется как и раньше FF70 = var_90. 34 - CStr2Ansi. Считывает из стека два элемента и первому присваивает второй, то есть в стэке на данный момент переменная var_90 и строка "Test", следовательно, эту команду можно читать как var_90 = "Test". У этого опкода параметров нет, следовательно, далее идет уже другой опкод 6C - ILdRf. Понимается он как push переменная. То есть следующие два байта FF70 декомпятся как var_90 и вставляются в команду как push var_90. 1B снова заносит уже другую строку в стэк, пусть это будет "Test2", 04 опять создает переменную и заносит ее в стэк, а 34 присваивает "Test2" этой переменной. В частности, FF74 это var_8C. 6C заносит эту переменную в стэк по накатанной стандартной схеме. Затем идет F5, про него мы уже говорили - он заносит в стэк следующие 4 байта. Далее уже поинтереснее: 59 - PopTmpLdAdStr. Резервирует в переменной var_88 содержимое верхней ячейки стэка, при этом занося заново это содержимое на вершину стэка дабы оно не терялось. 0A - ImpAdCallFPR4. Вот тут уже запахло чем то вкусным... мы видим чистой воды Call на функцию, адрес которой отсчитывается от блока данных по традиции. Пока не забыл - это два байта, прежде чем суммироваться с адресом блока данных, умножаются на 4 - и это правило действует всегда. Зачем так сделали создатели VM - я не знаю, но это надо учитывать. Итак что же мы имеем в блоке данных? А имеем мы VA на следующую заглушку:

Код (Text):
  1.  
  2. A1A0634000                   mov         eax,[004063A0]
  3. 0BC0                         or          eax,eax
  4. 7402                         je         .000402A77
  5. FFE0                         jmp         eax
  6. 68542A4000                   push        000402A54
  7. B870104000                   mov         eax,000401070
  8. FFD0                         call        eax

В этой конструкции нам важен push 402A54. Виртуальный адрес 402A54 указывает на структуру вида:

Структура CallAPI
strLibraryNameLong
strFunctionNameLong

Эти два виртуальных адреса указывают на имя Dll и имя функции, которая должна быть вызвана. Вот разработчики намудрили с виртуальной машиной, правда?


Разбор переходников на API

Теперь то мы знаем, почему VB так тормозит. Чтобы вызвать любую API функцию необходимо пройти немыслимое количество структур и заглушек, что тратит золотые такты процессора и жутко снижает скорость. Как видишь - никакой таблицы импорта нет. Мы имеем дело с именем Dll и функции, которые вызываются динамически функцией DllFunctionCall, которая экспортируется из всеми ними замусоленной msvbvm60.dll. Поэтому строки из блока, что был указан выше:

Код (Text):
  1.  
  2. B870104000                   mov         eax,000401070 --?3
  3. FFD0                         call        eax

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

Код (Text):
  1.  
  2. jmp DllFunctionCall

и вызывают его через call eax. Что находится в функции DllFunctionCall думаю и так понятно:

Код (Text):
  1.  
  2. hLib = GetModuleHandle(strLibraryName)
  3. hProc = GetProcAddress(hLib, strFunctionName)
  4. Call hProc

Это в упрощенном виде, для наглядности :smile3: В нашем же случае strLibraryName это user32.dll, а strFunctionName - это ShellExecute. Теперь то думаю ясно, зачем в пикоде было столько push'е и резервирования. Это всего лишь подготовка стэка для вызова ShellExecute. Поэтому, если свернуть все полученные нами данные, то выйдет что-то вроде:

Код (Text):
  1.  
  2.   loc_4043F1: var_90 = "Test"
  3.   loc_4043FB: var_8C = "Test2"
  4.   loc_404407: ShellExecute(0, var_8C, var_90, 0, 0, 0)

Ну что, продолжим исследовать пикод. 3C - SetLastSystemError. Тут все просто - это всеголишь переходник на API. Нам он не потребуется, так как он юзается виртульной машиной для отслеживания ошибок и не дает нам никаких важных данных для анализа пикода. 32 - FFreeStr. Очень интересная команда. Число ее параметров всегда переменно и определяется первыми двумя байтами. В данном случае первые два байта - 4, следовательно следующие 4 байта - две двухбайтовые переменные. В частности FF74 и FF70. Команда FFreeStr обнуляет эти строковые переменные: var_8C и var_90. То есть на нормальном языке это выглядит так:

Код (Text):
  1.  
  2. var_8C = ""
  3. var_90 = ""

Следующий и последний опкод 13 - ExitProcHresult. Как понятно из названия, он завершает процедуру. Это значит, что после него идет уже известная нам структура ProcDscInfo. Вот мы и разобрались, как декомпилировать пикод в уме. Теперь пора поговорить о реальных задачах по исследованию, для которых исследование в уме слишком долгое и геморное, потому исследовать будем в моем декомпиляторе.

Срубаем NAG скрины


Злостные наги

В любой программе есть две проблемы - триал и нагскрины. Если триал обычно можно обойти лишь найдя ключ в реестре, который отвечает за счетчик дней и написав простенький инлайн патч, который будет перед запуском EXE обнулять этот ключ, то с нагами такой номер не пройдет. Они надоедают либо до покупки проги, либо до отрубания этих злостных трюков :smile3: Думаю, лучшим примером нагскрина в виде мессаги будет простенький крякми на пикоде. Не уверен, что ты найдешь его в интернете, потому бери с диска, я его назвал "AC_Crackme_01.exe". Откроем-ка мы его в декомпиляторе, дабы посмотреть с чем имеем дело. Видим две процедуры, одна закрывает приложение по кнопке, а вторая, та что Form_Load:

Код (Text):
  1.  
  2.   loc_401A48: LitVar_Missing var_104
  3.   loc_401A4B: LitVar_Missing var_E4
  4.   loc_401A4E: LitVar_Missing var_C4
  5.   loc_401A51: LitI4 0
  6.   loc_401A56: LitVarStr var_94, "NAG"
  7.   loc_401A5B: FStVarCopyObj var_A4
  8.   loc_401A5E: FLdRfVar var_A4
  9.   loc_401A61: ImpAdCallFPR4 MsgBox(, , , , )
  10.   loc_401A66: FFreeVar var_A4 = "": var_C4 = "": var_E4 = ""
  11.   loc_401A71: ExitProcHresult

Видим вызов MsgBox по адресу 401A61. Как ты понимаешь, нам его нужно убрать. Тут есть несколько путей. Так как в данном случае вся функция это NAG, то можно просто сделать выход в самом начале функции, то есть по адресу loc_401A48 вбить опкод ExitProcHresult. Красиво, оригинально и работает :smile3:. Посмотрим теперь крякми посложнее "AC_Crackme_01_A.exe":

Код (Text):
  1.  
  2.   loc_401DE8: LitVar_Missing var_104
  3.   loc_401DEB: LitVar_Missing var_E4
  4.   loc_401DEE: LitVar_Missing var_C4
  5.   loc_401DF1: LitI4 0
  6.   loc_401DF6: LitVarStr var_94, "Another NAG"
  7.   loc_401DFB: FStVarCopyObj var_A4
  8.   loc_401DFE: FLdRfVar var_A4
  9.   loc_401E01: ImpAdCallFPR4 MsgBox(, , , , )
  10.   loc_401E06: FFreeVar var_A4 = "": var_C4 = "": var_E4 = ""
  11.   loc_401E11: FLdPr arg_8
  12.   loc_401E14: Me.Hide
  13.   loc_401E19: LitVar_Missing var_B4
  14.   loc_401E1C: PopAdLdVar
  15.   loc_401E1D: LitVar_Missing var_94
  16.   loc_401E20: PopAdLdVar
  17.   loc_401E21: ImpAdLdRf unk_4019F4
  18.   loc_401E24: NewIfNullPr
  19.   loc_401E27: Me.Show from_stack_1 from_stack_2
  20.   loc_401E2C: ExitProcHresult
  21.   loc_401E2D: ILdPr

Видим, что по адресу 401E01 выводится один наг с помощью MsgBox, затем 401E14 скрывается форма и 401E27 происходит загрузка основного окна. Здесь я рассмотрю сначала как можно обойти мессагу, а уж потом поговорим про форму. Как же обойти этот вызов MsgBox? А как бы мы его обошли в программировании? Наверное логичным по адресу 401DE8 прописать GoTo на адрес 401E11. Это мы и сделаем. В качестве GoTo используется BranchF а адрес рассчитывается относительно адреса начала данной функции. То есть начальный адрес 401DE8, а нам нужно перейти на 401E11, следовательно, 401E11h-401DE8h=29h, а значит, в начало функции надо приписать 1E2900 :smile3: Запускаем - нага как не бывало, осталось убрать форму нага - но это будет домашним заданием - думаю ты спрашишься без проблем.

Ломаем запрос пароля

Хорошим примером на эту тему будет крякми "AC_Crackme_02_A.exe". В нем нужно ввести верный серийник. Если серийник неверный, то выводится "fuxxor". Давай посмотрим его в декомпиляторе:

Код (Text):
  1.  
  2.   loc_401D4C: FLdRfVar var_94
  3.   loc_401D4F: FLdPrThis
  4.   loc_401D50: VCallAd Crackme.Frame1
  5.   loc_401D53: FStAdFunc var_90
  6.   loc_401D56: FLdPr var_90
  7.   loc_401D59: from_stack_1 = TextBox.Text
  8.   loc_401D5E: FLdZeroAd var_94
  9.   loc_401D61: FStStr var_8C
  10.   loc_401D64: FFree1Ad var_90
  11.   loc_401D67: LitStr "ExDec_Roxx"
  12.   loc_401D6A: FStStrCopy var_88
  13.   loc_401D6D: ILdRf var_88
  14.   loc_401D70: ILdRf var_8C
  15.   loc_401D73: EqStr
  16.   loc_401D75: BranchF loc_401DA4
  17.   loc_401D78: LitVar_Missing var_114
  18.   loc_401D7B: LitVar_Missing var_F4
  19.   loc_401D7E: LitVar_Missing var_D4
  20.   loc_401D81: LitI4 0
  21.   loc_401D86: LitVarStr var_A4, "yess"
  22.   loc_401D8B: FStVarCopyObj var_B4
  23.   loc_401D8E: FLdRfVar var_B4
  24.   loc_401D91: ImpAdCallFPR4 MsgBox(, , , , )
  25.   loc_401D96: FFreeVar var_B4 = "": var_D4 = "": var_F4 = ""
  26.   loc_401DA1: Branch loc_401DCD
  27.   loc_401DA4: ' Referenced from: 401D75
  28.   loc_401DA4: LitVar_Missing var_114
  29.   loc_401DA7: LitVar_Missing var_F4
  30.   loc_401DAA: LitVar_Missing var_D4
  31.   loc_401DAD: LitI4 0
  32.   loc_401DB2: LitVarStr var_A4, "fuxxor"
  33.   loc_401DB7: FStVarCopyObj var_B4
  34.   loc_401DBA: FLdRfVar var_B4
  35.   loc_401DBD: ImpAdCallFPR4 MsgBox(, , , , )
  36.   loc_401DC2: FFreeVar var_B4 = "": var_D4 = "": var_F4 = ""
  37.   loc_401DCD: ' Referenced from: 401DA1
  38.   loc_401DCD: ExitProcHresult
  39.   loc_401DCE: ImpAdLdFPR4

Просмотрев внимательно код видим сравнение в самом начале

Код (Text):
  1.  
  2.   loc_401D67: LitStr "ExDec_Roxx"
  3.   loc_401D6A: FStStrCopy var_88
  4.   loc_401D6D: ILdRf var_88
  5.   loc_401D70: ILdRf var_8C
  6.   loc_401D73: EqStr
  7.   loc_401D75: BranchF loc_401DA4

Понимать этот код следует примерно так:

Код (Text):
  1.  
  2. var_88="ExDec_Roxx"
  3. if (var_88=var_8C) then

Затем видим мессагу, что пасс верный. Собственно пасс-то мы узнали, да вот нам-то крякнуть надобы, чтобы подходил любой пароль неверный :smile3: BranchF по адресу 401D75 джампается на вывод инфы о неверном пароле, следовательно он понимается, как jne, то есть если EqStr возвращает что строки различны, то джампаемся. Вывод - инвертировать инструкцию. Следует понимать, что в VB всего три типа переходов, два из которых условные - 1C и 1D. ТО есть для инвертирования нам надо 1C заменить на 1D или наоборот. Меняем и, о чудо, - мессаги больше нет.

Более сложные приемы

Ну вот, крякам мы научились, пора бы попробовать себя в реально сложном деле - написать кейген. В качестве примера возьмем кейгенми "KeygenMe.exe". Тут нам больше потребуется Pro версия декомпилятора, так как для анализа кода лучше смотреть на подобие исходникак, нежеле на сотни строк пикодовых опкодов. Потребуется движок сворачивания команд и стэка, который прекрасно реализован в VB Decompiler Pro. Посмотрим на кейгенми под декомпилятором:

Код (Text):
  1.  
  2.   loc_405825: var_88 = TextBox_764.Text
  3.   loc_40583F: var_90 = TextBox_768.Text
  4.   loc_40585B: If (var_94 Or (var_94 = "")) Then '405892
  5.   loc_40587F:   MsgBox("Incorrect username or password!", 16, "Keygenme", var_F4,
  6.                 var_114)
  7.   loc_40588F:   GoTo loc_405D22
  8.   loc_405892: End If

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

Код (Text):
  1.  
  2.   loc_40589F: var_88 = TextBox_764.Text
  3.   loc_4058B4: If (Len(var_8C) &gt; 2) Then '405CF1

Теперь узнаем что имя не может быть менее 2х символов. Нам на это пофиг... анализируем далее:

Код (Text):
  1.  
  2.   loc_405944:     var_88 = TextBox_768.Text
  3.   loc_40594C:     var_B4 = CVar(var_8C) 'String
  4.   loc_40595B:     If Not(IsNumeric(var_B4)) Then '405992
  5.   loc_40597F:       MsgBox("No access!", 16, "Keygenme", var_F4, var_114)
  6.   loc_40598F:       GoTo loc_405B11
  7.   loc_405992:     End If

Если пароль не число - прога ругается. Что-ж, нам лучше, уже знаем, что пароль как-то вычисляется и представляет собой результат вычислений над числами. Проверим наше предположение и поглядим ниже:

Код (Text):
  1.  
  2.   loc_405A53:     If (CDbl(var_8C) = CDbl((CLng((Asc(CStr(Left(CVar
  3.                   (TextBox_768.Text), 1))) * Asc(CStr(Right(CVar(CStr
  4.                   (TextBox_768.Text)), 1))))) * Len(TextBox_764.Text))))
  5.                   Then '405AE0
  6.   loc_405ABE:     MsgBox("Thanks for registration!" & Chr(10) & Chr(10) &
  7.                   "Registered to:" & CVar(TextBox_764.Text), 64, "Keygenme",
  8.                   var_180, var_1A0)

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

Код (Text):
  1.  
  2. имя: GPcH
  3. пароль: 20448


Школа кейгенеров :smile3:

Кейген на том же VB будет выглядеть примерно так:

Код (Text):
  1.  
  2. strName="GPcH"
  3. strPass=Asc(Left$(strName,1)) * Asc(Right$(strName,1)) * Len(strName)

Примерно таким же методом можно закейгенить и другие алгоритмы, но тут без исследования, анализа и плотного разбора алго не обойтись - рассмотренный выше пример слишком идеален и вряд ли встретится в реальной программе. Но поверь - главное желание, а разобрать можно любой сложности алгоритм, был бы декомпиялтор. А что если Pro версии моего декомпилятора нет, а прогу крякнуть нужно? Поисследуем этот же кейгенми под Lite версией и поглядим что можно крякнуть:

Код (Text):
  1.  
  2.   loc_405A31: EqR4
  3.   loc_405A32: FFreeStr var_118 = "": var_124 = "": var_12C = ""
  4.   loc_405A3D: FFreeAd var_88 = "": var_90 = "": var_120 = ""
  5.   loc_405A48: FFreeVar var_B4 = "": var_D4 = "": var_F4 = ""
  6.   loc_405A53: BranchF loc_405AE0

Видим сравнение пароля с рассчетным эталоном оператором EqR4, затем идет переход на адрес 405AE0, если пароль неверен. Меняем этот переход с 1C на 1D, и наш кейгенми будет регаться на любой числовой пароль. Теперь поглядим enm ранее, если пароль проверяется на то, что он состоит только из чисел:

Код (Text):
  1.  
  2.   loc_40594F: ImpAdCallI2 IsNumeric()
  3.   loc_405954: NotI4
  4.   loc_405955: FFree1Ad var_88
  5.   loc_405958: FFree1Var var_B4 = ""
  6.   loc_40595B: BranchF loc_405992

Опять меняем 1С на 1D и можем в качестве пароля вводить все что угодно :smile3: Менять само собой надо по указанному адресу, открыв кейгенми в любом HEX редакторе, хотя я советую юзать лючший на мой взгляд продукт для этих целей - hiew от sen'а.

Заключение

Вот мы и научились с тобой находить пикод в EXE файле, декомпилировать его в уме и в декомпиляторе, познакомились с основными опкодами P-Code и теперь можем анализировать что угодно. Будь то вирус с целью написания для него антивируса, будь то прога которая не регается без ключа или банально для повышения своего скилла. И не верь тем кто говорит что пикода нигде уже нет... я видел проги с гвардантом и хаспом в пикоде, а немало огромных проектов, скомпилированных в пикод. А уж троев и червей на VB полно и чтобы их анализировать обязательно нужен декомпилятор или знания, которые я думаю ты получил, прочитав данную статью. Удачи тебе!

Заходи на www.dotfix.net - почерпнешь много полезной информации по устройству VB и не только. Декомпилятор VB бери с www.vb-decompiler.org

Файлы к статье здесь.

© GPcH

0 9.446
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532