5

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

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

感谢您的时间!

4

1 回答 1

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

该字段似乎未使用,并且始终设置为 0。

DWORD TimeDateStamp

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

WORD MajorVersion
WORD MinorVersion

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

DWORD Name

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

DWORD Base

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

DWORD NumberOfFunctions

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

DWORD NumberOfNames

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

PDWORD *AddressOfFunctions

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

PDWORD *AddressOfNames

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

PWORD *AddressOfNameOrdinals

该字段是一个 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...

顺便说一句,如果您从 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

//
// 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;

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

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

于 2010-10-28T11:56:15.920 回答