[FireNativeDLL] Создание полноценных DLL на Visual Basic

Обсуждение проектов наших жителей.
Вы можете выставить проект на тест или найти помощников для его реализации.

Модератор: BV

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

[FireNativeDLL] Создание полноценных DLL на Visual Basic

Сообщение Хакер » 07.11.2007 (Ср) 15:23

Что это такое?
Как известно, VB6 обладает одним очень большим недостатком - он не позволяет создавать обыкновенные DLL (т.е. такие DLL, функции из которых мы можем импортировать например при помощи Declare Sub/Function).

Меня эта несправедливость долгое время не устраивала, и я решил исправить её.

FireNativeDLL 1.0 - это средство, которое добавит в VB возможность создавать обычные DLL (т.н. в народе NativeDLL).



Как этим пользоваться?
  1. Установка FireNative DLL
    Для начала необходимо [скачать] дистрибутив FireNativeDLL. ← ссылка не работает, но ищите дальше в топике, кто-то выложил какую-то версию FNDLL.


    Дистрибутив представляет собой WinRarSFX-архив. После запуска, указываем путь, по которому у вас установлен VB6 (обычно это: C:\Program Files\Microsoft Visual Studio\VB98\ ) и нажимаем "Install".

    Установка FireNativeDLL завершена.


  2. Создание нового проекта
    Запускаете Visual Basic 6. После установки FireNativeDLL у вас должен появиться шаблон проекта "Standard DLL". Выбираете этот шаблон и нажимаете "Открыть":
    Изображение

  3. Разработка DLL
    Далее, откроется проект, состоящий из обычного модуля и модуля класса. Вы можете работать с проектом как с обычной ActiveX-библиотекой, т.е. добавлять формы, модули, классы, ресурсы и т.д. в свой проект.

    Помните, что наличие класса CStdDLLInfo обязательно в проекте.

    Опишите функционал библиотеки, создайте весь необходимый вам код.



  4. Экспорт функций DLL
    Пришло время определить, какие из функций вашего проекта должны быть доступны извне. Список этих функций необходимо определить в классе CStdDLLInfo. Открываете код класса CStdDLLInfo, читаете сопроводительную инструкцию. Далее, для каждой экспортируемой функции создаёте запись вида:
    Код: Выделить всё
    .Export "ВнешнееИмяФункции", AddressOf ВашаФункция

    внутри With Informer ... End With блока.

    ВнешнееИмяФункции - имя, под которым функция будет доступна извне. Оно может отличаться от настоящего имени функции в вашем коде.

    Помните, что экспортировать можно только функции, определённые в модулях.

  5. Точка входа DLL
    Точка входа DLL - процедура, имеющая три параметра (ByVal hInstance As Long, ByVal lReason As Long, ByVal lReserved As Long)которую Windows вызывает в 4 случаях:
    • Загрузка DLL в процесс
      Когда DLL подгружается в процесс, Windows вызывает точку входа, передавая в качестве аргумента lReason значение константы DLL_PROCESS_ATTACH.

      При использовании:
      • Explicit-импорта (с помощью TLB) это происходит при запуске приложения.
      • Implicit-импорта (с помощью Declare Sub/Function) это происходит при самом первом вызове какой-либо функции из библиотеки.
      • При импорте с помощью LoadLibrary/GetProcAddress это происходит во время вызова LoadLibrary()

      Важно отметить, что при вызове с DLL_PROCESS_ATTACH точка входа должна возвратить 1, обозначая тем самым, что внутренняя инициализация библиотеки прошла успешно.

      Если же ваша точка входа возвращает 0, при использовании:
      • Explicit-импорта (с помощью TLB) ваше приложение не запустится. Система выдаст сообщение об ошибке.
      • Implicit-импорта (с помощью Declare Sub/Function) VB выдаст ошибку File not found: 'имя_библиотеки.dll' при самом первом вызове функции из библиотеки.
      • При использовании LoadLibrary, LoadLibrary не загрузит библиотеку и возвратит 0.

      Точку входа в этом случае логичнее использовать для инициализации каких-то внутренних данных/объектов вашей библиотеки (если вы в этом конечно нуждаетесь).
    • Выгрузка DLL из процесса
      Когда DLL выгружается из процесса, Windows вызывает точку входа, передавая в качестве аргумента lReason значение константы DLL_PROCESS_DETACH.

      Как при использовании Implicit, так и при использовании Explicit -импорта, точка входа в этом случае будет вызвана при завершении вашего приложения.

      Фактически же, точка входа в этом случае вызывается при вызове функции FreeLibrary().

      Система абсолютно безразлична к тому, что возвращает точка входа в этом случае.

      Логичнее всего использовать точку входа в этом случае для выгрузки и освобождения памяти/объектов, занятой/созданных ею в процессе работы (если конечно такая необходимость у вас есть).
    • Создание нового потока внутри процесса
      При создании потока (нити выполнения) в контексте процесса (с помощью функций CreateThreed, CreateRemoteThreed и т.д) Windows вызовет точку входа каждой библиотеки, загруженной на тот момент в процесс, передавая в качестве аргумента lReason значение константы DLL_THREAD_ATTACH.

      Вы можете использовать эту возможность для инициализации каких-либо данных, специфичных для каждого потока.
    • Выход из потока
      При выходе из (разрушении) потока, Windows вызовет точку входа каждой библиотеки, загруженной в процесс на момент разрушения потока. При вызове в качестве аргумента lReason будет передано значение константы DLL_THREAD_DETACH.

      Вызов точки входа будет произведён в контексте того потока, из которого происходит выход.

    Если вам не нужны возможности точки входа, вы можете её не определять. Для этого, уберите (удалите или закомментируйте) процедуру DllEntryPoint в модуле modLibrary, а также строчку .SetEntryPoint AddressOf DllEntryPoint в классе CStdDLLInfo.


  6. Компилирование библиотеки
    Откомпилируйте свою библиотеку обычным способом.

    Если вы все сделали правильно, у вас сразу же получится готовая к использованию библиотека.

