PE文件格式分析


                                                  PE文件格式分析
                                                   _____pedump程序分析
                                                                                        作者 :   WY.lslrt
                                                                                       E-mail : wy_lslrt@yahoo.com.cn
                                                                                          QQ :   8968569
1. 开发工具VC++6.0
2. 开发环境windows xp

实例程序下载:attachments/pedump.rar 

  PE文件就是一种可执行文件的结构,它里面包含有可执行的代码,资源和一些其它的信息。了解PE文件格式对于很多方面都很重要,程序加壳脱壳,加密解密,可执行文件的压缩,资源的读取与修改。废话少说,下面开始分析一下PE文件。
  首先,得让大家系统地了解一下整个PE文件的结构,下面我画了一个PE文件的结构简图:

PE文件格式简图

  1. ┌─────────────┐
  2. │       MS-DOS             │
  3. │       MZ Header          │
  4. │                          │
  5. ├─────────────┤
  6. │    MS-DOS Real-Mode      │
  7. │       Stub Program       │
  8. │                          │
  9. ├─────────────┤
  10. │    PE File Signature     │
  11. ├─────────────┤
  12. │    PE File Header        │
  13. │                          │
  14. ├─────────────┤
  15. │       PE File            │
  16. │     Optional Header      │
  17. │                          │
  18. ├─────────────┤
  19. │  .text Section header    │
  20. ├─────────────┤
  21. │  .rdata Section header   │
  22. ├─────────────┤
  23. │           .              │
  24. │           .              │
  25. │           .              │
  26. ├─────────────┤
  27. │                          │
  28. │     .text Section        │
  29. │                          │
  30. ├─────────────┤
  31. │                          │
  32. │    .rdata Section        │
  33. │                          │
  34. └─────────────┘

  从这个图中大家可以大概了解了PE文件的整个构成,其中PE File Signature,PE File Header,PE File Optional Header组成了一个结构称为IMAGE_NT_HEADERS。
  PE文件中的第一块部分是MS-DOS Header,它和DOS系统下的可执行文件的第一个结构是一样的,下面是这个结构具体组成:
 
  winnt.h中的定义
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
  第一个域e_magic,它是用来标示MS-DOS可执行文件的一个标志,所有的MS-DOS可执行文件都将此值设为0x5A4D,也就是所说得字符"MZ",这也是为什么叫做MZ Header的原因。在这个结构中,还有一个与对我们有用,e_ifanew,它指向了IAMGE_NT_HEADERS的文件偏移。
  接下来这一个区域是MS-DOS Stub 程序,这是由编译器生成的一个简单的16位的程序,仅仅在DOS下显示字符串"This Program cannot be run in DOS mode",现在来讲已经没有多大的意义。
  紧接下来就是NT Headers了,这一个大的结构中包含了很多PE文件的非常有用的信息,如code和data的定位和大小,该文件运行在什么系列的系统下,堆栈的初始化大小。下面是NT Headers的具体结构
 
  winnt.h中的定义
typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

#ifdef _WIN64
typedef IMAGE_NT_HEADERS64                  IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64                 PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32                  IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32                 PIMAGE_NT_HEADERS;
#endif
  我们只需要看IMAGE_NT_HEADERS32,毕竟64位的系统还不是很普及。
  首先第一块就是PE File Signature,这是个DWORD的域,它其实是一个四字节的字符串,PE文件中,这个值为"PE/0/0",16位的NE文件则为"NE",LE文件则为"LE",下面说一下如何从文件中获得这个值:
 HANDLE     WY_hPEFile;
 IMAGE_DOS_HEADER   WY_MZDosHeader;
 IMAGE_NT_HEADERS   WY_NTHeader;
 //打开一个文件
 WY_hPEFile = CreateFile(WY_strFile,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); 
 //读取第一个结构
 ReadFile(WY_hPEFile,(LPVOID*)&WY_MZDosHeader,sizeof(IMAGE_DOS_HEADER),&WY_dwRetValue,NULL);
 //判断是否为可执行文件
 if(WY_MZDosHeader.e_magic != 0x5a4d)
 {
  MessageBox((LPCTSTR)"这不是可执行文件"); 
  return;
 }
 //是可执行文件
 //读取IMAGE_NT_HEADERS
 
 //设置文件指针,NT File Headers的位置可以通过e_lfanew加上文件指针得到
 WY_dwRetValue = SetFilePointer(WY_hPEFile,(LONG)(WY_MZDosHeader.e_lfanew),NULL,FILE_BEGIN);

 //读取IMAGE_NT_HEADER
 ReadFile(WY_hPEFile,(LPVOID*)&WY_NTHeader,sizeof(IMAGE_NT_HEADERS),&WY_dwRetValue,NULL);
 //读取后该结构的第一个域就是PE File Signature了
 //输出Signature
 CString  WY_strOpMsg //负责初始化输出字符串
 CListBox WY_ctrPEInfo //ListBox的控制对象
 //输出Signature
 WY_strOpMsg.Format("PE文件标志:Signature : 0x%x",WY_NTHeader.Signature);
 WY_ctrPEInfo.AddString(WY_strOpMsg);
  紧接着PE File Signature的就是PE File Header了,这个结构包含了整个文件最基本的信息,下面是具体结构:

  winnt.h中的定义

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
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_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
 
 //输出工作的处理器类型machine
 switch(WY_NTHeader.FileHeader.Machine)
 {
 case 0x14d:
  WY_strOpMsg.Format("工作于处理器类型: Inter i860");
  break;
 case 0x14c:
  WY_strOpMsg.Format("工作于处理器类型: Inter i386");
  break;
 case 0x162:
  WY_strOpMsg.Format("工作于处理器类型: MIPS R3000");
  break;
 case 0x166:
  WY_strOpMsg.Format("工作于处理器类型: MIPS R4000");
  break;
 case 0x183:
  WY_strOpMsg.Format("工作于处理器类型: DEC Alpha AXP");
  break;
 default:
  WY_strOpMsg.Format("工作于处理器类型: Unkown");
  break;
 }
 WY_ctrPEInfo.AddString(WY_strOpMsg);

NumberOfSections : 这个很重要,这个是该文件中的Section的个数,待会这个要用到很多。

 //输出section数目
 WY_strOpMsg.Format("Section 数目:NumberOfSections : %d个",WY_NTHeader.FileHeader.NumberOfSections);
 WY_ctrPEInfo.AddString(WY_strOpMsg);

TimeDateStamp : 这是由连接器生成的时间,是距December 31st,1969 at 4:00 P.M.的秒数。
PointerToSymbolTable : 这是COFF 符号表的文件偏移,这个域只用于OBJ文件和有COFF调试信息的PE文件。在IMAGE_DIRECTORY_ENTRY_DEBUG结构中可以获得更多的调试信息
NumberOfSymbols : 这是SymbolTable的个数

 WY_strOpMsg.Format("COFF 符号表 offset:PointerToSymbolTable : 0x%x",WY_NTHeader.FileHeader.PointerToSymbolTable);
 WY_ctrPEInfo.AddString(WY_strOpMsg);
 WY_strOpMsg.Format("符号表个数:NumberOfSymbols : 0x%x",WY_NTHeader.FileHeader.NumberOfSymbols);
 WY_ctrPEInfo.AddString(WY_strOpMsg);

SizeOfOptionalHeader : 这个值是和 IMAGE_OPTIONAL_HEADER的大小是一样的
Characteristics : 这个是PE文件的属性

