上篇文章 PE文件结构详解(二)可执行文件头 的结尾出现了一个大数组,这个数组中的每一项都是一个特定的结构,通过函数获取数组中的项可以用RtlImageDirectoryEntryToData函数,DataDirectory中的每一项都可以用这个函数获取,函数原型如下:

PVOID NTAPI RtlImageDirectoryEntryToData(PVOID Base, BOOLEAN MappedAsImage, USHORT Directory, PULONG Size);

Base:模块基地址。

MappedAsImage:是否映射为映象。

Directory:数据目录项的索引。

  1. #define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
  2. #define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
  3. #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
  4. #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
  5. #define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
  6. #define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
  7. #define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
  8. //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
  9. #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
  10. #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
  11. #define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
  12. #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
  13. #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
  14. #define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
  15. #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
  16. #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

Size:对应数据目录项的大小,比如Directory为0,则表示导出表的大小。

返回值表示数据目录项的起始地址。

这次来看看第一项:导出表。

导出表是用来描述模块中的导出函数的结构,如果一个模块导出了函数,那么这个函数会被
记录在导出表中,这样通过GetProcAddress函数就能动态获取到函数的地址。函数导出的方式有两种,一种是按名字导出,一种是按序号导出。这两
种导出方式在导出表中的描述方式也不相同。模块的导出函数可以通过Dependency walker工具来查看:

上图中红框位置显示的就是模块的导出函数,有时候显示的导出函数名字中有一些符号,像 ??0CP2PDownloadUIInterface@@QAE@ABV0@@Z,这种是导出了C++的函数名,编译器将名字进行了修饰。

下面看一下导出表的定义吧:

  1. typedef struct _IMAGE_EXPORT_DIRECTORY {
  2. DWORD   Characteristics;
  3. DWORD   TimeDateStamp;
  4. WORD    MajorVersion;
  5. WORD    MinorVersion;
  6. DWORD   Name;
  7. DWORD   Base;
  8. DWORD   NumberOfFunctions;
  9. DWORD   NumberOfNames;
  10. DWORD   AddressOfFunctions;     // RVA from base of image
  11. DWORD   AddressOfNames;         // RVA from base of image
  12. DWORD   AddressOfNameOrdinals;  // RVA from base of image
  13. } IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

结构还算比较简单,具体每一项的含义如下:

Characteristics:现在没有用到,一般为0。

TimeDateStamp:导出表生成的时间戳,由连接器生成。

MajorVersion,MinorVersion:看名字是版本,实际貌似没有用,都是0。

Name:模块的名字。

Base:序号的基数,按序号导出函数的序号值从Base开始递增。

NumberOfFunctions:所有导出函数的数量。

NumberOfNames:按名字导出函数的数量。

AddressOfFunctions:一个RVA,指向一个DWORD数组,数组中的每一项是一个导出函数的RVA,顺序与导出序号相同。

AddressOfNames:一个RVA,依然指向一个DWORD数组,数组中的每一项仍然是一个RVA,指向一个表示函数名字。

AddressOfNameOrdinals:一个RVA,还是指向一个WORD数组,数组中的每一项与AddressOfNames中的每一项对应,表示该名字的函数在AddressOfFunctions中的序号。

第一次接触这个结构的童鞋被后面的5项搞晕了吧,理解这个结构比结构本身看上去要复杂一些,文字描述不管怎么说都显得晦涩,所谓一图胜千言,无图无真相,直接上图:

在上图中,AddressOfNames
指向一个数组,数组里保存着一组RVA,每个RVA指向一个字符串,这个字符串即导出的函数名,与这个函数名对应的是
AddressOfNameOrdinals中的对应项。获取导出函数地址时,先在AddressOfNames中找到对应的名字,比如Func2,他在AddressOfNames中是第二项,然后从AddressOfNameOrdinals中取出第二项的值,这里是2,表示函数入口保存在AddressOfFunctions这个数组中下标为2的项里,即第三项,取出其中的值,加上模块基地址便是导出函数的地址。如果函数是以序号导出的,那么查找的时候直接用序号减去Base,得到的值就是函数在AddressOfFunctions中的下标。

用代码实现如下:

    1. DWORD* CEAT::SearchEAT( const char* szName)
    2. {
    3. if (IS_VALID_PTR(m_pTable))
    4. {
    5. bool bByOrdinal = HIWORD(szName) == 0;
    6. DWORD* pProcs = (DWORD*)((char*)RVA2VA(m_pTable->AddressOfFunctions));
    7. if (bByOrdinal)
    8. {
    9. DWORD dwOrdinal = (DWORD)szName;
    10. if (dwOrdinal < m_pTable->NumberOfFunctions && dwOrdinal >= m_pTable->Base)
    11. {
    12. return &pProcs[dwOrdinal-m_pTable->Base];
    13. }
    14. }
    15. else
    16. {
    17. WORD* pOrdinals = (WORD*)((char*)RVA2VA(m_pTable->AddressOfNameOrdinals));
    18. DWORD* pNames = (DWORD*)((char*)RVA2VA(m_pTable->AddressOfNames));
    19. for (unsigned int i=0; i<m_pTable->NumberOfNames; ++i)
    20. {
    21. char* pNameVA = (char*)RVA2VA(pNames[i]);
    22. if (strcmp(szName, pNameVA) != 0)
    23. {
    24. continue;
    25. }
    26. return &pProcs[pOrdinals[i]];
    27. }
    28. }
    29. }
    30. return NULL;
    31. }

