PE文件解析(PE File Parsing)

PE(Portable Executable) File Parsing

WIN32 PE文件解析

链接:https://pan.baidu.com/s/1SR6IgJ645IBTPWy3PXNQgQ
提取码:4veh

  • RVA:Relative Virtual Address(相对虚拟地址)。RVA 是指相对于映像基址的地址偏移量,在内存中定位特定数据的地址。
  • RAW:Raw Data(原始数据)。RAW 是指 PE 文件中未经任何处理或压缩的原始二进制数据,它直接从文件中读取,例如节的原始数据。有的也叫FOA(file offset address)

image-20230518235342704

PE头

DOS头

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;

64字节,主要是为了兼容当时比较盛行的DOS系统,我们只关心下面两个

  • e_magic:dos签名 4D5A(MZ)
  • e_lfanew:NT/PE头偏移(IMAGE_NT_HEADERS)
    • 如果该值为0,则该文件是一个DOS“MZ“可执行文件,Windows会启动DOS子系统来执行它,否则为Windows的PE可执行文件

DOS存根(stub)

e_lfanew偏移和_IMAGE_DOS_HEADER之间位置

前0xd字节是16位的汇编,输出This program cannot be run in DOS mode.后退出

dos头的e_cs e_ip一般指向这里

NT/PE头

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

Signature

PE签名 0x50450000(“PE00”)

标准PE头 IMAGE_FILE_HEADER FileHeader

typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 目标机器的体系结构类型。
WORD NumberOfSections; // 文件中的节的数量。
DWORD TimeDateStamp; // 文件创建或修改的时间戳。
DWORD PointerToSymbolTable; // COFF符号表的文件偏移量。
DWORD NumberOfSymbols; // COFF符号表中的符号数量。
WORD SizeOfOptionalHeader; // 可选头的大小(以字节为单位)。
WORD Characteristics; // 文件的特性标志。
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
  • Machine
    • PE文件的目标架构,执行文件所针对的CPU或处理器架构类型
  • NumberOfSections
    • 节表数量
  • TimeDateStamp
    • PE文件的创建或修改时间戳
  • PointerToSymbolTable
    • 符号表在文件中的偏移量
  • SizeOfOptionalHeader
    • IMAGE_OPTIONAL_HEADER32结构体长度
  • Characteristics
    • 标示文件的属性 是否为dll,是否可执行等,bitOR形式

扩展PE头 IMAGE_OPTIONAL_HEADER32 OptionalHeader

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 数据目录项在内存中的虚拟地址。
DWORD Size; // 数据目录项的大小(字节数)。
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//

WORD Magic; // 魔术字段,标识可选头的格式(32位或64位)。
BYTE MajorLinkerVersion; // 链接器的主版本号。
BYTE MinorLinkerVersion; // 链接器的次版本号。
//下面三个操作系统没用,但是有些软件可能会
DWORD SizeOfCode; // 代码段的大小,以字节为单位。
DWORD SizeOfInitializedData; // 已初始化数据段的大小,以字节为单位。
DWORD SizeOfUninitializedData; // 未初始化数据段的大小,以字节为单位。
DWORD AddressOfEntryPoint; // 程序入口点(程序的起始执行地址)相对于映像基址的偏移量。
DWORD BaseOfCode; // 代码段的起始地址相对于映像基址的偏移量。
DWORD BaseOfData; // 数据段(仅用于PE32)的起始地址相对于映像基址的偏移量。

//
// NT additional fields.
//