Практика
Давайте попробуем с помощью FireNativeDLL написать пробную DLL.
  1. Создаём новый проект Standard DLL.
  2. Убираем свою точку входа (см. выше как это сделать)
  3. Помещаем в модуль следующий код:
    Код: Выделить всё
    Public Function GetUserAge() As Integer
        GetUserAge = CInt(InputBox("Сколько вам лет?", "Привет!"))
    End SubFunction

    Public Sub ShowScreenSize
        MsgBox "Размер вашего экрана: " + Cstr(Screen.Width / Screen.TwipsPerPixelX ) + "x" + cstr(Screen.Height / Screen.TwipsperPixelY ) + " пикселей", vbInformation, "Сведения об экране"
    End Sub

  4. Теперь сделаем так, чтобы функции GetUserAge и ShowScreenSize стали экспортируемыми. Для этого откроем класс CStdDLLInfo и добавим туда по строчке для каждой функции. Таким образом, код класса будет выглядеть примерно так:

    Код: Выделить всё
    Public Sub GetDllInfo(ByVal Informer As Object)
        With Informer
            ' Формат записи удивительно прост
            ' Пример:
            ' .Export "SomeFunction", AddressOf MySomeFunction
            ' - "SomeFunction" - "экспортное" имя функции
            ' - MySomeFunction - имя функции в модуле
           
            ' Внимание:
            ' Все экспортируемые функции должны быть непременно
            ' в модуле (в модулях, - не обязательно все в одном)
           
            ' В остальном никаких ограничений нет
            '===================================
            .Export "GetAge", AddressOf GetUserAge
            .Export "ShowScreenResolution", AddressOf ShowScreenSize
           
           
            '====================================
           
            ' Если вы хотите определить свою собственную точку входа,
            ' сделайте это вызовом SetEntryPoint:
            '''''''     .SetEntryPoint AddressOf DllEntryPoint
        End With
    End Sub
  5. Компилируем библиотеку. Назовём её testlib.dll
  6. Запустим Dependency Walker ( Dependency Walker - стандартная утилита, позволяющая смотреть кто что экспортирует, и кто у кого чего импортирует (иными словами - иерархию зависимости) входящая в поставку Visual Studio 6. Поэтому она у вас должна быть. Должна, но не обязана - вы могли отключить установку Tools при инсталляции VS. В этом случае рекомендую вам ею обзавестись.), и убедимся, что наши функции экспортируются:
    Изображение
  7. А теперь проверим непосредственно вызов функций из DLL.
    Допустим, нашу библиотеку мы положим в корень диска C, и путь к ней теперь таков: c:\testlib.dll
    • Код: Выделить всё
      Private Declare Function GetAge Lib "c:\testlib.dll" () As Integer
      Private Declare Sub ShowScreenResolution Lib "c:\testlib.dll" ()
      Private Sub Form_Click()
          MsgBox "Ваш возраст: " + CStr(GetAge())
          ShowScreenResolution
      End Sub

      Запустите, щелкните по форме и убедитесь, что всё работает! 8)
    • Попробуем вызвать ShowScreenResolution через rundll32:
      Пуск->Выполнить->"rundll32 c:\testlib.dll ShowScreenResolution"->[ОК]
    • С++ (создайте проект Win32 Console Application):
      Код: Выделить всё
      #include "stdafx.h"
      #include <windows>


      int main(int argc, char* argv[])
      {
         HMODULE MyLib;
         FARPROC pFunction;

         MyLib = LoadLibrary("c:\\testlib.dll");
         if(MyLib)
         {
            pFunction = GetProcAddress(MyLib, "ShowScreenResolution");
            pFunction();

            FreeLibrary(MyLib);
         }
      }

Внимание!
Следует помнить о том, что:
  1. VB ожидает на входе получить строку в юникоде. Это значит, что возможно вам придётся делать A- и W- варианты функции (т.е. MyFunctionA, MyFunctionW ), в которых одним из аргументов является строка, либо один из этих вариантов.

    A-вариант функции должен перед использованием строки сделать с ней sMyStringArgument = StrConv(sMyStringArgument, vbUnicode). (Но учтите, что нам приходит немного не так строка, что ожидает VB, -- спасибо keks-n за объективное замечание)

    Декларировать такую функцию придётся как обычно -
    Declare Function ...... (ByVal sMyStringArgument As String) ....

    W-вариант же, наоборот, ничего не должен делать со строкой.

    Однако декларировать такую функцию придётся таким образом:
    Declare Function ... (ByVal sMyStringArgument As Long) ......

    А при вызове, передавать StrPtr(sSomeString).
  2. VB не предполагает, что функции, вызываемые с помощью Declare, могут выбрасывать VB-подобные ошибки.

    Поэтому, если внутри функции в вашей библиотеке произойдёт ошибка, которая при этом вами не обрабатывается (внутри функции нет On Error ... -структур), выполнение улетит чёрти-куда.

    Это означает, что вам всегда нужно предусматривать всевозможные ошибки, при проектировнии DLL-библиотеки.



Перспективы развития
У меня в планах сделать так, чтобы информация об экспортируемых функциях помещалась ещё и в TLB. Это значит, что во-первых, нам станет доступен Explicit-импорт функций.

Т.е. нам не нужно будет знать, как декларировать функции, нам вообще не нужно будет их декларировать - мы подключаем библиотеку в Project-References и сразу же можем использовать все интересующие нас функции.

Т.е. открыв Object Browser, мы сможем найти там все экспортируемые функции библиотеки.

Explicit-импорт хорош тем, что он происходит значительно быстрее (где-то в 3-4 раза быстрее), чем импорт через Declare.

Во-вторых, это значит, что мы сможем нормально обрабатывать ошибки, возникающие внутри библиотеки.

Ну и в-третьих, это значит, что нам абсолютно не придётся возиться с проблемой конвертирования строк Unicode->ANSI->Unicode, которая преследует нас при использовании Declare.

Рулеззз 8)


_____________________________

В принципе, для того, чтобы использовать FireNativeDLL прочитанной информации будет целиком и полностью достаточно. Прочтение следующего далее текста является совершенно не обязательным, поэтому его можно пропустить.

Тем не менее, самые любопытные могут продолжить. Новичков сразу предупрежу, что вы встретите для себя множество непонятных вещей, которые, возможно, вас отпугнут.

Опять же, говорю, понимание этой части не требуется для использования FireNativeDLL, поэтому, пусть вас не пугает то, что вы что-то не поймёте (или вообще ничего не поймёте :) ).

_____________________________

Как это работает?

Итак, приступим.

Для начала вспомним проект NativeDLL от tyomitch-а и GSerg-а. Там DLL создавалась EXE-шником, который компилировался VB. При этом, компоновщику (далее: линкеру) подсовывался ключ /FIXED:NO. Иными словами, мы заставляли компоновщика создавать секцию .reloc .

Я же выбрал несколько иной путь. Пойдём от того, что VB уже умеет создавать ActiveX DLL. А ActiveX DLL являются частным случаем NativeDLL, поэтому при создании ActiveX DLL мы уже имеем секцию релоков и главное - инициализацию рантайма .

Значит, наша задача сводится только к тому, чтобы сформировать в DLL-файле таблицу экспорта. А это относительно несложно.

Возникает вопрос: как получить список функций, которые создатель DLL хочет экспортировать? Список функций мы можем засунуть много куда (например - в ресурсы), но дело всё в том, что нам нужно знать не только имена функций, но ещё и их адреса в библиотеки.

Поэтому единственный вараинт, который мне пришёл в голову - сделать класс (СStdDLLInfo), который будет опрашиваться и который возвратит нам список имён/адресов для всех экспортируемых функций.

Итак, начнём делать функцию ProcessDLLFile(Byval sFileName as string), которая будет ActiveXDLL превращать в NativeDLL.

Для начала нам нужно каким-то образом создать экземпляр класса CStdDLLInfo. CoCreateInstance скажите вы? :) Ан нет - CoCreateInstance требуется, чтобы класс был зарегистрирован в реестре, а в условиях нашей задачи это не допустимо. Вы только представьте себе - нам нужно создать простенькую DLL-ку с двумя функциями (которую вообще нет нужды регистрировать где-либо), и нам придётся её регистрировать как ActiveX-библиотеку. Поэтому, пойдём другим путём - экземпляр класса можно создать с помощью IClassFactory::CreateInstance. Для вызова IClassFactory::CreateInstance нам нужно знать CLSID класса, который мы хотим создать. Где взять этот CLSID? Для каждой конкретной откомпилированной библиотеки он будет свой, поэтому зашить его в код (в отличие от IID-ов интерфейсов IUnknown и IClassFactory) мы не можем. Но мы знаем, что CLSID-ы хранятся в TLB, которую VB прячет в ресурсы каждой производимой им ActiveX библиотеки, а значит и в ресурсах нашей библы TLB есть.

Итак, нам нужно достать из ресурсов библиотеки TLB, найти в ней нужный класс, получить его CLSID, а затем создать экземпляр.

