WIN32: динамически загружаемые функции

yolki

Можно ли как-нибудь извернутся и сделать такой EXE, чтобы некоторые функции можно было динамически загрузить?
т.е. чтобы сработал примерно такой код:

H = LoadModule("a.exe",NULL);
MyProc = GetProcAddress(H,"MyFunc");

maggi14

Я, конечно, не доктор, но какую точку входа, по-Вашему, должна будет искать функция LoadModule?

lera98

А она разве точку входа ищет? Не таблицы экспорта смотрит?
2 : если такая комбинация LoadModule-GetProcAddress вдруг никак не пройдёт, то определи смещение функции вручную, открой в проге файл exe на чтение, засунь код функции в память и вызывай её.

bastii

А что за LoadModule? LoadLibrary пробывал?

laki

А если функция в экзешнике к глобальным переменным обращается?

bastii

Скорее вопрос где и когда CRT библиотека будет инициализироваться, или чем там эта функция будет пользоваться.

maggi14

Смотрит-смотрит. Ищет функцию DLLMain

gopnik1994

Да, можно, но при условии некоторых жеских ограничений.
Типа нельзя юзать глобальных переменных и пр...
Смысл в том, что если ДЛЛ заглужается не по своему базовому адресу, то она просто вся целиком перенаправляется на новый сегмент памяти, а ЕХЕ всегда грузится по БА 0х00400000 и никуда уходить не собирается, а на этом адресе итак уже висит ЕХЕ, который ее подгрузил... Но в принципе, всякие хуки можно делать и в ЕХЕ, и перенаправлять их себе же самому, например, сообщениями.

Marinavo_0507

А вот объясни, что значит "подгрузить динамически", если функция находится в том же бинарнике,
что и вся остальная программа, то есть скомпилирована вместе с ней?

kokoc88

Сделай из этого exe COM-сервер.

maggi14