DWORD ImageBase; // 程序的首选装载地址(映像基址)。
DWORD SectionAlignment; // 节的内存对齐大小,以字节为单位。
DWORD FileAlignment; // 文件对齐大小,以字节为单位。
WORD MajorOperatingSystemVersion; // 操作系统的主版本号要求。
WORD MinorOperatingSystemVersion; // 操作系统的次版本号要求。
WORD MajorImageVersion; // 映像文件的主版本号。
WORD MinorImageVersion; // 映像文件的次版本号。
WORD MajorSubsystemVersion; // 子系统的主版本号要求。
WORD MinorSubsystemVersion; // 子系统的次版本号要求。
DWORD Win32VersionValue; // 保留字段,用于指定Win32运行时版本。
DWORD SizeOfImage; // 映像的内存大小,包括所有头和节的大小。
DWORD SizeOfHeaders; // 头的总大小,包括所有头的大小。
DWORD CheckSum; // 映像文件的校验和。
WORD Subsystem; // 程序所依赖的子系统类型。
WORD DllCharacteristics; // DLL文件的特性标志。
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
  • Magic

    • 32位为0x10B,64位为0x20B
  • AddressOfEntryPoint

    • RVA值,程序入口点
  • ImageBase

    • PE文件被加载到内存地址,加载完后,EIP指向ImageBase+AddressOfEntryPoint
  • SizeOfImage

    • PE文件装载到虚拟内存后的大小
  • SizeOfHeaders

    • PE头按照FileAlignment对齐后的大小,包括section head
  • Subsystem

    • 区分系统文件和普通可执行文件
    • 系统驱动:drive 窗口:GUI 控制台:CUI 等
  • NumberOfRvaAndSizes

    • DataDirectory数目
  • DataDirectory

    • 数据目录项 索引位置
      导出表(Export Table) IMAGE_DIRECTORY_ENTRY_EXPORT (0)
      导入表(Import Table) IMAGE_DIRECTORY_ENTRY_IMPORT (1)
      资源表(Resource Table) IMAGE_DIRECTORY_ENTRY_RESOURCE (2)
      异常表(Exception Table) IMAGE_DIRECTORY_ENTRY_EXCEPTION (3)
      安全表(Security Table) IMAGE_DIRECTORY_ENTRY_SECURITY (4)
      重定位表(Base Relocation Table) IMAGE_DIRECTORY_ENTRY_BASERELOC (5)
      调试表(Debug Table) IMAGE_DIRECTORY_ENTRY_DEBUG (6)
      版本信息(Version Information) IMAGE_DIRECTORY_ENTRY_ARCHITECTURE (7)
      全局指针(Global Pointer) IMAGE_DIRECTORY_ENTRY_GLOBALPTR (8)
      TLS表(Thread Local Storage Table) IMAGE_DIRECTORY_ENTRY_TLS (9)
      载入配置表(Load Configuration Table) IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG (10)
      绑定导入表(Bound Import Table) IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (11)
      IAT表(Import Address Table) IMAGE_DIRECTORY_ENTRY_IAT (12)
      延迟导入描述符(Delay Import Descriptor) IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT (13)
      COM运行时描述符(COM Runtime Descriptor) IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR (14)
      保留(Reserved) IMAGE_DIRECTORY_ENTRY_RESERVED (15)

节区头

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节名称,最多8个字符(如果名称长度不足8个字符,用空格填充)。
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;

RAW(文件偏移)=RVA-VirtualAddress+PointerToRawData

这个换算有时候是不对的,比如说有未初始化初始的数据节,为了节约磁盘空间,VirtualSize比SizeOfRawData要大,换算后节区会不一样、

PE头内的话 RAW=RVA

导入表

_IMAGE_OPTIONAL_HEADER里DataDirectory的第二项,导入多少个库就有多少个_IMAGE_IMPORT_DESCRIPTOR结构,最后以一个空结构体结尾

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
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;

以notepad为例

>>>Name

就是当前模块依赖模块名字

image-20230519163927062

0x7604是RVA

image-20230519164252693

可以看出0x7604他在第一个section(PE文件的section相比于ELF很少)

RAW=RVA-VirtualAddress+PointerToRawData=0x7604-0x1000+0x400=0x6A04

image-20230519164805090

他的name字段就是0x7AAC,同样位于第一个节区RAW=0x7AAC-0x1000+0x400=0x6EAC

image-20230519164941785

image-20230519165226573

他的OriginalFirstThunk和FirstThunk分别指向INT(Import Name Table ,导入名称表) 和IAT(Import Address Table ,导入地址表)

