上篇文章 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. Windows Phone 8开发环境搭建

    开发Windows Phone需要两个工具:Windows Phone SDK 8.0和Visual Studio集成开发工具.我们购买的Visual Studio Ultimate 2012不包括W ...

  2. 【学习笔记】【C语言】函数

    一. 什么是函数 任何一个C语言程序都是由一个或者多个程序段(小程序)构成的,每个程序段都有自己的功能,我们一般称这些程序段为“函数”.所以,你可以说C语言程序是由函数构成的. 比如你用C语言编写了一 ...

  3. 【学习笔记】【C语言】三目运算符

    1.N目运算符 像逻辑非(!).负号(-)这种只连接一个数据的符号,称为“单目运算符”,比如!5.-5.像算术运算符.关系运算符.逻辑运算符这种连接二个数据的负号,称为“双目运算符”,比如6+7.8* ...

  4. ionic2 Navigation实现报错:No component factory found for "MyComponent"

    ionic2 写的代码里面,跳转的时候报了一个 No component factory found for "RechargeSucceed" recharge() { let ...

  5. Codevs 1092 不高兴的津津

    时间限制: 1 s   空间限制: 128000 KB   题目等级 : 白银 Silver 题目描述 Description 津津上初中了.妈妈认为津津应该更加用功学习,所以津津除了上学之外,还要参 ...

  6. POJ 2533

    最长上升子序列裸题在网上看到有两种方法...一种复杂度O(N^2),一种O(NlogN).orz O(N^2): #include<cstdio> #define N 1001 int m ...

  7. 查看某个模块的Tables

    在SE11 中 关于table的F4 help 有一个筛选条件是Package 同时由于不同的模块放在不同的Package中 很容易根据这个条件 获得某个模块的所有Tables     亲测有效  1 ...

  8. C++与Java多态的区别

    多态是指用父指针指向不同子类对象时,调用其共有的函数,不同的子类会有不同的行为.虽然C++和Java都具有多态机制,但是他们的实现不同,使用时的效果也会略有不同. 在C++中 普通函数调用:具体调用哪 ...

  9. 基于ArcGIS API for JavaScript的统计图表实现

    感谢原作者分享:https://github.com/shevchenhe/ChartLayer,在使用的过程中,需要自己进行调试修改,主要还是_draw函数,不同的ArcGIS JS API函数有差 ...

  10. vc列表控件的初始化

    void CManageProcessDlg::InitList() {  m_ListProcess.SetExtendedStyle(m_ListProcess.GetExtendedStyle( ...