PE文件格式 super版

来源:互联网 发布:小米note3卸载软件 编辑:程序博客网 时间:2024/06/05 19:30
以《逆向工程核心原理》和博客园shadow_leii的内容为基础。
图片
PE(Portable Executable)格式

◆PE是指32位的可执行文件,也称为  PE32
64位windows只是对PE格式做了一些简单的修改,64位的可执行文件称为PE+或PE32+

◆PE结构是一个平面连续的结构(如上图)【和数据恢复学的文件系统差不多】
从DOS头到节区头是  PE头部分,节区合成为  PE体

◆文件中使用  偏移(offset),内存中使用VA(Virtual Adress)来表示位置。

◆Window加载器 (PE加载器) 会浏览整个PE文件,再决定哪些要映射,文件的高偏移位置映射到高内存地址,低偏移位置映射到低内存地址。文件加载到内存时,情况会发生变化
(节区的大小、位置等。)
图片 




←-----------VA


------RVA(相对虚拟地址)

------基地址(Imagebase)
在NT架构中就是句柄

ImageBase+RVA=VA
计算机中为了提高处理文件、内存、网络包的效率,使用“最小基本单位”这一概念。
PE头和各节区后都有一个趋于,成为NULL填充。文件/内存中的节区起始位置应该在各文件/内存的最小单位的倍数上,空白趋于用null填充
◆基地址在NT架构中就是句柄来获取,HMODULEGetModuleHandle(LPCTSTR IpMouduleName)
◆32位windowos中,各进程分配有的4G虚拟内存,因此进程中VA值范围是00000000~FFFFFFFF
 
一。DOS头(IMAGE_DOS_HEADER结构体)[size:64byte]
{
+0hWORDe_magic //Magic DOS signature MZ(4Dh 5Ah)     DOS可执行文件标记
+2h WORDe_cblp//Bytes on last page of file  
+4hWORDe_cp//Pages in file
+6hWORD e_crlc//Relocations
+8hWORDe_cparhdr   //Size of header in paragraphs
+0ahWORD e_minalloc  //Minimun extra paragraphs needs
+0chWORDe_maxalloc  //Maximun extra paragraphs needs
+0ehWORDe_ss  //intial(relative)SS value    DOS代码的初始化堆栈SS
+10hWORDe_sp  //intial SP value               DOS代码的初始化堆栈指针SP
+12hWORDe_csum  //Checksum
+14hWORDe_ip  //    intial IP value                   DOS代码的初始化指令入口[指针IP]
+16hWORDe_cs  //intial(relative)CS value                    DOS代码的初始堆栈入口
+18hWORDe_lfarlc  //File Address of relocation table
+1ahWORDe_ovno        //    Overlay number
+1chWORDe_res[4]  //
Reserved words
+24hWORDe_oemid  //    OEM identifier(for e_oeminfo)
+26hWORD      e_oeminfo   //    OEM information;e_oemid specific 
+29hWORDe_res2[10]   //    
Reserved words
+3chLONG   e_lfanew     //Offset to start of PE header             指向PE文件头
} IMAGE_DOS_HEADER ENDS

结构体中有两个重要的数据成员。
第一个为e_magic,这个必须为MZ,即4D5A。
另一个是e_lfanew,这个成员的值为IMAGE_NT_HEADERS的偏移(指向PE文件头)。
其中,*e_lfanew这个字段的值:   PE Header 在磁盘文件中相对于文件开始的偏移地址.
 图片
图片
注释 :long---4字节相当于dword

 
一。DOS根存[size:不定]
DOS根存是16位的汇编指令,32为系统会自动忽略,但可在debug中运行
图片

图片

图片



二。NT头 (IMAGE_NT_HEADERS)
[size:248byte]15行
 

◆+0h      DWORD Signature;                 //4 bytes PE文件头  标志  :(e_lfanew)->‘PE’
◆+4h      IMAGE_FILE_HEADER FileHeader;        //20 bytes PE文件物理分布的信息
◆+18h    IMAGE_OPTIONAL_HEADER32 OptionalHeader;//224bytes PE文件逻辑分布的信息
图片

