• PE文件结构详解(三)PE导出表


    上篇文章 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;  
  • 相关阅读:
    集体编程智慧(发现的一些代码问题)
    jQuery实现图片伦播效果(淡入淡出+左右切换)
    require
    小技巧
    JavaScript--面向对象--猜拳游戏
    简单封装cookie操作
    less
    进程相关

    线程和进程相关
  • 原文地址:https://www.cnblogs.com/milantgh/p/3947427.html
Copyright © 2020-2023  润新知