>>>OriginalFirstThunk:INT表(导入名称表)

指向_IMAGE_THUNK_DATA32数组的RVA

typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // 指向转发字符串的指针(PBYTE)。
DWORD Function; // 指向函数地址的指针(PDWORD)。
DWORD Ordinal; // 导入函数的序号(用于按序号导入)。
DWORD AddressOfData; // 指向 IMAGE_IMPORT_BY_NAME 结构的指针(PIMAGE_IMPORT_BY_NAME)。
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 函数名称的提示(用于加速导入的查找过程)。
CHAR Name[1]; // 函数名称(以NULL结尾的ASCII字符串)。
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;

u1的最高位为1,去除这个1,其余就是导入函数的序号Ordinal,否则就是AddressOfData

image-20230519171519020

RAW=0x6D90 0x6D90处就是_IMAGE_THUNK_DATA32数组,同样以NULL结构体结尾

image-20230519172810749

RVA=0x7A7A RAW=0x6E7A,指向_IMAGE_IMPORT_BY_NAME结构体

image-20230519172307227

0xF为库中函数序号,后面是导入函数名称

后面0x7a5e RAW为0x6e50

image-20230519172921483

>>>FirstThunk:IAT表(导入地址表)

同样是指向_IMAGE_THUNK_DATA32数组的RVA

image-20230519174316053

RAW为0x6c4

image-20230519174410251

一堆不明所以的数据

丢到x32dbg看看

由于可执行文件PE是是进程第一个加载的模块,所以他几乎百分百都能抢到他原来的imagebase(除了开了aslr的情况),不需要去做重定位,所以这个IAT地址直接就是RVA+ImageBase

RVA+ImageBase=0x10012c4

image-20230519180741267

image-20230519181813832

0x77243e60这个地址就是PageSetupDlgw导入函数地址

image-20230519181542374

image-20230519180449954

类似于ELF里的got表,调用这种导入函数时是一个间接调用call *(iat)

由于这个notepad太老了,属于xp时代(和绑定导入表Bound Import Table有关),现在的操作系统上pe中IAT和INT表里的内容是一样的

导入表加载的大致流程就是(不同版本操作系统会有差别)

  1. 检查IMAGE_IMPORT_DESCRIPTOR.Name和FirstTunk,二者有一为空就停止加载导入表
  2. 检查OriginalFirstThunk是否为空,为空则从FirstTunk指向的IAT拿名称,否则用OriginalFirstThunk执行的INT拿名称

大致伪代码

PIMAGE_IMPORT_DESCRIPTOR pImportDes;//导入表位置
IMAGE_IMPORT_DESCRIPTOR EmptyImport = { 0 };
while (memcmp(pImportDes, &EmptyImport, sizeof(IMAGE_IMPORT_DESCRIPTOR))) {
//检查导入表名称
if (NULL == pImportDes->Name) {
break;
}
HMODULE hDll = LoadLibrary((LPCSTR)((DWORD)ImageBase + pImportDes->Name));
if (NULL == hDll) {
break;
}
//检查并获取IAT地址
if (NULL == pImportDes->FirstThunk) {
break;
}
DWORD* dwIAT = (DWORD *)((DWORD)pImportDes->FirstThunk+(DWORD)ImageBase);
//确定INT表位置
PIMAGE_THUNK_DATA pImportNameTable = (PIMAGE_THUNK_DATA)(pImportDes->OriginalFirstThunk + (DWORD)ImageBase);
if (NULL == pImportDes->OriginalFirstThunk) {//初始时IAT和INT内容一样
pImportNameTable = (PIMAGE_THUNK_DATA)(pImportDes->FirstThunk+(DWORD)ImageBase);
}
//判断高位是否为1,获得函数地址,并给IAT表复制
while (NULL != pImportNameTable->u1.Ordinal) {
DWORD dwPFNAddr;
if ((pImportNameTable->u1.Ordinal & 0x80000000)) {
dwPFNAddr = (DWORD)GetProcAddress(hDll, (LPCSTR)(pImportNameTable->u1.Ordinal&0x7fffffff));
}
else {
dwPFNAddr = (DWORD)GetProcAddress(hDll, (LPCSTR)
((PIMAGE_IMPORT_BY_NAME)(pImportNameTable->u1.AddressOfData))->Name+(DWORD)ImageBase);
}
*dwIAT = dwPFNAddr;
++dwIAT;
pImportNameTable++;
}
++pImportDes;
}