(1)。
DWORD Signature;                 [size:20 byte] 标志 : 50 45 00 00-------→PE

(2)。IMAGE_FILE_HEADER FileHeader  [size:20 byte]

 
        typedef struct _IMAGE_FILE_HEADER {
            +04h     WORD    Machine;                //运行平台
            +06h     WORD    NumberOfSections;        //文件区块数目
            +08h     DWORD   TimeDateStamp;            //文件创建日期和时间
            +0ch     DWORD   PointerToSymbolTable;    //指向符号表(主要用于调试)
            +10h     DWORD   NumberOfSymbols;        //符号表中符号个数
            +14h     WORD    SizeOfOptionalHeader;        //IMAGE_OPTIONAL_HEADER32 结构大小
            +16h     WORD    Characteristics;            //文件属性
            } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; 
图片 
1). Machine 代表了CPU的类型  //运行平台
    #define IMAGE_FILE_MACHINE_UNKNOWN           0
    #define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
    #define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
    #define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
    #define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
    #define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
    #define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
    #define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
    #define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
    #define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
    #define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
    #define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
    #define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
    ………………….
    #define IMAGE_FILE_MACHINE_CEE               0xC0EE     
    #define IMAGE_FILE_MACHINE_IA64              0x0200   //Intel x64
    #define IMAGE_FILE_MACHINE_AMD64            0x8664    //x64 
     #define                                                  0x0266 0x0366 0x0466    //MIPS
    #define                                                     0x0248    //APLHA64   
2)       NumberOfSections: 代表区块的数目,区块表紧跟在IMAGE_NT_HEADERS后面, 区块表大概是一个链表结构,链表长度由NumberOfSection的数值决定.
3)       TimeDataStamp: 表明文件的创建时间
4)       SizeOfOptionalHeader: 是IMAGE_NT_HEADERS的另一个子结构IMAGE_OPTIONAL_HEADER的大小 (E0 00即十进制224byte)

5)       Characteristics: 代表文件的属性EXE文件一般是0100h DLL文件一般是210Eh,多种属性可以用或运算同时拥有
                #define IMAGE_FILE_RELOCS_STRIPPED   0x0001 // 重定位信息被移除 
                #define IMAGE_FILE_EXECUTABLE_IMAGE   0x0002 // 文件可执行 
                #define IMAGE_FILE_LINE_NUMS_STRIPPED  0x0004 // 行号被移除 
                #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 符号被移除 
                ……..10 20 80 标志已经过时
                #define IMAGE_FILE_32BIT_MACHINE  0x0100 // 32位机器 
                #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // .dbg文件的调试信息被移除 
                #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400  //如果图像在可移动介质上,请将其复制并从交换文件运行。
                #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800  //如果图像在网络上,请将其复制并从交换文件运行。
                #define IMAGE_FILE_SYSTEM       0x1000 // 系统文件 
                #define IMAGE_FILE_DLL         0x2000 // 文件是一个dll 
                #define IMAGE_FILE_UP_SYSTEM_ONLY    0x4000 // 文件只能运行在单处理器上  
                 
……..8000 标志已经过时

*属性0F 01即10F 属于
32位机器、重定位信息被移除、文件可执行、行号被移除 、符号被移除
 