Загрузить TLB из ресурсов нам поможет функция LoadTypeLibEx. Постфикс Ex отличает её от LoadTypeLib тем, что у неё есть аргумент regkind, позволяющий нам загрузить TLB, не регистрируя её в реестре, а это очень важно для нас.

Передаём этой функции путь к библиотеке, значение константы REGKIND_NONE (означающее, что TLB не нужно регистрировать), и указатель на переменную-приёмник, в которую функция запишет указатель на некоторый объект.

Поподробнее об некотором объекте. :) Так вот, некоторый объект, это объект, с которым можно работать через интерфейс ITypeLib и получать информацию о типах, описанных в TLB. И тут мы встречаем новую проблему. Работать с объектами в VB мы можем только в двух случаях:
1) У нас есть описание интерфейса, который поддерживает этот объект, через который мы с ним будем работать.
2) Объект поддерживает интерфейс IDispath. Тогда мы можем вызывать методы "через точечку" даже не зная, есть они у объекта или нет.

К сожалению, у нас нет TLB с описанием интерфейса ITypeLib (его можно сделать при желании или взять у Edanmo, но это не так интересно как то, что я опишу далее), и наш некоторый объект не поддерживает IDispath.

Углубимся чуть-чуть в основы COM. Интерфейс - это, по сути, всего лишь массив Long-ов, в котором содержатся адреса методов. У каждого метода есть свой ID. Каждому методу соответствует свой DWORD (т.е. Long) в VTbl (в соответствии с ID-ом метода), который содержит адрес метода в памяти. Т.е. чтобы вызвать какой-то метод, надо узнать его адрес (из VTbl), вызвать его, передав в качестве первого аргумента указатель на объект, а в качестве остальных аргументов - те аргументы, которые должен принимать метод (если метод не имеет параметров, ничего больше и не передаётся).

Значит, чтобы вызвать какой-либо метод какого-либо объекта, нужно знать:
  • Указатель на объект
  • ID метода
  • Кол-во аргументов, которое ожидает метод

Указатель на объект у нас есть, ну а порядковый номер метода CreateInstance мы узнать всегда можем.

Итак, сделаем функцию, которая будет вызывать метод объекта по его ID.

Функция должна:
  • Получить адрес VTbl
  • Прибавить к нему (ID-1)*4 -- получив адрес ячейки, в которой находится адрес метода
  • Прочитать эту ячейку - получить указатель на метод.
  • Вызвать функцию по указателю, передав требуемые аргументы.

Хех :lol: И снова проблема: в VB нет встроенного ассемблера, поэтому мы никак не можем вызвать функцию по указателю :cry: .

Тем не менее, сдаваться мы не намерены, и поэтому будем искать выход из ситуации - способ вызвать функцию по указателю. Некоторые для этих целей используют CallWindowProc, но я решил пойти другим путём.

Так вот, встроенного ассемблера у нас в VB нет, но есть "невстроенный", например FASM :) Ничего не мешает нам написать нужный код на ассемблере, а потом тупо записать его поверх VB-шного кода.

Мы напишем на ассемблере код, а затем запишем его поверх функции ASMCall. Главное в этом деле - сделать так, чтобы функция ASMCall была больше, чем код, который мы напишем на асме.

После того, как мы запишем поверх родного кода ASMCall свой код, вызов ASMCall будет приводить к тому, что будет выполняться не VB-шный код, а тот код, который мы написали на ассемблере 8)

Для более-менее универсального STDCALL-вызова нам необходимо знать три вещи: адрес, кол-во аргументов, и сами аргументы.

Пусть наша функция ASMCall будет принимать три аргумента: указатель на функцию, указатель на массив аргументов и кол-во аргументов:

Код: Выделить всё
Public Function ASMCall(ByVal lpFunction As Long, ByVal lpArguments As Long, ByVal lArgCount As Long) As Long
  ' Тут будет мусор, который растянет функцию до нужных размеров.
  ' До каких - можно будет сказать только после написания кода на асме.
End Function


Теперь, обратимся собственно к ассемблерному коду. Я родил следующее (синтаксис FASM-а):

Код: Выделить всё
   pop   edx
   pop   eax
   pop   esi
   pop   ecx
   or   ecx, ecx
   jz   Calling
CopyDword:
   push   dword[esi+ecx*4-4]
   loop   CopyDword
Calling:
  push   edx
  jmp   eax


(кратко поясню, что делает этот код: в EDX мы помещаем адрес возврата, в EAX - адрес функции, в ESI - указатель на начало массива аргументов, в ECX - кол-во аргументов; сраниваем значение ECX (т.е. кол-во аргументов) с нулём, если оно нулевое - переходим к коду, который идёт после метки Calling. Если же значение ECX ненулевое - кладём в стек число, которое лежит по адресу ESI + ECX * 4 - 4. Уменьшаем ECX, и делаем так, пока ECX не станет равен 0.
Когда ECX станет равным нулю - все аргументы будут уложены в стек в обратном порядке (как требует соглашение STDCALL). Теперь кладём в стек адрес возврата и переходим к выполнению функции.

Некоторых смущает jmp eax вместо call eax. Так вот - JMP мы делаем из соображений экономии. Возврат после jmp произойдёт сразу туда куда надо, тогда как после call выполнение возвратится к инструкции, которая была бы после call. Т.е. после call должен был бы идти ещё и retn 0.)

Отлично! Код, делающий вызов по указателю у нас есть. Теперь осталось записать его вместо оригинального кода функции ASMCall.
Для этого мы воспользуемся функцией CopyMemory.

Методом проб и ошибок, было установлено, что код
Код: Выделить всё
    Dim c As Long
    c = &H12345678
    c = VarPtr(c)
    ASMCall = VarPtr(c)

достаточно велик, для того чтобы вместить в себя наш асм-код.

Итак, этот бредовый код мы помещаем в функцию ASMCall. Перед использованием функции ASMCall нам надо записать в неё нужный код. Для этого сделаем процедуру PrepareASMFunctions.

-- Что должна сделать PrepareASMFunctions?
-- Да сколько можно говорить - записать нужный код поверх ASMCall - скажите вы.

И будете неправы :) .

Дело в том, что память в Windows (вообще, не в Windows, а в IA-32) устроена так, что всё адресное пространство делится на страницы. Страница является ключевым понятием в работе виртуальной памяти: память отводится страницами, память характеризуется страницами, и т.д.

Так вот, у каждой страницы памяти есть атрибуты - что можно делать с этой страницей, а что нельзя. На аппаратном уровне есть всего два атрибута - можно ли читать/выполнять и можно ли писать. Но Windows отдаляет нас от аппаратного уровня, и заставляет нас поверить, что на самом деле атрибута 3: R / W / X - т.е. read / write / execute.

У страниц памяти, где располагается код, стоят атрибуты r-x. Т.е. записывать туда мы ничего не можем. Поэтому нам надо изменить атрибуты защиты памяти, записать, а потом вернуть прежние атрибуты.

Смена атрибутов производится функцией VirtualProtect.

Итак, немного протрудившись, имеем следующий код:

Код: Выделить всё
    Dim lPageAccess             As Long
    Dim bASMCall(0 To 16)       As Byte
    bASMCall(0) = &H5A  ' pop   edx             ' Кладём в EDX адрес возврата
    bASMCall(1) = &H58  ' pop   eax             ' Кладём в EAX адрес процедуры
    bASMCall(2) = &H5E  ' pop   esi             ' Кладём в ESI указатель на начало массива аргументов
    bASMCall(3) = &H59  ' pop   ecx             ' Кладём в ECX кол-во аргументов
    bASMCall(4) = &H9   ' or    ecx, ecx        ' Если аргументов нет, пропускаем след. шаг
    bASMCall(5) = &HC9
    bASMCall(6) = &H74  ' jz    вызов           ' Делаем сразу же вызов.
    bASMCall(7) = &H6   '
    bASMCall(8) = &HFF  ' push  dword[esi+ecx*4-4] ' Кладём в стек нужный аргумент
    bASMCall(9) = &H74
    bASMCall(10) = &H8E
    bASMCall(11) = &HFC
    bASMCall(12) = &HE2  ' loop  на пуш         ' Далаем это, пока аргументы не кончатся (ECX не станет = 0)
    bASMCall(13) = &HFA
    bASMCall(14) = &H52 ' push  edx             ' Возвращаем "на место" адрес возврата
    bASMCall(15) = &HFF ' jmp   eax             ' И переходим на процедуру. (Здесь мог быть call/retn 0 - но это больше, и дольше )
    bASMCall(16) = &HE0

    ' Записываем новый код в функцию ASMCall:
    VirtualProtect AddressOf ASMCall, 17, PAGE_READWRITE, lPageAccess
    CopyMemory AddressOf ASMCall, VarPtr(bASMCall(0)), 17
   
    ' Делаем страницы памяти вновь выполняемыми
    VirtualProtect AddressOf ASMCall, 17, lPageAccess, lPageAccess



Думаете, всё так хорошо, как кажется? :) Нет же! :)

Этот способ не будет работать в среде разработки - только лишь в скомпилированном проекте будет работать он.

--Что же делать? Неужели, не получится?
-- Ещё как получится. Мы ведь не намерены сдаваться? :)

Нам на помощь придёт условная компиляция. Мы тот же самый код поместим в библиотеку caller.dll .

А дальше, в зависимости от того, в IDE мы сейчас или нет будем использовать разный код - либо функцию из caller.dll, либо свою, т.е:

Код: Выделить всё

#If IN_IDE Then
Public Declare Function ASMCall Lib "caller.dll" Alias "StdCall" (ByVal lpFunction As Long, ByVal lpArguments As Long, ByVal lArgCount As Long) As Long
#End If

...

#If Not IN_IDE Then
' Нам нужна эта функция только если мы не в VBIDE.
' Т.е. только если мы - скомпилированный проект.
' В противном случае используется функция из caller.dll

Public Function ASMCall(ByVal lpFunction As Long, ByVal lpArguments As Long, ByVal lArgCount As Long) As Long
    ' Не смотря на бредовость нижеследующего кода, удалять его
    ' КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО. На самом деле этот код здесь
    ' только для того, чтобы раздуть ASMCall до нужных размеров.
    ' (Нужных - достаточных для записи туда своего кода)
    Dim c As Long
    c = &H12345678
    c = VarPtr(c)
    ASMCall = VarPtr(c)
End Function
#End If




Public Sub PrepareASMFunctions()
    #If Not IN_IDE Then
    ' Дело в том, что при запуске проекта из под VBIDE, проект компилируется
    ' в псевдокод. Это значит, что трюк с перезаписью кода функции не сработает
    ' Поэтому мы пошли на такую хитрость (да какая там хитрость?), как
    ' использование разных ASMCall-ов с помощью условной компиляции.
   
    ' Этот же код я поместил в библиотеку caller.dll, и вынес его в экспорты
    ' как функцию StdCall (потому что данный код делает вызов по соглашению StdCall)
   
    ' Так вот, если вы запускаете из под VBIDE - константа IN_IDE включена
    ' и вызывается функция из библиотеки.
    ' Если же вы хотите скомпилировать проект, убираете константу IN_IDE,
    ' компилируете, получаете вызов через реврайтинг родной функции.
   
    ' Таким образом, библиотека caller.dll НЕ НУЖНА для скомпилированного
    ' проекта.
   
    Dim lPageAccess             As Long
    Dim bASMCall(0 To 16)       As Byte
    bASMCall(0) = &H5A  ' pop   edx             ' Кладём в EDX адрес возврата
    bASMCall(1) = &H58  ' pop   eax             ' Кладём в EAX адрес процедуры
    bASMCall(2) = &H5E  ' pop   esi             ' Кладём в ESI указатель на начало массива аргументов
    bASMCall(3) = &H59  ' pop   ecx             ' Кладём в ECX кол-во аргументов
    bASMCall(4) = &H9   ' or    ecx, ecx        ' Если аргументов нет, пропускаем след. шаг
    bASMCall(5) = &HC9
    bASMCall(6) = &H74  ' jz    вызов           ' Делаем сразу же вызов.
    bASMCall(7) = &H6   '
    bASMCall(8) = &HFF  ' push  dword[esi+ecx*4-4] ' Кладём в стек нужный аргумент
    bASMCall(9) = &H74
    bASMCall(10) = &H8E
    bASMCall(11) = &HFC
    bASMCall(12) = &HE2  ' loop  на пуш         ' Далаем это, пока аргументы не кончатся (ECX не станет = 0)
    bASMCall(13) = &HFA
    bASMCall(14) = &H52 ' push  edx             ' Возвращаем "на место" адрес возврата
    bASMCall(15) = &HFF ' jmp   eax             ' И переходим на процедуру. (Здесь мог быть call/retn 0 - но это больше, и дольше )
    bASMCall(16) = &HE0

    ' Записываем новый код в функцию ASMCall:
    VirtualProtect AddressOf ASMCall, 17, PAGE_READWRITE, lPageAccess
    CopyMemory AddressOf ASMCall, VarPtr(bASMCall(0)), 17
   
    ' Делаем страницы памяти вновь выполняемыми
    VirtualProtect AddressOf ASMCall, 17, lPageAccess, lPageAccess
    #End If
End Sub



Вот! :) Вызывать по указателю мы научились. Теперь надо научится вызывать методы объектов, используя VTbl.

Я уже описывал, что для этого нам нужно сделать, поэтому просто выложу код:

Код: Выделить всё
Public Function CallVirtualMethod(ByVal lpObject As Long, ByVal lMethodID As Long, ParamArray Arguments()) As Long
    ' Вызов виртуального метода в плане техническом не отличается
    ' от вызова обычной процедуры.
   
    ' В плане же логическом - отличие заключается в том, что при вызове
    ' первым аргументом передаётся указатель на объект.
   
    ' Адреса виртуальных методов объекта содержатся в VTable.
    ' Нам нужно найти VTable объекта, взять оттуда адрес, вызвать
    ' процедуру по этому адресу, передав указатель на объект.
    ' Ничего сложного :)
   
    Dim lpVTable            As Long ' Указатель на VTable
    Dim lpMethod            As Long ' Адрес кода метода
    Dim lArguments()        As Long ' Массив аргументов
    Dim lArgCount           As Long ' Кол-во аргументов
    Dim i                   As Long
    GetMem4 lpObject, VarPtr(lpVTable)   ' Получаем адрес VTable
    GetMem4 lpVTable + (lMethodID - 1) * 4, VarPtr(lpMethod)
   
   
    lArgCount = UBound(Arguments) - LBound(Arguments) + 1
    ReDim lArguments(0 To lArgCount) ' +1, потому что нам нужно передать указатель на объект помимо всего
    For i = 1 To lArgCount
        lArguments(i) = CLng(Arguments(LBound(Arguments) + i - 1))
    Next i
   
    lArguments(LBound(lArguments)) = lpObject    ' И кладём туда указатель на объект
   
    ' Теперь осталось лишь вызвать интересующий нас метод
    CallVirtualMethod = ASMCall(lpMethod, VarPtr(lArguments(0)), lArgCount + 1)
End Function