#define IMAGE_FILE_RELOCS_STRIPPED           0x0001  // Relocation info stripped from file.
#define IMAGE_FILE_EXECUTABLE_IMAGE          0x0002  // File is executable  (i.e. no unresolved externel references).
#define IMAGE_FILE_LINE_NUMS_STRIPPED        0x0004  // Line nunbers stripped from file.
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED       0x0008  // Local symbols stripped from file.
#define IMAGE_FILE_AGGRESIVE_WS_TRIM         0x0010  // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE       0x0020  // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO         0x0080  // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE             0x0100  // 32 bit word machine.
#define IMAGE_FILE_DEBUG_STRIPPED            0x0200  // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   0x0400  // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP         0x0800  // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM                    0x1000  // System File.
#define IMAGE_FILE_DLL                       0x2000  // File is a DLL.
#define IMAGE_FILE_UP_SYSTEM_ONLY            0x4000  // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI         0x8000  // Bytes of machine word are reversed.

  这里有很多,我们只需要了解几个比较常用的,IMAGE_FILE_RELOCS_STRIPPED这个是文件需要重定位,IMAGE_FILE_EXECUTABLE_IMAGE这是可执行文件的标志,不是lib或obj,
IMAGE_FILE_SYSTEM 这是个系统文件标志,IMAGE_FILE_DLL这个就表明这个文件是个动态链接库dll了。
  其实一个文件的属性并不是单一的,它有可能是上面的几种组合,但是上面的定义很巧妙WORD是个16位的数据,它上面15种属性每种只是让其中1位置1,这样15种叠加起来不管哪几样相加,都不会重复,所以我们去分析时,只需对特定位置上的1bit进行比较,就可以得到它是哪种属性,下面就给出具体方法

 WY_strOpMsg.Format("PE文件属性:Characteristics : 0x%x",WY_NTHeader.FileHeader.Characteristics);
 WY_ctrPEInfo.AddString(WY_strOpMsg);
 WY_wPECharacter = WY_NTHeader.FileHeader.Characteristics & 0x1000; //获得第13位
 //判断文件属性
 if(WY_wPECharacter == 0x1000) //如果第13位为1,则是系统文件
 {
  WY_ctrPEInfo.AddString((LPCTSTR)"            系统文件");
 }
 else
 {
  WY_wPECharacter = WY_NTHeader.FileHeader.Characteristics & 0x2000;//获得第14位
  if(WY_wPECharacter == 0x2000) //如果第14位为1,则是DLL文件
  {
   WY_ctrPEInfo.AddString((LPCTSTR)"            DLL 文件");
  }
  else
  {
   WY_wPECharacter = WY_NTHeader.FileHeader.Characteristics & 0x0002; //获得第2位
   if(WY_wPECharacter == 0x0002) //如果第2位为1的,则是可执行文件
   {
    WY_ctrPEInfo.AddString((LPCTSTR)"            可执行文件");
   }
   else
   {
    WY_ctrPEInfo.AddString("            Unkown");
   }
  }
 }
为什么要从高位到低位呢,因为DLL作为动态链接库,它也是可执行的,所以要实现判断低位,那么就要误判了。
  紧接着FILE Header的是Optional Header 其实这个结构并不像它名字那样,而且是必须有的。下面是具体结构
  winnt.h中的定义
typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
这里我们只看32位的就行了。

Magic : 通常是0x10B
MajorLinkerVersion MinorLinkerVersion是链接器的version。
SizeOfCode,SizeofInitializedData分别是代码段的大小,数据段的大小,因为代码段经常只有一个,通常这个值和.text的大小是一样的
SizeOfUninitializedData : 这个大小是加载器在虚拟内存中为这些UninitializedData节划分的大小,并不代表示在文件中的大小。
AddressOfEntryPoint : 这个域指出了程序的进入点,加载器通过它可以知道,整个程序加载完成之后从哪里开始运行。这是一个RVA。在这里,要说一下RVA,它是Relative Virtual Address的缩写,也就是说它只在虚拟地址中才起作用,它是相对于进程的基地址的,如果进程被加载到基址0x00400000,如果一个RVA等于0x1000,那么这个实际的虚拟地址就应该是0x00401000,就好像MS-DOS Header中的e_ifanew是文件的偏移地址,那么它实际的地址是文件基质+e_ifanew。
BaseOfCode : 这也是个RVA,告诉加载器,代码段要加载到什么地方
BaseOfData : 这个同上。


ImageBase : 该域告诉加载器这个PE文件需要映射到内存的何处。也是基地址。可执行程序通常是0x10000,DLL通常为0x400000.
SectionAlignment : 内存对齐密度,通常为0x1000,就是4kB对齐,这和windows的内存管理有关。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。
FileAlignment : 这个上面那个类似,不过是在文件中的对齐密度,通常是0x200就是512,应该适合扇区大小有关系吧。
  中间几个major/minor...version没有多大的用处,就是版本号,从命名规则上应该都能知道是什么
Win32VersionValue这个域没有用
SizeOfImage : 内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。
SizeOfHeaders : 是所有头+节表的大小,也就等于文件尺寸减去文件中所有节的尺寸。
SubSystem : 这个是指明这个可执行文件是是什么样的可执行文件,是控制台,还是GUI的。

#define IMAGE_SUBSYSTEM_UNKNOWN              0   // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE               1   // Image doesn't require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI          2   // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI          3   // Image runs in the Windows character subsystem.(就是控制台程序)
#define IMAGE_SUBSYSTEM_OS2_CUI              5   // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI            7   // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS       8   // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       9   // Image runs in the Windows CE subsystem.

 //判断程序类型
 switch(WY_NTHeader.OptionalHeader.Subsystem)
 {
 case 0:
  WY_ctrPEInfo.AddString("Unknown subsystem.");
  break;
 case 1:
  WY_ctrPEInfo.AddString("Image doesn't require a subsystem.");
  break;
 case 2:
  WY_ctrPEInfo.AddString((LPCTSTR)"Windows GUI 程序.");
  break;
 case 3:
  WY_ctrPEInfo.AddString((LPCTSTR)" Windows 控制台程序.");
  break;
 case 5:
  WY_ctrPEInfo.AddString((LPCTSTR)"OS/2 控制台程序.");
  break;
 case 7:
  WY_ctrPEInfo.AddString((LPCTSTR)"Posix 控制台程序.");
  break;
 case 8:
  WY_ctrPEInfo.AddString((LPCTSTR)"Win9x driver.");
  break;
 case 9:
  WY_ctrPEInfo.AddString((LPCTSTR)"Windows CE 程序.");
  break;
 default:
  WY_ctrPEInfo.AddString("Unknown subsystem.");
  break;
 }

SizeOfStackReserve : 为初始化线程堆栈(<<windows核心编程>>中有讲)保留的虚拟内存空间,默认大小是0x100000(1MB).
SizeOfStackCommit : 初始化线程堆栈的大小,默认是0x1000(1个内存分页大小)。
SizeOfHeapReserve : 为初始化进程堆的保留虚拟内存空间。
SizeOfHeapCommit : 初识化进程堆的大小,默认也是一个分页大小。
DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] : 是16个IMAGE_DATA_DIRECTORY 结构数组。每个结构数组给出一个重要数据结构的RVA,比如引入地址表等。

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

有用的是VirtualAddress这是一个RVA但是它可以帮助我们确定该Data Directory在哪一个Section内。

  紧接着的Optional Header得是一组Section Headers它的个数是由File Header中的NumberOfSecions确定的,下面是Section Header的具体结构
  winnt.h中的定义

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
            DWORD   PhysicalAddress;
            DWORD   VirtualSize;
    } Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;

