给定它的名称,在 C++ 中获取导出的 dll 函数的序数的最简单方法是什么?(寻找一种不涉及自己解析 IAT 的方法......)
2 回答
我想不出任何非常简单的方法来做你想做的事。你至少有几个我可以看到的选项:
- 采取马克给出的路线,虽然它看起来有点笨拙并且可能有一些缺点。
- 使用名称指针表 (NPT) 和导出序号表 (EOT) 查找导出序号。
我看到第一个选项的主要问题是您不知道要尝试多少个序数(序数中可能存在间隙,因此指望GetProcAddress
返回 NULL 来表示结束是行不通的)。它也有些低效,因为它需要重复进行大量Win32 调用,并且基本上相当于对导出地址表的线性搜索。确实很不雅。
作为替代方案,您可以搜索 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 而言)。像往常一样,此代码按“原样”提供,不提供任何形式的保证;)
*Yes, in case you're wondering, I do still feel kind of dirty after writing all that Microsoft-style Windows code.
一种丑陋的方法是使用 dumpbin 命令运行系统调用并解析输出。但这与众所周知的瓷器店里的公牛一样优雅。
dumpbin /exports c:\windows\system32\user32.dll | grep FunctionOfInterest
否则,您可以编写一个简单的循环,使用序数调用GetProcAddress(传入 name 参数的低两个字节)。当函数指针与传递实际名称时返回的指针匹配时,就完成了。
这是没有错误检查的基本思想:
HANDLE hMod;
HANDLE byname, byord;
int ord;
hMod = LoadLibrary( "user32.dll" );
byname = GetProcAddress( hMod, "GetWindow" );
byord = 0;
ord = 1;
while ( 1 ) {
byord = GetProcAddress( hMod, (LPCSTR)ord );
if ( byord == byname ) {
printf( "ord = %d\n", ord );
break;
}
ord++;
}