C++ Win32 API枚举dll导出函数?

C++ Win32 API枚举dll导出函数?,c++,windows,winapi,dll,C++,Windows,Winapi,Dll,我发现了类似的问题,但没有找到我想要的答案。下面是: 对于本机Win32 dll,是否有Win32 API来枚举其导出函数名?转到Microsoft research并获取迂回库。它的一个例子正好符合你的要求。整个库基本上使绕过/重新路由win32函数调用变得非常容易。很酷的东西 编辑:还要注意,如果您只想查看导出表,您可以(至少在visual Studio中)设置项目属性以打印出导出/导入表。我不记得确切的选项,但谷歌应该很容易找到 **Edit2:*选项是“项目属性->链接器->调试->生

我发现了类似的问题,但没有找到我想要的答案。下面是:


对于本机Win32 dll,是否有Win32 API来枚举其导出函数名?

转到Microsoft research并获取迂回库。它的一个例子正好符合你的要求。整个库基本上使绕过/重新路由win32函数调用变得非常容易。很酷的东西

编辑:还要注意,如果您只想查看导出表,您可以(至少在visual Studio中)设置项目属性以打印出导出/导入表。我不记得确切的选项,但谷歌应该很容易找到


**Edit2:*选项是“项目属性->链接器->调试->生成映射文件->是(/MAP)

如果您只是想知道在DLL中导出哪些函数,可以使用Microsoft的(depends.exe)。但是,如果您确实需要以编程方式发现导出,这对您没有帮助。

dumpbin/exports
正是您想要的,但这是一个开发人员工具,而不是Win32 API

使用
DONT_RESOLVE\u DLL\u 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]);
#包括
HMODULE lib=LoadLibraryEx(“library.dll”,NULL,不解析dll\u引用);
assert((PIMAGE\u DOS\u HEADER)lib)->e\u magic==IMAGE\u DOS\u签名);
PIMAGE\u NT\u HEADERS=(PIMAGE\u NT\u HEADERS)((字节*)lib+((PIMAGE\u DOS\u header)lib)->e\u lfanew);
断言(头->签名==图像\u NT\u签名);
断言(header->OptionalHeader.numberofrvandsize>0);
PIMAGE\u EXPORT\u DIRECTORY exports=(PIMAGE\u EXPORT\u DIRECTORY)((字节*)lib+头->
OptionalHeader.DataDirectory[图像\目录\条目\导出].VirtualAddress);
断言(导出->AddressOfNames!=0);
字节**名称=(字节**)((int)lib+导出->地址名称);
对于(inti=0;iNumberOfNames;i++)
printf(“导出:%s\n”,(字节*)lib+(int)名称[i]);

完全未经测试,但我认为这或多或少是正确的。(著名的临终遗言)

如果您不想麻烦地编写自己的代码,而宁愿使用已经存在的DLL来实现此目的,我建议您使用。附带源代码,您可以根据需要进行修改。没有GPL可担心


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

我可能错了,老实说,我没有仔细检查,但我相信在一个模块上使用ephemient的代码可能存在一些兼容性问题,该模块是在与您的进程不同的体系结构下构建的。(再说一遍,我现在可能完全是在胡说八道)


github上有一个名为的项目,它使用相同的技术(尽管它自己将文件加载到内存中),但似乎有一些检查,以根据二进制文件的体系结构查找导出。您最可能感兴趣的代码是。

虽然ephemient正确地指出,
LoadLibraryEx
DONT\u RESOLVE\u DLL\u REFERENCES
可以大大简化此任务,但您可以使其比他所展示的更简单。您可以使用
symenumerationsymbols
为自己列出符号,而不是自己查找和枚举DLL的导出目录

虽然只比他的代码稍微简单(没有断言,他的代码只有六行),但这至少在理论上提供了一点额外的灵活性,以防微软有朝一日决定稍微更改可执行格式,和/或更改HMODULE所指的内容,使其不再工作(因为这些细节大部分都没有正式的文档记录)。

试试这个:

#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);
    }
}
输出:

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

这是12年前提出的问题,但我想指出所提出的解决方案中存在的一些问题

它们都不包含序号(导出时不包含名称字符串)。 序数索引之间的潜在间隙使问题变得复杂。序数有一个起始基数(IMAGE_EXPORT_目录的“base”字段),但不能保证序数是连续的

我不想花时间编写代码,但一种方法是通过索引0对NumberOfFunctions进行迭代。
然后在第二个(内部)循环中,将0中的索引与NumberOfNames匹配到AddressOfNameOrdinals数组中。
如果将函数索引与AddressOfNameOrdinals数组索引相匹配,则这是到AddressOfNames数组的索引(必须解析的偏移量)。如果没有匹配(超过NumberOfNames索引),则这是顺序导出。
如果AddressOfFunctions条目中的函数索引为0,则它只是一个序数间隔,您可以跳到下一个索引。

要获得实际的序号(用于打印为字符串),您需要在NumberOfFunctions循环索引中添加“Base”。

工作得很好,因此我的快速Python端口(使用ctypes)工作正常。谢谢!需要注意的是,加载后使用
DONT\u RESOLVE\u DLL\u引用
标志调用函数可能会出错,因为加载的模块不会调用
DllMain
。为什么不自己映射内存文件而不是DONT\u RESOLVE\u DLL\u引用?可能会更快。@masterxilo:
 LoadLibrary[Ex]
是否将二进制文件映射到地址空间。为什么要使事情复杂化?似乎需要
不解析DLL\U引用
,因为导出字典中的虚拟地址在仅映射内存时无效。LoadLibrary显然进行了一些必要的转换。至少我在Win10 x64上看到了这一点。
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