Итак. Мы дошли до самого интересного. Опрашивая некоторый объект, мы доходим до IClassFactory::CreateInstance.

Вызывая IClassFactory::CreateInstance, получаем ссылку на объект класса CStdDLLInfo.

Вызываем у него метод GetDllInfo и он заполняет нужными данными наши структуры, а также сообщает нам о желании автора определить свою точку входа.

Список экспортируемых функций и их RVA мы имеем, остаётся создать свою таблицу экспорта и сохранить изменения в файле.


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

Пытаемся вызвать функцию из библиотеки и ... о чудо! - функция вызывается :)

А теперь пытаемся вызвать такую функцию, которая требует инициализации рантайма (например такую, как ShowScreenResolution), и получаем облом.

Я некоторое время не мог понять, почему оно так. Но не буду мучить читателя, а сразу скажу - дело в том, что родная точка входа библиотеки инициализирует рантайм лишь частично. А полная инициализация рантайма происходит лишь при создании IClassFactory.

Для нас это не очень приятная новость, потому что это означает то, что нам придётся внедрять в DLL код, который будет делать и первичную и вторичную инициализацию рантайма. О как :)

Сложно? Но мы не сдаёмся, помните?

Итак, открываем ActiveX библиотеку, сделанную при помощи VB в каком-нибудь отладчике.

Я буду использовать свой любимый отладчик - OllyDbg :)

Когда мы откроем библиотеку и посмотрим на точку входа библиотеки, мы увидим следующее (на самом деле - следующее мы не увидим. А конкретно - мы не увидим многочисленных стрелочек, которые я сам пририсовал и кусочек таблицы релоков. Я сейчас поясню, что и зачем):

Изображение

Напомню - наша задача сейчас - внедрить в библиотеку код инициализации рантайма.

Наш код должен:
  1. Вызвать msvbvm60!UserDLLMain
  2. Создать экземпляр IClassFactory, если только Reason=DLL_PROCESS_ATTACH.
  3. Уничтожить его (если создали)
  4. Вызвать пользовательскую точку входа

Ясно, что этот код будет слишком большим, и записать его поверх того, что изображено на картинке ну никак не получится.

Поэтому, код мы поместим в свободное место в секции кода. Но разместить код там мало, надо ещё как-то передать на него управление.

Я не просто так поместил на картинку кусочек таблицы релокации. Дело в том, что два push-а, которые помещают в стек какие то VA (а не RVA) могут делать это лишь в том случае, если эти VA всегда будут действительными. А они будут всегда действительными только в том случае, если при загрузке по нестандартной базе их кто-то подправит. А подправить их смогут лишь релоки.

Смотрим в релоки, и правда - dword-ы по смещениям &h11001426 и &h1100142b релочятся. Это с одной стороны хорошо, с другой плохо. Плохо потому, что если мы запишем туда какой-либо код, во время релокации он непременно испортится (если конечно не получится так, что в этих местах не окажутся какие-либо выгодные нам числа).

Поэтому, единственное, что мы можем безболезненно править - это число, которое я на рисунке выделил зелёным (pop-ы мы не учитываем).

Сейчас, это инструкция перехода туда, куда указывает синяя стрелка. Мы же сделаем так, что это будет инструкция перехода туда, куда указывает красная стрелка (она пока никуда не указывает - потому что мы ещё ничего не внедряли).

Определились, в "зелёный" DWORD мы запишем rel32-операнд перехода, так, чтобы он был на наш код (далее "хукер").

Что же должен делать хукер? Я уже описывал это, поэтому перейдём сразу к коду (синтаксис FASM-а):

Код: Выделить всё
; Стандартная фишка - обращение к стеку через ebp.
; в EBP сохраняем ESP, и освобождаем себе мозги от лишних дум
        push    ebp
        mov     ebp, esp
        add     ebp, 4

; Создаём фальшивый стековый фрейм. В принципе, могли бы и
; не создавать, а отмотать esp вперёд после вызова. Было бы
; быстрее. Ладно, потом исправлю :)
        push    dword[ebp+20d]
        push    dword[ebp+16d]
        push    dword[ebp+12d]
        push    dword[ebp+08d]
        push    dword[ebp+04d]
        call    0x00351030          ; Число взято с потолка. Потом запишем сюда настоящее число

; Сраниванием Reason c DLL_PROCESS_ATTACH.
        cmp     dword[ebp+16d], 1
        jne     SkipRTInitializing ; Если нет, пропускаем вторичную инициализацию
        mov     ecx, [esp+16d]      ; Кладём в ECX hInstance = ImageBase
        lea     edx, [ecx+0x00351030] ; Помещаем в EDX VA DllGetClassObject.

        lea     eax, [ecx+0x00351030] ; Приёмник
        push    eax
        push    eax

        lea     eax, [ecx+0x00351030] ; IID
        push    eax

        lea     eax, [ecx+0x00351030] ; classid
        push    eax

        call    edx        ; Вызывем DllGetClassObject.
        pop     edx      ; Кладём в EDX указатель на ссылку на объект.
        mov     edx, [edx] ; Получаем ссылку на объект

        mov     ecx, [edx] ; Получаем указатель на VTbl
        add     ecx, 8 ; Прибавляем 8, получаем указатель на Release.
        push    edx ; Помещаем указатель на объект в стек
        call    dword[ecx] ' и вызываем Release.

SkipRTInitializing:
        pop     ebp ; Возвращаем старое значение ebp
        pop     eax ; Помещаем в стек адрес возврата
        add     esp, 8 ; "Убираем" из стека два ненужных аргумента
        push    eax ; Кладём адрес возврата на место
        db      0xe9, 0,0,0,0 ; FASM был настолько интеллектуален, что
                                     ;   не дал мне возможности сделать call rel32
                                     ;   оптимизируя его до call rel8
                                     ;  Я его перехитрил, тем не менее :)
        retn    12d              ; Родной returner (если NEP не определена)


В скомпилированном виде этот код занимает чуть меньше 100 байт. Внедряем его в конец в свободное место в секции кода, после чего вместо левых 00351030 записываем RVA нужных нам данных, которые мы поместили в секцию данных (т.н. "кусочек данных", см. исходники DLL-модификатора).

Собственно говоря, на этом наше нелёгкое дело заканчивается. Функция ProcessDLLFile готова.

Возникает другой вопрос. А когда мы должны вызывать её, и вообще, как отловить момент компиляции файла?

Отловить момент компиляции относительно легко - для этого переименую link.exe в old_link.exe, а сами встанем на место link.exe .

Теперь нас будут вызывать в процессе компиляции, передавая нам множество интересных данных, но нас из них интересует лишь ключ /OUT:"какой_то_путь".

Задача нашего link.exe - перенаправить вызов на оригинальный линкер (old_link.exe), дождаться его завершения, а потом, скормить файл функции ProcessDLLFile.

Есть тут, правда, два не очень приятных момента:
1) Функция LoadTypeLibEx отказывается работать внутри link.exe
2) Вызывать ProcessDLLFile лучше всего асинхронно, чтобы не подтормаживать среду.

По этим двум причинам я вынес ProcessDLLFile в отдельное приложение, которое запускает псведо-линкером и выполняется асинхронно.

That's all :D
Последний раз редактировалось Хакер 10.11.2007 (Сб) 6:39, всего редактировалось 6 раз(а).
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 07.11.2007 (Ср) 15:24

Ну и по традиции:

1) Заценить.
2) Высказаться.

:lol:
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 07.11.2007 (Ср) 15:35

—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

homeworld
Продвинутый пользователь
Продвинутый пользователь
 
