Предисловие
В этой статье (а возможно и цикле статей) я попытаюсь рассказать о том, как добавить поддержку скриптовых языков в программу, написанную на ассемблере. Наверное, это самый простой путь реализовать поддержку макросов в своей программе и, может быть, кому-то это и пригодится ;).
Пара слов о компиляторе
Я не буду здесь поднимать бесполезный вопрос о том какой компилятор самый лучше и т.п. - в конце концов это дело вкуса. Скажу только, что я буду использовать fasm (http://flatassembler.net) и, соответственно, полагаю, что для читателя не будет сюрпризом увидеть
add eax, dword [ecx]
вместо
add eax, dword ptr [ecx]
Но вообще-то это, наверное, не проблема ;)
Colib
Говоря о использовании COM в ассемблере нельзя не сказать о работах Ernest Murphy, в том числе о библиотеке Colib, которая предоставляет нам набор удобных функций для работы с COM-объектами. Но у нее есть один минус - она ориентирована на MASM. Хотя минус это или плюс решать не мне и если ваш любимый ассемблер - MASM32 (с которым, кстати, и поставляется вышеозначенная библиотека), то вы вполне можете радоваться, т.к. часть работы уже сделана за вас. Но изучить прилагающиеся к ней примеры не помешает в любом случае. Еще советую почитать статью "Using COM in Assembly Language" by Bill Tyler.
Основы Component Object Model
В этом разделе я постараюсь кратко объяснить некоторые основы технологии COM, которые необходимы для понимания материала, изложенного в данной статье. Как обычно, те кто уже имеет нужные знания (а нужно нам не так уж и много) могут спокойно пропустить этот раздел и перейти сразу ко второй части. А для оставшихся продолжаем.
Введение
Технология COM - это мощный инструмент, позволяющий разбить монолитное приложения на самостоятельные компоненты. Такая необходимость неизбежно должна была возникнуть, учитывая темпы развития индустрии программирования.
Что же такое COM? Не более, чем набор правил, определяющих каким образом могут взаимодействовать компоненты, написанные в общем случае разными людьми и на разных языках программирования, это стандарт, призванный обеспечить возможность их совместной работы. Идея, лежащая в основе, довольно проста - код, который должен стать всеобщим достоянием оформляется в виде своеобразного мини-приложения, так называемого COM-объекта. А функции, которые должны быть доступны извне группируются в интерфейсы этого объекта. Через них и осуществляется взаимодействие с клиентами. Интерфейсов может быть сколько угодно - правило здесь только одно - спецификация COM требует, чтобы каждый объект содержал специальный интерфейс IUnknown, а также чтобы любой другой интерфейс так или иначе наследовался от него.
Интерфейс IUnknown
Этот интерфейс занимает в COM особое место и это не случайно - с его помощью можно всегда получить остальные интерфейсы, поддерживающиеся объектом, кроме этого он предоставляет нам некоторые базовые средства управления жизнью объекта. Вот 3 его метода (функции интерфейсов называются методами):
HRESULT QueryInterface(REFIID iid, void **ppvObject) - данный метод используется для получение указателя на интерфейс объекта по идентификатору. Если запрошенный интерфейс поддерживается объектом, то будет возвращено значение S_OK и указатель на интерфейс помещен в ppvObject. В противном случае возвращается E_NOINTERFACE.
ULONG AddRef(void) - инкрементирует счетчик использования объекта и должен вызываться всякий раз, когда некто собирается использовать объект (например, он должен вызывается всякий раз, когда метод QueryInterface возвращает указаетель на какой-нибудь интерфейс объекта). Возвращаемое значение - новое значение счетчика ссылок.
ULONG Release(void) - этот метод выполняет обратные действия и должен вызываться, когда клиент закончил работу с объектом.
Как было сказано выше, эти методы должны быть первыми методами любого интерфейса и именно в таком порядке (порядок следования очень важен - это следует из структуры интерфейса, принятой в COM).
Структура объектов и интерфейсов
Так как спецификация COM не предусматривает никаких особых стандартов в отношении реализации объектов, то мы не будем заострять на этом внимания, скажу лишь, что любой объект можно условно разделить на две части - первая содержит указатели на интерфесы, а вторая - различные данные (в том числе и вышеупомянутый счетчик ссылок).
А вот интерфейсы в отличии от объектов должны иметь четко оговоренную структуру.
Основой интерфейса является массив адресов функций (часто называемый просто "vtable"). В качестве примера рассмотрим простейший интерфейс, состоящий из четырех методов (три из них - методы IUnknown).
...
proc Addref, pi
enter
...
return
...
proc Method1, pi
enter
...
return
...
IsomeInterface dd IsomeInterface_vtable
IsomeInterface_vtable dd QueryInterface
dd Addref
dd Release
dd Method1
Довольно просто, не так ли? Чтобы вызвать определенный метод нужно знать только его положение в vtable, правда тут есть одна тонкость - первым параметром методу всегда передается указатель на интерфейс - даже если в описании метода значится void т.е. вызов Release будет выглядеть не так:
mov eax, [IsomeInterface]
call dword [eax + 8]
а вот так:
mov eax, [IsomeInterface]
push Isome_interface
call dword [eax + 8]
Дело в том, что описания методов даются, как правило, для языков высокого уровня, а там эту работу за нас выполняет компилятор и, например, на C аналогичный вызов действительно будет таким:
pIUnknown->Release();
Script Engines.
Предположим, что у нас в наличии есть такая программка:
(Легко заметить, что она ничего не делает кроме того, что показывает окно открытия файла по нажатию на кнопку)
format PE GUI 4.0
entry start
include 'include\win32ax.inc'
section '.scrpt' code readable executable
start: invoke GetModuleHandleA, 0
mov [hInst], eax
invoke DialogBoxParamA, eax, 1000, HWND_DESKTOP, DlgProc, 0
invoke ExitProcess, 0
proc DlgProc, hwnd, msg, wparam, lparam
enter
cmp [msg], WM_INITDIALOG
jz wminitdialog
cmp [msg], WM_COMMAND
jz wmcommand
cmp [msg], WM_CLOSE
jz wmclose
sub eax, eax
jmp finish
wminitdialog:
mov eax, [hwnd]
mov [hWnd], eax
invoke VirtualAlloc, 0, MAX_PATH, MEM_COMMIT, PAGE_READWRITE
mov [szFileName], eax
jmp processed
wmcommand: cmp [wparam], BN_CLICKED shl 16 + 1001
jnz processed
mov eax, [szFileName]
mov ecx, MAX_PATH
@@: mov byte [eax], 0
inc eax
dec ecx
jnz @B
push szFilter
push szTitle
push dword [hwnd]
call GetFileName
jmp processed
wmclose: invoke VirtualFree, [szFileName], MAX_PATH, MEM_DECOMMIT
invoke EndDialog, [hwnd], 0
processed: sub eax, eax
inc eax
finish: return
proc GetFileName, hParent, lpTitle, lpFilter
enter
mov eax, [hParent]
mov [ofn.hwndOwner], eax
mov eax, [hInst]
mov [ofn.hInstance], eax
mov eax, [lpFilter]
mov [ofn.lpstrFilter], eax
mov eax, [szFileName]
mov [ofn.lpstrFile], eax
mov [ofn.nMaxFile], MAX_PATH
mov eax, [lpTitle]
mov [ofn.lpstrTitle], eax
mov [ofn.Flags], OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_LONGNAMES
invoke GetOpenFileName, ofn
return
section '.data' data readable writeable
ofn OPENFILENAMEA
szFilter db 'VBScripts', 0, '*.vbs', 0, \
'All files (heh... you can try...)', 0, '*.*', 0, 0
szTitle db 'Just open file...', 0
szFileName dd 0
hInst dd 0
hWnd dd 0
section '.idata' import data readable writeable
library kernel32, 'kernel32.dll', \
user32, 'user32.dll', \
comdlg32, 'comdlg32.dll'
import kernel32, ExitProcess, 'ExitProcess', \
VirtualAlloc, 'VirtualAlloc', \
VirtualFree, 'VirtualFree', \
GetModuleHandleA, 'GetModuleHandleA'
import user32, DialogBoxParamA, 'DialogBoxParamA', \
EndDialog, 'EndDialog', \
MessageBoxA, 'MessageBoxA'
import comdlg32, GetOpenFileName, 'GetOpenFileNameA'
section '.rsrc' resource data readable
directory RT_DIALOG, dialogs
resource dialogs, 1000, LANG_ENGLISH+SUBLANG_DEFAULT, main_dlg
dialog main_dlg, 'RunScript', 70, 70, 70, 50, WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
dialogitem 'BUTTON', 'Execute Script!', 1001, 8, 15, 55, 15, WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
enddialog
А также имеется скрипт:
Dim a, b
a = InputBox("A:")
b = MsgBox("A^2=" & a^2, 64, "--")
(Да простят меня Боги за появление здесь этих крамольных строк ;)
И кроме этого есть еще желание заставить его выполнятся. Ну что же приступим...
На сегодняшний день всеми нами любимая корпорация Microsoft предоставляет скиптовые движки JavaScript и VBScript совершенно свободно - единственное, чего они хотят, так это упоминание о них в AboutBox'e программы, что, согласитесь, довольно нетипично.
Физически эти движки находятся в файлах jscript.dll и vbscript.dll, но оформлены в виде COM-объектов, а значит наша цель - написать приложение, которое может работать с COM, чем мы сейчас и займемся.
Самое первое, что нужно сделать - это поставить вызов функции CoInitialize где-нибудь в начале, например, при создании окна:
wminitdialog: mov eax, [hwnd]
mov [hWnd], eax
invoke CoInitialize, 0
Данная функция инициализирует библиотеку COM и должна быть вызвана перед использованием любых CoXXX-функций.
Теперь с помощью API CoCreateInstance попросим систему создать для нас объект, который предоставит интерфейсы для управления движком VBScript.
invoke CoCreateInstance, CLSID_VBScript, 0, CLSCTX_INPROC_SERVER, \
IID_IActiveScript, pIActiveScript
Первый параметр представляет собой идентификатор класса, к которому принадлежит создаваемый объект. Нужный нам объект принадлежит к классу "VB Script Language", имеющему ID 0b54f3741h-5b07h-11cfh-0a4b0h-00aa004a55e8h
Второй параметр указывает где должен быть запущен код, создающий объект. Значение CLSCTX_INPROC_SERVER говорит, чтобы это было сделано в контексте нашего процесса.
Третий представляет собой идентификатор запрашиваемого интерфейса. В нашем случае он должен быть таким: 0bb1a2ae1h-0a4f9h-11cfh-8fh20h-0805f2c0d064
Как вы уже знаете, COM-объекты доступны только через свои интерфейсы и нужный нам VBScript здесь не исключение - для управления движком нам предоставляются два интерфейса (хотя, если быть точным, то их больше, но здесь мы рассмотрим только два):
IActiveScript - содержит методы для инициализации движка и управления его состоянием. А с помощью IActiveScriptParse мы можем указать скрипт, который нужно выполнить.
В случае успешного заверешения вызова CoCreateInstance в pIActiveScript будет помещен указатель на интерфейс IActiveScript, который мы запросили, указав GUID 0bb1a2ae1h-0a4f9h-11cfh-8fh20h-0805f2c0d064.
Но нам нужен еще и интерфейс IActiveScriptParse, давайте его сейчас получим. Так как он является интерфесом того же объекта, что и IActiveScript мы можем воспользоваться методом QueryInterface последнего.
mov edx, [pIActiveScript]
mov edx, [edx]
push pIActiveScriptParse
push IID_IActiveScriptParse
push [pIActiveScript]
call dword [edx]
Этот код поместит указатель на IActiveScriptParse в переменную pIActiveScriptParse.
Теперь у нас есть все, чтобы управлять скриптовым движком. Думаете это все? Самое интересное еще впереди ;)
IActiveScriptSite и IActiveScriptSiteWindow.
Теперь у нас есть движок, исполняющий скрипт и средства управления им. В принципе, мы можем попробовать скормить ему что-нибудь уже сейчас, но осталась одна проблема - движок живет своей внутренней жизнью и мы все равно ничего не узнаем о результатах. Значит, мы должны обеспечить его средствами связи с нашим приложением.
Надеюсь, ни для кого не станет сюрпризом, что это "средство связи" является ничем иным, как еще одним объектом ;) Стало быть, настало применить на практике все теоритические знания о COM, потому что нам предстоит создать этот объект и реализовать все методы его интерфейсов. Интерфейсов будет два - IActiveScriptSite и IActiveScriptSiteWindow.
IActiveScriptSite для нас чрезвычайно важен т.к. помимо все прочего с его помощью движок информирует приложение о всех событиях, произошедших при обработке скрипта. Вот его методы в порядке следования в vtable:
А вот интерфейс IActiveScriptSiteWindow реализовывать строго говоря необязательно, но мы все же это сделаем. И вот почему - когда скрипт захочет как-нибудь взаимодействовать с GUI (например, отобразить обычный мессадж бокс) движку потребуется хэндл окна, которому этот месседж бокс принадлежит. Что он сделает? Запросит указатель на интерфейс IActiveScriptSiteWindow и с помощью метода GetWindow получит вожделенный hWnd. Если обнаружится, что объект не поддерживает IActiveScriptSiteWindow или что-нибудь в этом роде, то движок сообщит об ошибке и прекратит выполнение скрипта. Вот именно поэтому нам нужно реализовать этот интерфейс - чтобы увидеть результаты работы.
Реализация объекта.
Как я уже говорил спецификация COM не предусматривает никаких особых стандартов реализации объектов. Мы выберем самый простой путь - наш объект будет содержать:
- указатели на интерфейсы IActiveScriptSite и IActiveScriptSiteWindow,
- счетчик ссылок (любой хороший объект должен содержать счетчик ссылок или хотя бы делать вид, что содержит ;)
Перво-наперво нужно определить, где это все будет храниться. Мы воспользуемся API LocalAlloc и выделим для него немного памяти.
invoke LocalAlloc, LMEM_FIXED, 12
mov [ActiveScriptSiteObject], eax
mov [eax + 8], 1 ;Инициализируем счетчик ссылок
Теперь у нас есть объект - дело за интерфейсами. Сперва реализуем методы IUnknown:
proc IActiveScriptSite_QueryInterface, pi, iid, ppvObject
enter
invoke IsEqualGUID, [iid], IID_IUnknown
test eax, eax
jnz .s_ok
invoke IsEqualGUID, [iid], IID_IActiveScriptSite
test eax, eax
jnz .s_ok
invoke IsEqualGUID, [iid], IID_IActiveScriptSiteWindow
test eax, eax
jnz .S_OK_W
.NoInterface: mov eax, E_NOINTERFACE
return
.s_ok: mov eax, [ActiveScriptSiteObject]
mov eax, [eax]
mov edx, [ppvObject]
mov [edx], eax
mov eax, S_OK
return
.S_OK_W: mov eax, [ActiveScriptSiteObject]
mov eax, [eax + 4]
mov edx, [ppvObject]
mov [edx], eax
mov eax, S_OK
return
Здесь мы с помощью API IsEqualGUID сверяем переданный нам GUID с эталонным и если они совпадают, то возвращаем указатель на соответствующий интерфейс. Пояснение здесь требуется только одно - так как наш объект не имеет самостоятельного IUnknown, то в ответ на идентификатор 00000000-0000-0000-0c00000000000046h (GUID IUnknown) мы вернем указатель на IActiveScriptSite, что в данном случае одно и тоже.
proc IActiveScriptSite_AddRef, pi
enter
mov ecx, [ActiveScriptSiteObject]
inc dword [ecx + 8]
mov eax, [ecx + 8]
return
Тут вообще комментариев не требуются ;)
proc IActiveScriptSite_Release, pi
enter
mov ecx, [ActiveScriptSiteObject]
dec dword [ecx + 8]
mov eax, [ecx + 8]
test eax, eax
jnz .ret_
invoke LocalFree, ecx
.ret_: return
Наконец, последний метод - Release - он уменьшает значение счетчика до тех пор, пока оно не станет равным 0, затем память освобождается с помощью LocalFree.
Теперь остается имплементировать следующие методы:
Как видите, их довольно много, однако спешу вас обрадовать - не все из них требуется реализовывать полностью. Например, если вы не планируете какой-либо особой реакции своего приложения на начало исполнения скрипта можно вполне обойтись такой реализацией OnScriptEnter:
proc IActiveScriptSite_OnEnterScript, pi
enter
mov eax, S_OK
return
Или например, если вы хотите доверить движку пользоваться для вывода текста установками по умолчанию, то можно GetLCID реализовать так:
proc IActiveScriptSite_GetLCID, pi, plcid
enter
mov eax, E_NOTIMPL
return
На такой манер можно написать все методы IActiveScriptSite, что мы и сделаем.
proc IActiveScriptSite_GetItemInfo, pi, pstrName, dwReturnMask, ppunkItem, ppTypeInfo
enter
mov eax, TYPE_E_ELEMENTNOTFOUND
return
proc IActiveScriptSite_GetDocVersionString, pi, pbstrVersionString
enter
mov eax, E_NOTIMPL
return
proc IActiveScriptSite_OnScriptTerminate, pi, pvarResult, pexcepinfo
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnStateChange, pi, ssScriptState
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnEnterScript, pi
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnLeaveScript, pi
enter
mov eax, S_OK
return
Только в метод OnScriptError (угадайте в какой ситуации он вызывается;)) мы кое-что добавим:
proc IActiveScriptSite_OnScriptError, pi, pase
enter
invoke MessageBoxA, 0, mess1, mess1_title, MB_OK + MB_ICONERROR
mov eax, S_OK
return
Теперь при обнаружение ошибки в скрипте будет появляться окно с соответствующим сообщением.
OK, с IActiveScriptSite вроде разобрались - настала очередь IActiveScriptSiteWindow. Тут будет проще - нужно написать все два метода.
proc IActiveScriptSiteWindow_GetWindow, pi, phwnd
enter
mov ecx, [phwnd] ;Вернем hWnd нашего окна.
mov eax, [hWnd]
mov [ecx], eax
mov eax, S_OK
return
proc IActiveScriptSiteWindow_EnableModeless, pi, fEnable
enter
mov eax, S_OK
return
Так как оба эти интерфейса принадлежат одному объекту, то пусть используют одни и те же стандартные методы. Обе Vtable разместим в сегменте данных, они будут такими:
pIActiveScriptSite_vtable dd IActiveScriptSite_vtable
pIActiveScriptSiteWindow_vtable dd IActiveScriptSiteWindow_vtable
IActiveScriptSite_vtable dd IActiveScriptSite_QueryInterface
dd IActiveScriptSite_AddRef
dd IActiveScriptSite_Release
dd IActiveScriptSite_GetLCID
dd IActiveScriptSite_GetItemInfo
dd IActiveScriptSite_GetDocVersionString
dd IActiveScriptSite_OnScriptTerminate
dd IActiveScriptSite_OnStateChange
dd IActiveScriptSite_OnScriptError
dd IActiveScriptSite_OnEnterScript
dd IActiveScriptSite_OnLeaveScript
IActiveScriptSiteWindow_vtable dd IActiveScriptSite_QueryInterface
dd IActiveScriptSite_AddRef
dd IActiveScriptSite_Release
dd IActiveScriptSiteWindow_GetWindow
dd IActiveScriptSiteWindow_EnableModeless
Ну и напоследок поместим указатели на интерфейсы в выделенной для объекта памяти:
invoke LocalAlloc, LMEM_FIXED, 12
mov [ActiveScriptSiteObject], eax
mov [eax + 8], 1 ;Инициализируем счетчик ссылок
mov dword [eax], pIActiveScriptSite_vtable
mov dword [eax + 4], pIActiveScriptSiteWindow_vtable
Все! Наш объект готов, методы имплементированы, осталось только подготовить и запустить скрипт.
Подготовка.
Что же скрывается за словом "подготовить"? Ну во-первых, конечно же открыть файл со скриптом (если вы помните, его имя мы получаем при помощи GetOpenFileName). Открываем как обычно, используя API CreateFile:
invoke CreateFileA, [szFileName], GENERIC_READ, FILE_SHARE_READ, \
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
mov [hFile], eax
Кроме этого нам нужен его размер:
invoke GetFileSize, eax, 0
mov [FileSize], eax
Вычислим сколько памяти нужно выделить (зачем умножать на 2 объясню чуть позже):
mov ecx, eax
inc eax
inc eax
shl eax, 1
push eax
add eax, ecx
mov [buffer_size], eax
Выделяем память при помощи VirtualAlloc:
invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_READWRITE
mov [pMemory], eax
Первые FileSize байт будут заняты содержимым файла, остальные пока свободны:
add eax, dword [FileSize]
inc eax
mov [pScript], eax
push eax
lea ecx, [esp]
invoke ReadFile, [hFile], [pMemory], [FileSize], ecx, NULL
pop ecx
За ненадобностью закрываем файл.
invoke CloseHandle, [hFile]
pop eax
Вот теперь раскажу зачем была выделена дополнительная память. Дело в том, что сейчас наш скрипт в памяти представляет собой обычную ASCII-последовательность, а VBScript engine оперирует со строками в формате Wide-Character (т.е. каждый символ занимает слово, а не байт). Поэтому наш скрипт еще предстоит конвертировать, что проще всего сделать с помощью MultiByteToWideChar:
invoke MultiByteToWideChar, 0, 0, [pMemory], -1, [pScript], eax
Запуск.
В этом разделе мы рассмотрим как инициализировать движок и передать ему скрипт.
Во-первых нужно увеличить значение счетчика нашего объекта, чтобы он не уничтожился раньше времени ;)
mov edx, [pIActiveScriptSite]
mov edx, [edx]
push [pIActiveScriptSite]
call dword [edx + Addref]
Далее скажем IActiveScript, что использовать в качестве IActiveScriptSite необходимо реализованный нами интерфейс, для чего используем метод SetScriptSite, передав ему соответствующий указатель:
mov edx, [pIActiveScript]
mov edx, [edx]
push [pIActiveScriptSite]
push [pIActiveScript]
call dword [edx + 0ch]
Теперь инициализируем движок с помощью метода InitNew интерфейса IActiveScriptParse:
mov edx, [pIActiveScriptParse]
push edx
mov edx, [edx]
call dword [edx + 0ch]
Загружаем скрипт, используя метод ParseScriptText. У этого метода куча аргументов, но в данном случае мы можем заменить их нулями. Хотя последний, конечно, не желательно, но я, лентяй этакий, поленился найти описание структуры EXCEPINFO и добавить его в заголовочный файл ;)))
mov edx, [pIActiveScriptParse]
mov edx, [edx]
mov eax, [pScript]
sub ecx, ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push eax
push [pIActiveScriptParse]
call dword [edx + 14h]
После этого движоку будет загружен исходный код скрипта, но выполняться он не будет, до тех пор, пока движок не получит от нас соответствующей команды. Это объясняется тем, что движок сейчас пребывает не в том состоянии. Всего состояний 6, вот они:
SCRIPTSTATE_UNINITIALIZED
SCRIPTSTATE_INITIALIZED
SCRIPTSTATE_STARTED
SCRIPTSTATE_CONNECTED
SCRIPTSTATE_DISCONNECTED
SCRIPTSTATE_CLOSED
Собственно, названия говорят сами за себя, отмечу лишь, что для нас наиболее важно состояние SCRIPTSTATE_CONNECTED - именно оно заставит выполняться загруженный скрипт, поэтому, пользуясь методом SetScriptState, говорим движку, что пора бы уже исполнить чертов скрипт:
mov edx, [pIActiveScript]
mov edx, [edx]
push SCRIPTSTATE_CONNECTED
push [pIActiveScript]
call dword [edx + 14h]
mov edx, [pIActiveScriptSite]
mov edx, [edx]
push [pIActiveScriptSite]
call dword [edx + Addref]
invoke VirtualFree, [pMemory], [buffer_size], MEM_DECOMMIT
Ну вот мы заставили выполниться скрипт, теперь дело осталось за малым - правила хорошего тона требуют, чтобы мы по завершении работы прибрали за собой весь мусор. Давайте сделаем это в процедуре диалогового окна.
wmclose: mov edx, [pIActiveScript]
push edx
mov edx, [edx]
call dword [edx + 1Ch] ;Метод Close
mov edx, [pIActiveScriptParse]
push edx
mov edx, [edx]
call dword [edx + Release]
mov edx, [pIActiveScript]
push edx
mov edx, [edx]
push [pIActiveScript]
call dword [edx + Release]
mov edx, [pIActiveScriptSite]
push edx
mov edx, [edx]
call dword [edx + Release]
Надеюсь, коментарии излишни ;)))?
Код приложения
;---------------------------------------------------------------------
; RunScript.asm
;---------------------------------------------------------------------
format PE GUI 4.0
entry start
include 'RunScript.inc'
section '.scrpt' code readable executable
start: invoke GetModuleHandleA, 0
mov [hInst], eax
invoke DialogBoxParamA, eax, 1000, HWND_DESKTOP, DlgProc, 0
invoke ExitProcess, 0
proc DlgProc, hwnd, msg, wparam, lparam
enter
cmp [msg], WM_INITDIALOG
jz wminitdialog
cmp [msg], WM_COMMAND
jz wmcommand
cmp [msg], WM_CLOSE
jz wmclose
sub eax, eax
jmp finish
wminitdialog: mov eax, [hwnd]
mov [hWnd], eax
invoke VirtualAlloc, 0, MAX_PATH, MEM_COMMIT, PAGE_READWRITE
mov [szFileName], eax
call Create_Script_Engine
jmp processed
wmcommand: cmp [wparam], BN_CLICKED shl 16 + 1001
jnz processed
mov eax, [szFileName]
mov ecx, 257
@@: mov byte [eax], 0
inc eax
dec ecx
jnz @B
push szFilter
push szTitle
push dword [hwnd]
call GetFileName
mov ecx, [szFileName]
mov ecx, [ecx]
jecxz processed
call Get_Script
test eax, eax
jz processed
push eax
call Run_Script
jmp processed
wmclose: invoke VirtualFree, [szFileName], MAX_PATH, MEM_DECOMMIT
mov edx, [pIActiveScript]
push edx
mov edx, [edx]
call dword [edx + Close]
mov edx, [pIActiveScriptParse]
push edx
mov edx, [edx]
call dword [edx + Release]
mov edx, [pIActiveScript]
push edx
mov edx, [edx]
push [pIActiveScript]
call dword [edx + Release]
mov edx, [pIActiveScriptSite]
push edx
mov edx, [edx]
call dword [edx + Release]
invoke EndDialog, [hwnd], 0
processed: sub eax, eax
inc eax
finish: return
proc Create_Script_Engine
enter
invoke CoInitialize, 0
invoke CoCreateInstance, CLSID_VBScript, 0, \
CLSCTX_INPROC_SERVER, IID_IActiveScript, pIActiveScript
test eax, eax
js error.create_instance
invoke LocalAlloc, LMEM_FIXED, 12
test eax, eax
jz error.alloc
mov [ActiveScriptSiteObject], eax
mov dword [eax], pIActiveScriptSite_vtable
mov dword [eax + 4], pIActiveScriptSiteWindow_vtable
mov dword [eax + 8], 1
mov edx, [pIActiveScript]
mov edx, [edx]
push pIActiveScriptParse
push IID_IActiveScriptParse
push [pIActiveScript]
call dword [edx + QueryInterface]
test eax, eax
js .ok
mov edx, pIActiveScriptSite_vtable
mov [pIActiveScriptSite], edx
.ok: sub eax, eax
return
error:
.alloc: mov eax, E_OUTOFMEMORY
return
.create_instance:
sub eax, eax
dec eax
return
proc Get_Script
enter
invoke CreateFileA, [szFileName], GENERIC_READ, FILE_SHARE_READ, \
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL
test eax, eax
jz LoadScript.finish
mov [hFile], eax
invoke GetFileSize, eax, 0
mov [FileSize], eax
mov ecx, eax
inc eax
inc eax
shl eax, 1
push eax
add eax, ecx
mov [buffer_size], eax
invoke VirtualAlloc, 0, eax, MEM_COMMIT, PAGE_READWRITE
mov [pMemory], eax
add eax, dword [FileSize]
inc eax
mov [pScript], eax
push eax
lea ecx, [esp]
invoke ReadFile, [hFile], [pMemory], [FileSize], ecx, NULL
pop ecx
invoke CloseHandle, [hFile]
pop eax
invoke MultiByteToWideChar, 0, 0, [pMemory], -1, [pScript], eax
mov eax, [pScript]
.finish: return
proc Run_Script, lpScript
enter
mov edx, [pIActiveScriptSite]
mov edx, [edx]
push [pIActiveScriptSite]
call dword [edx + Addref]
mov edx, [pIActiveScript]
mov edx, [edx]
push [pIActiveScriptSite]
push [pIActiveScript]
call dword [edx + SetScriptSite]
mov edx, [pIActiveScriptParse]
mov edx, [edx]
push [pIActiveScriptParse]
call dword [edx + InitNew]
mov edx, [pIActiveScriptParse]
mov edx, [edx]
mov eax, [lpScript]
sub ecx, ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push ecx
push eax
push [pIActiveScriptParse]
call dword [edx + ParseScriptText]
mov edx, [pIActiveScript]
mov edx, [edx]
push SCRIPTSTATE_CONNECTED
push [pIActiveScript]
call dword [edx + SetScriptState]
mov edx, [pIActiveScriptSite]
mov edx, [edx]
push [pIActiveScriptSite]
call dword [edx + Addref]
invoke VirtualFree, [pMemory], [buffer_size], MEM_DECOMMIT
return
proc IActiveScriptSite_GetLCID, pi, plcid
enter
mov eax, E_NOTIMPL
return
proc IActiveScriptSite_GetItemInfo, pi, pstrName, dwReturnMask, ppunkItem, ppTypeInfo
enter
mov eax, TYPE_E_ELEMENTNOTFOUND
return
proc IActiveScriptSite_GetDocVersionString, pi, pbstrVersionString
enter
mov eax, E_NOTIMPL
return
proc IActiveScriptSite_OnScriptTerminate, pi, pvarResult, pexcepinfo
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnStateChange, pi, ssScriptState
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnScriptError, pi, pase
enter
invoke MessageBoxA, 0, mess1, mess1_title, MB_OK + MB_ICONERROR
mov eax, S_OK
return
proc IActiveScriptSite_OnEnterScript, pi
enter
mov eax, S_OK
return
proc IActiveScriptSite_OnLeaveScript, pi
enter
mov eax, S_OK
return
proc IActiveScriptSite_AddRef, pi
enter
mov ecx, [ActiveScriptSiteObject]
inc dword [ecx + 8]
mov eax, [ecx + 8]
return
proc IActiveScriptSite_Release, pi
enter
mov ecx, [ActiveScriptSiteObject]
dec dword [ecx + 8]
mov eax, [ecx + 8]
test eax, eax
jnz .ret_
invoke LocalFree, ecx
.ret_: return
proc IActiveScriptSite_QueryInterface, pi, iid, ppvObject
enter
invoke IsEqualGUID, [iid], IID_IUnknown
test eax, eax
jnz .s_ok
invoke IsEqualGUID, [iid], IID_IActiveScriptSite
test eax, eax
jnz .s_ok
invoke IsEqualGUID, [iid], IID_IActiveScriptSiteWindow
test eax, eax
jnz .S_OK_W
.NoInterface: mov eax, E_NOINTERFACE
return
.s_ok: mov eax, [ActiveScriptSiteObject]
mov eax, [eax]
mov edx, [ppvObject]
mov [edx], eax
mov eax, S_OK
return
.S_OK_W: mov eax, [ActiveScriptSiteObject]
mov eax, [eax + 4]
mov edx, [ppvObject]
mov [edx], eax
mov eax, S_OK
return
proc IActiveScriptSiteWindow_GetWindow, pi, phwnd
enter
mov ecx, [phwnd]
mov eax, [hWnd]
mov [ecx], eax
mov eax, S_OK
return
proc IActiveScriptSiteWindow_EnableModeless, pi, fEnable
enter
mov eax, S_OK
return
proc GetFileName, hParent, lpTitle, lpFilter
enter
mov eax, [hParent]
mov [ofn.hwndOwner], eax
mov eax, [hInst]
mov [ofn.hInstance], eax
mov eax, [lpFilter]
mov [ofn.lpstrFilter], eax
mov eax, [szFileName]
mov [ofn.lpstrFile], eax
mov [ofn.nMaxFile], MAX_PATH
mov eax, [lpTitle]
mov [ofn.lpstrTitle], eax
mov [ofn.Flags], OFN_EXPLORER or OFN_FILEMUSTEXIST or OFN_LONGNAMES
invoke GetOpenFileName, ofn
return
section '.data' data readable writeable
ofn OPENFILENAMEA
mess1 db "Cant't parse it!", 0
mess1_title db 'script engine', 0
szFilter db 'VBScripts', 0, '*.vbs', 0, \
'All files (heh... you can try...)', 0, '*.*', 0, 0
szTitle db 'Just open fucking file...', 0
szFileName dd 0
hInst dd 0
ActiveScriptSiteObject dd 0
hWnd dd 0
hFile dd 0
FileSize dd 0
buffer_size dd 0
pMemory dd 0
pScript dd 0
pIActiveScript dd 0
pIActiveScriptParse dd 0
pIActiveScriptSite dd 0
CLSID_VBScript GUID 0b54f3741h, 5b07h, 11cfh, 0a4h, 0b0h, 0, 0aah, 0, 4ah, 55h, 0e8h
IID_IActiveScript GUID 0bb1a2ae1h, 0a4f9h, 11cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h
IID_IActiveScriptParse GUID 0bb1a2ae2h, 0a4f9h, 11cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h
IID_IActiveScriptSite GUID 0d57d7817h, 0e9b7h, 04a82h, 85h, 74h, 01h, 0d0h, 0f9h, 3dh, 61h, 70h
IID_IActiveScriptSiteWindow GUID 0d10f6761h, 083e9h, 011cfh, 8fh, 20h, 0, 80h, 5fh, 2ch, 0d0h, 64h
IID_IUnknown GUID 0, 0, 0, 0ch, 0, 0, 0, 0, 0, 0, 46h
pIActiveScriptSite_vtable dd IActiveScriptSite_vtable
pIActiveScriptSiteWindow_vtable dd IActiveScriptSiteWindow_vtable
IActiveScriptSite_vtable dd IActiveScriptSite_QueryInterface
dd IActiveScriptSite_AddRef
dd IActiveScriptSite_Release
dd IActiveScriptSite_GetLCID
dd IActiveScriptSite_GetItemInfo
dd IActiveScriptSite_GetDocVersionString
dd IActiveScriptSite_OnScriptTerminate
dd IActiveScriptSite_OnStateChange
dd IActiveScriptSite_OnScriptError
dd IActiveScriptSite_OnEnterScript
dd IActiveScriptSite_OnLeaveScript
IActiveScriptSiteWindow_vtable dd IActiveScriptSite_QueryInterface
dd IActiveScriptSite_AddRef
dd IActiveScriptSite_Release
dd IActiveScriptSiteWindow_GetWindow
dd IActiveScriptSiteWindow_EnableModeless
QueryInterface = 0
Addref = 4
Release = 8
Close = 1ch
SetScriptSite = 0ch
InitNew = 0ch
ParseScriptText = 14h
SetScriptState = 14h
section '.idata' import data readable writeable
library kernel32, 'kernel32.dll', \
user32, 'user32.dll', \
ole32, 'ole32.dll', \
comdlg32, 'comdlg32.dll'
import kernel32, ExitProcess, 'ExitProcess', \
LocalAlloc, 'LocalAlloc', \
LocalFree, 'LocalFree', \
GetModuleHandleA, 'GetModuleHandleA', \
CreateFileA, 'CreateFileA', \
CloseHandle, 'CloseHandle', \
ReadFile, 'ReadFile', \
GetFileSize, 'GetFileSize', \
MultiByteToWideChar, 'MultiByteToWideChar', \
VirtualAlloc, 'VirtualAlloc', \
VirtualFree, 'VirtualFree'
import ole32, CoInitialize, 'CoInitialize', \
CoCreateInstance, 'CoCreateInstance', \
CoUninitialize, 'CoUninitialize', \
IsEqualGUID, 'IsEqualGUID'
import user32, DialogBoxParamA, 'DialogBoxParamA', \
EndDialog, 'EndDialog', \
MessageBoxA, 'MessageBoxA'
import comdlg32, GetOpenFileName, 'GetOpenFileNameA'
section '.rsrc' resource data readable
directory RT_DIALOG, dialogs
resource dialogs, 1000, LANG_ENGLISH+SUBLANG_DEFAULT, main_dlg
dialog main_dlg, 'RunScript', 70, 70, 70, 50, WS_CAPTION+WS_POPUP+WS_SYSMENU+DS_MODALFRAME
dialogitem 'BUTTON', 'Execute Script!', 1001, 8, 15, 55, 15, WS_VISIBLE+WS_TABSTOP+BS_PUSHBUTTON
enddialog
;---------------------------------------------------------------------
; RunScript.inc
;---------------------------------------------------------------------
include 'include\win32ax.inc'
CLSCTX_INPROC_SERVER equ 1
CLSCTX_INPROC_HANDLER equ 2
CLSCTX_LOCAL_SERVER equ 4
CLSCTX_INPROC_SERVER16 equ 8
CLSCTX_REMOTE_SERVER equ 10h
CLSCTX_INPROC_HANDLER16 equ 20h
CLSCTX_INPROC_SERVERX86 equ 40h
CLSCTX_INPROC_HANDLERX86 equ 80h
CLSCTX_ESERVER_HANDLER equ 100h
E_NOTIMPL equ 80004001h
E_NOINTERFACE equ 80004002h
TYPE_E_ELEMENTNOTFOUND equ 8002802Bh
S_OK equ 0
LMEM_FIXED equ 0h
E_OUTOFMEMORY equ 8007000Eh
SCRIPTSTATE_UNINITIALIZED equ 0h
SCRIPTSTATE_STARTED equ 1h
SCRIPTSTATE_CONNECTED equ 2h
SCRIPTSTATE_DISCONNECTED equ 3h
SCRIPTSTATE_CLOSED equ 4h
SCRIPTSTATE_INITIALIZED equ 5h
struc GUID dd1, dw1, dw2, db1, db2, db3, db4, db5, db6, db7, db8
{
.dd1 dd dd1
.dw1 dw dw1
.dw2 dw dw2
.db1 db db1
.db2 db db2
.db3 db db3
.db4 db db4
.db5 db db5
.db6 db db6
.db7 db db7
.db8 db db8
}
MAX_PATH equ 260
struc OPENFILENAMEA
{
.lStructSize dd 4ch
.hwndOwner dd 0
.hInstance dd 0
.lpstrFilter dd 0
.lpstrCustomFilter dd 0
.nMaxCustFilter dd 0
.nFilterIndex dd 0
.lpstrFile dd 0
.nMaxFile dd 0
.lpstrFileTitle dd 0
.nMaxFileTitle dd 0
.lpstrInitialDir dd 0
.lpstrTitle dd 0
.Flags dd 0
.nFileOffset dw 0
.nFileExtension dw 0
.lpstrDefExt dd 0
.lCustData dd 0
.lpfnHook dd 0
.lpTemplateName dd 0
}
struct OPENFILENAMEA
На сегодня все :).
Файл к статье.
[C] Hangatyr