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

发布于 2024-08-21 06:50:23 字数 74 浏览 6 评论 0原文

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

What's the easiest way in C++ to get an ordinal of an exported dll function, given its name?
(Looking for a way which doesn't invlove parsing the IATs myself...)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

花辞树 2024-08-28 06:50:23

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

  1. 采用马克给出的路线,尽管它看起来确实有点笨拙并且可能有一些缺点。
  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 而言)。像往常一样,此代码“按原样”提供,不提供任何形式的保证;)

*是的,以防万一您想知道,在编写了所有 Microsoft 风格的内容后,我确实仍然感觉有点肮脏Windows 代码。

I can't think of any terribly simple way to do what you want. You have at least a couple of options that I can see:

  1. Take the route given by Mark, though it does seem a bit kludgy and may have some shortcomings.
  2. Use the Name Pointer Table (NPT) and Export Ordinal Table (EOT) to find export ordinals.

The main problem I see with the first option is that you don't know how many ordinals to try (there can be gaps in the ordinal numbers, so counting on GetProcAddress returning NULL to signal the end won't work). It's also somewhat inefficient because it requires making a lot of Win32 calls repeatedly and it basically amounts to a linear search of the export address table. Pretty inelegant, indeed.

As an alternative, you can search the NPT and use the resultant index into the EOT to obtain an ordinal. This is a more elegant approach because it arrives at the ordinal in the most direct way possible (it is actually the same method the dynamic linker uses to resolve export names to their addresses). Also, because the NPT is lexically sorted, it's possible to do a binary search which is obviously preferable to the other method's linear search. In fact, a single call to GetProcOrdinal implemented with this method should be slightly faster than just one call to GetProcAddress. Perhaps more importantly, this method doesn't depend on any unknowns (i.e. number of ordinals). The disadvantage to this method is that it's not as simple as the other method.

You could use the Debug Help Library to help avoid doing some of the parsing of the PE file image (this is what I did initially), but it turns out that parsing the required parts of the PE image is not that difficult. I think avoiding the dependency on the Debug Help Library is worth the minimal extra effort required to parse the PE image headers.

Getting down to business, here is an example implementation in 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 is where the interesting bits happen. The code is hopefully fairly self-explanatory; however, to fully understand it may require a bit of knowledge about the PE file format, which I'm not about to get into here (there's plenty of info on the web about it). FindNptProc is simply a convenience function that does the binary search of the NPT. GetExportDirectoryTable is another convenience function that parses the PE headers to locate the export directory table.

The code above compiles cleanly for me under Visual Studio 2008 and Windows XP (SP3), but YMMV. I'm not really a Windows guy*, so this might not be the cleanest code portability-wise (in terms of different versions of Windows). As usual, this code is provided "as is" with no warranty of any kind ;)

*Yes, in case you're wondering, I do still feel kind of dirty after writing all that Microsoft-style Windows code.

胡渣熟男 2024-08-28 06:50:23

一种丑陋的方法是使用 dumpbin 命令运行系统调用并解析输出。但这与众所周知的瓷器店里的公牛几乎一样优雅。

dumpbin /exports c:\windows\system32\user32.dll | grep FunctionOfInterest

否则,您可以编写一个简单的循环,使用序数调用 GetProcAddress (在名称参数的低两个字节中传递)。当函数指针与传递实际名称时返回的指针匹配时,就完成了。

这是没有错误检查的基本思想:

  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++;
     }

An ugly way would be to run a system call with a dumpbin command and parse the output. But that has about the same elegance as a bull in the proverbial china shop.

dumpbin /exports c:\windows\system32\user32.dll | grep FunctionOfInterest

Otherwise, you could write a simple loop calling GetProcAddress with ordinals (passed in the low two bytes of the name parameter). When the function pointer matches the pointer returned when passing the actual name, then you are done.

Here is the basic idea without error checking:

  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++;
     }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文