导出表

_IMAGE_OPTIONAL_HEADER里DataDirectory的第一项,导出表只有一个

typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 导出表的特性标志。
DWORD TimeDateStamp; // 导出表的时间戳。
WORD MajorVersion; // 导出表的主要版本号。
WORD MinorVersion; // 导出表的次要版本号。
DWORD Name; // 模块名称的 RVA(相对虚拟地址)。
DWORD Base; // 导出函数的起始序号。
DWORD NumberOfFunctions; // 导出函数的数量。
DWORD NumberOfNames; // 导出函数名称的数量。
DWORD AddressOfFunctions; // 导出函数地址表的 RVA(相对虚拟地址)。
DWORD AddressOfNames; // 导出函数名称表的 RVA(相对虚拟地址)。
DWORD AddressOfNameOrdinals; // 导出函数序号表的 RVA(相对虚拟地址)。
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;

导出函数名称表和导出函数序号表是一一对应的,可以说是一张表

NumberOfFunctions函数,导出时不按序号顺序导出,空余位也计入,比如1,5,2,3 但是NumberOfFunctions=5

所以就需要导出函数序号表的将函数地址和名称联系起来

导出函数可以没有名字但是一定要有序号

看一下GetProcAddress是如何解析的

FARPROC GetProcAddress(HMODULE hModule ,LPCWSTR lpProcName)

lpProcName参数是name时(大于 0x10000)

  • 根据AddressOfNames的RVA找到导出函数名称表(里面时字符串指针的RVA),利用strcmp比较字符串,找到名称,并记录下该名称在导出函数名称表的name_index,名称表里的指针指向的名字都是A-Za-z排序好的,按照朴素的二分法效率蛮高的 毕竟最多log2(n) 次嘛:)
  • AddressOfNameOrdinals找到导出函数序号表,根据named_index找到对应的orinal
  • AddressOfFunctions找到导出函数地址表的(EAT Export Address Table),根据orinal找到函数的RVA

lpProcName参数是序号时(小于 0x10000)

  • 序号减去Base(起始序号)得到index
  • 根据index去函数地址表去找

重定位表

typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;//整个结构体大小
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;

VirtualAddress是一个页对齐地址,TypeOffset的低12位记录了需要修正位置的页内偏移 TypeOffset高4位记录了重定位信息的类型,如下所示

//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0//无重定位操作,用于4字节对齐
#define IMAGE_REL_BASED_HIGH 1//重定位指向位置的高2个字节需要被修正
#define IMAGE_REL_BASED_LOW 2//重定位指向位置的低2个字节需要被修正
#define IMAGE_REL_BASED_HIGHLOW 3//重定位指向位置的全部4个字节需要被修正
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5
#define IMAGE_REL_BASED_RESERVED 6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9
#define IMAGE_REL_BASED_DIR64 10//64位程序中,重定位指向位置的8个字节需要被修正

TLS表

显示tls动态使用__teb里TlsSlots的位置

BOOL TlsSetValue(
[in] DWORD dwTlsIndex,
[in, optional] LPVOID lpTlsValue
);

<kernelbase.TlsSetValue> | mov edi,edi |
| push ebp |
| mov ebp,esp |
| push esi |
| mov esi,dword ptr ss:[ebp+0x8] | 参数1
| push edi |
| mov edi,dword ptr fs:[0x18] | 取得teb开头位置
| cmp esi,0x40 | 40:'@'
| jae kernelbase.75C008CB |
| mov eax,dword ptr ss:[ebp+0xC] | 参数2
| mov dword ptr ds:[edi+esi*4+0xE10],eax |
| mov eax,0x1 |
| pop edi |
| pop esi |
| pop ebp |
| ret 0x8 |