(3)。 IMAGE_OPTIONAL_HEADER32 OptionalHeader  [size:224 byte]
图片
紧接IMAGE_FILE_HEADER之后,IMAGE_OPTIONAL_HEADER的大小由IMAGE_FILE_HEADER中倒数第二个成员(SizeOfOptionalHeader)指定. IMAGE_OPTIONAL_HEADER结构体如下:
          typedef struct _IMAGE_OPTIONAL_HEADER {
          +18h    WORD    Magic;    //映像文件的状态
          +1Ah    BYTE    MajorLinkerVersion;    //连接器的主版本号
          +1Bh    BYTE    MinorLinkerVersion;    //连接器的次版本号
          +1Ch    DWORD   SizeOfCode;    //代码段的大小,如果有多个代码段则为总和
          +20h    DWORD   SizeOfInitializedData; //初始化数据段大小.如果多个则为总和
          +24h    DWORD   SizeOfUninitializedData;//未初始化数据段大小,.如果多个则为总和.bbs
          +28h    DWORD   AddressOfEntryPoint;    //PE文件入口地址的RAV:OEP = ImageBase + RAV
          +2Ch    DWORD   BaseOfCode;    //代码块起始地址的RVA
          +30h    DWORD   BaseOfData;//数据块的起始地址的RVA
                      //
                      // NT additional fields.
                      //
          +34h    DWORD   ImageBase;    //可执行文件的基址ImageBase
          +38h    DWORD   SectionAlignment; //每一个块必须保证始于这个值的整数倍
          +3Ch    DWORD   FileAlignment; //对齐映射文件部分原始数据 2 or 512 or 64: 默认为512
          +40h    WORD    MajorOperatingSystemVersion;//要求的操作系统的主版本号
          +42h    WORD    MinorOperatingSystemVersion;//要求的操作系统的次版本号
          +44h    WORD    MajorImageVersion;//映像的主版本号
          +46h    WORD    MinorImageVersion;//映像的次版本号
          +48h    WORD    MajorSubsystemVersion;//子系统的主版本号
          +4Ah    WORD    MinorSubsystemVersion;//子系统的次版本号
          +4Ch    DWORD   Win32VersionValue;//保留值.必须为0
          +50h    DWORD   SizeOfImage;//映像文件的大小
          +54h    DWORD   SizeOfHeaders;
          +58h    DWORD   CheckSum;//映像文件的校验和
          +5Ch    WORD    Subsystem;//运行此映像的字系统
          +5Eh    WORD    DllCharacteristics;//映像文件的DLL特征
          +60h    DWORD   SizeOfStackReserve;//堆栈保留字节. 0x100000
          +64h    DWORD   SizeOfStackCommit;//线程开始提交的初始堆栈大小
          +68h    DWORD   SizeOfHeapReserve;//为初始进程保留的虚拟内存总数
          +6Ch    DWORD   SizeOfHeapCommit;//进程开始提交初始虚拟内存的大小
          +70h    DWORD   LoaderFlags;
          +74h    DWORD   NumberOfRvaAndSizes; //0x10
          +78h    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
          //指向第一个IMAGE_DATA_DIRECTORY
          } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;  

在IMAGE_OPTIONAL_HEADER32结构体中需要关注的成员。这些值是文件运行必须的,设置错误会导致文件无法正常运行。
图片

1) Magic:32位可执行文件来:0x010B

         64位可执行文件来:0x020B 
2) AddressOfEntryPoint: 程序执行的入口RVA地址 (指程序最先执行的代码位置)
3) ImageBase: 建议的装载地址 
进程虚拟内存的范围00000000~FFFFFFFF,EXE、DLL文件被装载到用户内存0~07FFFFFF,SYS文件被装载到内核内存80000000~FFFFFFFF。一般而言,一般开发工具(VB/VC++/Delphi),创建的EXE文件ImageBase为00400000,DLL为10000000

执行PE文件时,PE装载器先创建进程,再将文件载入内存,然后保存EIP=ImageBase+AddressOfEntryPoint
4)SectionAlignment:为内存中节的对齐大小,一般为0×00001000  FileAlignment:为PE文件中节的对齐大小(200??)
        也就是说,每个节被装入的地址必定是本字段指定数值的整数倍。
5) SizeOfImage:映像文件的大小 (虚拟内存中大小)
6)
SizeOfHeaders:整个PE头的大小。该值必须为FileAlignment的整数倍。(文件开始到第一节区的偏移量)
7)Subsystem字段:区分系统驱动文件sys和普通可执行文件exe、dll
        1        系统驱动(sys)
        2        窗口应用程序(notepad.exe)
        3        控制台应用程序(cmd)
8)
NumberOfRvaAndSizes:指定datadirectory的数组个数
9) 
DataDirectory为数据目录表数组,比较重要:共有16个表项


