Я не могу придумать ни одного ужасно простого способа сделать то, что ты хочешь. У меня есть по крайней мере несколько вариантов, которые я вижу:
- Пройдите по маршруту, указанному Марком, хотя он кажется немного запутанным и может иметь некоторые недостатки.
- Используйте Таблицу указателя имени (NPT) и Порядковый номер таблицы экспорта (EOT), чтобы найти порядковые номера экспорта.
Основная проблема, которую я вижу с первым вариантом, заключается в том, что вы не знаете, сколько ординалов попробовать ). Это также несколько неэффективно, потому что требует многократных вызовов Win32 lot , и это в основном равносильно линейному поиску таблицы адресов экспорта. Действительно, довольно не элегантно.
В качестве альтернативы вы можете искать NPT и использовать результирующий индекс в EOT для получения порядкового номера. Это более элегантный подход, потому что он приходит к порядковому номеру самым прямым возможным способом (на самом деле это тот же метод, который динамический компоновщик использует для преобразования имен экспорта в их адреса). Кроме того, поскольку NPT отсортирован по лексическому принципу, можно выполнить бинарный поиск, который, очевидно, предпочтительнее линейного поиска другого метода. Фактически, один вызов GetProcOrdinal
, реализованный с помощью этого метода, должен быть немного быстрее, чем просто один вызов GetProcAddress
. Возможно, еще важнее то, что этот метод не зависит от каких-либо неизвестных (то есть числа порядковых чисел). Недостатком этого метода является то, что он не так прост, как другой метод.
Вы можете использовать Библиотеку справки по отладке, чтобы избежать некоторых операций анализа образа PE-файла (это то, что я делал изначально), но оказывается, что разбор необходимых частей образа PE не так уж и сложен. Я думаю, что избежание зависимости от библиотеки справки отладки стоит минимальных дополнительных усилий, необходимых для анализа заголовков образа PE.
Приступая к делу, вот пример реализации в C:
#include <stdio.h>
#include "windows.h"
/// Efficiently searches a module's name pointer table (NPT) for the named
/// procedure.
///
/// @param[in] npt Address of the NPT to search.
///
/// @param[in] size Number of entries in the NPT.
///
/// @param[in] base Base address of the module containing the NPT. This is
/// used to resolve addresses in the NPT (which are relative
/// to the module's base address).
///
/// @param[in] proc String containing the name of the procedure to search
/// for.
///
/// @return Returns the index into the NPT of the entry matching the named
/// procedure. If no such matching entry exists, the function returns
/// -1.
///
DWORD FindNptProc (PDWORD npt, DWORD size, PBYTE base, LPCSTR proc)
{
INT cmp;
DWORD max;
DWORD mid;
DWORD min;
min = 0;
max = size - 1;
while (min <= max) {
mid = (min + max) >> 1;
cmp = strcmp((LPCSTR)(npt[mid] + base), proc);
if (cmp < 0) {
min = mid + 1;
} else if (cmp > 0) {
max = mid - 1;
} else {
return mid;
}
}
return -1;
}
/// Gets a pointer to a module's export directory table (EDT).
///
/// @param[in] module Handle to the module (as returned by GetModuleHandle).
///
/// @return Returns a pointer to the module's EDT. If there is an error (e.g.
/// if the module handle is invalid or the module has no EDT) the
/// function will return NULL.
///
PIMAGE_EXPORT_DIRECTORY GetExportDirectoryTable (HMODULE module)
{
PBYTE base; // base address of module
PIMAGE_FILE_HEADER cfh; // COFF file header
PIMAGE_EXPORT_DIRECTORY edt; // export directory table (EDT)
DWORD rva; // relative virtual address of EDT
PIMAGE_DOS_HEADER mds; // MS-DOS stub
PIMAGE_OPTIONAL_HEADER oh; // so-called "optional" header
PDWORD sig; // PE signature
// Start at the base of the module. The MS-DOS stub begins there.
base = (PBYTE)module;
mds = (PIMAGE_DOS_HEADER)module;
// Get the PE signature and verify it.
sig = (DWORD *)(base + mds->e_lfanew);
if (IMAGE_NT_SIGNATURE != *sig) {
// Bad signature -- invalid image or module handle
return NULL;
}
// Get the COFF file header.
cfh = (PIMAGE_FILE_HEADER)(sig + 1);
// Get the "optional" header (it's not actually optional for executables).
oh = (PIMAGE_OPTIONAL_HEADER)(cfh + 1);
// Finally, get the export directory table.
if (IMAGE_DIRECTORY_ENTRY_EXPORT >= oh->NumberOfRvaAndSizes) {
// This image doesn't have an export directory table.
return NULL;
}
rva = oh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
edt = (PIMAGE_EXPORT_DIRECTORY)(base + rva);
return edt;
}
/// Gets the ordinal of an exported procedure.
///
/// @param[in] module Handle (as returned by GetModuleHandle) of the module
/// that exports the procedure.
///
/// @param[in] proc String containing the name of the procedure.
///
/// @return Returns the procedure's ordinal. If an ordinal for the procedure
/// could not be located (e.g. if the named procedure is not exported
/// by the specified module) then the function will return -1.
///
DWORD GetProcOrdinal (HMODULE module, LPCSTR proc)
{
PBYTE base; // module base address
PIMAGE_EXPORT_DIRECTORY edt; // export directory table (EDT)
PWORD eot; // export ordinal table (EOT)
DWORD i; // index into NPT and/or EOT
PDWORD npt; // name pointer table (NPT)
base = (PBYTE)module;
// Get the export directory table, from which we can find the name pointer
// table and export ordinal table.
edt = GetExportDirectoryTable(module);
// Get the name pointer table and search it for the named procedure.
npt = (DWORD *)(base + edt->AddressOfNames);
i = FindNptProc(npt, edt->NumberOfNames, base, proc);
if (-1 == i) {
// The procedure was not found in the module's name pointer table.
return -1;
}
// Get the export ordinal table.
eot = (WORD *)(base + edt->AddressOfNameOrdinals);
// Actual ordinal is ordinal from EOT plus "ordinal base" from EDT.
return eot[i] + edt->Base;
}
int main (int argc, char *argv [])
{
LPCSTR procName;
HMODULE module = NULL;
LPCSTR moduleName;
DWORD ordinal;
if (argc != 3) {
printf("A DLL name and procedure name must be specified\n");
return EXIT_FAILURE;
}
moduleName = argv[1];
procName = argv[2];
if (NULL == LoadLibrary(moduleName)) {
printf("Could not load library %s\n", moduleName);
return EXIT_FAILURE;
}
module = GetModuleHandle(moduleName);
if (NULL == module) {
printf("Couldn't get a handle to %s\n", moduleName);
return EXIT_FAILURE;
}
ordinal = GetProcOrdinal(module, procName);
if (-1 == ordinal) {
printf("Could not find ordinal for %s in %s\n", procName, moduleName);
} else {
printf("Found %s at ordinal %d\n", procName, ordinal);
}
return EXIT_SUCCESS;
}
GetProcOrdinal
- вот где происходят интересные биты. Надеюсь, код достаточно понятен; однако, чтобы полностью понять, может потребоваться немного знаний о формате PE-файла, о котором я не собираюсь здесь рассказывать (в Интернете достаточно информации об этом). FindNptProc
- это просто вспомогательная функция, которая выполняет двоичный поиск NPT. GetExportDirectoryTable
- это еще одна вспомогательная функция, которая анализирует заголовки PE для поиска таблицы каталогов экспорта.
Приведенный выше код компилируется для меня чисто под Visual Studio 2008 и Windows XP (SP3), но YMMV. Я на самом деле не парень Windows *, так что это может быть не самым чистым переносимым кодом (с точки зрения разных версий Windows). Как обычно, этот код предоставляется "как есть" без каких-либо гарантий;)
* Да, если вам интересно, я делаю все еще чувствую себя немного грязно после написания всего этого кода Windows в стиле Microsoft.