Дата публикации статьи: 08.11.2005 10:37

ANDLL
Вживление VB-кода в другие процессы

Обсудить в форуме

Вступление

Иногда может понадобиться внедрить свой код, написанный на VB в память другого объекта. Например, это может пригодиться в том случае, если вам требуется произвести определенный контроль над некоторым внешним приложением (и вовсе не обязательно, что бы это был вирус…). В этой статье приводиться пример, как внедрить код, полностью написанный на VB в память другого процесса.

Что будем вживлять

Сам код, который мы будем «вживлять» в АП другого процесса должен быть написан в виде ActiveX Dll. Создадим класс, с примерно таким содержимым:

Option Explicit

Public Sub Main()
    Form1.Show 1
End Sub

Private Sub Class_Initialize()
    MsgBox "Инициализация..."
End Sub

Private Sub Class_Terminate()
    MsgBox "Удаление..."
End Sub

' (полный код см. в примере).

Далее, скомпилируем этот проект и ОБЯЗАТЕЛЬНО установим в Project->Settings->Component галочку Binary compatibility. Это делается для того, что бы в дальнейшем UUID класса и его интерфейса не менялся при каждой перекомпиляции проекта. Важно отметить, что сам класс может иметь сколько угодно Private и Friend функций, но я рекомендую воздержаться от объявления public переменных и методов внутри класса, так как это может привести к изменению порядка следования функций в vtable… Теперь нам надо вытащить ранее обозначенные UUID’ы классов и интерфейсов из скомпилированного Dll-файла.
Для этого воспользуемся славной утилитой OLE View, входящей в MS Visual Studio 6.0. Запустим ее(Start->All programs->Microsoft Visual Studio 6.0->Microsoft Visual Studio 6.0 Tools->OLE View), нажмем кнопочку File->View typelib и вы берем наш Dll файл.
Пред вами предстанет декомпилированный вариант TLB-файла, вшитого в нашу Dll. Выглядеть он будет примерно так:

// Generated .IDL file (by the OLE/COM Object Viewer)
// 
// typelib filename: Project1.dll

[
  uuid(CD0E8058-C3E5-4A7B-9B81-089B8ADB0086),
  version(2.0)
]
library ProjectXent
{
    // TLib :     // TLib : OLE Automation : {00020430-0000-0000-C000-000000000046}
    importlib("STDOLE2.TLB");

    // Forward declare all types defined in this typelib
    interface _Class1;

    [
      odl,
      uuid(B6306DEB-9CD8-4F3B-9DCC-55E64B75EB8B),
      version(1.0),
      hidden,
      dual,
      nonextensible,
      oleautomation
    ]
    interface _Class1 : IDispatch {
        [id(0x60030000)]
        HRESULT Main();
    };

    [
      uuid(BC220BF2-AAF8-41B2-8A55-6DE71AF838D0),
      version(1.0)
    ]
    coclass Class1 {
        [default] interface _Class1;
    };
};

Нам вовсе не обязательно разбираться, что эти каракули обозначают, достаточно выделить из них два значения, выделенных цветными маркерами. Запишите эти значения IID и CLSID на бумажку – они вам еще пригодятся… Учтите, что эти значения отличаются для каждой конкретной Dll. Однако, если стоит обозначенная выше галочка Binary compatibility, то это значение не будет меняться при повторной перекомпиляции Dll.

Как будем вживлять

Для вживления напишем отдельную EXE-программу. Эта программа будет просто запускаться, «вживлять» Dll в другой процесс и сразу же завершаться. Непосредственным вживлением будет заниматься маленький «переходничок», написанный на ассемблере. Приведу здесь его код.

BITS 32

;Здесь будет лежать адрес страницы, в которую загружен этот файл
mov ebp,0
jmp Run

;Здесь будут лежать адреса системных API-функций, которые мы будем использовать
ObjectCLSID dd 0, 0, 0, 0
InterfaceIID dd 0, 0, 0, 0
LoadLibrary dd 00
ExitThread dd 0
CoCreateInstance dd 0
CoInitialize dd 0
CoUninitialize dd 0
OLE32DLL db "ole32.dll",0