Size = sizeof(_IMAGE_DATA_DIRECTORY) * 16

sizeof(_IMAGE_DATA_DIRECTORY) = 8 bytes

_IMAGE_DATA_DIRECTORY结构体以及成员定义:


         typedef struct _IMAGE_DATA_DIRECTORY {

               DWORD   VirtualAddress;

               DWORD   Size;

           } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

           #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

           // Directory Entries

           #define IMAGE_DIRECTORY_ENTRY_EXPORT            0   // Export Directory

           #define IMAGE_DIRECTORY_ENTRY_IMPORT             1   // Import Directory

           #define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory

           #define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory

           #define IMAGE_DIRECTORY_ENTRY_SECURITY          4   // Security Directory

           #define IMAGE_DIRECTORY_ENTRY_BASERELOC        5   // Base Relocation Table

           #define IMAGE_DIRECTORY_ENTRY_DEBUG             6   // Debug Directory

           //      IMAGE_DIRECTORY_ENTRY_COPYRIGHT        7   // (X86 usage)

           #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE  7   // Architecture Specific Data

           #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR         8   // RVA of GP

           #define IMAGE_DIRECTORY_ENTRY_TLS                 9   // TLS Directory

           #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10 // Load Configuration Directory

           #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT     11 // Bound Import Directory in headers

           #define IMAGE_DIRECTORY_ENTRY_IAT              12   // Import Address Table

           #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT    13   // Delay Load Import Descriptors

           #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor 
图片
 图片

三。节区头(IMAGE_SECTION_HEADER)
[size:40byte]两行半

Section Header 数量:_IMAGE_FILE_HEADER结构体中NumberOfSections成员。

Section Header 定位:紧跟在IMAGE_NT_HEADERS后面

 #define IMAGE_SIZEOF_SHORT_NAME   8
  typedef struct _IMAGE_SECTION_HEADER {
      dword    Name[IMAGE_SIZEOF_SHORT_NAME];
      union {
              DWORD   PhysicalAddress;
              DWORD   VirtualSize;
      } Misc;
      DWORD   VirtualAddress;    //内存中偏移地址
      DWORD   SizeOfRawData;    //PE文件中对其之后的大小
      DWORD   PointerToRawData;//为PE块区在PE文件中偏移
      DWORD   PointerToRelocations;
      DWORD   PointerToLinenumbers;
      WORD    NumberOfRelocations;
      WORD    NumberOfLinenumbers;
      DWORD   Characteristics;    //块区的属性:可读、可写..
  } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 
  #define IMAGE_SIZEOF_SECTION_HEADER          40
重要数据成员
1)      Name[IMAGE_SIZEOF_SHORT_NAME]:

这是一个由8位的ASCII 码名,用来定义区块的名称。(其实并没有限制必须使用ascii,可以放任何值,甚至可以用null填充)

多数区块名都习惯性以一个“.”作为开头(例如:.text),这个“.” 实际上是不是必须的。

值得我们注意的是,如果区块名超过 8 个字节,则没有最后的终止标志“NULL” 字节。 
2)      Virtual Size:表对应的区块的大小,这是区块的数据在没有进行对齐处理前的实际大小。
3)      Virtual Address:该区块装载到内存中的RVA 地址。
4)      SizeOfRawData:该区块在磁盘中所占的大小。
5)      PointerToRawData:该区块在磁盘中的偏移。
6)      Characteristics:该区块的属性。(如代码/数据/可读/可写等)的标志。

 图片
图片 




参考RVA to RWA (内存地址与文件偏移间的映射)[偏移是在硬盘的概念]
Windows 装载器在装载DOS部分、PE文件头部分和节表(区块表)部分是不进行任何特殊处理的,而在装载节(区块)的时候则会自动按节(区块)的属性做不同的处理。 
步骤:
1)查找RVA所在节区(.text / .rsrc /.data) 
2)根据公式计算:
RAW-PointToRawData =RVA-VirtualAddress
    RAW=
RVA-VirtualAddress+PointToRawData 
**(这个
VirtualAddress是节区头的VirtualAddress,其实就是距离基地址的RVA)

