在给定名称的情况下,C++中最简单的方法来获取导出的dll函数的序数是什么?
(正在寻找一种不会干扰自己解析IAT的方法...)

最佳答案

我想不出任何非常简单的方法来完成您想要的事情。您至少可以看到几个选择:

  • 采取Mark给出的路线,尽管看上去有些困惑,并且可能存在一些缺点。
  • 使用名称指针表(NPT)和导出序号表(EOT)查找导出序号。

  • 我看到的第一个选项的主要问题是,您不知道要尝试多少个序数(序数中可能有空格,因此依靠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 header 以定位导出目录表。

    上面的代码为我在Visual Studio 2008和Windows XP(SP3)下干净地编译,但是为YMMV。我并不是Windows专家,所以这可能不是最干净的代码可移植性(就Windows的不同版本而言)。与往常一样,此代码按“原样”提供,不提供任何形式的保证;)

    *是的,如果您想知道的话,在编写所有Microsoft风格的Windows代码后,我仍然感到有些肮脏。

    关于c++ - 以编程方式从函数名称获取序数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/2273603/

    10-11 04:59