Окончательного дзена я достиг покурив текст с какой-то из страниц из кэша гугля (заранее извиняюсь за возможные кривости с форматированием и тэгами кода):
Речь в этой статье пойдет о том, как можно загрузить исполняемый файл в адресное пространство процесса и вызвавать экспортированную функцию, не создавая новый процесс. "Зачем нужен такой изврат" возможно возникнет вопрос у досуживо читателя. Ответ на него крайне прост.
Думаю, не открою Америки, если скажу, что код профессионального программиста содержит кроме основного функционала тесты, контролирующие правильность его работы (их называют unit-test-ами). В общем случае чем больше таких тестов и чем большее число ситуаций они покрывают, тем меньше ручная работа по тестированию, и, соответственно, тем спокойнее спит программист.
В свою практику я начал вводить эти тесты следующим образом. Прекрасым программистом MadMax было быстро написано приложение unit_tester, которое загружает записанные в список DLL. У каждой из этих DLL вызывается экспортируемая функция, которые и отвечает за запуск unit-тестов в этой библиотеке.
Эта схема проста и прекрасно работает до тех пор, пока код тестов содержится в библиотеках. При попытке загрузить и вызвать функцию из .exe программа благополучно падает. Наша сегодняшняя статья как раз и рассматривает причины этих падений и методы обхода проблемы.
Итак начнем. Нам нужно научиться выполнять такой код в случае, если psz_dll содержит имя .exe:
Код: Выделить всё
bool call_dll( const char *psz_dll, const char *psz_func )
{
HMODULE hdll = ::LoadLibrary( psz_dll );
void (__cdecl *proc)(HINSTANCE h ) = 0;
(FARPROC&)proc = ::GetProcAddress( hdll, psz_func );
proc( hdll );
::FreeLibrary( hdll );
}
На наше счастье функция LoadLibrary и GetProcAddress срабатывают вполне успешно и мы получаем нормальный адрес на функцию, описанную в .exe как экспортированную. Однако при попытке выполнения сколько-нибудь содержательного кода в рамках импортируемой функции система выдает ошибку выполнения.
Связано это со следующими проблемами:
Некорректна ссылка на таблицу импорта
Сама таблица импорта не инициализированна
Не инициализированна библиотека CRT
Восстанавливаем таблицу импорта
В случае, если наша программа собрана компоновщиком link из Visual C++ с опциями по умолчанию, восстановить таблицу импорта не составлит труда. Для начала нужно получить адрес импорта. Делается это и вовсе примитивно.
1. Вспомним, что HMODULE, возвращаемый нам LoadLibrary, в Win32 всего лишь указатель в адресном пространстве процесса. А указывает он на структуру IMAGE_DOS_HEADER:
IMAGE_DOS_HEADER *p = (IMAGE_DOS_HEADER*)hinst;
Естественно, хороший программист проверит значение поля pdosh->e_magic - оно должно быть равно знакомому нам с далеких времен ДОС-а "MZ"
Далее, получаем указатель на заголовок PE.
IMAGE_NT_HEADERS *pnth = (IMAGE_NT_HEADERS*)((byte*)h+pdosh->e_lfanew);
Естественно, полное описание формата PE файла выходит далеко за рамки данного опуса. Поэтому упомяну лишь, что в заголовке OptionalHeader присутствует таблица размещения определенных разделов файла, в которой перечисленны смещения от начала файла и размеры соответствующих блоков данных. Итак, нас интересует:
IMAGE_DATA_DIRECTORY *pimport_directory = pnth->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_IMPORT;
if( !pimport_directory->VirtualAddress)return true;
В директории импорта размещены списки используемых DLL, в которых указаны импортированные функции. Соответственно, задача восстановить таблицу становится тривиальной:
Код: Выделить всё
IMAGE_IMPORT_DESCRIPTOR *pimport_descr =
(IMAGE_IMPORT_DESCRIPTOR*)((byte*)h+pimport_directory->VirtualAddress);
for( ; pimport_descr->Characteristics != 0; pimport_descr++ )
{
char *psz_dll_name = (char*)((byte*)h+pimport_descr->Name);
HMODULE hmodule = ::LoadLibrary( psz_dll_name );
if( !hmodule )
return false;
IMAGE_THUNK_DATA *pimport_list = (IMAGE_THUNK_DATA*)((byte*)h+pimport_descr->OriginalFirstThunk);
IMAGE_THUNK_DATA *pthunk_list = (IMAGE_THUNK_DATA*)((byte*)h+pimport_descr->FirstThunk);
for( ; pimport_list->u1.Function; pimport_list++, pthunk_list++ )
{
IMAGE_IMPORT_BY_NAME *pimp_name = (IMAGE_IMPORT_BY_NAME*)((byte*)h+pimport_list->u1.AddressOfData);
FARPROC proc = ::GetProcAddress( hmodule, (char*)pimp_name->Name );
if( !proc )
return false;
pthunk_list->u1.Function = (unsigned)proc;
}
}
Окончательного дзена я достиг покурив текст с какой-то из страниц из кэша гугля (заранее извиняюсь за возможные кривости с форматированием и тэгами кода):
Речь в этой статье пойдет о том, как можно загрузить исполняемый файл в адресное пространство процесса и вызвавать экспортированную функцию, не создавая новый процесс. "Зачем нужен такой изврат" возможно возникнет вопрос у досуживо читателя. Ответ на него крайне прост.
Думаю, не открою Америки, если скажу, что код профессионального программиста содержит кроме основного функционала тесты, контролирующие правильность его работы (их называют unit-test-ами). В общем случае чем больше таких тестов и чем большее число ситуаций они покрывают, тем меньше ручная работа по тестированию, и, соответственно, тем спокойнее спит программист.
В свою практику я начал вводить эти тесты следующим образом. Прекрасым программистом MadMax было быстро написано приложение unit_tester, которое загружает записанные в список DLL. У каждой из этих DLL вызывается экспортируемая функция, которые и отвечает за запуск unit-тестов в этой библиотеке.
Эта схема проста и прекрасно работает до тех пор, пока код тестов содержится в библиотеках. При попытке загрузить и вызвать функцию из .exe программа благополучно падает. Наша сегодняшняя статья как раз и рассматривает причины этих падений и методы обхода проблемы.
Итак начнем. Нам нужно научиться выполнять такой код в случае, если psz_dll содержит имя .exe:
[code]bool call_dll( const char *psz_dll, const char *psz_func )
{
HMODULE hdll = ::LoadLibrary( psz_dll );
void (__cdecl *proc)(HINSTANCE h ) = 0;
(FARPROC&)proc = ::GetProcAddress( hdll, psz_func );
proc( hdll );
::FreeLibrary( hdll );
}[/code]
На наше счастье функция LoadLibrary и GetProcAddress срабатывают вполне успешно и мы получаем нормальный адрес на функцию, описанную в .exe как экспортированную. Однако при попытке выполнения сколько-нибудь содержательного кода в рамках импортируемой функции система выдает ошибку выполнения.
Связано это со следующими проблемами:
Некорректна ссылка на таблицу импорта
Сама таблица импорта не инициализированна
Не инициализированна библиотека CRT
Восстанавливаем таблицу импорта
В случае, если наша программа собрана компоновщиком link из Visual C++ с опциями по умолчанию, восстановить таблицу импорта не составлит труда. Для начала нужно получить адрес импорта. Делается это и вовсе примитивно.
1. Вспомним, что HMODULE, возвращаемый нам LoadLibrary, в Win32 всего лишь указатель в адресном пространстве процесса. А указывает он на структуру IMAGE_DOS_HEADER:
IMAGE_DOS_HEADER *p = (IMAGE_DOS_HEADER*)hinst;
Естественно, хороший программист проверит значение поля pdosh->e_magic - оно должно быть равно знакомому нам с далеких времен ДОС-а "MZ"
Далее, получаем указатель на заголовок PE.
IMAGE_NT_HEADERS *pnth = (IMAGE_NT_HEADERS*)((byte*)h+pdosh->e_lfanew);
Естественно, полное описание формата PE файла выходит далеко за рамки данного опуса. Поэтому упомяну лишь, что в заголовке OptionalHeader присутствует таблица размещения определенных разделов файла, в которой перечисленны смещения от начала файла и размеры соответствующих блоков данных. Итак, нас интересует:
IMAGE_DATA_DIRECTORY *pimport_directory = pnth->OptionalHeader.DataDirectory+IMAGE_DIRECTORY_ENTRY_IMPORT;
if( !pimport_directory->VirtualAddress)return true;
В директории импорта размещены списки используемых DLL, в которых указаны импортированные функции. Соответственно, задача восстановить таблицу становится тривиальной:
[code]IMAGE_IMPORT_DESCRIPTOR *pimport_descr =
(IMAGE_IMPORT_DESCRIPTOR*)((byte*)h+pimport_directory->VirtualAddress);
for( ; pimport_descr->Characteristics != 0; pimport_descr++ )
{
char *psz_dll_name = (char*)((byte*)h+pimport_descr->Name);
HMODULE hmodule = ::LoadLibrary( psz_dll_name );
if( !hmodule )
return false;
IMAGE_THUNK_DATA *pimport_list = (IMAGE_THUNK_DATA*)((byte*)h+pimport_descr->OriginalFirstThunk);
IMAGE_THUNK_DATA *pthunk_list = (IMAGE_THUNK_DATA*)((byte*)h+pimport_descr->FirstThunk);
for( ; pimport_list->u1.Function; pimport_list++, pthunk_list++ )
{
IMAGE_IMPORT_BY_NAME *pimp_name = (IMAGE_IMPORT_BY_NAME*)((byte*)h+pimport_list->u1.AddressOfData);
FARPROC proc = ::GetProcAddress( hmodule, (char*)pimp_name->Name );
if( !proc )
return false;
pthunk_list->u1.Function = (unsigned)proc;
}
}[/code]