Автор: Сергей Чубченко. Дата публикации: 25.08.2006
Тонкости исследования команд виртуальной машины Visual Basic 5.0/6.0
Введение
Думаю многие знакомы с программами на пикоде. Даже если аналитик в жизни не видел Visual Basic и его компилятор, то все равно хотя бы раз он сталкивался с P-Code. В отличии от стандартного машинного кода, исполняемого напрямую процессором, P-Code это набор мнемоник виртуальной машины VB, которые исполняются движком msvbvmXX.dll. Olly Debugger тут не особый помощник (хотя для кого как), IDA тем более. Тут нужен либо декомпилятор, либо мозги. Надеюсь что второе есть точно у всех, кто читает эти строки. Именно поэтому листинги из декомпилятора я буду приводить в статье лишь для наглядности, основной же упор будет на декомпилирование, используя в работе только HIEW.
Немного о структурах
Пикод еще нам придется поискать в EXE файле - он так просто не лежит. Для этого придется разобраться хотябы с частью структур VB, которые зашиты в EXE файл. Начнем с рассмотрения оригинальной точки входа в программу. Чтобы на нее перейти из HEX редактора HIEW нам потребуется лишь загрузить EXE’шник в данный HEX редактор и нажать поочереди Enter, Enter, F8, F5. У кого хиев купленный знают как оптимизировать эту операцию до командной строки, но это так, к слову.
Нашему взгляду будет представлено примерно следующее:
P-Code Inside
Весь смысл этой мегасложной строчки заключается в вызове функции ThunRTMain из MSVBVM60. В параметрах этой функции передается указатель на VB Header. Вот его примерный вид:
Структура VBHeader
ThunRTMain из описанной структуры получает все необходимые данные и адреса структур необходимые для настройки и запуска EXE. Функция ProcCallEngine в случае присутствия поля aSubMain запускает функцию, расположенную по адресу, указанному в этом поле структуры, которая является главной в проекте и не равна 0 если в EXE файле присутствовал модуль с функцией Main. Если данная функция отсутствует, то грузится первая форма и управление передается функции Form_Initialize, а если она отсутствует то Form_Load. Соотвественно если и этой функции нет и вообще в форме события не используются, то запускается WindowProc цикл ожидания событий как в любой программе (ассемблерщики меня поймут). Нам из этой структуры потребуются лишь aSubMain и структура ProjectInfo. Последняя имеет вид:
Структура ProjectInfo
Обозначенная таблица для нас как распутье. Продолжать далее имеет смысл только если в aNativeCode стоит 0. Это означает что все функции в программе на пикоде и имеет смысл с ними разобраться. Все полезные нам данные мы получим из aObjectTable это указатель на структуру объектов (форм модулей и так далее), среди прочих данных в которой можно найти адреса процедур и событий.
Структура ObjectTable
Теперь когда мы определились с таблицей объектов, давайте посмотрим поподробнее на массив этих самых объектов. ObjectsArray это массив структур TObject. Число элементов массива определяется полем iObjectsCount. Рассмотрим ка поподробнее структуру TObject и производную от нее TObjectInfo.
Структура TObject
Как видите эта структура уже поинтереснее. Поле aNTSObjectName означает что это собственно за модуль. Если это поле содержит адрес на frmMain то это уже может нам намекнуть что функции, находящиеся в массиве из следующей структуры отвечают за главную форму программы.
Структура TObjectInfo
Если не брать во внимание ивенты объектов на форме, а только созданные пользователем функции, то все их адреса можно найти в таблице, на которую указывает aMethodTable. Эта таблица для каждого модуля или формы конечно своя, что логично. Потому чтобы получить доступ ко всем функциям необходимо перебрать все формы и модули (а также классмодули, юзерконтролы). В общем если у Вас много свободного времени, можете уже сейчас писать плагин для HIEW, который будет это все автоматизировать, я же подробнее остановлюсь на тех данных, что находится по адресам из таблицы методов. А находится там собственно не пикод, и не ассемблерные команды, как многие могут подумать. Там очередная структура, коими любит нас потчевать микрософт.
Структура ProcDscInfo
Я преднамеренно урезал эту структуру, так как нам из нее потребуется только ProcSize и адрес на таблицу ProcTable. Из таблицы ProcTable нам потребуется в свою очередь только адрес на блок данных. Вот об этих данных думаю нам надо поговорить поподробнее. Все опкоды пикода, которые так или иначе оперируют с данными (будь то строки, API функции и так далее) ссылаются на них не по абсолютному а по относительному адресу. Относительный этот адрес именно от начала блока данных. Так что не зная адреса на блок данных мы по сути не сможем ничего декомпилировать. Отсюда рассмотрим следующую структуру, расположенную по адресу ProcTable:
Структура ProcTable
SomeTemp это просто набор ненужных нам полей, которые я объединил дабы немного сэкономить места в статье. Они нам не потребуются, для нас важен только адрес DataConst. Теперь думаю логично задаться вопросом а что мы узнали? Только размер пикодовой процедуры и адрес на блок данных. А где же сама пикодовая процедура? Адрес этой процедуры с P-Code напрямую нигде не прописан, зато мы его можем получить вычитанием из адреса начала структуры ProcDscInfo поля sProcDscInfo.ProcSize. Как видно пикод идет прямо перед структурой ProcDscInfo. Чтож, теперь когда мы знаем откуда брать методы (про ивенты я умолчу всвязи в ограничением объема статьи), можно приступать исследовать этот самый пикод.
Как исследовать
Узнали мы адрес начала блока пикода а что дальше? А дальше, что бы хоть как-то понять пикод, нам потребуется таблица назначения опкодов. Только предупрежу: таблица эта неполная, так как публичная и имеет кое какие ошибки (хотя их и не так много), потому доверять ей на все 100 не советую, хотя это всетаки лучшее, что можно добыть в инете на данный момент. Таблица представлена в виде:
Опкод может состоять из одного или двух байт. До FBh опкода невключительно идут однобайтовые. Далее идут двухбайтовые. Так что если Вы встретишь к примеру FEh, то следующий за ним байт также относится к опкоду. <размер> в таблице это число параметров - 1. Из одного ли байта состоит опкод или из двух, все равно -1 :) Не знаю зачем так сделали составители таблицы, главное нам это придется учитывать при разборе кода (само собой в моем декомпиляторе используется совсем другая таблица, на составление которой ушло более 5 лет, но представить ее я не могу по понятным соображениям). Теперь посмотрим на пример пикодового блока:
Пикод в нашем блоке начинается с F4. Посмотрим по таблице, чтоже означает этот опкод. Согласно таблице это LitI2_Byte и имеет один байт в параметрах. Запомним раз и навсегда, Lit это всегда push. I2 это 2х байтный integer. Вот небольшая табличка всех возможных значений чтобы было проще исследовать:
Типы данных
Так как после F4 идет 0, то логично предположить, что эта команда push 0. Продолжим далее. 2B - это PopTmpLdAd2. Читать следует так Pop from stack to Temp variable and Load Address to stack. Пишу по английски, так как на русском это будет малопонятно. Итак эта переменная всего лишь резервирует содержимое верхней ячейки стека во временную переменную - нам это не потребуется, так как мы не исполняем код а декомпилируем. Но для понятности расскажу. Следующие 2 байта это та самая временная переменная FF6E. Если это число из открицательного перевести в положительное, то получится 92. VB Decompiler в этом случае выведет var_92. Если бы число было положительным, то это была бы не локальная а глобальная переменная. Думаю теперь все ясно, приступим к следующей команде. F5 - LitI4. Понимаем как push следующие 4 байта, то есть push 0. Далее опять идет 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 - и это правило действует всегда. Что же мы имеем в блоке данных? А имеем мы Virtual Address на следующую заглушку:
В этой конструкции нам важен push 00402A54. Виртуальный адрес 00402A54 указывает на структуру вида:
Структура CallAPI
Эти два виртуальных адреса указывают на имя Dll и имя функции, которая должна быть вызвана. Вот разработчики намудрили с виртуальной машиной, правда?
Разбор переходников на API
Теперь то мы знаем, почему Visual Basic так тормозит. Чтобы вызвать любую API функцию необходимо пройти немыслимое количество структур и заглушек, что тратит золотые такты процессора и жутко снижает скорость. Как видим - никакой таблицы импорта нет. Мы имеем дело с именем Dll и функции, которые вызываются динамически функцией DllFunctionCall, которая экспортируется из msvbvm60.dll. Поэтому строки из блока, что был указан выше:
получают адрес на переходник:
и вызывают его через call eax. Что находится в функции DllFunctionCall думаю и так понятно:
Это в упрощенном виде, для наглядности. В нашем же случае strLibraryName это user32.dll, а strFunctionName - это ShellExecute. Теперь думаю ясно, зачем в пикоде было столько push’е и резервирования. Это всеголишь подготовка стэка для вызова ShellExecuteю Поэтому если свернуть все полученные нами данные то выйдет чтото вроде:
Ну что, продолжим исследовать пикод. 3C - SetLastSystemError. Тут все просто - это всеголишь переходник на API. Нам он не потребуется, так как он используется виртульной машиной для отслеживания ошибок и не дает нам никаких важных данных для анализа пикода. 32 - FFreeStr. Очень интересная команда. Число ее параметров всегда переменно и определяется первыми двумя байтами. В данном случае первые два байта - 4, следовательно следующие 4 байта - две двухбайтовые переменные. В частности FF74 и FF70. Команда FFreeStr обнуляет эти строковые переменные: var_8C и var_90. То есть на нормальном языке это выглядит так:
Следующий и последний опкод 13 - ExitProcHresult. Как понятно из названия он завершает процедуру. Это значит, что после него идет уже известная нам структура ProcDscInfo.
Вот мы и разобрались как декомпилировать пикод в уме. Надеюсь эта статья оказалась для Вас полезной!
Декомпилируем p-code в уме
Тонкости исследования команд виртуальной машины Visual Basic 5.0/6.0
Введение
Думаю многие знакомы с программами на пикоде. Даже если аналитик в жизни не видел Visual Basic и его компилятор, то все равно хотя бы раз он сталкивался с P-Code. В отличии от стандартного машинного кода, исполняемого напрямую процессором, P-Code это набор мнемоник виртуальной машины VB, которые исполняются движком msvbvmXX.dll. Olly Debugger тут не особый помощник (хотя для кого как), IDA тем более. Тут нужен либо декомпилятор, либо мозги. Надеюсь что второе есть точно у всех, кто читает эти строки. Именно поэтому листинги из декомпилятора я буду приводить в статье лишь для наглядности, основной же упор будет на декомпилирование, используя в работе только HIEW.
Немного о структурах
Пикод еще нам придется поискать в EXE файле - он так просто не лежит. Для этого придется разобраться хотябы с частью структур VB, которые зашиты в EXE файл. Начнем с рассмотрения оригинальной точки входа в программу. Чтобы на нее перейти из HEX редактора HIEW нам потребуется лишь загрузить EXE’шник в данный HEX редактор и нажать поочереди Enter, Enter, F8, F5. У кого хиев купленный знают как оптимизировать эту операцию до командной строки, но это так, к слову.
Нашему взгляду будет представлено примерно следующее:
push 0004042E8 ;’VB5!’
call ThunRTMain ;MSVBVM60 --?2
P-Code Inside
Весь смысл этой мегасложной строчки заключается в вызове функции ThunRTMain из MSVBVM60. В параметрах этой функции передается указатель на VB Header. Вот его примерный вид:
Структура VBHeader
Поле Тип Описание
Signature String * 4 Сигнатура "VB5!"
RuntimeBuild Integer Показатель рантаймовости
LanguageDLL String * 14 Языковая библиотека
BackupLanguageDLL String * 14 Не влияет на работу EXE
RuntimeDLLVersion Integer Версия рантайм библиотеки
LanguageID Long Язык программы
BackupLanguageID Long Используется совместно с LanguageDLL
aSubMain Long Main процедура, запускаемая при старте EXE.
Если отсутствует, то при загрузке грузится самая первая форма
aProjectInfo Long Указатель на структуру ProjectInfo
fMDLIntObjs Long
fMDLIntObjs2 Long
ThreadFlags Long Флаги потока
ThreadCount Long Число потоков (смысл малопонятен,
так как VB не позволяет создавать многопоточные программы)
FormCount Integer Число форм в данном файле
ExternalComponentCount Integer Число внешних OCX компонентов
ThunkCount Long
aGUITable Long Указатель на GUITable
aExternalComponentTable Long Указатель на ExternalComponentTable
aComRegisterData Long Указатель на ComRegisterData
oProjectExename Long Адрес строки с именем EXE файла
oProjectTitle Long Адрес строки с заголовком проекта
oHelpFile Long Адрес строки с именем Help файла
oProjectName Long Адрес строки с именем проекта
ThunRTMain из описанной структуры получает все необходимые данные и адреса структур необходимые для настройки и запуска EXE. Функция ProcCallEngine в случае присутствия поля aSubMain запускает функцию, расположенную по адресу, указанному в этом поле структуры, которая является главной в проекте и не равна 0 если в EXE файле присутствовал модуль с функцией Main. Если данная функция отсутствует, то грузится первая форма и управление передается функции Form_Initialize, а если она отсутствует то Form_Load. Соотвественно если и этой функции нет и вообще в форме события не используются, то запускается WindowProc цикл ожидания событий как в любой программе (ассемблерщики меня поймут). Нам из этой структуры потребуются лишь aSubMain и структура ProjectInfo. Последняя имеет вид:
Структура ProjectInfo
lTemplateVersion Long Версия VB совместимости
aObjectTable Long Указатель на aObjectTable
lNull1 Long
aStartOfCode Long Начало кода (нам бесполезно)
aEndOfCode Long Конец кода (нам бесполезно)
lDataBufferSize Long Размер буфера для хранения различных данных
aThreadSpace Long Пространство потока
aVBAExceptionhandler Long Указатель на функцию обработки ошибок
aNativeCode Long Если не равно нулю, то это Native Code, иначе P-Code
uIncludeID Byte
aExternalTable Long Указатель на таблицу API функций
lExternalCount Long Число импортируемых API функций
Обозначенная таблица для нас как распутье. Продолжать далее имеет смысл только если в aNativeCode стоит 0. Это означает что все функции в программе на пикоде и имеет смысл с ними разобраться. Все полезные нам данные мы получим из aObjectTable это указатель на структуру объектов (форм модулей и так далее), среди прочих данных в которой можно найти адреса процедур и событий.
Структура ObjectTable
lNull1 Long
aExecProj Long
aProjectInfo2 Long
lConst1 Long
lNull2 Long
aProjectObject Long
uuidObjectTable Long
Flag2 Long
Flag3 Long
Flag4 Long
fCompileType Integer
iObjectsCount Integer Число объектов
iCompiledObjects Integer
iObjectsInUse Integer
aObjectsArray Long Указатель на массив объектов
(единственное что для нас в этой структуре важно помимо iObjectsCount)
lNull3 Long
lNull4 Long
lNull5 Long
aNTSProjectName Long
lLcID1 Long
lLcID2 Long
lNull6 Long
lTemplateVersion Long
Теперь когда мы определились с таблицей объектов, давайте посмотрим поподробнее на массив этих самых объектов. ObjectsArray это массив структур TObject. Число элементов массива определяется полем iObjectsCount. Рассмотрим ка поподробнее структуру TObject и производную от нее TObjectInfo.
Структура TObject
aObjectInfo Long Указатель на структуру ObjectInfo
lConst1 Long
aPublicBytes Long Указатель на массив публичных переменных
aStaticBytes Long Указатель на массив статических переменных
aModulePublic Long Указатель на публичные переменных
aModuleStatic Long Указатель на статические переменные
aNTSObjectName Long Указатель на имя объекта
lMethodCount Long Число методов объекта
aMethodNameTable Long Указатель на массив адресов функций
oStaticVars Long Смещение на переменные из aModuleStatic
lObjectType Long Тип объекта
lNull2 Long
Как видите эта структура уже поинтереснее. Поле aNTSObjectName означает что это собственно за модуль. Если это поле содержит адрес на frmMain то это уже может нам намекнуть что функции, находящиеся в массиве из следующей структуры отвечают за главную форму программы.
Структура TObjectInfo
iConst1 Integer
iObjectIndex Integer Индекс объекта
aObjectTable Long Указатель на таблицу объектов
(требуется для разбора форм и лежащих на них объектов...
нам же в данной статье не потребуется)
lNull1 Long
aObjectDescriptor Long
lConst2 Long
lNull2 Long
aObjectHeader Long Указатель на ObjectHeader
aObjectData Long Указатель на ObjectData
следующие члены валидны только если программа скомпилирована в пикод
iMethodCount Integer Число методов
iNull3 Integer
aMethodTable Long Указатель на массив указателей на методы
iConstantsCount Integer Число констант
iMaxConstants Integer Максимальновозможное число констант
lNull4 Long
lFlag1 Long
aConstantTable Long Указатель на массив указателей на константы
Если не брать во внимание ивенты объектов на форме, а только созданные пользователем функции, то все их адреса можно найти в таблице, на которую указывает aMethodTable. Эта таблица для каждого модуля или формы конечно своя, что логично. Потому чтобы получить доступ ко всем функциям необходимо перебрать все формы и модули (а также классмодули, юзерконтролы). В общем если у Вас много свободного времени, можете уже сейчас писать плагин для HIEW, который будет это все автоматизировать, я же подробнее остановлюсь на тех данных, что находится по адресам из таблицы методов. А находится там собственно не пикод, и не ассемблерные команды, как многие могут подумать. Там очередная структура, коими любит нас потчевать микрософт.
Структура ProcDscInfo
ProcTable Long
field_4 Integer
FrameSize Integer
ProcSize Integer
...
Я преднамеренно урезал эту структуру, так как нам из нее потребуется только ProcSize и адрес на таблицу ProcTable. Из таблицы ProcTable нам потребуется в свою очередь только адрес на блок данных. Вот об этих данных думаю нам надо поговорить поподробнее. Все опкоды пикода, которые так или иначе оперируют с данными (будь то строки, API функции и так далее) ссылаются на них не по абсолютному а по относительному адресу. Относительный этот адрес именно от начала блока данных. Так что не зная адреса на блок данных мы по сути не сможем ничего декомпилировать. Отсюда рассмотрим следующую структуру, расположенную по адресу ProcTable:
Структура ProcTable
SomeTemp String*52
DataConst Long
SomeTemp это просто набор ненужных нам полей, которые я объединил дабы немного сэкономить места в статье. Они нам не потребуются, для нас важен только адрес DataConst. Теперь думаю логично задаться вопросом а что мы узнали? Только размер пикодовой процедуры и адрес на блок данных. А где же сама пикодовая процедура? Адрес этой процедуры с P-Code напрямую нигде не прописан, зато мы его можем получить вычитанием из адреса начала структуры ProcDscInfo поля sProcDscInfo.ProcSize. Как видно пикод идет прямо перед структурой ProcDscInfo. Чтож, теперь когда мы знаем откуда брать методы (про ивенты я умолчу всвязи в ограничением объема статьи), можно приступать исследовать этот самый пикод.
Как исследовать
Узнали мы адрес начала блока пикода а что дальше? А дальше, что бы хоть как-то понять пикод, нам потребуется таблица назначения опкодов. Только предупрежу: таблица эта неполная, так как публичная и имеет кое какие ошибки (хотя их и не так много), потому доверять ей на все 100 не советую, хотя это всетаки лучшее, что можно добыть в инете на данный момент. Таблица представлена в виде:
<опкод>tab<размер>tab<название>
Опкод может состоять из одного или двух байт. До FBh опкода невключительно идут однобайтовые. Далее идут двухбайтовые. Так что если Вы встретишь к примеру FEh, то следующий за ним байт также относится к опкоду. <размер> в таблице это число параметров - 1. Из одного ли байта состоит опкод или из двух, все равно -1 :) Не знаю зачем так сделали составители таблицы, главное нам это придется учитывать при разборе кода (само собой в моем декомпиляторе используется совсем другая таблица, на составление которой ушло более 5 лет, но представить ее я не могу по понятным соображениям). Теперь посмотрим на пример пикодового блока:
00 00 00 00-00 00 00 00-00 00 00 00-F4 00 2B 6E
FF F5 00 00-00 00 F5 00-00 00 00 1B-0C 00 04 70
FF 34 6C 70-FF 1B 0D 00-04 74 FF 34-6C 74 FF F5
00 00 00 00-59 78 FF 0A-0E 00 18 00-3C 32 04 00
74 FF 70 FF-13 00 00 00
Пикод в нашем блоке начинается с F4. Посмотрим по таблице, чтоже означает этот опкод. Согласно таблице это LitI2_Byte и имеет один байт в параметрах. Запомним раз и навсегда, Lit это всегда push. I2 это 2х байтный integer. Вот небольшая табличка всех возможных значений чтобы было проще исследовать:
Типы данных
UI1 Byte
Bool Boolean
I2 Integer
I4 Long
R4 Single
R8 Double
Cy Currency
Var Variant
Str String
Так как после F4 идет 0, то логично предположить, что эта команда push 0. Продолжим далее. 2B - это PopTmpLdAd2. Читать следует так Pop from stack to Temp variable and Load Address to stack. Пишу по английски, так как на русском это будет малопонятно. Итак эта переменная всего лишь резервирует содержимое верхней ячейки стека во временную переменную - нам это не потребуется, так как мы не исполняем код а декомпилируем. Но для понятности расскажу. Следующие 2 байта это та самая временная переменная FF6E. Если это число из открицательного перевести в положительное, то получится 92. VB Decompiler в этом случае выведет var_92. Если бы число было положительным, то это была бы не локальная а глобальная переменная. Думаю теперь все ясно, приступим к следующей команде. F5 - LitI4. Понимаем как push следующие 4 байта, то есть push 0. Далее опять идет 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 - и это правило действует всегда. Что же мы имеем в блоке данных? А имеем мы Virtual Address на следующую заглушку:
A1A0634000 mov eax,[004063A0]
0BC0 or eax,eax
7402 je .000402A77
FFE0 jmp eax
68542A4000 push 000402A54
B870104000 mov eax,000401070
FFD0 call eax
В этой конструкции нам важен push 00402A54. Виртуальный адрес 00402A54 указывает на структуру вида:
Структура CallAPI
strLibraryName Long
strFunctionName Long
Эти два виртуальных адреса указывают на имя Dll и имя функции, которая должна быть вызвана. Вот разработчики намудрили с виртуальной машиной, правда?
Разбор переходников на API
Теперь то мы знаем, почему Visual Basic так тормозит. Чтобы вызвать любую API функцию необходимо пройти немыслимое количество структур и заглушек, что тратит золотые такты процессора и жутко снижает скорость. Как видим - никакой таблицы импорта нет. Мы имеем дело с именем Dll и функции, которые вызываются динамически функцией DllFunctionCall, которая экспортируется из msvbvm60.dll. Поэтому строки из блока, что был указан выше:
B870104000 mov eax,000401070 --?3
FFD0 call eax
получают адрес на переходник:
jmp DllFunctionCall
и вызывают его через call eax. Что находится в функции DllFunctionCall думаю и так понятно:
hLib = GetModuleHandle(strLibraryName)
hProc = GetProcAddress(hLib, strFunctionName)
Call hProc
Это в упрощенном виде, для наглядности. В нашем же случае strLibraryName это user32.dll, а strFunctionName - это ShellExecute. Теперь думаю ясно, зачем в пикоде было столько push’е и резервирования. Это всеголишь подготовка стэка для вызова ShellExecuteю Поэтому если свернуть все полученные нами данные то выйдет чтото вроде:
loc_4043F1: var_90 = "Test"
loc_4043FB: var_8C = "Test2"
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. То есть на нормальном языке это выглядит так:
var_8C = ""
var_90 = ""
Следующий и последний опкод 13 - ExitProcHresult. Как понятно из названия он завершает процедуру. Это значит, что после него идет уже известная нам структура ProcDscInfo.
Вот мы и разобрались как декомпилировать пикод в уме. Надеюсь эта статья оказалась для Вас полезной!
Комментарии |
Добавил: Admin Дата: 25.08.2006 Приложения к статье: P-Code List of all opcodes Сайт декомпилятора: VB P-Code and Native Code Decompiler |
Добавление комментария |