Win32 API枚举DLL导出函数?

41

我找到了类似的问题,但没有答案符合我所寻求的。所以就来问一下:

对于一个本地Win32 DLL,是否有Win32 API可以枚举其导出函数名称?

8个回答

55

dumpbin /exports 就是你想要的,但它是一个开发人员工具,而不是Win32 API。

LoadLibraryExDONT_RESOLVE_DLL_REFERENCES 一起使用通常不被推荐,但在这种情况下却很有用——它完成了将 DLL 映射到内存中的重要工作(但你实际上不需要或者不想使用库中的任何内容),这使得读取头文件变得非常简单:通过 LoadLibraryEx 返回的模块句柄就指向了它。

#include <winnt.h>
HMODULE lib = LoadLibraryEx("library.dll", NULL, DONT_RESOLVE_DLL_REFERENCES);
assert(((PIMAGE_DOS_HEADER)lib)->e_magic == IMAGE_DOS_SIGNATURE);
PIMAGE_NT_HEADERS header = (PIMAGE_NT_HEADERS)((BYTE *)lib + ((PIMAGE_DOS_HEADER)lib)->e_lfanew);
assert(header->Signature == IMAGE_NT_SIGNATURE);
assert(header->OptionalHeader.NumberOfRvaAndSizes > 0);
PIMAGE_EXPORT_DIRECTORY exports = (PIMAGE_EXPORT_DIRECTORY)((BYTE *)lib + header->
    OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
assert(exports->AddressOfNames != 0);
BYTE** names = (BYTE**)((int)lib + exports->AddressOfNames);
for (int i = 0; i < exports->NumberOfNames; i++)
    printf("Export: %s\n", (BYTE *)lib + (int)names[i]);

完全没有测试过,但我认为它大体上是正确的。(著名的最后一句话。)


1
工作得很好,我迅速将其移植到Python(使用ctypes),效果很好。谢谢! - Peter Hansen
15
需要注意的是,在使用 DONT_RESOLVE_DLL_REFERENCES 标志加载后,调用函数可能会导致程序出现问题,因为所加载的模块没有调用 DllMain - Janusz Lenar
1
为什么不自己内存映射文件,而不是使用DONT_RESOLVE_DLL_REFERENCES?这样可能会更快。 - masterxilo
2
@masterxilo:LoadLibrary[Ex]会将二进制文件映射到地址空间中。为什么要把事情复杂化呢? - IInspectable
1
似乎需要使用DONT_RESOLVE_DLL_REFERENCES,因为在仅内存映射时导出字典中的虚拟地址无效。LoadLibrary显然进行了一些必要的转换。至少我在我的Win10 x64上看到了这个问题。 - Chad Schouggins
问题在于,当文件存在问题(如格式错误等)时,它将显示一个必须点击的错误消息框。另外 https://devblogs.microsoft.com/oldnewthing/20050214-00/?p=36463 - Simon Mourier

12

试试这个:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void EnumExportedFunctions (char *, void (*callback)(char*));
int Rva2Offset (unsigned int);

typedef struct {
    unsigned char Name[8];
    unsigned int VirtualSize;
    unsigned int VirtualAddress;
    unsigned int SizeOfRawData;
    unsigned int PointerToRawData;
    unsigned int PointerToRelocations;
    unsigned int PointerToLineNumbers;
    unsigned short NumberOfRelocations;
    unsigned short NumberOfLineNumbers;
    unsigned int Characteristics;
} sectionHeader;

sectionHeader *sections;
unsigned int NumberOfSections = 0;

int Rva2Offset (unsigned int rva) {
    int i = 0;

    for (i = 0; i < NumberOfSections; i++) {
        unsigned int x = sections[i].VirtualAddress + sections[i].SizeOfRawData;

        if (x >= rva) {
            return sections[i].PointerToRawData + (rva + sections[i].SizeOfRawData) - x;
        }
    }

    return -1;
}

void EnumExportedFunctions (char *szFilename, void (*callback)(char*)) {
    FILE *hFile = fopen (szFilename, "rb");

    if (hFile != NULL) {
        if (fgetc (hFile) == 'M' && fgetc (hFile) == 'Z') {
            unsigned int e_lfanew = 0;
            unsigned int NumberOfRvaAndSizes = 0;
            unsigned int ExportVirtualAddress = 0;
            unsigned int ExportSize = 0;
            int i = 0;

            fseek (hFile, 0x3C, SEEK_SET);
            fread (&e_lfanew, 4, 1, hFile);
            fseek (hFile, e_lfanew + 6, SEEK_SET);
            fread (&NumberOfSections, 2, 1, hFile);
            fseek (hFile, 108, SEEK_CUR);
            fread (&NumberOfRvaAndSizes, 4, 1, hFile);

            if (NumberOfRvaAndSizes == 16) {
                fread (&ExportVirtualAddress, 4, 1, hFile);
                fread (&ExportSize, 4, 1, hFile);

                if (ExportVirtualAddress > 0 && ExportSize > 0) {
                    fseek (hFile, 120, SEEK_CUR);

                    if (NumberOfSections > 0) {
                        sections = (sectionHeader *) malloc (NumberOfSections * sizeof (sectionHeader));

                        for (i = 0; i < NumberOfSections; i++) {
                            fread (sections[i].Name, 8, 1, hFile);
                            fread (&sections[i].VirtualSize, 4, 1, hFile);
                            fread (&sections[i].VirtualAddress, 4, 1, hFile);
                            fread (&sections[i].SizeOfRawData, 4, 1, hFile);
                            fread (&sections[i].PointerToRawData, 4, 1, hFile);
                            fread (&sections[i].PointerToRelocations, 4, 1, hFile);
                            fread (&sections[i].PointerToLineNumbers, 4, 1, hFile);
                            fread (&sections[i].NumberOfRelocations, 2, 1, hFile);
                            fread (&sections[i].NumberOfLineNumbers, 2, 1, hFile);
                            fread (&sections[i].Characteristics, 4, 1, hFile);
                        }

                        unsigned int NumberOfNames = 0;
                        unsigned int AddressOfNames = 0;

                        int offset = Rva2Offset (ExportVirtualAddress);
                        fseek (hFile, offset + 24, SEEK_SET);
                        fread (&NumberOfNames, 4, 1, hFile);

                        fseek (hFile, 4, SEEK_CUR);
                        fread (&AddressOfNames, 4, 1, hFile);

                        unsigned int namesOffset = Rva2Offset (AddressOfNames), pos = 0;
                        fseek (hFile, namesOffset, SEEK_SET);

                        for (i = 0; i < NumberOfNames; i++) {
                            unsigned int y = 0;
                            fread (&y, 4, 1, hFile);
                            pos = ftell (hFile);
                            fseek (hFile, Rva2Offset (y), SEEK_SET);

                            char c = fgetc (hFile);
                            int szNameLen = 0;

                            while (c != '\0') {
                                c = fgetc (hFile);
                                szNameLen++;
                            }

                            fseek (hFile, (-szNameLen)-1, SEEK_CUR);
                            char* szName = calloc (szNameLen + 1, 1);
                            fread (szName, szNameLen, 1, hFile);

                            callback (szName);

                            fseek (hFile, pos, SEEK_SET);
                        }
                    }
                }
            }
        }

        fclose (hFile);
    }
}

例子:

void mycallback (char* szName) {
    printf ("%s\n", szName);
}

int main () {
    EnumExportedFunctions ("C:\\Windows\\System32\\user32.dll", mycallback);
    return 0;
}

输出:

ActivateKeyboardLayout
AddClipboardFormatListener
AdjustWindowRect
AdjustWindowRectEx
AlignRects
AllowForegroundActivation
AllowSetForegroundWindow
AnimateWindow
AnyPopup
AppendMenuA
AppendMenuW
ArrangeIconicWindows
AttachThreadInput
BeginDeferWindowPos
BeginPaint
BlockInput
BringWindowToTop
BroadcastSystemMessage
BroadcastSystemMessageA
BroadcastSystemMessageExA
BroadcastSystemMessageExW
BroadcastSystemMessageW
BuildReasonArray
CalcMenuBar
.....etc

7
请到微软研究室下载Detours库。其中一个示例恰好符合您的要求。整个库使得win32函数调用的重定向变得非常简单。这是相当酷的东西。
链接:Detours 编辑:还要注意,如果您只想查看导出表,您可以(至少在Visual Studio中)将项目属性设置为打印导出/导入表。我不能记住确切的选项,但应该很容易找到。 编辑2: 该选项是“项目属性”->“链接器”->“调试”->“生成MapFile”->“是(/MAP)”。

4
虽然 ephemient 的做法是正确的,使用带有 DONT_RESOLVE_DLL_REFERENCES 参数的 LoadLibraryEx 函数可以大大简化这个任务,但你可以比他展示的方法更加简单。不需要自己查找和枚举 DLL 导出目录,你可以使用 SymEnumerateSymbols 函数来为你列出符号。
虽然这种方法与他的代码只有微小的差别(没有 assert 语句,他的代码只有几行),但至少在理论上,在 Microsoft 决定稍微更改可执行文件格式或者更改 HMODULE 指向的确切位置时,它提供了一些额外的灵活性(因为这些细节通常没有正式记录)。

1

如果您不想费心编写自己的代码,而是更愿意使用已经存在于此目的的DLL,则我建议PE文件格式DLL。附带源代码,以便您可以根据需要进行修改。无需担心GPL。

还提供了一个GUI应用程序,显示如何使用该DLL。


0

如果你只是想找到一个查看 DLL 中导出函数的方法,你可以使用微软的 dependency walker(depends.exe)。但如果你需要以编程方式发现导出函数,这并不能帮助你。


0

这个问题已经存在了12年,但是我想指出一些解决方案中存在的问题。

其中没有一个考虑到序数(没有名称字符串的导出项)。由于序数索引之间可能存在间隙,因此问题变得更加复杂。序数有一个起始基数(IMAGE_EXPORT_DIRECTORY的“Base”字段),但不能保证序数是连续的。

不想花时间编写代码,但一种方法是通过从索引0到NumberOfFunctions进行迭代。
然后在第二个(内部)循环中,将该索引与0到NumberOfNames中AddressOfNameOrdinals数组进行匹配。
如果您将函数索引与AddressOfNameOrdinals数组索引匹配,则该索引为AddressOfNames数组的索引(必须解析的偏移量)。如果您在NumberOfNames索引范围内未找到匹配,则它是一个序数导出项。
如果AddressOfFunctions条目中的函数索引为0,则只是一个序数间隙,可以跳至下一个索引。
要获取实际的序数(以字符串形式输出),请将“Base”添加到NumberOfFunctions循环索引中。


0

我可能错了,说实话我没有进行双重检查,但我认为在使用ephemient的代码时,如果模块是在与您的进程不同的架构下构建的,可能会存在一些兼容性问题。(再次声明,我现在可能在胡说八道)

Github上有一个名为dll2def的项目,它使用相同的技术(尽管它自己将文件加载到内存中),但似乎已经设置了一些检查来根据二进制文件的架构查找导出项。您最有兴趣的代码应该在this file中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接