Резюмирую.
1. Имелось в виду не LoadModule, а LoadLibrary. LoadModule загружает экзешник и выдает код ошибки.
2. Загрузить .exe с помощью LoadLibrary можно. Если верить MSDN, для .ехе можно будет использовать FindResource или LoadResource.
3. Про невозможность использования GetProcAddress в MSDN прямо не скзано, однако непонятно, откуда будет браться этот адрес, если exeшники ничего в .def-файле не экспортируют.
MSDN:
Platform SDK: DLLs, Processes, and Threads
LoadLibrary
The LoadLibrary function maps the specified executable module into the address space of the calling process.
For additional load options, use the LoadLibraryEx function.
HMODULE LoadLibrary(
LPCTSTR lpFileName
);
Parameters
lpFileName
[in] Pointer to a null-terminated string that names the executable module (either a .dll or .exe file). The name specified is the file name of the module and is not related to the name stored in the library module itself, as specified by the LIBRARY keyword in the module-definition (.def) file.
If the string specifies a path but the file does not exist in the specified directory, the function fails. When specifying a path, be sure to use backslashes (\ not forward slashes (/).
If the string does not specify a path, the function uses a standard search strategy to find the file. See the Remarks for more information.
Return Values
If the function succeeds, the return value is a handle to the module.
If the function fails, the return value is NULL. To get extended error information, call GetLastError.
Windows Me/98/95: If you are using LoadLibrary to load a module that contains a resource whose numeric identifier is greater than 0x7FFF, LoadLibrary fails. If you are attempting to load a 16-bit DLL directly from 32-bit code, LoadLibrary fails. If you are attempting to load a DLL whose subsystem version is greater than 4.0, LoadLibrary fails. If your DllMain function tries to call the Unicode version of a function, LoadLibrary fails.
Remarks
To enable or disable error messages displayed by the loader during DLL loads, use the SetErrorMode function.
LoadLibrary can be used to map a DLL module and return a handle that can be used in GetProcAddress to get the address of a DLL function. LoadLibrary can also be used to map other executable modules. For example, the function can specify an .exe file to get a handle that can be used in FindResource or LoadResource. However, do not use LoadLibrary to run an .exe file, use the CreateProcess function.
If the module is a DLL not already mapped for the calling process, the system calls the DLL's DllMain function with the DLL_PROCESS_ATTACH value. If DllMain returns TRUE, LoadLibrary returns successfully. If DllMain returns FALSE, the system unloads the DLL from the process address space and LoadLibrary returns NULL.
It is not safe to call LoadLibrary from DllMain. For more information, see the Remarks section in DllMain.
Module handles are not global or inheritable. A call to LoadLibrary by one process does not produce a handle that another process can use — for example, in calling GetProcAddress. The other process must make its own call to LoadLibrary for the module before calling GetProcAddress.
If lpFileName does not include a path and there is more than one loaded module with the same base name and extension, the function returns a handle to the module that was loaded first.
If no file name extension is specified in the lpFileName parameter, the default library extension .dll is appended. However, the file name string can include a trailing point character (.) to indicate that the module name has no extension. When no path is specified, the function searches for loaded modules whose base name matches the base name of the module to be loaded. If the name matches, the load succeeds. Otherwise, the function searches for the file. The search order used depends on the setting of the HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode value.
Windows 2000/NT and Windows Me/98/95: The SafeDllSearchMode value does not exist.
If SafeDllSearchMode is 1 (the default the search order is as follows:
The directory from which the application loaded.
The system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
Windows Me/98/95: This directory does not exist.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The current directory.
The directories that are listed in the PATH environment variable.
If SafeDllSearchMode is 0, the search order is as follows:
The directory from which the application loaded.
The current directory.
The system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit system directory. There is no function that obtains the path of this directory, but it is searched.
Windows Me/98/95: This directory does not exist.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The directories that are listed in the PATH environment variable.
Windows XP: The default value of HKLM\System\CurrentControlSet\Control\Session Manager\SafeDllSearchMode is 0 (the current directory is searched before the system and Windows directories).
The first directory searched is the one directory containing the image file used to create the calling process (for more information, see the CreateProcess function). Doing this allows private dynamic-link library (DLL) files associated with a process to be found without adding the process's installed directory to the PATH environment variable.
The search path can be altered using the SetDllDirectory function. This solution is recommended instead of using SetCurrentDirectory or hard-coding the full path to the DLL.
If a path is specified and there is a redirection file for the application, the function searches for the module in the application's directory. If the module exists in the application's directory, LoadLibrary ignores the specified path and loads the module from the application's directory. If the module does not exist in the application's directory, LoadLibrary loads the module from the specified directory. For more information, see Dynamic Link Library Redirection.
If you call LoadLibrary with the name of an assembly without a path specification and the assembly is listed in the system compatible manifest, the call is automatically redirected to the side-by-side assembly.
The Visual C++ compiler supports a syntax that enables you to declare thread-local variables: _declspec(thread). If you use this syntax in a DLL, you will not be able to load the DLL explicitly using LoadLibrary or LoadLibraryEx. If your DLL will be loaded explicitly, you must use the thread local storage functions instead of _declspec(thread).
MSDN:
Platform SDK: DLLs, Processes, and Threads
GetProcA

kamputer

а ЕХЕ всегда грузится по БА 0х00400000 и никуда уходить не собирается, а на этом адресе итак уже висит ЕХЕ, который ее подгрузил...
Пиздёж.
Во-первых, изначально при сборке екзешника base address можно задать любым (в VS, например, у линкера есть соотв. опция).
Во-вторых, в PE есть такая штука, как relocation table. Та самая, которая в частности используется для того, чтобы dll-ки можно было по любому адресу загружать. Но и в exe она тоже вполне может присутствовать (секция .reloc и тогда его вообще можно (мб, для этого придётся немного поработать ручками) по любому адресу загружать.

kamputer

3. Про невозможность использования GetProcAddress в MSDN прямо не скзано, однако непонятно, откуда будет браться этот адрес, если exeшники ничего в .def-файле не экспортируют.
Экспорты в exe очень даже могут быть, и GetProcAddress умеет лукапить их адреса.

bmostr56

Кстати, у gcc, скажем, есть опция --export-dynamic (у линкера). Более того, по умолчанию в Linux, насколько я помню, она включена (Используется это, скажем, в libglade). В VC++ cl.exe есть что-либо подобное? Какая опция?

gopnik1994

типа умный?
Во-первых, изначально при сборке екзешника base address можно задать любым (в VS, например, у линкера есть соотв. опция).
Можно, конечно! А ты часто этим занимаешься? А где гарантия, что в это твой супер-пупер адрес уже не загрузилась какая-нть другая библиотека?
Во-вторых, в PE есть такая штука, как relocation table. Та самая, которая в частности используется для того, чтобы dll-ки можно было по любому адресу загружать. Но и в exe она тоже вполне может присутствовать (секция .reloc и тогда его вообще можно (мб, для этого придётся немного поработать ручками) по любому адресу загружать.
Ключевое слово тут "может", но не содержит... Может подскажешь, как сделать так, чтобы exe-шник содержал relocation table? Дописать вручную не предлагать...

gopnik1994

Не знаю, на чем это требуется напрогать, но, например, под Дельфей директива exports может присутствовать и в EXE.
LoadLibrary, GetProcAddress будут работать.
Но функция, которую ты вызываешь, не должна использовать никаких глобальных переменных, либо все статические указатели релоцировать на реальный адрес, в который был загружен ехе-шник под видом библиотеки.

kamputer

>типа умный?
Вообще-то - туповат, в Aurum в своё время не взяли
>Можно, конечно!
Рад, что смог открыть для тебя что-то новое.
>А ты часто этим занимаешься? А где гарантия, что в это твой супер-пупер адрес уже не загрузилась какая-нть другая библиотека?
Ну, вообще-то, при запуске "естественным" образом, имидж исполняемого файла мапится в адресное пространство самым первым, так что это проблемы "какой-нть другой библиотеки". Для полуестественных извращений типа субжевых - это возможность такого же плана, как и конфликт по адресам двух dll, который вполне себе решается с помощью релокации одной из них с помощью relocation table.
>Ключевое слово тут "может", но не содержит...
За всех не скажу, но вот на моей машине, например, ntoskrnl.exe содержит
>Может подскажешь, как сделать так, чтобы exe-шник содержал relocation table? Дописать вручную не предлагать...
Для вижуал-сишного линкера ключ "/fixed:no".

kamputer

>Но функция, которую ты вызываешь, не должна использовать никаких глобальных переменных, либо все статические указатели релоцировать на реальный адрес, в который был загружен ехе-шник под видом библиотеки.
Есть обоснованные подозрения (проверять лень ) что проблема как раз-таки в том, что для загруженных через LoadLibrary exe-шников (в отличие от dll) релокация автоматически не выполняется. Проделайте её ручками, и почти всё будет работать. При этом, естественно, надо понимать, что стартап-код rtl (и чёрт знает, что ещё) в нём не отработал, поэтому кое-что работать всё-таки не будет.
А да, ещё наверно, для него импорты автоматом не настраиваются. Так что чтобы вызвать в нём что-нибудь, что использует что-нибудь из каких-нибудь dll, то тоже ручками химичить надо.
Ну и т.д.

yolki

Да, модуль Program может содержать exports. Другое дело - как на это реагирует компилятор? Может, и игнорит - но факт: экзешники получаются разные.
pr.dpr:

Program Project1;

uses
SysUtils,
Classes;

{$R *.res}
procedure HelloFunction;
begin
WriteLn('Very hello world!');
end;
exports
HelloFunction name 'HelloFunction';

begin
HelloFunction;
end.
Нормально компилируется.
loader.dpr:

{$APPTYPE CONSOLE}
program loader;
uses sysutils,classes,windows;
var
HM: THandle;
p:procedure;
begin
HM:=LoadLibrary('pr.exe');
p:=GetProcAddress(HM,'HelloFunction');
p;
end.
валится с AV (memory address 0x000000 cannot be 'read')
HM = 0x960000
если загружать
project1.dpr:

library Project1;

uses
SysUtils,
Classes;

{$R *.res}
procedure HelloFunction;
begin
WriteLn('Very hello world!');
end;
exports
HelloFunction;
begin
end.
то всё нормально.

kamputer

>Может, и игнорит - но факт: экзешники получаются разные.
Бля.
dumpbin.exe /exports pr.exe
(Ну или tdump.exe или что там у вас?)

kamputer

>валится с AV (memory address 0x000000 cannot be 'read')
Потому что p == 0 ? // Ниибаца я телепат
Потому что фунция не заэкспортилась / заэкспортилась с другим именем? (см. пред. пост)

yolki

GetProcAddress возвращает nil - очевидно ж.

V:\XXX>dumpbin /exports pr.exe
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file pr.exe

File Type: EXECUTABLE IMAGE

Section contains the following exports for pr.exe

0 characteristics
0 time date stamp Thu Jan 01 03:00:00 1970
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 000122AC HelloFunction

Summary

1000 .edata
1000 .idata
1000 .rdata
2000 .reloc
2000 .rsrc
1000 .tls
1000 BSS
12000 CODE
1000 DATA
И что это может дать?

yolki


V:\XXX>dumpbin /exports Project1.dll
Microsoft (R) COFF Binary File Dumper Version 6.00.8168
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.


Dump of file Project1.dll

File Type: DLL

Section contains the following exports for Project1.dll

0 characteristics
0 time date stamp Thu Jan 01 03:00:00 1970
0.00 version
1 ordinal base
1 number of functions
1 number of names

ordinal hint RVA name

1 0 000123C0 HelloFunction

Summary

1000 .edata
1000 .idata
2000 .reloc
2000 .rsrc
1000 BSS
12000 CODE
1000 DATA

kamputer

Хз
На первый взгляд всё правильно.
GetProcAddress должна не ноль возвращать

yolki

Ае! разрулил
AV из-за обращения к WriteLn - видимо из-за того, что инициализатор RTL не проработал.

{$APPTYPE CONSOLE}
Program Project1;

uses
SysUtils,
Classes;

{$R *.res}
function FunctionX(x:double):double; cdecl;
begin
Result:=x*x+x;
end;
exports
FunctionX name 'FunctionX';
begin
writeln(FunctionX(10;
end.

работает.
встречный вопрос - можно заставить етот инициализатор RTL проработать?
например, когда я подключаю MSVC Dll в проект на Delphi?

kamputer

>встречный вопрос - можно заставить етот инициализатор RTL проработать?
Ну entry point у твоего exe (а её можно выковырять из PE-заголовка, на который указывает HINSTANCE загруженного модуля) по умолчанию настроен на rtl-ную функцию, которая всё, что надо инитит, создаёт глобальные объекты, и т.д., потом вызывает main (ну или что там у вас а после его возврата делает необходимый клинап.
Соответственно, тебе надо как-то добиться, чтобы отрабатывала только первая часть последовательности. Тут уже скорее всего грязные хаки потребуются, типа патчения на лету кода и/или игр со стеком. Хотя, мб, можно и как-то красиво разрулить - надо думать.
И ещё rtl-ный код не отработает корректно, если сначала не сделаешь релокацию адресов (если она необходима) и не настроишь импорты (rtl-код юзает внешние dll, да и сам запросто может быть в dll оформлен). Я не проверял, но вроде бы для exe LoadLibrary это не делает.

gopnik1994

> Вообще-то - туповат, в Aurum в своё время не взяли
А ты просился? Кстати, туда за ум "брали". Это были просто знакомые люди. См. нынешние приватные треды.
> Ну, вообще-то, при запуске "естественным" образом, имидж исполняемого файла мапится в адресное
> пространство самым первым, так что это проблемы "какой-нть другой библиотеки".
Именно так, так что твой ехе-шник, будучи загруженным естественным образом, подмапится по своему базовому адресу, а будучи подгруженной LoadLibrary - подмапиться в непредсказуемое заранее место.
>>Ключевое слово тут "может", но не содержит...
>За всех не скажу, но вот на моей машине, например, ntoskrnl.exe содержит
Это всего лишь исключение, подтверждающее правило.
>> как сделать так, чтобы exe-шник содержал relocation table? Дописать вручную не предлагать...
>Для вижуал-сишного линкера ключ "/fixed:no".
Клева, если так. В дельфю такую-же хочу...

gopnik1994

правильный ответ в данном случае: не использовать фич дельфи, а использовать WinAPI..
либо прикрутить таки ключик к компилятору дельфи...

gopnik1994

Кстати, а под VC такой номер с ехе-шником пройдет?
В том смысле, что будет ли ехе-шник скомпиленый с ключиком /fixed:no и загруженный через LoadLibrary полноценным по своим возможностям? Будет ли там работать тот же самый cout<< ?

kamputer

>А ты просился? Кстати, туда за ум "брали". Это были просто знакомые люди. См. нынешние приватные треды.
Блять, ну когда же в этом форуме будет смайлик :lopata: ?

kamputer

>правильный ответ в данном случае: не использовать фич дельфи, а использовать WinAPI..
Даже для чистого WinAPI потребуются усилия над ним после подгрузки (релокация, настройка импортов, возможно, ещё что-то).
>Кстати, а под VC такой номер с ехе-шником пройдет?
>В том смысле, что будет ли ехе-шник скомпиленый с ключиком /fixed:no и загруженный через LoadLibrary полноценным по своим возможностям? Будет ли там работать тот же самый cout<< ?
Вообще говоря - нет. Выше я уже писал, почему.
Оставить комментарий
Имя или ник:
Комментарий: