以编程方式从函数名称获取序数

时间:2010-02-16 14:36:04

标签: c++ dll export ordinal

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

2 个答案:

答案 0 :(得分:14)

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

  1. 采取马克给出的路线,虽然看起来确实有点笨拙,可能有一些缺点。
  2. 使用名称指针表(NPT)和导出序数表(EOT)查找导出序号。
  3. 我看到的第一个选项的主要问题是你不知道要尝试多少个序数(序数中可能存在间隙,所以指望GetProcAddress返回NULL以表示结束赢了'工作)。它也有点低效,因为它需要重复进行批次的Win32调用,它基本上相当于对导出地址表进行线性搜索。确实很不优雅。

    作为替代方案,您可以搜索NPT并将结果索引用于EOT以获得序数。这是一种更优雅的方法,因为它以尽可能最直接的方式到达序号(实际上它与动态链接器用于将导出名称解析为其地址的方法相同)。另外,因为NPT是词法排序的,所以可以进行二分搜索,这显然比其他方法的线性搜索更好。实际上,使用此方法实现的对GetProcOrdinal的单个调用应该比仅仅一次调用GetProcAddress稍快一些。也许更重要的是,这种方法不依赖于任何未知数(即序数的数量)。这种方法的缺点是它不像其他方法那么简单。

    你可以使用Debug Help Library来帮助避免对PE文件映像进行一些解析(这是我最初做的),但事实证明解析PE映像的必需部分并不困难。我认为避免对Debug Help Library的依赖是值得解析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代码后,我仍感觉有点脏。

答案 1 :(得分:4)

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