PE文件结构详解(三)PE导出表的更多相关文章

  1. PE文件结构详解(四)PE导入表

    PE文件结构详解(二)可执行文件头的最后展示了一个数组,PE文件结构详解(三)PE导出表中解释了其中第一项的格式,本篇文章来揭示这个数组中的第二项:IMAGE_DIRECTORY_ENTRY_IMPO ...

  2. PE文件结构详解(六)重定位

    前面两篇 PE文件结构详解(四)PE导入表 和 PE文件结构详解(五)延迟导入表 介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的: 在这里 ...

  3. PE文件结构详解(二)可执行文件头

    在PE文件结构详解(一)基本概念里,解释了一些PE文件的一些基本概念,从这篇开始,将详细讲解PE文件中的重要结构. 了解一个文件的格式,最应该首先了解的就是这个文件的文件头的含义,因为几乎所有的文件格 ...

  4. PE文件结构详解(五)延迟导入表

    PE文件结构详解(四)PE导入表讲 了一般的PE导入表,这次我们来看一下另外一种导入表:延迟导入(Delay Import).看名字就知道,这种导入机制导入其他DLL的时机比较“迟”,为什么要迟呢?因 ...

  5. PE文件结构详解(三)

    0x01 前言 上一篇讲到了数据目录表的结构和怎找到到数据目录表(DataDirectory[16]),这篇我们我来讲讲数据目录表后面的另一个结构——区块表. 0x01 区块 区块就是PE载入器将PE ...

  6. PE文件结构详解(一)基本概念

    PE(Portable Execute) 文件是Windows下可执行文件的总称,常见的有DLL,EXE,OCX,SYS等,事实上,一个文件是否是PE文件与其扩展名无关,PE文件可以是任 何扩展名.那 ...

  7. PE文件结构详解

    (注:最左边是文件头的偏移量.) IMAGE_DOS_HEADER STRUCT { +0h WORD e_magic // Magic DOS signature MZ(4Dh 5Ah) DOS可执 ...

  8. 小甲鱼PE详解之输入表(导出表)详解(PE详解09)

    小甲鱼PE详解之输出表(导出表)详解(PE详解09) 当PE 文件被执行的时候,Windows 加载器将文件装入内存并将导入表(Export Table) 登记的动态链接库(一般是DLL 格式)文件一 ...

  9. PE文件详解(三)

    本文转自小甲鱼的PE文件详解系列传送门 PE文件到内存的映射 在执行一个PE文件的时候,windows 并不在一开始就将整个文件读入内存的,二十采用与内存映射文件类似的机制. 也就是说,windows ...

随机推荐

  1. ubuntu 12.04 安装 codeblock 12.11

      原文地址:http://qtlinux.blog.51cto.com/3052744/1136779 参考文章:http://blog.csdn.net/dszsy1990/article/det ...

  2. AOJ 0558 Cheese

    Cheese Time Limit : 8 sec, Memory Limit : 65536 KB チーズ (Cheese) 問題 今年も JOI 町のチーズ工場がチーズの生産を始め,ねずみが巣から ...

  3. UINavigationController的popViewControllerAnimated问题

    UINavigationController是IOS编程中的一个view controller的容器,通过栈管理viewControllers,每一次push操作都将在栈顶添加一个view contr ...

  4. 使用tortoisegit管理git 和 权限验证

    1 安装Git 2 安装Tortoise 3 在git.oschina 或者 github上创建项目 4 Tortoise配置 TortoiseGit 使用扩展名为ppk的密钥,而不是ssh-keyg ...

  5. winform INI文件操作辅助类

    using System;using System.Runtime.InteropServices;using System.Text; namespace connectCMCC.Utils{ // ...

  6. Android开发之计算器(一)界面设计之activity_main布局文件

    <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android=&quo ...

  7. zip生成

    生成zip文件官方网站:http://www.phpconcept.net/pclzip/ 用法一: 1 <?php 2     include_once('pclzip.lib.php'); ...

  8. 51nod1270 数组的最大代价(简单dp)

    ---恢复内容开始--- 1270 数组的最大代价 题目来源: HackerRank 基准时间限制:1 秒 空间限制:131072 KB 分值: 20 难度:3级算法题  收藏  关注 数组A包含N个 ...

  9. PHP中strtotime函数使用方法分享

    在PHP中有个叫做strtotime的函数.strtotime 实现功能:获取某个日期的时间戳,或获取某个时间的时间戳.strtotime 将任何英文文本的日期时间描述解析为Unix时间戳[将系统时间 ...

  10. Oracle 表的访问方式(2)-----索引扫描

    索引扫描(Index scan) 我们先通过index查找到数据对应的rowid值(对于非唯一索引可能返回多个rowid值),然后根据rowid直接从表中得到具体的数据,这种查找方式称为索引扫描或索引 ...