请选择 进入手机版 | 继续访问电脑版

DecoyMini 技术交流社区

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 1183|回复: 0

[技术前沿] [原创] PE 文件结构剖析之导出目录表

[复制链接]

5

主题

0

回帖

0

荣誉

Rank: 1

UID
1625
积分
22
精华
0
沃币
3 枚
注册时间
2024-3-8
发表于 2024-3-25 19:22:16 | 显示全部楼层 |阅读模式
本帖最后由 zer0daysec 于 2024-3-26 08:43 编辑
这一次我们将讨论导出目录表,这部分在 PE 结构中是非常重要的,回忆一下,在 NT 头中的可选头最后一个字段便是描述目录表总体信息,一般为 16 个成员,但最后一个成员全为 0,那么实际 "有效" 的只有 15 个有效成员

有专门宏定义来定义它们:

  1. #define IMAGE_DIRECTORY_ENTRY_EXPORT          0x0   //[0x78] 1)
  2. #define IMAGE_DIRECTORY_ENTRY_IMPORT          0x1   //[0x80] 2)
  3. #define IMAGE_DIRECTORY_ENTRY_RESOURCE        0x2   //[0x88] 3)
  4. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       0x3   //[0x90] 4)
  5. #define IMAGE_DIRECTORY_ENTRY_SECURITY        0x4   //[0x98] 5)
  6. #define IMAGE_DIRECTORY_ENTRY_BASERELOC       0x5   //[0xA0] 6)
  7. #define IMAGE_DIRECTORY_ENTRY_DEBUG           0x6   //[0xA8] 7)
  8. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    0x7   //[0xB0] 8)
  9. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       0x8   //[0xB8] 9)
  10. #define IMAGE_DIRECTORY_ENTRY_TLS             0x9   //[0xC0] 10)
  11. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG     0xA   //[0xC8] 11)
  12. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT    0xB   //[0xD0] 12)
  13. #define IMAGE_DIRECTORY_ENTRY_IAT             0xC   //[0xD8] 13)
  14. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    0xD   //[0xE0] 14)
  15. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR  0xE   //[0xE8] 15)
复制代码

本篇主要讲解的是第一个

导出目录表一般出现在 Dll 文件中,由于前面采用的示例未有导出目录表,为了做演示,便随便拿系统中某个 Dll (kernel32.dll) 来做解析。

导出目录表是 PE 文件为其它程序提供 API 的一种函数示例导出方式,除此之外,还可导出自身一些变量以及类,供第三方程序使用,导出目录表结构

  1. typedef struct _IMAGE_EXPORT_DIRECTORY {
  2.     DWORD   Characteristics;          // 1) 保留,恒为0x00000000
  3.     DWORD   TimeDateStamp;            // 2) 时间戳
  4.     WORD    MajorVersion;             // 3) 主版本号,一般不赋值
  5.     WORD    MinorVersion;             // 4) 子版本号,一般不赋值
  6.     DWORD   Name;                     // 5) 模块名称
  7.     DWORD   Base;                     // 6) 索引基数
  8.     DWORD   NumberOfFunctions;        // 7) 导出地址表中的成员个数
  9.     DWORD   NumberOfNames;            // 8) 导出名称表中的成员个数
  10.     DWORD   AddressOfFunctions;       // 9) 导出地址表(EAT)
  11.     DWORD   AddressOfNames;           // 10) 导出名称表(ENT)
  12.     DWORD   AddressOfNameOrdinals;    // 11) 指向导出序列号数组
  13. } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
复制代码

Characteristics 字段恒为 0,保留;

TimeDateStamp 字段为导出表创建的时间 (GMT);

Name 字段为存储模块名 (ASCII 字符) 的 RVA;

Base 字段为导出 API 函数索引值的基数,函数索引值 = 导出函数索引值 - 基数,一般情况下为 0;

NumberOfFunctions 字段为导出地址表 (EAT) 中成员数量;

NumberOfNames 字段为导出名称表 (ENT) 中成员数量,ENT 的数量 <= EAT 的数量;

AddressOfFunctions 字段为导出地址表;

AddressOfNames 字段为导出名称表;

AddressOfNameOrdinals 字段为导出序列号数组;

有三张表非常重要,分别为导出地址表 EAT、导出名称表 ENT、导出序号表 EOT,三者的关系:



函数导出有两种方式,一种仅以序号导出,另一种是同时以序号和名称导出。需要注意的是,EAT 的数量会大于等于 ENT 的数量。

  1. LIBRARY "Dll1"
  2. EXPORTS
  3.   fun1 @1 NONAME
  4.   fun2 @2 NONAME
  5.   fun3 @3 NONAME

  6. LIBRARY "Dll1"
  7. EXPORTS
  8.   fun1 @1
  9.   fun2 @2
  10.   fun3 @3 NONAME
