【问题标题】:Getting ordinal from function name programmatically以编程方式从函数名中获取序数
【发布时间】:2011-01-17 10:23:03
【问题描述】:

在 C++ 中获取导出的 dll 函数的序号的最简单方法是什么? (寻找一种不涉及自己解析 IAT 的方法......)

【问题讨论】:

  • 解析表有什么问题?这并不难,你只需要编写一次函数。

标签: c++ dll export ordinal


【解决方案1】:

我想不出任何非常简单的方法来做你想做的事。我可以看到您至少有几个选项:

  1. 走马克给出的路线,虽然它看起来有点笨拙,可能有一些缺点。
  2. 使用名称指针表 (NPT) 和导出序号表 (EOT) 查找导出序号。

我看到的第一个选项的主要问题是你不知道要尝试多少个序数(序数中可能有间隙,所以指望GetProcAddress 返回 NULL 来表示结束是行不通的)。这也有点低效,因为它需要重复进行 lot 的 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 而言)。像往常一样,此代码按“原样”提供,不提供任何形式的保证;)

*是的,如果您想知道的话,在编写完所有 Microsoft 风格的 Windows 代码之后,我确实仍然觉得有点脏。

【讨论】:

    【解决方案2】:

    一种丑陋的方法是使用 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++;
         }
    

    【讨论】:

    • 谢谢。恕我直言,仍然不是很优雅。
    • @Danra:我同意它不是非常优雅。
    • 如果您想要优雅,请提高抽象级别。将这些东西放在一个函数中,并将其命名为 GetProcOrdinal。在调用时,它看起来确实非常优雅。
    猜你喜欢
    • 2014-10-05
    • 1970-01-01
    • 2018-05-07
    • 2014-09-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-14
    • 1970-01-01
    相关资源
    最近更新 更多