;Отсюда начинается наша программа
Run:

mov edi,esp  ;Сохранаяем значение esp в edi
push dword 0 ;Выделяем место в стеке под наш объект

mov eax,ebp               ;
add eax,OLE32DLL          ;
push eax                  ;==>LoadLibrary("ole32.dll")
call [ebp+LoadLibrary]    ;

push dword 0              ;
call [ebp+CoInitialize]   ;==>CoInitialize(0)


push edi                       ;
mov eax,ebp                    ;
add eax,InterfaceIID           ;
push eax                       ;
push dword 1                   ;==>CoCreateInstance(ObjectCLSID,0,1,InterfaceIID,ObjectPtr)
push dword 0                   ;
mov eax,ebp                    ;
add eax,ObjectCLSID            ;
push eax                       ;
call [ebp+CoCreateInstance]    ;

mov ecx,eax    ;
jecxz Continue ;==>Выходим, если не удалось создать объект
jmp Exit       ;

Continue:
mov ebx,[edi]  ;Сохраняем в ebx адрес нашего объекта

mov esi,[ebx] ;Теперь в esi указатель на vtable

push ebx      ;
call [esi+28] ;==>ebx->Main()

push ebx      ;
call [esi+8]  ;==>ebx->Release()

call [ebp+CoUninitialize] ;==>CoUninitialize()

Exit:
push dword 0          ;
call [ebp+ExitThread] ;==>ExitThread(0)

Код хорошо комментирован и в нем разберется любой, кто хоть немного знает ассемблер. Поэтому внесу лишь маленькие комментарии. Для начала мы загружаем(используя LoadLibrary) библиотеку OLE32.dll. Затем инициализируем COM для нашего потока(CoInitialize) и создаем экземпляр нашего COM-объекта (CoCreateInstance). Проверяем, создался ли объект и если нет, то выходим. Иначе вызываем процедуру Main нашего класса и, когда она возвращает управление, выгружаем класс (Release). Затем выгружаем поддержку COM (CoUninitialize) и выхдим из потока (ExitThread).

EXE-программа, встраивающая код

Создадим проект типа Standard EXE и заменим форму на модуль. В модуле напишем следующий код:

Option Explicit

Sub Main()
    Dim mObjectCLSID As GUID128
    Dim mInterfaceIID As GUID128
    
    'Определяем UUID'ы класса и интерфейса
    UuidFromString "BC220BF2-AAF8-41B2-8A55-6DE71AF838D0", mObjectCLSID
    UuidFromString "B6306DEB-9CD8-4F3B-9DCC-55E64B75EB8B", mInterfaceIID
        
    'Загружаем код и ресурса
    Dim nCode As String
    nCode = LoadResData(101, "ASM")
    
    'Определяем Id нашего процесса
    Dim nExplorerProcessId As Long
    Dim hTrayWnd As Long
    hTrayWnd = FindWindow("Shell_TrayWnd", vbNullString)
    Call GetWindowThreadProcessId(hTrayWnd, nExplorerProcessId)
    If nExplorerProcessId = 0 Then Stop
    
    'Приаттачиваем Dll к этому процессу...
    AttachDLLToProcess nExplorerProcessId, (nCode), mObjectCLSID, mInterfaceIID
End Sub