Сообщения: 110
Зарегистрирован: 07.12.2006 (Чт) 13:17

Сообщение homeworld » 07.11.2007 (Ср) 15:58

Интересно, буду пробывать, потом отпишусь.

Viper
Артефакт VBStreets
Артефакт VBStreets
Аватара пользователя
 
Сообщения: 4389
Зарегистрирован: 12.04.2005 (Вт) 17:50
Откуда: Н.Новгород

Сообщение Viper » 07.11.2007 (Ср) 16:25

Однако! Респектище, бум заценивать!

З.Ы. ИМХО. Было б лучше запульнуть это статьей на основной сайт.
Последний раз редактировалось Viper 07.11.2007 (Ср) 16:31, всего редактировалось 1 раз.
Весь мир матрица, а мы в нем потоки байтов!

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 07.11.2007 (Ср) 16:28

Однако! Респектище, бум заценивать!

Спасибо :)

З.Ы. ИМХО. Было б лучше запульнуть это статьей на основной форум.


Ты имел ввиду основной сайт? :)

Дык, я планировал, даже гайдару написал. Просто здесь это будет, как бы так сказать, более заметно. И поиском находится будет, в случае чего.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Viper
Артефакт VBStreets
Артефакт VBStreets
Аватара пользователя
 
Сообщения: 4389
Зарегистрирован: 12.04.2005 (Вт) 17:50
Откуда: Н.Новгород

Сообщение Viper » 07.11.2007 (Ср) 16:34

нды, опечатался. Просто там в виде статьи это будет лучше выглядеть. А здесь как-то такое большое количество текста плохо воспринимается
Весь мир матрица, а мы в нем потоки байтов!

ANDLL
Великий гастроном
Великий гастроном
Аватара пользователя
 
Сообщения: 3450
Зарегистрирован: 29.06.2003 (Вс) 18:55

Сообщение ANDLL » 07.11.2007 (Ср) 17:25

За наличие исходников уважаю, но если бы там был ВЕСЬ проект, было бы куда лучше. Так уж принято в мире нормальных программистов - либо продукт коммерческий, и его исходники храняться в тайне(относительной), либо он открыт - и все могут просто взять и отредактировать его под свои нужды(мало ли какие) или найти и изучить интересующих конкретно их кусок
Гастрономия - наука о пище, о ее приготовлении, употреблении, переварении и испражнении.
Блог

Antonariy
Повелитель Internet Explorer
Повелитель Internet Explorer
Аватара пользователя
 
Сообщения: 4824
Зарегистрирован: 28.04.2005 (Чт) 14:33
Откуда: Мимо проходил

Сообщение Antonariy » 07.11.2007 (Ср) 17:42

Титанически...
И изящно:
Я же выбрал несколько иной путь. Пойдём от того, что VB уже умеет создавать ActiveX DLL. А ActiveX DLL являются частным случаем NativeDLL, поэтому при создании ActiveX DLL мы уже имеем секцию релоков и главное - инициализацию рантайма .

Значит, наша задача сводится только к тому, чтобы сформировать в DLL-файле таблицу экспорта. А это относительно несложно.
Лучший способ понять что-то самому — объяснить это другому.

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 2981
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Москва

Сообщение jangle » 07.11.2007 (Ср) 18:05

Неужели свершилось? Полноценные Native DLL на VB? Будем тестить...

Matew
Постоялец
Постоялец
Аватара пользователя
 
Сообщения: 894
Зарегистрирован: 28.06.2004 (Пн) 17:44
Откуда: Дальний Восток, г. Ха

Сообщение Matew » 08.11.2007 (Чт) 7:54

А у меня нескромное предложение, что бы этот текст, хотя бы частично, перевели на английский, для всемирного тестирования.
Алкоголь и сканеры-ваши враги! Не верите-смотрите аватару :-)

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 08.11.2007 (Чт) 7:56

Кто-нибудь хоть попробовал создать с помощью этого средства DLL? Понравилось?

Предложения/пожелания? И т.д.?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Twister
Теоретик
Теоретик
Аватара пользователя
 
Сообщения: 2251
Зарегистрирован: 28.06.2005 (Вт) 12:32
Откуда: Алматы

Сообщение Twister » 08.11.2007 (Чт) 8:47

Привет, Хакер.

Вижу не малую работу ты проделал. Главное, что достойную.
Antonariy верно подметил - труд титанический. Как только я начал читать о способе, которым ты определяешь адреса экспортных процедур, мне чуть плохо не стало. Придумать что-то эффективное, но менее геморойное наверняка трудно, но постараться, ИМХО, стоит. Вот я и начну... ;)

Если заглянуть в SDK довольно не плохого протектора VMProtect (примеры для VB, разумеется), то можно увидеть, каким способом он определяет начало и конец участка кода, который необходимо защитить (виртуализировать). Касаемо других языков - в код тупо зашивается байтовая сигнатура, но тот кто работал с VB привык к тому, что все приходится делать через одно место. Вот и здесь - сигнатуру не прошить. Но выход есть. Нужно использовать в начале экспортной функции код, типа:
Код: Выделить всё
Private Sub ExportFunction()
VarPtr "Sign_Begin_Export"
' ... Our cool code
End Sub
Глянем на полученный диззасемблерный листинг нашей функции:
Код: Выделить всё
00402000: push ebp
00402001: mov ebp, esp
00402003: sub esp, 00000008h
00402006: push 004010C6h ; MSVBVM60.DLL.__vbaExceptHandler
0040200B: mov eax, fs:[00h]
00402011: push eax
00402012: mov fs:[00000000h], esp
00402019: sub esp, 00000008h
0040201C: push ebx
0040201D: push esi
0040201E: push edi
0040201F: mov var_08, esp
00402022: mov var_04, 004010B0h
00402029: mov edx, 00401C08h ; "Sign_Begin_Export"
0040202E: lea ecx, var_14
00402031: mov var_14, 00000000h
00402038: call MSVBVM60.DLL.__vbaStrCopy
0040203E: lea eax, var_14
00402041: push eax
00402042: call [00401058h] ; VarPtr(arg_1)
00402048: lea ecx, var_14
0040204B: call MSVBVM60.DLL.__vbaFreeStr
00402051: push 00402063h ; "‹M?_^3Ad‰'#1"
00402056: jmp 402062h
00402058: lea ecx, var_14
0040205B: call MSVBVM60.DLL.__vbaFreeStr
00402061: ret
В начале мы видим стандартный пролог, далее установку SEH-обработчика. Это тоже не маловажно - от этого будем отталкиваться, а точнее - наоборот, к этому придем в самом конце ;) Хочу заметить, что использовать необходимо именно VarPtr, а не StrPtr, как мне вначале показалось. Дело в том, что если StrPtr одна в нашей функции и больше в ней нет ни чего (интересно, кому нужна такая функция?), то пролог не генерится и функция выглядит совсем по другому:
Код: Выделить всё
00401FF0: push 00401BE8h ; "Sign_Begin_Export"
00401FF5: call [00401058h] ; VarPtr(arg_1)
00401FFB: xor eax, eax
00401FFD: retn 0004h
Конечно приятнее, что код стал намного меньше, но нам это не подойдет.
Вернемся к предыдущему ("большому") ассемблерному листингу. После установки SEH, первое что бросается в глаза - это загрузка в регистр edx адреса нашей сигнатурной стороки (в файле она хранится в юникоде). Вот за это и зацепимся, ибо при вызове VarPtr со строкой, адрес строки всегда заносится в edx.

