上篇文章 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. VC中调用COM组件的方法(转载)

    原文参考:http://hi.baidu.com/mingyueye/item/53ebecd44da76917d80e4449 总结一下在VC中调用COM组件的方法 准备及条件: COM服务器为进程 ...

  2. 几道hihocoder不会做的题

    1.https://hihocoder.com/problemset/problem/1433?sid=970287 boarding passes,不会做,看的别人的代码,现在还不是很理解. 2.  ...

  3. java8个基本类型和它们所占的字节数

    byte : 1字节 short : 2字节 int : 4字节 float :4字节 long : 8字节 double : 8字节 char :2字节 boolean : 1字节 补充说明:在实际 ...

  4. 腾讯云 安全组配置及与MySQL 远程登录失败原因浅析

    前言,知道自己腾讯云服务器安全组配置并在安全组里开放了所有端口的用户可以粗略的看看下边的内容,否则就仔细看看吧. 因为有学习及业务需要,我要在腾讯云上安装了CentOS7.2版本的服务器上安装MySQ ...

  5. WP开发笔记——WP7 SDK使用技巧

    俗话说的好,工欲善其事,必先利其器. 入门WP开发之前,免不了要先接触开发环境和开发工具.使用WP7 SDK进行开发,我们需要掌握SDK的一些实用技巧,以便我们的开发. 一.开启/关闭电脑键盘输入 W ...

  6. Git的学习总结和使用时遇到的问题。

                        git 是一款非常强大的版本控制工具,现在市场占有率应该是一家独大了,以前用svn的童鞋估计都转投git阵营了吧   加上很多公司也用git管理自己的项目,所以 ...

  7. Winform ListView根据条件定位到指定行

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...

  8. 重写DataGridViewColumn

    做个项目需要用到DataGridView,这个控件还是挺好用的,但是今天却发现无法实现自己想要的功能.主要是DataGridViewCheckBoxColumn这个列虽然是提供了复选框,但是却未能在复 ...

  9. 【PHP】phpcms html去除空白

    // 文件路径:/phpcms/libs/classes/template_cache.class.php 42行 // 第四第五行是新增的 $content = $this->template ...

  10. AJAX局部更新演出排期

    <script language="javascript" type="text/javascript"> function createXMLHt ...