四。IAT(Import Address Table)在块里面
IAT保存的内容与window操作系统的核心进程、内存、dll结构相关
IAT是一种表格,用来记录程序正在使用哪些库,哪些函数

------------------------------------------------------------------------------------------------------------------------------------------
参考:DLL“动态链接库”,需要的时候调用
◆加载DLL方式
1.“显式链接”,程序使用DLL时加载,使用完毕后释放内存
2.“隐式链接 ”,程序开始的时候加载DLL,程序终止时释放占用的内存
◆所有API调用均采用通过某处地址的值来实现,(如:call dword ptr DS:[01001104],就是通过DS:[0111104]的值,该值就是加载DLL的函数地址
*因为系统的不同(dos、xp、9x、vista、7等) kernel32.dll的版本各不相同,对应的函数地址都不同,因此为了兼容,将个版本确切的函数地址存在某个特定地址,如编译器把CreatFileW()函数的实际地址存在了01001104,并通过
call dword ptr DS:[01001104]来调用,编译器并不存在call7C8107F0(某个版本CreatFileW()函数的实际地址)
还有原因是重定向,DLL加载到内存中,位置可能因前一个dll占用了而重定向,实际中无法保证dll被加载到指定的ImageBase(但exe却能准确加载到自身
ImageBase,还有一个原因是,PE头中表示地址不实用VA而是RVA(相对虚拟地址) 
 ------------------------------------------------------------------------------------------------------------------------------------------

IMAGE_IMPORT_DESCRIPTOR    [SIZE:20 bytes]
(记录这PE要导入哪些库文件,向库提供服务/函数)
执行一个普通程序时往往需要导入多个库,导入多少库就存在多少个 
IMAGE_IMPORT_DESCRIPTOR结构体
       typedef struct _IMAGE_IMPORT_DESCRIPTOR {
            union {
               。。。。
                DWORD   OriginalFirstThunk; // RVA 指向INT (IMAGE_IMPORT_BY_NAME)
               };
                DWORD   TimeDateStamp;    
                DWORD   ForwarderChain;     // -1 if no forwarders
                DWORD   Name;            //dll 名称
                DWORD   FirstThunk;         //指向引入函数真实地址单元处的RVA  IAT(IMAGE_ADDRESS_TABLE)
                    } IMAGE_IMPORT_DESCRIPTOR;
        typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

*其中OriginalFirstThunk和FirstThunk非常类似,指向两个本质上相同的数组IMAGE_THUNK_DATA。
INT IMAGE_IMPORT_BY_NAME
图片
IAT输入顺序 :
1.读取IID的Name成员,获取库名称字符串(“kernel32.dll”)
2.装载相应库。
→LoadLibrary(“kernel32.dll” 
3.读取IID的OriginalFirstThunk成员,获取INT地址
4.逐一读取INT中数组的值,获取相应 
IMAGE_IMPORT_BY_NAME地址(RVA)
5.使用 
IMAGE_IMPORT_BY_NAME的Hint(ordinal)或者Name项,获取相应函数的起始地址
             
→GetProcAddress(GetCurrentThreadld)
6.读取IID的FirstThunk(IAT)成员,获取IAT地址
7.重复4~7直到INT结束(遇到NULL时) 

定位查找IMAGE_IMPORT_DESCRIPTO结构
图片

图片
图片 

在可选头看到importRVA 44 34
而4434位于rdata,rdata位于404000(在od里看到)
在节区头rdata看到pointertorawdata为4000 
所以RAW=RVA-VirtualAddress+PointToRawData
4434-4000+4000=4434 
图片
阴影部分即IID ,IID的大小为20字节

看第一个
OriginalFirstThunk 70 44 00 00 
即4470RVA
4470位于rdata,rdata位于404000(在od里看到)

在节区头rdata看到pointertorawdata为4000
所以
4470-4000+4000=4470
 图片
第一个值为46F0(RVA)进入该地址可以看到导入的API函数字符串 
图片

看最后一个FirstThunk:4000(RVA)转为RAW即4000
图片 

 
【*内容都可以在PE view看到】
 图片

获取dll调用的所有函数(就是用上面的INT和IAT来看函数)
IMAGE_IMPORT_DESCRIPTOR中的第一个参数和最后一个参数,original_first_thunk 和first_thunk分别指向了INT(输入名称表)IAT(输入地址表)这两个表里面分别记录了指向调用函数名的地址,和此函数在dll中的序号(序号用来快速索引dll中的函数) 

 需要注意的地方

INT 和IAT数组在一开始的时候,里面存放的地址都是一样的,他们都是指向所调用函数的名字的字符串。而在加载到内存的时候,IAT的值会发生变换,它的值存放的是dll中函数实际被调用的地址,在加载到内存后,就只需要保存IAT就可以了,利用它来调用函数

 


四。EAT(IMAGE_EXPORT_DIRECTORY)
1.可以在IMAGE_OPTIONAL_HEADER找到EXPORT TABLE的RVA
图片
结构
IMAGE_EXPORT_DIRECTORY STRUCT
Characteristics    DWORD?; 未使用,总是定义为0
TimeDateStamp    DWORD?       ; 文件生成时间
MajorVersion    WORD?; 未使用,总是定义为0
MinorVersion    WORD?; 未使用,总是定义为0
Name            DWORD?; 模块的真实名称
Base                    DWORD?; 基数,加上序数就是函数地址数组的索引值
NumberOfFunctionsDWORD?; 导出函数的总数
NumberOfNamesDWORD?; 以名称方式导出的函数的总数
AddressOfFunctionsDWORD?; 指向输出函数地址的RVA
AddressOfNamesDWORD?; 指向输出函数名字的RVA
AddressOfNameOrdinalsDWORD?; 指向输出函数序号的RVA
IMAGE_EXPORT_DIRECTORY ENDS
重要成员
NumberOfFunctions                            实际export函数的个数
NumberOfNames                       export 函数中具名的函数个数
AddressOfFunctions                    export函数地址数组(数组元素个数=NumberOfFunctions)
AddressOfNames                                函数名称地址数组(数组元素个数=NumberOfNames)
AddressOfNameOrdinals                    Ordinal地址数组(数组元素个数=NumberOfNames)

图片 

如何获得函数地址?
GetProcAddress()操作原理
1.利用“AdressOfName”成员转到“函数名称数组”                             [4个字节组成的数组]
2. 
“函数名称数组” 存储着字符串地址。通过比较(stcmp)字符串,查找指定的函数名称(此时数组的索引称为name_index)
    [索引到是第0、1、2、3。。。。个]
3. 利用AddressOfNameOrdinals成员,转到Ordinal数组                   
4.在Ordinal数组中通过name_index[索引到的0、1、2、3。。。个]查找相应ordinal值                [2个字节组成的数组][00 00 01 00 02 00 03 00 04 00这样排序的数组]
5.利用AddressOfFunctions成员转到“函数地址数组(EAT)”                    [4个字节组成的数组]
6.在函数地址数组中将刚刚求的的ordinal用作数组索引,获得指定函数的起始地址。

理解:如 通过AdressOfName找到第2个是目标数组,到AddressOfNameOrdinals数组找2个数组,记住该值,在AddressOfFunctions数组用该值找  函数的地址

对于没有函数名称的导出函数,可以通过
Ordinals查找它们的地址。从Ordinals值中减去IMAGE_EXPORT_DIRECTORY的base值得到一个数,使用该数作为“函数地址数组”的索引,即可查到相应的函数地址。

最后:
1)EAT提到的
ordinal究竟是什么
把ordinal想成目标导出函数的编号就好了,有时有些函数不会对外公开函数名,仅用编号(ordinal) ,导入并使用这类函数的时候,先用ordinal查找相应的函数地址后在调用比如下面示例(1)通过函数名称来获取函数地址,示例(2)则使用函数的ordinal来获取函数地址
示例(1) pFunc=GetProcAddress(    "TestFunc"    )
示例(2)pFunc=GetProcAddress(5);