Итак, порядок наших действий:
1. Идем в секцию ".text", ищем там нашу юникодную сигнатурную строку. Вычисляем и запоминаем ее RVA.
2. Бежим по секции диззассемблером и ищем инструкцию mov edx, offset "Sign_Begin_Export"
3. Как нашли, идем назад и ищем инструкции стандартного пролога и установки SEH-кадра.
4. Адрес инструкции push ebp и будет адресом нашей экспортируемой функции.

Не очень быстро это все будет работать, конечно, но думаю терпимо.

По хорошему, тебе нужно будет связаться с одним интересным чуваком (к сожалению лично я с ним не знаком). Зовут его GPcH - он автор VB Decompiler (сайт DotFix.net). Я думаю, он сможет помочь в развитии проекта или даст несколько дельных советов, если вы, конечно, с ним договоритесь...
А я все практикую лечение травами...

Viper
Артефакт VBStreets
Артефакт VBStreets
Аватара пользователя
 
Сообщения: 4389
Зарегистрирован: 12.04.2005 (Вт) 17:50
Откуда: Н.Новгород

Сообщение Viper » 08.11.2007 (Чт) 9:17

Хакер, тупой HelloWorld работает нормально, до чего-либо более сурьезного пока ноги не дошли
Весь мир матрица, а мы в нем потоки байтов!

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 2981
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Москва

Сообщение jangle » 08.11.2007 (Чт) 10:08

У меня не работает! После компиляции проекта Dependency Walker не показывает в экспорте DLL функции GetAge и ShowScreenSize.
Проект приложил в аттач

Изображение
У вас нет доступа для просмотра вложений в этом сообщении.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 08.11.2007 (Чт) 10:34

Twister писал(а):Как только я начал читать о способе, которым ты определяешь адреса экспортных процедур, мне чуть плохо не стало.


Тебе просто ООП противно, а он там используется, причём неслабо используется. Не по этому-ли противно стало? :lol:

На самом деле в v2 я хочу сделать так, что будет достаточно будет добавить коммент ' Explort в объявлении функции, чтобы она стала экспортируемой.

По хорошему, тебе нужно будет связаться с одним интересным чуваком (к сожалению лично я с ним не знаком). Зовут его GPcH - он автор VB Decompiler (сайт DotFix.net). Я думаю, он сможет помочь в развитии проекта или даст несколько дельных советов, если вы, конечно, с ним договоритесь...

Да низачто :) Всё сделаю сам и только лишь сам.

Хакер, тупой HelloWorld работает нормально, до чего-либо более сурьезного пока ноги не дошли

В смысле - времени нету, или что-то более сурьозное не получается?



jangle, молодец - ты нашёл первый баг!


Дело в том, что изначально в коде:
Код: Выделить всё

        ' Наличие экспорта этой функций в библиотеке (как выяснилось) обязательно
        Informer.Export "DllCanUnloadNow", GetProcAddress(hLib, "DllCanUnloadNow")
        Informer.Export "DllGetClassObject", GetProcAddress(hLib, "DllGetClassObject")
        If lCoClasses > 1 Then
            ' Экспортируем только если есть ещё CoClass-ы.
            Informer.Export "DllRegisterServer", GetProcAddress(hLib, "DllRegisterServer")
            Informer.Export "DllUnregisterServer", GetProcAddress(hLib, "DllUnregisterServer")
        End If

Не было условия If lCoClasses > 1 Then, и поэтому всегда изначальнл было 4 экспортирумых функции.

Далее там идёт условие:
Код: Выделить всё
If ExportCount = 4 Then End       ' Если экспортов нет - выходим


C учётом новой фичи (изберательного экспорта Dll(Un)RegisterServer) код нужно проапгрейдить до:
Код: Выделить всё
If ExportCount = 2 - 2 * (lCoClasses > 1) Then End       ' Если экспортов нет - выходим


Пофиксенный дистрибутив залит по старому адресу.

Ставится поверх уже установленного.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

Viper
Артефакт VBStreets
Артефакт VBStreets
Аватара пользователя
 
Сообщения: 4389
Зарегистрирован: 12.04.2005 (Вт) 17:50
Откуда: Н.Новгород

Сообщение Viper » 08.11.2007 (Чт) 10:46

Хакер, в смысле времени нет.
Весь мир матрица, а мы в нем потоки байтов!

Twister
Теоретик
Теоретик
Аватара пользователя
 
Сообщения: 2251
Зарегистрирован: 28.06.2005 (Вт) 12:32
Откуда: Алматы

Сообщение Twister » 08.11.2007 (Чт) 11:02

Тебе просто ООП противно, а он там используется, причём неслабо используется. Не по этому-ли противно стало?
Отчасти ты прав. ООП фтопку. :)
На самом деле в v2 я хочу сделать так, что будет достаточно будет добавить коммент ' Explort в объявлении функции, чтобы она стала экспортируемой.
Способ, предложенный мной - первый шаг к этому. Кстати, ты не сказал, что думаешь по поводу этого способа.
Всё сделаю сам и только лишь сам
Я предложил просто задать ему пару вопросов, которые могут впоследствии возникнуть. Этот человек имеет немалый опыт за плечами...

Вообще, я заметил, что уровень твоих познаний в области системы заметно возрос в последнее время. Не пора ли к нам, поглубже, да по ниже?... :wink:
А я все практикую лечение травами...

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 08.11.2007 (Чт) 11:57

Кстати, ты не сказал, что думаешь по поводу этого способа.

Нет, мне он не нравится. Из-за него в функции появляется лишний мусорный-маркерный код.

Я предложил просто задать ему пару вопросов, которые могут впоследствии возникнуть. Этот человек имеет немалый опыт за плечами...

У меня нет никаких вопросов. А если появляются, я стараюсь решать их сам, не прибегая к чьей-либо помощьи. Лишь в крайнем случае, когда выхода нет (или он далеко), а время поджимает - я "снисхожу" ( :) ) до того, чтобы спросить.

Именно по этой причине, нельзя увидеть ни одного топика с вопросом по VB на этом форуме (или на любом другом) - особенно в последнее время.

К тому же, у меня в планах было написание аналога VBDecompiller-а, более совершенного, чем GPcH-шный продукт.

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

В области внутреннего устройства и особенностей архитектуры IA-32 - возможно. А в той области, которой касается статья - он давно высокий.

Знаешь ведь, чем я занимаюсь? :wink: По сравнение с этим - штучки вроде FireNativeDLL - цветочки.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 2981
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Москва

Сообщение jangle » 08.11.2007 (Чт) 12:28

Попробовал обновленный дистрибутив, баг пофиксен, функции появились в экспорте DLL и нормально вызываются из PowerBASIC.

Нашел второй баг - ошибка, при вызове DLL функцией, экспортируемой функции экзешника. Вобщем такая ситуация:

Пусть есть некий EXE файл, который импортирует из StandardDLL1.dll функцию "test", но при этом, этот экзешник имеет свою секцию экспорта (редко, но и такое бывает), и позволяет StandardDLL1.dll вызывать из себя некие функции. Получаются перекрестные кэллбаки между EXE и DLL.


export.exe

Код: Выделить всё
#Compile Exe "export.exe"
#Dim All

Declare Function test  Lib "StandardDLL1.dll" Alias "test" () As Integer

Function Hello Alias "Hello"() Export As Long
    MsgBox "Hello"
End Function

Function PBMain () As Long
    test
End Function



StandardDLL1.dll

Код: Выделить всё
Private Declare Function Hello Lib "export.exe" () As Long

Public Function test() As Integer
Hello
End Function


