Введение в использование скриптовых движков: Часть 1 — Архив WASM.RU

Все статьи

Введение в использование скриптовых движков: Часть 1 — Архив WASM.RU

Предисловие

В этой статье (а возможно и цикле статей) я попытаюсь рассказать о том, как добавить поддержку скриптовых языков в программу, написанную на ассемблере. Наверное, это самый простой путь реализовать поддержку макросов в своей программе и, может быть, кому-то это и пригодится ;).

Пара слов о компиляторе

Я не буду здесь поднимать бесполезный вопрос о том какой компилятор самый лучше и т.п. - в конце концов это дело вкуса. Скажу только, что я буду использовать 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:

  • QueryInterface
  • AddRef
  • Release
  • GetLCID
  • GetItemInfo
  • GetDocVersionString
  • OnScriptTerminate
  • OnStateChange
  • OnScriptError
  • OnEnterScript
  • OnLeaveScript

А вот интерфейс 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.

Теперь остается имплементировать следующие методы:

  • GetLCID
  • GetItemInfo
  • GetDocVersionString
  • OnScriptTerminate
  • OnStateChange
  • OnScriptError
  • OnEnterScript
  • OnLeaveScript
  • GetWindow
  • EnableModeless

Как видите, их довольно много, однако спешу вас обрадовать - не все из них требуется реализовывать полностью. Например, если вы не планируете какой-либо особой реакции своего приложения на начало исполнения скрипта можно вполне обойтись такой реализацией 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

На сегодня все :).

Файл к статье.

2002-2013 (c) wasm.ru