Name : 指出该节的名称。
VirtualSize : 指出了该节在未作对齐操作前的大小。
VirtualAddress : 字段是本节的起始RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。而且这个地址是按照内存页对齐的,它的值总是SectionAlignment的值的整数倍。
SizeOfRawData : 是经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(注: 假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。该值等于VirtualSize字段的值按照FileAlignment字段的值对齐后的大小。
PointerToRawData : 这是节基于文件的起始偏移量,是从文件头(文件的第一个字节)开始算起的值。PE装载器通过本域值找到节数据在文件中的位置。
Characteristics : 这个是这个Section的属性,定义如下,分析方法和PE文件的属性分析方法一样。


#define IMAGE_SCN_TYPE_NO_PAD                0x00000008  // Reserved.

#define IMAGE_SCN_CNT_CODE                   0x00000020  // Section contains code.
#define IMAGE_SCN_CNT_INITIALIZED_DATA       0x00000040  // Section contains initialized data.
#define IMAGE_SCN_CNT_UNINITIALIZED_DATA     0x00000080  // Section contains uninitialized data.

#define IMAGE_SCN_LNK_OTHER                  0x00000100  // Reserved.
#define IMAGE_SCN_LNK_INFO                   0x00000200  // Section contains comments or some other type of information.
#define IMAGE_SCN_LNK_REMOVE                 0x00000800  // Section contents will not become part of image.
#define IMAGE_SCN_LNK_COMDAT                 0x00001000  // Section contents comdat.

#define IMAGE_SCN_NO_DEFER_SPEC_EXC          0x00004000  // Reset speculative exceptions handling bits in the TLB entries for this section.
#define IMAGE_SCN_GPREL                      0x00008000  // Section content can be accessed relative to GP
#define IMAGE_SCN_MEM_FARDATA                0x00008000
#define IMAGE_SCN_MEM_PURGEABLE              0x00020000
#define IMAGE_SCN_MEM_16BIT                  0x00020000
#define IMAGE_SCN_MEM_LOCKED                 0x00040000
#define IMAGE_SCN_MEM_PRELOAD                0x00080000

#define IMAGE_SCN_ALIGN_1BYTES               0x00100000  //
#define IMAGE_SCN_ALIGN_2BYTES               0x00200000  //
#define IMAGE_SCN_ALIGN_4BYTES               0x00300000  //
#define IMAGE_SCN_ALIGN_8BYTES               0x00400000  //
#define IMAGE_SCN_ALIGN_16BYTES              0x00500000  // Default alignment if no others are specified.
#define IMAGE_SCN_ALIGN_32BYTES              0x00600000  //
#define IMAGE_SCN_ALIGN_64BYTES              0x00700000  //
#define IMAGE_SCN_ALIGN_128BYTES             0x00800000  //
#define IMAGE_SCN_ALIGN_256BYTES             0x00900000  //
#define IMAGE_SCN_ALIGN_512BYTES             0x00A00000  //
#define IMAGE_SCN_ALIGN_1024BYTES            0x00B00000  //
#define IMAGE_SCN_ALIGN_2048BYTES            0x00C00000  //
#define IMAGE_SCN_ALIGN_4096BYTES            0x00D00000  //
#define IMAGE_SCN_ALIGN_8192BYTES            0x00E00000  //

#define IMAGE_SCN_LNK_NRELOC_OVFL            0x01000000  // Section contains extended relocations.
#define IMAGE_SCN_MEM_DISCARDABLE            0x02000000  // Section can be discarded.
#define IMAGE_SCN_MEM_NOT_CACHED             0x04000000  // Section is not cachable.
#define IMAGE_SCN_MEM_NOT_PAGED              0x08000000  // Section is not pageable.
#define IMAGE_SCN_MEM_SHARED                 0x10000000  // Section is shareable.
#define IMAGE_SCN_MEM_EXECUTE                0x20000000  // Section is executable.
#define IMAGE_SCN_MEM_READ                   0x40000000  // Section is readable.
#define IMAGE_SCN_MEM_WRITE                  0x80000000  // Section is writeable.

下面给出的是获得所有Section Header的代码

 IMAGE_SECTION_HEADER  WY_SectionHeader[26];
 WORD    WY_wSecNum;

 WY_wSecNum = WY_NTHeader.FileHeader.NumberOfSections;
 for(WY_i = 0;WY_i < WY_wSecNum;WY_i++) //循环获得Section Headers
 {
  ReadFile(WY_hPEFile,(LPVOID)(WY_SectionHeader+WY_i),sizeof(IMAGE_SECTION_HEADER),&WY_dwRetValue,NULL);
 }
 for(WY_i = 0;WY_i < WY_wSecNum;WY_i++) //输出每个Section的信息
 {
  WY_strOpMsg.Format("Section名称:Name : %s",WY_SectionHeader[WY_i].Name);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  if(WY_strOpMsg.Find("UPX") != -1)  //如果Section的Name中含有UPX,那么很有可能被UPX加过壳了。
  {
   WY_ctrPEInfo.AddString((LPCTSTR)"             UPX加壳");
  }
  WY_strOpMsg.Format("Section大小:SizeOfRawData : %dB",WY_SectionHeader[WY_i].SizeOfRawData);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  WY_strOpMsg.Format("Section属性(具体含义请查看msdn):Characteristics : 0x%x",WY_SectionHeader[WY_i].Characteristics);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  WY_strOpMsg.Format("在文件中的偏移量:PointerToRawData : 0x%x",WY_SectionHeader[WY_i].PointerToRawData);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  WY_strOpMsg.Format("VirtualAddress: 0x%x",WY_SectionHeader[WY_i].VirtualAddress);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  WY_ctrPEInfo.AddString("_______________________________________________________________");
 }
  前面说过Data Directory的结构,通过其中的Virtual Address可以确定在哪一个Section里,这两个Virtual Address都是RVA怎么可以确定呢,因为加载器既然要将这些结构加载到内存中,肯定结构直接肯定有些偏移,既然Data Directory是在某一个Section中的,在内存中的偏移肯定和在文件中的是一样的,所以两个Virtual Address相减便得到了再Section中的,加上PointerToRawData就得到了在文件中的偏移。下面便是计算每一个Data Directory的文件偏移

 DWORD  WY_dwImgDirOffset[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //IMAGE_DATA_DIRECTORY在文件中的偏移量
 int  WY_nSecDirin[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];  //每个Data Directory所在的Section
 //开始计算各个DATA_DIRECTORY的文件偏移量
 for(WY_i = 0;WY_i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES;WY_i++)
 {
  WY_dwTmp = WY_NTHeader.OptionalHeader.DataDirectory[WY_i].VirtualAddress;

  for(WY_j = 0;WY_j < WY_wSecNum;WY_j++) 
  {
   //判断在哪个section里
   if(WY_dwTmp >= WY_SectionHeader[WY_j].VirtualAddress && WY_dwTmp < (WY_SectionHeader[WY_j].VirtualAddress +
                                                                                            WY_SectionHeader[WY_j].SizeOfRawData))
   {
    WY_nSecDirin[WY_i] = WY_j; //在哪一个Section
    break;
   }
  }
  //计算得出文件偏移
  WY_dwImgDirOffset[WY_i] = (WY_dwTmp - WY_SectionHeader[WY_j].VirtualAddress) + WY_SectionHeader[WY_j].PointerToRawData;
  if(WY_dwImgDirOffset[WY_i]){   
   WY_strOpMsg.Format("%s Offset : 0x%x in %s",WY_pchDirName[WY_i],WY_dwImgDirOffset[WY_i],WY_SectionHeader[WY_j].Name);
   WY_ctrPEInfo.AddString(WY_strOpMsg);
  }
 }

  说完了前面所有的重要结构之后,我们就该探讨探讨一下剩下的最主要部分Section里面都有什么些东西,首先说一下资源吧。
  资源,说白了就是一个PE文件所需要展示给用户看得的东西,如图标,位图,对话框,菜单,字串,版本啦一类的东西。资源通常包含在.rsrc Section中,由三种结构IMAGE_RESOURCE_DIRECTORY,IMAGE_RESOURCE_DIRECTORY_ENTRY,IMAGE_RESOURCE_DATA_ENTRY和数据组成的一个树型的逻辑结构
IMAGE_RESOURCE_DIRECTORY
 资源结构见图

  1. ┌──────┐
  2. │            │
  3. │            │
  4. │ ID:0(ROOT) │
  5. │            │
  6. │            │
  7. │Entries Num │
  8. ├──────┤                       ┌──────┐
  9. │    Id      │---------------------> │            │                    
  10. ├──────┤                       │            │
  11. │ Named      │--------               │ ID:2       │
  12. ├──────┤        |              │ (Bitmap)   │
  13. │    Id      │---     |              │            │
  14. ├──────┤   |    |              │ Entries Num│
  15. │  ......    │   |    |              ├──────┤
  16. └──────┘   |    |              │   Named    │--------------
  17.                    |    |              ├──────┤              |
  18.                    |    |              │ ......     │              |
  19.                    |    |              └──────┘              |        ┌──────┐
  20.                    |    |                                            |        │            │
  21.                    |    |                                            |------> │  ID:22     │
  22.                    |    |                                                     │            │
  23.                    |    ---->                                                 │(My Bitmap) │
  24.                    |                                                          │            │
  25.                    ---->                                                      │Entries Num │
  26.                                                                               ├──────┤
  27.                                                                               │  Data Dir  │---------------> ┌──────┐
  28.                                                                               └──────┘                 │            │                                                                                                                                              │   Data Dir │---------->Data
  29.                                                                                                                │            │
  30.                                                                                                                └──────┘


这是我画的一个简图,大家从上图可以看出,资源是以一个IMAGE_RESOURCE_DIRECTORY结构为根,紧接着下面跟着的是几个IMAGE_RESOURCE_DIRECTORY_ENTRY结构,指向了下一层DIRECTORY,总共是三层,第三层指向了IMAGE_RESOURCE_DATA_ENTRY结构,这就是树的叶子了,这个结构包含了数据的位置,大小。下面是这几个结构
  winnt.h中定义
typedef struct _IMAGE_RESOURCE_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    WORD    NumberOfNamedEntries;
    WORD    NumberOfIdEntries;
//  IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;

typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
    union {
        struct {
            DWORD NameOffset:31;
            DWORD NameIsString:1;
        };
        DWORD   Name;
        WORD    Id;
    };
    union {
        DWORD   OffsetToData;
        struct {
            DWORD   OffsetToDirectory:31;
            DWORD   DataIsDirectory:1;
        };
    };
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
    DWORD   OffsetToData;
    DWORD   Size;
    DWORD   CodePage;
    DWORD   Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;
  先来看一下IMAGE_RESOURCE_DIRECTORY,这个结构只有最后两个有用,这两个数目决定了紧跟其后的entry的数目,也就是说决定了下一层Directory的数目。
  IMAGE_RESOURCE_DIRECTORY_ENTRY这个结构看似复杂其实很简单,第一个域,Name,其实它并不是资源的名字,它是名字的文件偏移或者是资源ID,如何判断这就要看它的最高位是否置位,如果置位了那么它的低31位就是指向名字结构的文件偏移,这个偏移可不是对文件头的偏移,也不是RVA,而是相对于这个IMAGE_RESOURCE_DIRECTORY结构起始地址的偏移,如果没有置位,那么它就是这个资源以ID的形式命名,这个域就是这个资源的ID。下一个OffsetToData的偏移也是如此。
  说了上面那么多,如果Name指向的是名字,怎么样去获得这个名字呢?先给出这个名字的结构

  winnt.h中的定义
typedef struct _IMAGE_RESOURCE_DIRECTORY_STRING {
    WORD    Length;
    CHAR    NameString[ 1 ];
} IMAGE_RESOURCE_DIRECTORY_STRING, *PIMAGE_RESOURCE_DIRECTORY_STRING;


typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
    WORD    Length;
    WCHAR   NameString[ 1 ];
} IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;

  其实使用的都是unicode字符,所以我们就用第二个结构。结构第一个域给出的是这个名字字符串的长度,注意,unicode可是两个字节一个字符不管是不是字母还是汉字。这个Length只是有多少个字符,并没有说多少个字节。NameString[1]只是这个名字的第一个字符,其实就是说明了,长度是在字符串开始的前两个字节。我们用(Name & 0x80000000) == 0x80000000(掩去低31位,只比较最高位)是不是指向名字,指向后在(Name & 0x7FFFFFFF) + DirStarAddress(& 0x7FFFFFFF只留下第31位)就得到了在文件中的偏移,然后根据Length,在Length域后面读取Length*2字节,就是名字了。
  再来看看OffsetToData,它指向两种不同的结构,一个是上面所说的IMAGE_RESOURCE_DIRECTORY,另外一个就是IMAGE_RESOURCE_DATA_ENTRY了,当OffsetToData的最高位置位时,它就指向IMAGE_RESOURCE_DIRECTORY,否则指向IMAGE_RESOURCE_DATA_ENTRY了。
  因此要想完整的获得整个资源所有信息,就得将这个资源树遍历一边。方法有两种,一种是递归,这个方法很简单,只不过是当资源过大时,这样就显得有些慢了,我们可以根据这个资源树的一些特性,用非递归实现了,根目录只有一个IMAGE_RESOURCE_DIRECTORY,就不需要循环了,第二层就必须要循环了,要读取紧跟根目录后面Entry然后获得下一层的Directory,循环多少次呢?该根DIRECTORY有两个域,刚才说过了就是两个Number,这是我们循环的总数,我们还需要判断entry里的Name域是否指向名字,其实Named Entry指向的肯定需要名字,Id Entry指向的肯定不需要名字,因此两个循环可以分开写,这样就可以去掉了判断,但是增加了代码长度,看刚才的简图,第二层的Directory一般都是资源的类型,资源的类型总数并不多,所以可以将两个循环写在一起,加上判断。读取第三层的时候,这个时候就到了用户自定的了,这个时候用户资源可能会很多,每一种都可能几十或者上百,这个时候如果将两个循环写开,则就不需要判断了,虽然增加了代码长度,但是只是不多的长度,却省掉了上百次的比较,增加了速度。下面给出具体的代码(给大家提个醒,在CString中Formatunicode字符串的时候,格式符需要用%S,而不是%s)

 DWORD           WY_dwTmp;  //作为临时保存文件偏移量
 DWORD     WY_dwTmp1;  //同上
 DWORD     WY_dwTmp2;  //同上
 IMAGE_RESOURCE_DIRECTORY  WY_tmpRescDir;  //临时保存IMAGE_RESOURCE_DIRECTORY
 IMAGE_RESOURCE_DIRECTORY  WY_tmpRescDir1;  //同上
 IMAGE_RESOURCE_DIRECTORY_ENTRY  WY_tmpRDEntry;  //临时保存Entry
 IMAGE_RESOURCE_DATA_ENTRY  WY_tmpRescDEntry; //临时保存Data Entry
 IMAGE_RESOURCE_DIR_STRING_U  WY_DirStrU;  //获得资源名称。
 WCHAR                                  *WY_RescName = NULL;

 //获得资源信息
 SetFilePointer(WY_hPEFile,(LONG)WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE],NULL,FILE_BEGIN);
 if(!ReadFile(WY_hPEFile,(LPVOID)&WY_RescImg,sizeof(IMAGE_RESOURCE_DIRECTORY),&WY_dwRetValue,NULL))
 {
  return;
 }
 WY_strOpMsg.Format("ROOT: %d个Named Entries %d个ID Entries",WY_RescImg.NumberOfNamedEntries,WY_RescImg.NumberOfIdEntries);
 WY_ctrPEInfo.AddString(WY_strOpMsg);
 //开始循环获得资源,因为是嵌套循环,非递归方法,所以每个for循环里大概都是类似的语句.这里的偏移量都是相对第一个resource dir来说的
 if(WY_RescImg.NumberOfNamedEntries || WY_RescImg.NumberOfIdEntries)     //读取第一层的entry结构
 {
  for(WY_i = 0;WY_i < (WY_RescImg.NumberOfNamedEntries + WY_RescImg.NumberOfIdEntries);WY_i++)//第一层循环
  {
   ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRDEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&WY_dwRetValue,NULL);
   if((WY_tmpRDEntry.Name & 0x80000000) == 0x80000000) //判断最高为是否为1,如果为1则去获得名称
   {
    WY_dwTmp = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);//保存文件指针
    SetFilePointer(WY_hPEFile,(LONG)((WY_tmpRDEntry.Name & 0x7fffffff) +                                                                                                                                                   WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
    ReadFile(WY_hPEFile,(LPVOID)&WY_DirStrU,sizeof(IMAGE_RESOURCE_DIR_STRING_U),&WY_dwRetValue,NULL); //读取DirStrU结构
    WY_RescName = (WCHAR*)malloc(sizeof(WCHAR)*(WY_DirStrU.Length));
    WY_RescName[0] = WY_DirStrU.NameString[0]; //这句和下边一句获取名字
    ReadFile(WY_hPEFile,(LPVOID)(WY_RescName+1),(WY_DirStrU.Length-1)*2,&WY_dwRetValue,NULL);
    SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp,NULL,FILE_BEGIN);//恢复本层指针
   }
   if((WY_tmpRDEntry.OffsetToData & 0x80000000) == 0x80000000) //这指向另外一个IMAGE_RESOURCE_DIRECTORY结构的偏移
   {
    WY_dwTmp = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT); //保存指针,因为指针要跳转,而下个循环还要从这个地址开始读
    SetFilePointer(WY_hPEFile,(LONG)((WY_tmpRDEntry.OffsetToData & 0x7fffffff)+                                                                                                                                           WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
    ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRescDir,sizeof(IMAGE_RESOURCE_DIRECTORY),&WY_dwRetValue,NULL); //读取子Directory结构
    if(WY_RescName)
    {
     WY_strOpMsg.Format("  Name : %S,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",WY_RescName,                                               (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
     free(WY_RescName);
     WY_RescName = NULL;
    }
    else
    {
     switch(WY_tmpRDEntry.Name)
     {
     case 1:
      WY_strOpMsg.Format("  类型:CURSOR,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 2:
      WY_strOpMsg.Format("  类型:BITMAP,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 3:
      WY_strOpMsg.Format("  类型:ICON,  Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 4:
      WY_strOpMsg.Format("  类型:MENU,  Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 5:
      WY_strOpMsg.Format("  类型:DIALOG,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 6:
      WY_strOpMsg.Format("  类型:STRING,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 7:
      WY_strOpMsg.Format("  类型:FONTDIR,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                        (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 8:
      WY_strOpMsg.Format("  类型:FONT,  Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 9:
      WY_strOpMsg.Format("  类型:ACCELERATOR,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                   (WY_tmpRDEntry.Offset ToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 10:
      WY_strOpMsg.Format("  类型:RCDATA,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                         (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 11:
      WY_strOpMsg.Format("  类型:MESSAGETABLE,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                   (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 12:
      WY_strOpMsg.Format("  类型:GROUP_CURSOR,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                   (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 13:
      WY_strOpMsg.Format("  类型:GROUP_ICON,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                     (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 16:
      WY_strOpMsg.Format("  类型:VERSION,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                        (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 17:
      WY_strOpMsg.Format("  类型:DLGINCLUDE,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                     (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 19:
      WY_strOpMsg.Format("  类型:PLUGPLAY,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                       (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 20:
      WY_strOpMsg.Format("  类型:VXD,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",
                                              (WY_tmpRDEntry.OffsetToData  & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 21:
       WY_strOpMsg.Format("  类型:ANICURSOR,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                      (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 22:
      WY_strOpMsg.Format("  类型:ANIICON,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                        (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     case 23:
      WY_strOpMsg.Format("  类型:RT_HTML,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                        (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
      break;
     default:
      WY_strOpMsg.Format("  类型:UNKNOWN,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                        (WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir.NumberOfNamedEntries,WY_tmpRescDir.NumberOfIdEntries);
     }
     
    }
    WY_ctrPEInfo.AddString(WY_strOpMsg);
    if(WY_tmpRescDir.NumberOfNamedEntries) //读取Name Entry   
    {
     for(WY_j = 0;WY_j < WY_tmpRescDir.NumberOfNamedEntries;WY_j++) //第二层循环
     {
      ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRDEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&WY_dwRetValue,NULL);
      if((WY_tmpRDEntry.Name & 0x80000000) == 0x80000000) //判断是否含有名字
      {
       WY_dwTmp1 = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);
       SetFilePointer(WY_hPEFile,(LONG)((WY_tmpRDEntry.Name & 0x7fffffff) +
                                                                                         WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
       ReadFile(WY_hPEFile,(LPVOID)&WY_DirStrU,sizeof(IMAGE_RESOURCE_DIR_STRING_U),&WY_dwRetValue,NULL);
       WY_RescName = (WCHAR*)malloc(sizeof(WCHAR)*(WY_DirStrU.Length));
       WY_RescName[0] = WY_DirStrU.NameString[0];
       ReadFile(WY_hPEFile,(LPVOID)(WY_RescName+1),(WY_DirStrU.Length-1)*2,&WY_dwRetValue,NULL);
       SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp1,NULL,FILE_BEGIN);
      }
      if((WY_tmpRDEntry.OffsetToData & 0x80000000) == 0x80000000) //
      {
       WY_dwTmp1 = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);  //保存本层指针
       SetFilePointer(WY_hPEFile,(LONG)((WY_tmpRDEntry.OffsetToData & 0x7fffffff)+
                                                               WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);//获得子结构的文件偏移地址
       ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRescDir1,sizeof(IMAGE_RESOURCE_DIRECTORY),&WY_dwRetValue,NULL);
       if(WY_RescName)
       {
        WY_strOpMsg.Format("    Resc3 Name : %S,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                                  WY_RescName+1,(WY_tmpRDEntry.OffsetToData & 0x7fffffff),
                                                                              WY_tmpRescDir1.NumberOfNamedEntries,WY_tmpRescDir1.NumberOfIdEntries);
        free(WY_RescName);
        WY_RescName = NULL;
       }
       else
       {
        WY_strOpMsg.Format("    Resc3 ID : %d,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                                                                  WY_tmpRDEntry.Name,(WY_tmpRDEntry.OffsetToData & 0x7fffffff),
                                                                            WY_tmpRescDir1.NumberOfNamedEntries,WY_tmpRescDir1.NumberOfIdEntries);
       }
       WY_ctrPEInfo.AddString(WY_strOpMsg);
       ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRDEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),
                                                                 &WY_dwRetValue,NULL);//读取最后一个entry信息                                                              if(WY_tmpRescDir1.NumberOfIdEntries) //到最后一层了,entry结构中指向的必为data entry结构,所以直接                                                                                                //处理不必判断
       {
        WY_dwTmp2 = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);
        SetFilePointer(WY_hPEFile,(LONG)(WY_tmpRDEntry.OffsetToData +
                                                                               WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
        ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRescDEntry,sizeof(IMAGE_RESOURCE_DATA_ENTRY),                                                                                      &WY_dwRetValue,NULL);
        WY_strOpMsg.Format("       ID : %d,Size : %dB,Offset : 0x%x,                                                                                                                     WY_tmpRDEntry.Name,WY_tmpRescDEntry.Size,WY_tmpRescDEntry.OffsetToData);
        WY_ctrPEInfo.AddString(WY_strOpMsg);
        SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp2,NULL,FILE_BEGIN);
       }
       SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp1,NULL,FILE_BEGIN);
      }
     }
    }
    if(WY_tmpRescDir.NumberOfIdEntries)  //读取Id Entry
    {
     for(WY_j = 0;WY_j < WY_tmpRescDir.NumberOfIdEntries;WY_j++) //第二层循环
     {
      ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRDEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),&WY_dwRetValue,NULL);
      if((WY_tmpRDEntry.OffsetToData & 0x80000000) == 0x80000000) //
      {
       WY_dwTmp1 = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);  //保存本层指针
       SetFilePointer(WY_hPEFile,(LONG)((WY_tmpRDEntry.OffsetToData & 0x7fffffff)+
                                                                       WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
       ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRescDir1,sizeof(IMAGE_RESOURCE_DIRECTORY),&WY_dwRetValue,NULL);
       WY_strOpMsg.Format("    Resc3 ID : %d,Offset : 0x%x,Named Entry : %d个 ID Entry : %d个",                                        WY_tmpRDEntry.Name,(WY_tmpRDEntry.OffsetToData & 0x7fffffff),WY_tmpRescDir1.NumberOfNamedEntries,WY_tmpRescDir1.NumberOfIdEntries);
       WY_ctrPEInfo.AddString(WY_strOpMsg);
       ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRDEntry,sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY),                                                                &WY_dwRetValue,NULL);  //读取最后一个entry信息
       if(WY_tmpRescDir1.NumberOfIdEntries)
       {
        WY_dwTmp2 = SetFilePointer(WY_hPEFile,0,NULL,FILE_CURRENT);
        SetFilePointer(WY_hPEFile,(LONG)(WY_tmpRDEntry.OffsetToData +
                                                                               WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_RESOURCE]),NULL,FILE_BEGIN);
        ReadFile(WY_hPEFile,(LPVOID)&WY_tmpRescDEntry,sizeof(IMAGE_RESOURCE_DATA_ENTRY),                                                                        &WY_dwRetValue,NULL);
        WY_strOpMsg.Format("       ID : %d,Size : %dB,Offset : 0x%x",                                                                      WY_tmpRDEntry.Name,WY_tmpRescDEntry.Size,(WY_tmpRescDEntry.OffsetToData & 0x7fffffff));
        WY_ctrPEInfo.AddString(WY_strOpMsg);
        SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp2,NULL,FILE_BEGIN);
       }
       SetFilePointer(WY_hPEFile,(LONG)WY_dwTmp1,NULL,FILE_BEGIN);
      }
     }
    }
    SetFilePointer(WY_hPEFile,WY_dwTmp,NULL,FILE_BEGIN); //恢复本层文件指针
   }
  }
 }
这一段代码比较长,循环也很多,大家把整个资源的分布搞懂,再来理解这段代码就很轻松了。

   接下来,就该是函数导出表了,大家如果写过dll,那么就很明白什么是函数导出表了。它通常包含在.edata中,下面我简单的说下导出表(如果像更进一步的了解,请参看<<windows核心编程>>)
   当PE装载器执行一个程序,它将相关DLLs都装入该进程的地址空间。然后根据主程序的引入函数信息,查找相关DLLs中的真实函数地址来修正主程序。PE装载器搜寻的是DLLs中的引出函数。
   DLL/EXE要引出一个函数给其他DLL/EXE使用,有两种实现方法: 通过函数名引出或者仅仅通过序数引出。比如某个DLL要引出名为"GetSysConfig"的函数,如果它以函数名引出,那么其他DLLs/EXEs若要调用这个函数,必须通过函数名,就是GetSysConfig。另外一个办法就是通过序数引出。什么是序数呢? 序数是唯一指定DLL中某个函数的16位数字,在所指向的DLL里是独一无二的。例如在上例中,DLL可以选择通过序数引出,假设是16,那么其他DLLs/EXEs若要调用这个函数必须以该值作为GetProcAddress调用参数。这就是所谓的仅仅靠序数引出。不提倡仅仅通过序数引出函数这种方法,这会带来DLL维护上的问题。一旦DLL升级/修改,程序员无法改变函数的序数,否则调用该DLL的其他程序都将无法工作。
   现在我们看一下导出结构。象导出表一样,可以通过数据目录找到导出表的位置。这儿,导出表是数据目录的第一个成员,又可称为IMAGE_EXPORT_DIRECTORY。该结构中共有11个成员,定义如下
  winnt.h中的定义

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

我们来说几个重要的域
Name : 模块的真实名称的RVA。本域是必须的,因为文件名可能会改变。这种情况下,PE装载器将使用这个内部名字。
Base : 基数,加上序数就是函数地址数组的索引值了。
NumberOfFunctions : 模块引出的函数/符号总数。
NumberOfNames : 通过名字引出的函数/符号数目。该值不是模块引出的函数/符号总数,这是由上面的NumberOfFunctions给出。本域可以为0,表示模块可能仅仅通过序数引出。如果模块根本不引出任何函数/符号,那么数据目录中导出表的RVA为0。
AddressOfFunctions : 模块中有一个指向所有函数/符号的RVAs数组,本域就是指向该RVAs数组的RVA。模块中所有函数的RVAs都保存在一个数组里,其实这个RVA只是指像一个数组的起始地址,在数组中,每一个元素的值才是函数的RVA
AddressOfNames : 类似上个域,模块中有一个指向所有函数名的RVAs数组,本域就是指向该RVAs数组的RVA。
AddressOfNameOrdinals : RVA,指向包含上述 AddressOfNames数组中相关函数之序数的16位数组。
  导出表的设计是为了方便PE装载器工作。首先,模块必须保存所有引出函数的地址以供PE装载器查询。模块将这些信息保存在AddressOfFunctions域指向的数组中,而数组元素数目存放在NumberOfFunctions域中。 因此,如果模块引出40个函数,则AddressOfFunctions指向的数组必定有40个元素,而NumberOfFunctions值为40。现在如果有一些函数是通过名字引出的,那么模块必定也在文件中保留了这些信息。这些 名字的RVAs存放在一数组中以供PE装载器查询。该数组由AddressOfNames指向,NumberOfNames包含名字数目。考虑一下PE装载器的工作机制,它知道函数名,并想以此获取这些函数的地址。至今为止,模块已有两个模块: 名字数组和地址数组,但两者之间还没有联系的纽带。因此我们还需要一些联系函数名及其地址的东东。PE参考指出使用到地址数组的索引作为联接,因此PE装载器在名字数组中找到匹配名字的同时,它也获取了 指向地址表中对应元素的索引。 而这些索引保存在由AddressOfNameOrdinals域指向的另一个数组(最后一个)中。由于该数组是起了联系名字和地址的作用,所以其元素数目必定和名字数组相同,比如,每个名字有且仅有一个相关地址,反过来则不一定: 每个地址可以有好几个名字来对应。因此我们给同一个地址取"别名"。为了起到连接作用,名字数组和索引数组必须并行地成对使用,譬如,索引数组的第一个元素必定含有第一个名字的索引,以此类推。

AddressOfNames    AddressOfNameOrdinals
|    | 
RVA of Name 1 <--> Index of Name 1
RVA of Name 2 <--> Index of Name 2
RVA of Name 3 <--> Index of Name 3              
RVA of Name 4 <--> Index of Name 4              
...           ...  ...
RVA of Name N <--> Index of Name N

  下面举一两个例子说明问题。如果我们有了导出函数名并想以此获取地址,可以这么做:

1.定位到PE header。
2.从数据目录读取导出表的虚拟地址。
3.定位导出表获取名字数目(NumberOfNames)。
4.并行遍历AddressOfNames和AddressOfNameOrdinals指向的数组匹配名字。如果在AddressOfNames 指向的数组中找到匹配名字,从AddressOfNameOrdinals 指向的数组中提取索引值。例如,若发现匹配名字的RVA存放在AddressOfNames 数组的第77个元素,那就提取AddressOfNameOrdinals数组的第77个元素作为索引值。如果遍历完NumberOfNames 个元素,说明当前模块没有所要的名字。
5.从AddressOfNameOrdinals 数组提取的数值作为AddressOfFunctions 数组的索引。也就是说,如果值是5,就必须读取AddressOfFunctions 数组的第5个元素,此值就是所要函数的RVA。
  现在我们在把注意力转向IMAGE_EXPORT_DIRECTORY 结构的Base成员。您已经知道AddressOfFunctions 数组包含了模块中所有引出符号的地址。当PE装载器索引该数组查询函数地址时,让我们设想这样一种情况,如果程序员在.def文件中设定起始序数号为200,这意味着AddressOfFunctions 数组至少有200个元素,甚至这前面200个元素并没使用,但它们必须存在,因为PE装载器这样才能索引到正确的地址。这种方法很不好,所以又设计了Base 域解决这个问题。如果程序员指定起始序数号为200,Base 值也就是200。当PE装载器读取Base域时,它知道开始200个元素并不存在,这样减掉一个Base值后就可以正确地索引AddressOfFunctions 数组了。有了Base,就节约了200个空元素。
  注意Base并不影响AddressOfNameOrdinals数组的值。尽管取名"AddressOfNameOrdinals",该数组实际包含的是指向AddressOfFunctions 数组的索引,而不是什么序数啦。
  讨论完Base的作用,我们继续下一个例子。假设我们只有函数的序数,那么怎样获取函数地址呢,可以这么做:
1.定位到PE header。
2.从数据目录读取导出表的虚拟地址。
3.定位导出表获取Base值。
4.减掉Base值得到指向AddressOfFunctions 数组的索引。 将该值与NumberOfFunctions作比较,大于等于后者则序数无效。 通过上面的索引就可以获取AddressOfFunctions 数组中的RVA了。
  可以看出,从序数获取函数地址比函数名快捷容易。不需要遍历AddressOfNames 和 AddressOfNameOrdinals 这两个数组。然而,综合性能必须与模块维护的简易程度作一平衡。总之,如果想通过名字获取函数地址,需要遍历AddressOfNames 和 AddressOfNameOrdinals 这两个数组。如果使用函数序数,减掉Base值后就可直接索引AddressOfFunctions 数组。
  如果一函数通过名字引出,那在GetProcAddress中可以使用名字或序数。但函数仅由序数引出情况又怎样呢? 现在就来看看。
"一个函数仅由序数引出"意味着函数在AddressOfNames 和 AddressOfNameOrdinals 数组中不存在相关项。记住两个域,NumberOfFunctions 和 NumberOfNames。这两个域可以清楚地显示有时某些函数没有名字的。函数数目至少等同于名字数目,没有名字的函数通过序数引出。比如,如果存在70个函数但AddressOfNames数组中只有40项,这就意味着模块中有30个函数是仅通过序数引出的。现在我们怎样找出那些仅通过序数引出的函数呢?这不容易,必须通过排除法,比如,AddressOfFunctions 的数组项在AddressOfNameOrdinals 数组中不存在相关指向,这就说明该函数RVA只通过序数引出。
  下面给出读取Export Table的例子,这个时候已经用到了文件映射,因为要读取函数的名字,但是名字长度不确定,所以不能靠文件I/O来读取了,必须将整个文件映射到内存中靠判断结束符去读取。

 LPVOID     WY_lpPEFile;
 HANDLE     WY_hFileMap;
 PIMAGE_EXPORT_DIRECTORY   WY_lpExpDir;
 DWORD                                  *WY_dwExpNamesArr;
 WORD                                   *WY_wExpOrdinArr;
 DWORD                                  *WY_dwExpFunRVAArr;
 char                                   *WY_pcName;

 WY_hFileMap = CreateFileMapping(WY_hPEFile,NULL,PAGE_READONLY,0,0,NULL);
 WY_lpPEFile = MapViewOfFile(WY_hFileMap,FILE_MAP_READ,0,0,0);


 //开始获得exports table的信息,这里因为有许多东西需要提取,所以用到了文件内存映射
 if(WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_EXPORT])
 {
  WY_ctrPEInfo.AddString("Exports Infomation");
  WY_lpExpDir = (PIMAGE_EXPORT_DIRECTORY)((DWORD)WY_lpPEFile + WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_EXPORT]);
  //通过RVA计算获得模块名文件指针
  WY_pcName = (char*)(WY_lpExpDir->Name-WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].VirtualAddress+
                                                           WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].PointerToRawData+(DWORD)WY_lpPEFile);
  WY_strOpMsg.Format("导出模块名称 : %s,BASE : %d,函数个数 : %d,函数名称个数 : %d",WY_pcName,WY_lpExpDir->Base,
                                   WY_lpExpDir->NumberOfFunctions,WY_lpExpDir->NumberOfNames);
  WY_ctrPEInfo.AddString(WY_strOpMsg);
  //通过RVA计算获得函数名数字起始文件指针
  WY_dwExpNamesArr = (DWORD*)(WY_lpExpDir->AddressOfNames-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].PointerToRawData+                                       (DWORD)WY_lpPEFile);
  //通过RVA计算获得序号表数组起始文件指针
  WY_wExpOrdinArr = (WORD*)(WY_lpExpDir->AddressOfNameOrdinals-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].PointerToRawData+                                      (DWORD)WY_lpPEFile);
  //通过RVA计算获得函数RVAs数组起始文件指针
  WY_dwExpFunRVAArr = (DWORD*)(WY_lpExpDir->AddressOfFunctions-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].PointerToRawData+                                        (DWORD)WY_lpPEFile);
  WY_ctrPEInfo.AddString((LPCTSTR)"ordin索引值  函数RVA地址       函数名");
  for(WY_i = 0;WY_i < (int)WY_lpExpDir->NumberOfNames;WY_i++)
  {
   //根据函数名称数组中的名称RVA计算名称的起始文件指针
   WY_pcName = (char*)(WY_dwExpNamesArr[WY_i]-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_EXPORT]].PointerToRawData+                                    (DWORD)WY_lpPEFile);
   WY_strOpMsg.Format("%4d     %8xh         %s",WY_wExpOrdinArr[WY_i],
                                           WY_dwExpFunRVAArr[WY_wExpOrdinArr[WY_i]-WY_lpExpDir->Base+1],WY_pcName);
   WY_ctrPEInfo.AddString(WY_strOpMsg);
  }
 }

  现在我们开始讨论引入表了。引入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。每个结构包含PE文件引入函数的一个相关DLL的信息。比如,如果该PE文件从10个不同的DLL中引入函数,那么这个数组就有10个成员。该数组以一个全0的成员结尾。下面是具体结构
  winnt.h中的定义

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;            // 0 for terminating null import descriptor
        DWORD   OriginalFirstThunk;         // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
    };
    DWORD   TimeDateStamp;                  // 0 if not bound,
                                            // -1 if bound, and real date/time stamp
                                            //     in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
                                            // O.W. date/time stamp of DLL bound to (Old BIND)

    DWORD   ForwarderChain;                 // -1 if no forwarders
    DWORD   Name;
    DWORD   FirstThunk;                     // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

  结构第一项是一个union子结构。事实上,这个union子结构只是给 OriginalFirstThunk 增添了个别名,也可以称其为"Characteristics"。 该成员项含有指向一个IMAGE_THUNK_DATA 结构数组的RVA。
  什么是 IMAGE_THUNK_DATA? 这是一个dword类型的集合。通常为指向一个 IMAGE_IMPORT_BY_NAME 结构的指针。注意 IMAGE_THUNK_DATA 包含了指向一个IMAGE_IMPORT_BY_NAME 结构的指针。有几个 IMAGE_IMPORT_BY_NAME 结构,我们收集起这些结构的RVA (IMAGE_THUNK_DATAs)组成一个数组,并以0结尾,然后再将数组的RVA放入 OriginalFirstThunk。此IMAGE_IMPORT_BY_NAME 结构存有一个引入函数的相关信息。看一下IMAGE_IMPORT_BY_NAME 和IMAGE_THUNK_DATA的具体结构。
  winnt.h中的定义

typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD    Hint;
    BYTE    Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

typedef struct _IMAGE_THUNK_DATA32 {
    union {
        PBYTE  ForwarderString;
        PDWORD Function;
        DWORD Ordinal;
        PIMAGE_IMPORT_BY_NAME  AddressOfData;
    } u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
可以看出来THUNK结构其实就是一个DWORD数据。
Hint : 指示本函数在其所驻留DLL的引出表中的索引号。该域被PE装载器用来在DLL的引出表里快速查询函数。该值不是必须的,一些连接器将此值设为0。
Name[1] : 含有引入函数的函数名。函数名是一个ASCIIZ字符串。注意这里虽然将Name的大小定义成字节,其实它是可变尺寸域,只不过我们没有更好方法来表示结构中的可变尺寸域
我们接着来说Descriptor其它的成员
TimeDateStamp 和 ForwarderChain 只想另外的结构,TimeDateStamp如果最高为为1的话,则指向一个新的包括新的模块名,和时间辍。不过不是很重要,就暂且不说了
Name : 含有指向DLL名字的RVA,即指向DLL名字的指针,也是一个ASCIIZ字符串。
FirstThunk 与 OriginalFirstThunk 非常相似,它也包含指向一个 IMAGE_THUNK_DATA 结构数组的RVA(当然这是另外一个IMAGE_THUNK_DATA 结构数组)。
  为什么我们需要两个完全相同的数组? 为了回答该问题,我们需要了解当PE文件被装载到内存时,PE装载器将查找IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 这些结构数组,以此决定引入函数的地址。然后用引入函数真实地址来替代由FirstThunk指向的 IMAGE_THUNK_DATA 数组里的元素值。由OriginalFirstThunk 指向的RVA数组始终不会改变
假设我们要列出某个PE文件的所有引入函数,可以照着下面步骤走:

1.定位第一个 IMAGE_IMPORT_DESCRIPTOR 结构。
2.检查 OriginalFirstThunk值。若不为0,顺着 OriginalFirstThunk 里的RVA值转入那个RVA数组。
3.对于每个数组元素,我们比对元素值是否等于IMAGE_ORDINAL_FLAG32。如果该元素值的最高二进位为1, 那么函数是由序数引入的,可以从该值的低字节提取序数。如果元素值的最高二进位为0,就可将该值作为RVA转入 IMAGE_IMPORT_BY_NAME 数组,跳过 Hint 就是函数名字了。再跳至下一个数组元素提取函数名一直到数组底部(它以null结尾)。现在我们已遍历完一个DLL的引入函数,接下去处理下一个DLL。
4.即跳转到下一个 IMAGE_IMPORT_DESCRIPTOR 并处理之,如此这般循环直到数组见底。(IMAGE_IMPORT_DESCRIPTOR 数组以一个全0域元素结尾)。
  下面给出获得引入表信息的代码:
 //开始获得imports table的信息

 PIMAGE_IMPORT_DESCRIPTOR   WY_lpImpDescript;
 PIMAGE_THUNK_DATA        WY_pThunkData;

 if(WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_IMPORT])
 {
  WY_ctrPEInfo.AddString("Imports Information");
  WY_lpImpDescript = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)WY_lpPEFile + WY_dwImgDirOffset[IMAGE_DIRECTORY_ENTRY_IMPORT]);
  while(WY_lpImpDescript->OriginalFirstThunk || WY_lpImpDescript->TimeDateStamp
   || WY_lpImpDescript->ForwarderChain || WY_lpImpDescript->Name || WY_lpImpDescript->FirstThunk)
  {
   //获得该引入模块的名称
   WY_pcName = (char*)(WY_lpImpDescript->Name-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].PointerToRawData+                                    (DWORD)WY_lpPEFile);
   WY_strOpMsg.Format("    模块名称: %s",WY_pcName);
   WY_ctrPEInfo.AddString(WY_strOpMsg);
   //获得该模块中的函数名与序号
   //获得THUNK数组的起始指针
   WY_pThunkData = (PIMAGE_THUNK_DATA)(WY_lpImpDescript->OriginalFirstThunk-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].PointerToRawData+                                         (DWORD)WY_lpPEFile);
   while(WY_pThunkData->u1.AddressOfData)
   {
    if((WY_pThunkData->u1.Ordinal & 0x80000000) == 0x80000000) //并不指向地址而是函数序号
    {
     WY_strOpMsg.Format("       序号: %xh",(WY_pThunkData->u1.Ordinal & 0x7fffffff));
     WY_ctrPEInfo.AddString(WY_strOpMsg);
    }
    else
    { //指向函数名数组
     //此处借用这个指针变量只是为了获得序号和名字结构的起始指针,不要被变量的名字所迷惑
     WY_wExpOrdinArr = (WORD*)((DWORD)WY_pThunkData->u1.AddressOfData-
WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].VirtualAddress+WY_SectionHeader[WY_nSecDirin[IMAGE_DIRECTORY_ENTRY_IMPORT]].PointerToRawData+                                                          (DWORD)WY_lpPEFile);
     WY_pcName = (char*)((DWORD)WY_wExpOrdinArr + (DWORD)sizeof(WORD));
     WY_strOpMsg.Format("       序号: %8xh   函数名: %s",*WY_wExpOrdinArr,WY_pcName);
     WY_ctrPEInfo.AddString(WY_strOpMsg);
    }
    WY_pThunkData++;
   }
   WY_lpImpDescript++;
  }
 }
  至此就已经将PE文件中最重要的几个部分说完了,如果大家想对PE做更加深的了解,请查阅MSDN。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值