Смекни!
smekni.com

Вызов функции в другом процессе (стр. 2 из 2)

typedef struct _IMAGE_EXPORT_DIRECTORY { ... DWORD Base; DWORD NumberOfFunctions; DWORD NumberOfNames; DWORD AddressOfFunctions; // RVA from base of image DWORD AddressOfNames; // RVA from base of image DWORD AddressOfNameOrdinals; // RVA from base of image } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

Здесь:

AddressOfFunctions – RVA (смещение от начала файла) массива, содержащего RVA функций.

AddressOfNames – RVA массива, содержащего RVA имён функций.

AddressOfNameOrdinals – RVA массива индексов функций. Элемент n этого массива содержит индекс в массиве адресов функций, соответствующей n-ному элементу в массиве имён функций.

ПРЕДУПРЕЖДЕНИЕ Во-первых, элементы этого массива имеют тип WORD и размер 2 байта. Во-вторых, MSDN и статья Мэтта Питрека «Форматы PE и COFF объектных файлов» содержат одну и туже ошибку, относящуюся к интерпретации содержимого этого массива. Правильно написано в статье Максима М. Гумерова «Загрузчик PE-файлов» и здесь :)

NumberOfFunctions – количество элементов массива адресов функций.

NumberOfNames – количество элементов массива имён функций и массива индексов функций.

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

В результате, для поиска адреса функции, экспортируемой по имени, нужно сделать примерно следующее (в псевдокоде):

// Ищем в массиве имён функций совпадающее имя int nameIndex = FindFunctionName(AddressOfNames, NumberOfNames, name); // Получаем соответствующий имени индекс функции WORD funcIndex = AddressOfNameOrdinals[nameIndex]; // Получаем RVA функции DWORD funcRVA = AddressOfFunctions[funcIndex];
ПРЕДУПРЕЖДЕНИЕ По MSDN и Питреку, последняя строчка алгоритма должна выглядеть так: DWORD funcRVA = AddressOfFunctions[funcIndex - Base]; Где Base – базовое значение ординала. Как показывает практика, Base вычитать не надо.

Код

В конце концов у меня получилось три функции. Первая находит секцию экспорта:

// Определяет RVA секции экспорта int GetExportSectionRVA(HANDLE hProcess, const void* baseAddress) { // Читаем DOS-заголовок IMAGE_DOS_HEADER dos_header; ReadProcessMemory( hProcess, baseAddress, &dos_header, sizeof(dos_header), NULL); // Читаем PE-заголовок IMAGE_NT_HEADERS pe_header; ReadProcessMemory( hProcess, reinterpret_cast<const BYTE*>(baseAddress) + dos_header.e_lfanew, &pe_header, sizeof(pe_header), NULL); // Смещение секции экспорта return pe_header.OptionalHeader.DataDirectory [IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; }

Вторая перебирает массив имён функций в поиске заданного имени:

// Ищет в массиве имён функций заданное имя, возвращает индекс или –1 int FindName( HANDLE hProcess, const void* baseAddress, DWORD AddressOfNames, DWORD count, const char* name) { // Для сравнения имени его нужно прочитать, для этого нужно знать размер int size = lstrlenA(name) + 1; std::auto_ptr<char> candidate(new char[size]); // Перебираем имена в массиве имён функций for (int index = 0; index < count; index++) { DWORD nameRVA; // Читаем адрес начала строки ReadProcessMemory( hProcess, reinterpret_cast<const BYTE*>(baseAddress) + AddressOfNames + index * sizeof(DWORD), &nameRVA, sizeof(nameRVA), NULL); // Читаем строку ReadProcessMemory( hProcess, reinterpret_cast<const BYTE*>(baseAddress) + nameRVA, candidate.get(), size, NULL); if (strcmp(name, candidate.get()) == 0) { // Она! Сваливаем :) return index; } } // Такой функции нет return -1; }

Третья функция использует первые две и находит нужную функцию в указанной DLL в указанном процессе:

// Находит нужную функцию в указанной DLL в указанном процессе. void* GetProcAddress(HANDLE hProcess, HMODULE hLib, const char* name) { // Нам нужен именно адрес загрузки! А результат работы // LoadLibrary бывает иногда неожиданным.. char* baseAddress = reinterpret_cast<char*> (reinterpret_cast<DWORD>(hLib) & 0xFFFF0000); // Смещение секции экспорта int export_offset = GetExportSectionRVA(hProcess, baseAddress); if (export_offset <= 0) { // Какие-то проблемы с экспортом return NULL; } // Читаем заголовок секции экспорта IMAGE_EXPORT_DIRECTORY export; ReadProcessMemory( hProcess, baseAddress + export_offset, &export, sizeof(export), NULL); // Индекс в массиве функций WORD funcIndex = -1; if (reinterpret_cast<DWORD_PTR>(name) > 0x0000ffff) { // Функция экспортируется по имени. Ищем имя int nameIndex = FindName( hProcess, baseAddress, export.AddressOfNames, export.NumberOfNames, name); if (nameIndex < 0) { // Такой функции нет return NULL; } // Читаем индекс (они двухбайтные!!!) ReadProcessMemory( hProcess, baseAddress + export.AddressOfNameOrdinals + nameIndex * sizeof(WORD), &funcIndex, sizeof(funcIndex), NULL); } else { // Функция экспортируется по ординалу WORD funcOrdinal = reinterpret_cast<DWORD>(name); if ((funcOrdinal < export.Base) || (funcOrdinal >= export.Base + export.NumberOfFunctions)) { // Такой функции нет return NULL; } // Индекс это ординал минус база funcIndex = funcOrdinal - export.Base; } if ((funcIndex < 0) || (funcIndex >= export.NumberOfFunctions)) { // Такой функции нет return NULL; } // Читаем адрес DWORD funcRVA; ReadProcessMemory( hProcess, baseAddress + export.AddressOfFunctions + funcIndex * sizeof(DWORD), &funcRVA, sizeof(funcRVA), NULL); // Результат это базовый адрес + RVA return (baseAddress + funcRVA); }
ПРИМЕЧАНИЕ Для оптимизации можно было бы сначала скопировать в свой процесс всю секцию экспорта (размер секции хранится в IMAGE_NT_HEADERS::OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size), а потом уже её разбирать. Но, поскольку заметных глазу задержек не возникает, я остановился на текущей реализации.

Пример

В качестве примера я написал три приложения: aggressor.exe, victim.exe и insider.dll. Victim и insider абсолютно пассивны, все действия выполняются aggressor-ом. Aggressor:

запускает victim.exe;

загружает в него insider.dll;

получает адреса трёх экспортируемых функций;

вызывает эти функции;

выгружает insider.dll из victim.exe .

ПРИМЕЧАНИЕ Чтобы это действительно работало, надо положить все три исполняемых модуля в один каталог.

Для реализации перечисленных действий, да и вообще на будущее, в aggressor реализованы следующие полезные функции:

namespace OtherProcess { // // Вызывает функцию из заданного процесса, возвращает // описатель потока, который эту функцию выполняет HANDLE AsynchronousCall( HANDLE hProcess, void* address, void* parameter, DWORD* pid); // // Вызывает функцию из заданного процесса, дожидается завершения её работы bool SynchronousCall( HANDLE hProcess, void* address, void* parameter, DWORD* result); // // Загружает DLL в указанный процесс HMODULE LoadLibrary(HANDLE hProcess, const TCHAR* path); // // Выгружает DLL в указанном процессе void FreeLibrary(HANDLE hProcess, HMODULE hLib); // // Находит нужную функцию в указанной DLL в указанном процессе void* GetProcAddress(HANDLE hProcess, HMODULE hLib, const char* name); };

Предназначение функций, я надеюсь, понятно из их названий и кратких комментариев. Понимание реализации также не должно вызвать затруднений, прокомментировано всё достаточно подробно, да и сам код не такой уж головоломный. Успешных вам вызовов!

Список литературы

Джеффри Рихтер, «Programming Application for Microsoft Windows», четвёртое издание.

Тихомиров В.А. «Перехват API-функций в Windows NT/2000/XP».

Мэтт Питрек «Форматы PE и COFF объектных файлов»

Максим М. Гумеров «Загрузчик PE-файлов»