索引小于0x40会根据索引存到teb的0xE10处

0:004> dt _teb
ntdll!_TEB
.........
+0xe10 TlsSlots : [64] Ptr32 Void 0x100大小只能存40个Ptr32
+0xf10 TlsLinks : _LIST_ENTRY

隐式tls会用到tls表

typedef struct _IMAGE_TLS_DIRECTORY32 {
DWORD StartAddressOfRawData;//TLS初始化数据的起始地址,是VA
DWORD EndAddressOfRawData;//TLS初始化数据的结束地址,是VA
DWORD AddressOfIndex; // 索引 PDWORD 就是下面_tls_index里的值,这个地址处一般就是0
DWORD AddressOfCallBacks; // Tls回调函数的数组指针 PIMAGE_TLS_CALLBACK *
DWORD SizeOfZeroFill;
union {
DWORD Characteristics;
struct {
DWORD Reserved0 : 20;
DWORD Alignment : 4;
DWORD Reserved1 : 8;
} DUMMYSTRUCTNAME;
} DUMMYUNIONNAME;

} IMAGE_TLS_DIRECTORY32;

程序开始时会把StartAddressOfRawData~EndAddressOfRawData(.tls section)处的数据拷到teb.ThreadLocalStoragePointer指向的位置

0:004> dt _teb
ntdll!_TEB
.........
+0x02c ThreadLocalStoragePointer : Ptr32 Void
+0x030 ProcessEnvironmentBlock : Ptr32 _PEB
__declspec(thread) int tls_i = 0x12345678;
int main() {
tls_i = 0x0;
return 0;
}
4:   tls_i = 0x0;
mov eax,dword ptr [_tls_index (042A1ACh)] ;eax=0
mov ecx,dword ptr fs:[2Ch] ;ecx=ThreadLocalStoragePointer
mov edx,dword ptr [ecx+eax*4] ;edx=*ThreadLocalStoragePointer
mov dword ptr [edx+104h],0 ;104似乎固定

资源表

typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries;//以字符串作为id的资源的个数
WORD NumberOfIdEntries;//以数值作为id的资源的个数
// 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;
} DUMMYSTRUCTNAME;
DWORD Name;
WORD Id;
} DUMMYUNIONNAME;//最高位为1,低WORD指向字符串id的节内偏移,为0,低WORD为数值id
union {
DWORD OffsetToData;
struct {
DWORD OffsetToDirectory:31;
DWORD DataIsDirectory:1;
} DUMMYSTRUCTNAME2;//最高位为1,低word是目录项的节内偏移,为0,低WORD是数据项的节内偏移
} DUMMYUNIONNAME2;
} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;

但是现在是固定三层,所以结构固定,导致DUMMYSTRUCTNAME解析和上面不太一样

**第一层: **

DUMMYUNIONNAME 资源类型

/*
* Predefined Resource Types
*/
#define RT_CURSOR MAKEINTRESOURCE(1)
#define RT_BITMAP MAKEINTRESOURCE(2)
#define RT_ICON MAKEINTRESOURCE(3)
#define RT_MENU MAKEINTRESOURCE(4)
#define RT_DIALOG MAKEINTRESOURCE(5)
#define RT_STRING MAKEINTRESOURCE(6)
#define RT_FONTDIR MAKEINTRESOURCE(7)
#define RT_FONT MAKEINTRESOURCE(8)
#define RT_ACCELERATOR MAKEINTRESOURCE(9)
#define RT_RCDATA MAKEINTRESOURCE(10)
#define RT_MESSAGETABLE MAKEINTRESOURCE(11)\

**第二层: **

DUMMYUNIONNAME 对应的资源ID

第三层:资源数据

DUMMYUNIONNAME为代码页

DUMMYSTRUCTNAME2处rva处指向的数据按照下面结构解析

typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData;
DWORD Size;
DWORD CodePage;
DWORD Reserved;
} IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY;