如何在 C# 中读取(本机)DLL 的导出函数名称?

Ela*_*lad 5 .net c# dll dllexport

我知道我可以阅读PE 规范以编写执行此操作的代码。但是,由于我手头没有很多时间,我希望你们中的一些人可能已经准备好发送这样的代码示例。

重要提示: 32bit 和 64bit 有什么区别吗?

感谢您的时间!

Svi*_*ack 5

PE 文件导出

与导入函数相反的是导出函数以供 EXE 或其他 DLL 使用。PE 文件在 .edata 部分存储有关其导出函数的信息。通常,Microsoft 链接器生成的 PE EXE 文件不会导出任何内容,因此它们没有 .edata 部分。Borland 的 TLINK32 总是从 EXE 导出至少一个符号。大多数 DLL 执行导出函数并具有 .edata 部分。.edata 部分(也称为导出表)的主要组件是函数名称、入口点地址和导出序数值的表。在 NE 文件中,导出表的等效项是入口表、常驻名称表和非常驻名称表。这些表作为 NE 报头的一部分存储,而不是存储在不同的段或资源中。

.edata 部分的开头是一个 IMAGE_EXPORT_DIRECTORY 结构(见表 10)。该结构紧随其后的是结构中字段指向的数据。

表 10. IMAGE_EXPORT_DIRECTORY格式

DWORD Characteristics
Run Code Online (Sandbox Code Playgroud)

此字段似乎未使用且始终设置为 0。

DWORD TimeDateStamp
Run Code Online (Sandbox Code Playgroud)

指示此文件创建时间的时间/日期戳。

WORD MajorVersion
WORD MinorVersion
Run Code Online (Sandbox Code Playgroud)

这些字段似乎未使用并设置为 0。

DWORD Name
Run Code Online (Sandbox Code Playgroud)

带有此 DLL 名称的 ASCIIZ 字符串的 RVA。

DWORD Base
Run Code Online (Sandbox Code Playgroud)

导出函数的起始序号。例如,如果文件导出序号值为 10、11 和 12 的函数,则此字段包含 10。要获取函数的导出序号,您需要将此值添加到 AddressOfNameOrdinals 数组的相应元素。

DWORD NumberOfFunctions
Run Code Online (Sandbox Code Playgroud)

AddressOfFunctions 数组中的元素数。这个值也是这个模块导出的函数数。理论上,此值可能与 NumberOfNames 字段(下一个)不同,但实际上它们始终相同。

DWORD NumberOfNames
Run Code Online (Sandbox Code Playgroud)

AddressOfNames 数组中的元素数。该值似乎总是与 NumberOfFunctions 字段相同,导出函数的数量也是如此。

PDWORD *AddressOfFunctions
Run Code Online (Sandbox Code Playgroud)

该字段是一个 RVA,指向一个函数地址数组。函数地址是此模块中每个导出函数的入口点 (RVA)。

PDWORD *AddressOfNames
Run Code Online (Sandbox Code Playgroud)

该字段是一个 RVA,指向一个字符串指针数组。字符串是此模块中导出函数的名称。

PWORD *AddressOfNameOrdinals
Run Code Online (Sandbox Code Playgroud)

该字段是一个 RVA,指向一个 WORD 数组。WORD 是该模块中所有导出函数的导出序号。但是,不要忘记添加在 Base 字段中指定的起始序号。

导出表的布局有些奇怪(见图 4 和表 10)。正如我之前提到的,导出函数的要求是名称、地址和导出序号。您可能认为 PE 格式的设计者会将所有这三个项目放入一个结构中,然后拥有这些结构的数组。相反,导出条目的每个组件都是数组中的一个元素。共有三个这样的数组(AddressOfFunctions、AddressOfNames、AddressOfNameOrdinals),它们彼此平行。要查找有关第四个函数的所有信息,您需要查找每个数组中的第四个元素。

替代文字

图 4. 导出表布局

表 11. EXE 文件的典型导出表

Name:            KERNEL32.dll
  Characteristics: 00000000
  TimeDateStamp:   2C4857D3
  Version:         0.00
  Ordinal base:    00000001
  # of functions:  0000021F
  # of Names:      0000021F

  Entry Pt  Ordn  Name
  00005090     1  AddAtomA
  00005100     2  AddAtomW
  00025540     3  AddConsoleAliasA
  00025500     4  AddConsoleAliasW
  00026AC0     5  AllocConsole
  00001000     6  BackupRead
  00001E90     7  BackupSeek
  00002100     8  BackupWrite
  0002520C     9  BaseAttachCompleteThunk
  00024C50    10  BasepDebugDump
  // Rest of table omitted...
Run Code Online (Sandbox Code Playgroud)

顺便说一下,如果您转储 Windows NT 系统 DLL(例如,KERNEL32.DLL 和 USER32.DLL)的导出,您会注意到,在许多情况下,有两个函数在末尾仅相差一个字符。名称,例如 CreateWindowExA 和 CreateWindowExW。这就是 UNICODE 支持是如何透明地实现的。以 A 结尾的函数是 ASCII(或 ANSI)兼容函数,而以 W 结尾的函数是函数的 UNICODE 版本。在您的代码中,您没有明确指定要调用的函数。而是通过预处理器#ifdefs 在WINDOWS.H 中选择适当的函数。这段来自 Windows NT WINDOWS.H 的摘录展示了它是如何工作的示例:

#ifdef UNICODE
#define DefWindowProc  DefWindowProcW
#else
#define DefWindowProc  DefWindowProcA
#endif // !UNICODE
Run Code Online (Sandbox Code Playgroud)
//
// Export Format
//

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Run Code Online (Sandbox Code Playgroud)

编辑:在 PE 格式中,导出表没有区别,函数地址只是 64 位地址的 RVA。

来源: http : //msdn.microsoft.com/en-us/library/ms809762.aspx

  • 非常感谢详细的解答!! (2认同)