Вобщем все срабатывает нормально, до момента возврата из экспортируемой EXE файлом функции Hello, при выходе программа слетает с исключением - Memory Access Violation.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 08.11.2007 (Чт) 12:31

jangle
Очень сомневаюсь, что это мой баг.

PB-шный EXE-шник в студию.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 2981
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Москва

Сообщение jangle » 08.11.2007 (Чт) 12:52

В аттаче два примера:

1. export_in_VB_DLL.exe - вызывает StandardDLL1.dll, а та в свою очередь, функцию Hello из export_in_VB_DLL.exe При выходе из функции Hello - Memory Access Violation

2. export_in_PowerBASIC_DLL.exe - вызывает PowerBasic_DLL.dll, а та в свою очередь, функцию Hello из export_in_PowerBASIC_DLL.exe При выходе из функции Hello - ошибок нет.

Выходит это все таки твой баг...
У вас нет доступа для просмотра вложений в этом сообщении.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 08.11.2007 (Чт) 15:00

jangle
Поставь какой-нибудь On Error в VB-шную процедуру. ... Resume Next или ... Goto EmptyErrorHandler и метку EmptyErrorHandler в конце.
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

SCINER
Новичок
Новичок
Аватара пользователя
 
Сообщения: 25
Зарегистрирован: 27.01.2005 (Чт) 20:46
Откуда: Казань

Сообщение SCINER » 09.11.2007 (Пт) 0:06

Вот такой код вылетает с ошибкой. Импортированная функция не срабатывает.

Код: Выделить всё
#Compile Exe
#Dim All
#Include "WIN32API.INC"

Declare Function Test Lib "testdll.dll" Alias "Test" (ByVal dwYear As Long) As Long
Declare Function Test(ByVal Long) As Long

Function PBMain () As Long
  Dim nTid As Long
  Thread Create ThreadEngine(0) To nTid
  Sleep 1000
End Function

Function ThreadEngine(ByVal wParam As Dword) As Long
   Call Test(2009)
End Function


Код импортируемой функции:
Код: Выделить всё
Function Test(ByVal dwYear As Long) As Long
  MsgBox "Test" & vbCrLf & CStr(dwYear), vbInformation, "UnitTest"
End Function

jangle
Википедик
Википедик
Аватара пользователя
 
Сообщения: 2981
Зарегистрирован: 03.06.2005 (Пт) 12:02
Откуда: Москва

Сообщение jangle » 09.11.2007 (Пт) 0:17

Хакер писал(а):jangle
Поставь какой-нибудь On Error в VB-шную процедуру. ... Resume Next или ... Goto EmptyErrorHandler и метку EmptyErrorHandler в конце.


Помогло, ошибка больше не выскакивает

SCINER
Новичок
Новичок
Аватара пользователя
 
Сообщения: 25
Зарегистрирован: 27.01.2005 (Чт) 20:46
Откуда: Казань

Сообщение SCINER » 09.11.2007 (Пт) 1:13

Так, работает:

Код: Выделить всё
#Compile Exe
#Dim All
#Include "WIN32API.INC"

Function PBMain () As Long
  Threaded hThread???
  Thread Create ThreadEngine(0) To hThread???
  Sleep 3000
End Function

Function ThreadEngine(ByVal wParam As Long) As Long

    Dim hDLL As Long
    Dim pFunction As Long
    Dim dwYear As Long
    Local fRV As Long 'Function return value

    hDLL = LoadLibrary("testdll.dll")

    If hDLL Then
        pFunction = GetProcAddress(hDLL, "Test")
        dwYear = 2015
        ! push dwYear        ; place 1st parameter on stack
        ! call pFunction     ; call the function
        ! mov fRV, eax       ; place return value from eax into variable
        FreeLibrary hDLL
    End If

End Function

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 09.11.2007 (Пт) 6:43

SCINER
EXE-шник в студию.

(Сдаётся мне, что про On Error опять забыли)

З.Ы.: А ты не тот SCINER, что вместе в BV модерит раздел на forum.sources.ru ?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

SCINER
Новичок
Новичок
Аватара пользователя
 
Сообщения: 25
Зарегистрирован: 27.01.2005 (Чт) 20:46
Откуда: Казань

Сообщение SCINER » 09.11.2007 (Пт) 9:39

А ты не тот SCINER, что вместе в BV модерит раздел на forum.sources.ru ?
Он самый.

On Error пробовал ставить не помогает.
А еще не компилируется проект где 5 модулей и 11 классов.
Вернее компиллиуется но с ошибкой в конце — «Device I/О error» или иногда «Error loading DLL». Файл конечно получается, и экспортируемую функцию через валкер видно. Но LoadLibrary с этой библиотекой возвращает 0.

Хакер
Телепат
Телепат
Аватара пользователя
 
Сообщения: 16095
Зарегистрирован: 13.11.2005 (Вс) 2:43
Откуда: Казахстан, Петропавловск

Сообщение Хакер » 09.11.2007 (Пт) 9:45

Он самый.

Нуу... велкам :)

Device I/O error происходить может только в одном месте - здесь:
Код: Выделить всё
    Open sFileName For Binary As #1
        Put #1, 1, lib.FileImage()
        Put #1, lib.DosHeader.lfanew + 248 + 1, lib.Objects(0)
        Put #1, lib.DosHeader.lfanew + 248 + 1 + 1 * Len(LS), lib.Objects(1)
        Put #1, lib.DosHeader.lfanew + 248 + 1 + (lib.PEHeader.Objects - 1) * Len(LS), LS
        Put #1, 1, lib.DosHeader
    Close #1


Мне кажется странным, что происходит именно Device I/O error, а не скажем, Access denied или Path/file access error.

Ну а Error loading DLL выдаётся средой в случае, если произошла какая-то ошибка в линк-хукере, т.е. в этом коде:

Код: Выделить всё

    Dim hLinker         As Long     ' Хэндл процесса линкера
    Dim lKeyBegin       As Long     ' Начало ключа /OUT:
    Dim sComStr         As String   ' Командная строка
    Dim sFileName       As String   ' Имя файла

    sComStr = Command$

    hLinker = OpenProcess(SYNCHRONIZE, 0, Shell(App.Path + "\old_link.exe " + sComStr))
    If hLinker = 0 Then
        MsgBox "Не удалось получить дескриптор процесса компоновщика", vbCritical, "Ошибка"
    End If

    WaitForSingleObject hLinker, -1 ' Ждём, пока линкер сделает своё дело

    ' Закрываем более ненужный нам хэндл
    CloseHandle hLinker

    lKeyBegin = InStr(1, sComStr, "/OUT:""") + 6
    sFileName = Mid$(sComStr, lKeyBegin, InStr(lKeyBegin, sComStr, Chr(34)) - lKeyBegin)
    Shell App.Path + "\fndll_modifer.exe " + sFileName



А можно всё-таки PB-шный EXE-шник и неудачно отмодифицированную библиотеку (если она не сверхсекретная, конечо же)?
—We separate their smiling faces from the rest of their body, Captain.
—That's right! We decapitate them.

SCINER
Новичок
Новичок
Аватара пользователя
 
Сообщения: 25
Зарегистрирован: 27.01.2005 (Чт) 20:46
Откуда: Казань

Сообщение SCINER » 09.11.2007 (Пт) 9:50

Код: Выделить всё
Open sFileName For Binary As #1
Может всетаки через FreeFile.

Код: Выделить всё
Shell App.Path + "\fndll_modifer.exe " + sFileName
А здесь думаю не помешает
Код: Выделить всё
ReturnCorrectPath(App.Path)

След.

Вернуться в Наши проекты

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 1

    TopList