复制代码

kernel32.dll 中导出目录表的 RVA 为 0x92CA0,大小为 0xDC60,位于 .rdata 区段中,转换成文件偏移为 0x78CA0



在 010Editor 里也验证了这个结果



代码实现:

  1. // 12.解析导出目录表
  2. DWORD dwExportTableRVA = OptionalHeader.
  3.         DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;

  4. PIMAGE_EXPORT_DIRECTORY pExportTableVA = (PIMAGE_EXPORT_DIRECTORY)(dwFileAddr +
  5.         Rva2Foa(pNtHeader, dwExportTableRVA));

  6. // 12.1 解析模块的名称
  7. std::cout << "\nName: " << (char*)(dwFileAddr +
  8.         Rva2Foa(pNtHeader, pExportTableVA->Name)) << std::endl;

  9. // 12.2 分别获取序号表、名称表、地址表的 VA
  10. WORD* pwOrdTable = (WORD*)(dwFileAddr +
  11.         Rva2Foa(pNtHeader, pExportTableVA->AddressOfNameOrdinals));
  12. DWORD* pdwNamesTable = (DWORD*)(dwFileAddr +
  13.         Rva2Foa(pNtHeader, pExportTableVA->AddressOfNames));
  14. DWORD* pdwFunctionsTable = (DWORD*)(dwFileAddr +
  15.         Rva2Foa(pNtHeader, pExportTableVA->AddressOfFunctions));

  16. std::cout << "Ordinal\tAddress\t\tNames" << std::endl;
  17. // 函数地址表中的个数就是函数的个数
  18. for (DWORD i = 0; i < pExportTableVA->NumberOfFunctions; ++i)
  19. {
  20.         // 判断当前函数地址是否有效
  21.         if (NULL == pwOrdTable[i])
  22.         {
  23.                 continue;
  24.         }

  25.         BOOL bHaveName = FALSE;
  26.         // 查看当前函数有没有名称
  27.         for (DWORD j = 0; j < pExportTableVA->NumberOfNames; ++j)
  28.         {
  29.                 // 如果序号表中保存了当前地址的下标,说明是名称导出
  30.                 // i 表示当前地址下标,j 是名称表和序号表的下标
  31.                 if (i == pwOrdTable[j])
  32.                 {
  33.                         bHaveName = TRUE;
  34.                         std::cout << "0x" << std::setfill('0') << std::setw(4) << i + pExportTableVA->Base
  35.                                 << "\t 0x" << std::setw(8) << pdwFunctionsTable[i] << "\t"
  36.                                 << (char*)(dwFileAddr + Rva2Foa(pNtHeader, pdwNamesTable[j])) << std::endl;
  37.                 }
  38.         }
  39.         // 这是一个序号导出的函数
  40.         if (!bHaveName)
  41.         {
  42.                 std::cout << "0x" << std::setfill('0') << std::setw(4) << i + pExportTableVA->Base
  43.                         << "\t 0x" << std::setw(8) << pdwFunctionsTable[i] << "\t"
  44.                         << "[None]" << std::endl;
  45.         }
  46. }
复制代码

运行结果:



GetProcAddress 函数是个典型例子,向它提供模块名的句柄和函数名就能获取到函数地址,自己可以去实现一个这样一个函数。

测试 (GetProcAddress(GetModuleHandle("kernel32.dll"), "AllocConsole");):

  1. // AllocConsole 0x23b00
  2. for (DWORD i = 0; i < pExportTableVA->NumberOfNames; ++i)
  3. {
  4.         char* functionName = (char*)(dwFileAddr + Rva2Foa(pNtHeader, pdwNamesTable[i]));
  5.         if (strcmp(functionName, "AllocConsole") == 0)
  6.         {
  7.                 std::cout << "AllocConsole Address in kernel32.dll: " << kernel32.dll 载入基地址 + pdwFunctionsTable[pwOrdTable[i]] << std::endl;
  8.         }
  9. }
复制代码

最后以一张图理清楚其中的关系:



本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|小黑屋|DecoyMini 技术交流社区 ( 京ICP备2021005070号 )

GMT+8, 2024-6-23 00:35 , Processed in 0.058633 second(s), 22 queries .

Powered by Discuz! X3.4

Copyright © 2001-2023, Tencent Cloud.

快速回复 返回顶部 返回列表