Public Sub AttachDLLToProcess(ByVal nRemoteProcessId As Long, ByRef Code() As Byte, _
    ByRef nObjUUID As GUID128, ByRef nIntUUID As GUID128)

    'Сохраняем в переменную начало нашего кода
    Dim nFirstByteAddr As Long
    nFirstByteAddr = VarPtr(Code(0))
    
    'Загружаем kernel32.dll и OLE32.dll(точнее, они уже загружены, 
    'так что просто получаем их hInstance)
    Dim hKernelLib As Long, pProc As Long
    hKernelLib = LoadLibrary("kernel32.dll")
    Dim hOle32Lib As Long
    hOle32Lib = LoadLibrary("ole32.dll")
    
    'Тут слегка модифицируем код. Записываем в него адреса нужных ему системных API-функций
    Call CopyMemory(nFirstByteAddr + &HA, VarPtr(nObjUUID), 16)
    Call CopyMemory(nFirstByteAddr + &H1A, VarPtr(nIntUUID), 16)
    pProc = GetProcAddress(hKernelLib, "LoadLibraryA")
    Call CopyMemory(nFirstByteAddr + &H2A, VarPtr(pProc), 4)
    pProc = GetProcAddress(hKernelLib, "ExitThread")
    Call CopyMemory(nFirstByteAddr + &H2E, VarPtr(pProc), 4)
    pProc = GetProcAddress(hOle32Lib, "CoCreateInstance")
    Call CopyMemory(nFirstByteAddr + &H32, VarPtr(pProc), 4)
    pProc = GetProcAddress(hOle32Lib, "CoInitialize")
    Call CopyMemory(nFirstByteAddr + &H36, VarPtr(pProc), 4)
    pProc = GetProcAddress(hOle32Lib, "CoUninitialize")
    Call CopyMemory(nFirstByteAddr + &H3A, VarPtr(pProc), 4)
    
    'Открываем "внешний" процесс
    Dim hRemoteProcess As Long
    hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, nRemoteProcessId)
    
    'Резервируем в нем страницу для нашего кода
    'Здесь есть один недостаток: эта страница так и останется в оперативной помяти, 
    'т.к. нет кода, который бы ее выгружал.
    'Итого здесь мы потеряем 4Кб оперативной памяти
    Dim hMem As Long
    hMem = VirtualAllocEx(hRemoteProcess, 0, UBound(Code) + 1, MEM_COMMIT, PAGE_READWRITE)
    
    'Подредактируем нашу код в соответствии с адресом страницы, который мы узнали
    Call CopyMemory(nFirstByteAddr + &H1, VarPtr(hMem), 4)

    'теперь копируем наш код в эту страницу и выставляем ей права чтения 
    'и выполнения(PAGE_EXECUTE_READ)
    Dim tmpLng As Long
    WriteProcessMemory hRemoteProcess, hMem, nFirstByteAddr, UBound(Code) + 1, tmpLng
    VirtualProtectEx hRemoteProcess, hMem, UBound(Code) + 1, PAGE_EXECUTE_READ, tmpLng
    
    'Запускаем наш код в отдельном потоке
    CreateRemoteThread hRemoteProcess, 0, 0, hMem, 0, 0, tmpLng
    
    'Закрываем "внешний" процесс(в смысле его описатель...)
    CloseHandle hRemoteProcess
    
    'Закрываем описатель kernel32.dll и ole32.dll
    FreeLibrary hKernelLib
    FreeLibrary hOle32Lib
End Sub

Не забудьте заменить IID и CLSID на те, что вы извлекли из Dll файла (в самом начале). Кроме того, наш ассемблерный переходник надо скомпилировать и засунуть в файл ресурсов, скажем под именем ASM и номером 101.
Собственно комментировать сам код я не буду, отправляю вас к моей статье об удалении исполняемого файла после завершения программы. Кстати, рекомендую вам сначала почитать ее, она проще и механизм «вживления» там точно такой же.

API-функции

Приведем описание некоторых API-функций.
За описанием функций VirtualAlloc, WriteProcessMemory и т.п. отсылаю вас в указанную выше статью.
CoInitialize, CoUninitialize – Инициализирует и, соответственно, деинициалищирует механизм COM для данного потока.
LoadLibrary – загружает указанную Dll в АП текущего процесса
CoCreateInstance – создает экземпляр указанного COM-класса и возвращает указатель на указанный интерфейс(а не на IClassFactory, как CoCreateObject).
UuidFromString – Преобразует строку в UUID

Благодарности и прочее

Во-первых, традиционно, благодарность авторам компилятора nasmw, который использовался для компиляции «переходничка».
Во-вторых благодарность tyomitch’у, благодаря которому я нашем переходничке параметры передаются с права на лево.
И в-третьих рекомендую зайти на форум http://bbs.vbstreets.ru. Там много чего интересного…


Автор: ANDLL
E-Mail: easytools@mail.ru