PE解析思路
本文的目的是编写解析器,故本文叙述的PE细节不像其他文章那么详细,想学习更加详细的解析可以翻到文本最后的参考文章
0x01 Headers 解析
头部分分为5部分:
- Dos Header
- NT Header
- FileHeader
- Optional Header
- Section Header
Dos Header
Dos部分为兼容古老的DOS 16位系统,目前来说用处已经不大,整体结构在winnt.h中定义为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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,该参数为整个PE文件的开头部分,可用于区别ELF和PE
在PE中,一般表现为0x5A4D,字符串表现为MZ:
ELF文件中,表现为:

第二个使用到的参数为DOS Header中的最后一个参数e_lfanew,该参数用来指向exe文件的开始位置,也就是
第二部分Nt Header的位置从Dos Header 到 Nt Header中间空出来的部分数据可以理解为历史残留,都是一些脏数据,一般都会有这样一段话:This program cannot be run in DOS mode

Nt Header
Nt Header 结构体(winnt.h)
1 2 3 4 5 6 7 8 9 10 11 | 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; |
Nt Header的开头为一个Signature,该值通过宏进行定义为PE文件的固定值,不受32位或者64位影响:
1 | #define IMAGE_NT_SIGNATURE 0x00004550 // PE |


File Header
File Header 结构体(winnt.h)
1 2 3 4 5 6 7 8 9 | 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,在wint.h中定义了宏,目前常见的应该只有IMAGE_FILE_MACHINE_I38632位程序和IMAGE_FILE_MACHINE_AMD6464位程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | #define IMAGE_FILE_MACHINE_UNKNOWN 0#define IMAGE_FILE_MACHINE_TARGET_HOST 0x0001 // Useful for indicating we want to interact with the host and not a WoW guest.#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_THUMB 0x01c2 // ARM Thumb/Thumb-2 Little-Endian#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARM Thumb-2 Little-Endian#define IMAGE_FILE_MACHINE_AM33 0x01d3#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel 64#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon#define IMAGE_FILE_MACHINE_CEF 0x0CEF#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian#define IMAGE_FILE_MACHINE_CEE 0xC0EE |
可用来区分Windows 下的32位程序和64位程序:


第二个是NumberOfSections,就是节的数量,关于节遍历时需要用到,还有对于节的增加和合并时需要对该参数的值进行修改
第三个是SizeOfOptionalHeader,关于可选PE头的大小,默认情况下,32位程序该值为E0,64位程序该值为F0
最后一个参数Characteristic可以理解为操作权限
Optional Header
Optional Header 结构体(winnt.h)
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
pe32:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | 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; |
pe64:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | typedef struct _IMAGE_OPTIONAL_HEADER64 { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; ULONGLONG 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; ULONGLONG SizeOfStackReserve; ULONGLONG SizeOfStackCommit; ULONGLONG SizeOfHeapReserve; ULONGLONG SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64; |
通过对比可以发现在32位的Optional Header中多了一个DWORD BaseOfData

在不做修改的情况下,OptionalHeader的长度是固定的,所以32位程序在File Header中的SizeOfOptionalHeader为0xE8,而64位程序的SizeOfOptionalHeader为0xF0,通过上图也可以看出在ImageBase,SIzeOfStackReserve,SizeOfStackeCommit,SizeOfHeapReserve, SizeOfHeapCommit这五个参数中,64位程序的参数长度为ULONGLONG,就是8字节QDWORD,加上少掉的一个DWORD,刚好默认情况下64位程序的OptionalHeader比32位的多0x04*5-0x04=16个字节


在OptionalHeader的开头Magic参数会再一次显示32位和64位的区别:
在winnt.h中也定义了相关的宏:
1 2 | #define IMAGE_NT_OPTIONAL_HDR32_MAGIC 0x10b#define IMAGE_NT_OPTIONAL_HDR64_MAGIC 0x20b |


所以总结之后可以得到区分32位和64位程序的方式:
- 通过OptionalHeader->Magic
- 通过FileHeader->Machine
- 通过FileHeader->SizeOfOptionalHeader
OptionalHeader中会用到的参数:
- AddressOfEntryPoint:程序入口地址,简称为OEP
- ImageBase:镜像基址
- SectionAlignment:内存对齐大小
- FileAlignment:文件对齐大小
OptionalHeader中解析到的OEP为相对偏移并且这个偏移应该是内存对齐后的偏移

载入x32dbg查看,此时发现OEP的地址为A80233,这时候就要提到ImageBase了,可以看到在OptionalHeader中的ImageBase为0x400000,但是程序此时的ImageBase为0xA80233 - 0x10233 = 0xA70000,这个原因是OptionalHeader中解析到的ImageBase为理想状况下的,内存的地址是连续,0x40000只有一个,所以有且只有一个程序可以刚好分配到0x40000作为镜像基址,那么其他程序怎么办,这时候就会产生一个重定位的情况,这个后面会说到,总之,当程序载入内存时,相对偏移是不会变的,会发生变化的是镜像基址,当然这里指的相对偏移,是指内存对齐后的相对偏移,在PE解析中,存在着文件对齐的情况,特别是该程序中,文件对齐与内存对齐是不一致的,所以当程序从FileBuffer加载到内存成为ImageBuffer后,需要对程序进行拉伸,对齐成内存对齐,那么相应的某些本身处于文件对齐的地址,就需要通过转化,转换成内存对齐地址


SizeOfImage:经过内存对齐后整个镜像大小
SizeOfCode:文件对齐下镜像大小
SizeOfHeaders: 头+节表文件对齐下的大小
BaseOfCode: 内存对齐下代码的开始(一般为SectionAlignment的整数倍)
BaseOfData:内存对齐下数据段的开始(一般为SectionAlignment的整数倍)
Section Header
SectionHeader(winnt.h):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #define IMAGE_SIZEOF_SHORT_NAME 8#define IMAGE_SIZEOF_SECTION_HEADER 40typedef 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; |
节表是整个PE文件最重要的一部分,因为只有通过他进行内存地址的转化,以及各种节都是通过节表进行索引,
Name:一个Char数组,用来存储节名
Misc:这是一个union联合体,具体的含义这里不作解释,一般表现为VirtualSize,或者直接把它当作VirtualSize也行,这里指的是每个节的未对齐大小
VirtualAddress:指的是内存对齐后节的开始地址,这里也是一个相对地址
SizeOfRawData:指的是进行文件对齐后的节大小
在该文件中,FileAlignment(文件对齐)的大小为0x200,所以这里RawSize就会对齐为0x200的整数倍
PointerToRawData:文件对齐后该节的起始地址

- 通过sizeofHeaders可以看到整个Header的文件对齐长度为0x400,所以第一个节的起始地址就是0x400

PointerToRelocations\PointerToLinenumbers\NumberOfRelocations\NumberOfLinenumbers,暂时用不到:
Characteristics:为每个节的权限

- 这里数值的结果是通过
|得到的,参考微软官方的Section Flags:- 0x60000020 -> 0x20000000 | 0x40000000 | 0x00000020
- IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_CODE
- 0x40000040-> 0x40000000 | 0x00000040
- IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA
- 0xC00000040 -> 0x80000000 | 0x40000000 | 0x00000040
- IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA
- 0x60000020 -> 0x20000000 | 0x40000000 | 0x00000020
?
0x02 Data Directory 解析
Data Directory位于Optional Header,结构体(winnt.h):
1 2 3 4 | typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size;} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; |
在Optional中该结构体数组数量为16个:#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
各个表的序号:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #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 |
该结构体中的Size不影响对于各个表的解析,影响表的解析是VirtualAddress
Export Directory
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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; |
前面4个参数可以不用管,重点在后面的6个参数:
- Name: 指向导出表文件名
- Base:导出函数的起始序号
- NumberOfFunctions:导出函数的个数
- NumberOfNames:以函数名称导出的函数个数
- AddressOfFunctions:导出函数的地址表,既然是地址,那么宽度就是DWORD
- AddressOfNames:导出函数的名称表,表中存放的是函数名称字符串的首地址
- AddressOfNameOrdinals:导出函数的序号表,表中存放的是函数的序号,宽度为Word
PE文件中函数导出有两种方式:
- 序号导出
- 函数名称导出
AddressOfFunctions指向的是一张导出函数表,而NumberOfFunctions是这张表的长度
AddressOfNames指向的是导出函数名称表,NumberOfNames是该表的长度
AddressOfNameOrdinals指向的是导出函数的序号表
这里需要注意导出函数表和其余两张表的长度是不一定相同的,而AddressOfNames和AddressOfNameOrdinals指向的表的长度是一样长度
注意Ordinals序号表中,序号为偏移值,真的序号应该是要加上导出表中的Base参数的值,作为起始序号:
例:DWORD base = 10;
NameOrdinals Table:
| 1 |
|---|
| 3 |
| 4 |
| 6 |
| 7 |
那么真正的序号应该为:
| 11 |
|---|
| 13 |
| 14 |
| 16 |
| 17 |
当导出表的DataDirectory中的VirtualAddress为0时,说明该PE文件不存在导出函数,当然这个可以人为修改,在PE解析中需要通过该VirtualAddress找到导出表存储的位置,如果该值为0,则无法找到
函数名称表AddressOfNames和函数序号表AddressOfNameOrdinals的数量是一样的,当某个函数在导出时写了NoName不以名称导出时,那么此时导出函数序号表中也不会有该函数的函数序号
通过函数名称找函数地址的步骤:
- 遍历函数名称表,找到对应的函数名称,将其下标作为索引1
- 通过1得到的索引1在函数序号表中读取索引值,该值再次作为索引2
- 通过2得到的索引2在函数地址表中读取索引值,该值即为函数地址
通过函数序号找函数地址的步骤:
- 通过提供的序号,减去导出表结构体中的Base参数,得到的值作为索引
- 通过索引去函数地址表中读取索引值,该值即为函数地址
通过对比两种方式的索引可以发现,通过函数序号找函数地址的时候根本不需要用到AddressOfNameOrdinals,Ordinals是跟Names绑定的一张表,所以才会发现这两张表的成员数量是相同的
Import Directory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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;typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; |
通过DataDirectory索引到的VirtuallAddress为导入表的描述符,Name为该导入dll的名称,为偏移量,这里所有的描述符的值都可以理解为偏移,偏移到的是真正存储的位置,这里通过ForwarderChain来确定是否是最后一张导入表描述符,通过,如果该值为-1,则说明该描述符为最后一个导入表描述符
INT 和 IAT
INT:Import Name Table,根据名称找函数
IAT:Import Address Table,根据地址找函数
文件加载前:
OriginalFirstThunk指向的是INT表,INT表中就会涉及到新的结构体:IMAGE_THUNK_DATA,该结构体同样是属于偏移,所以会区分32位和64位
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | typedef struct _IMAGE_THUNK_DATA64 { union { ULONGLONG ForwarderString; // PBYTE ULONGLONG Function; // PDWORD ULONGLONG Ordinal; ULONGLONG AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1;} IMAGE_THUNK_DATA64;typedef IMAGE_THUNK_DATA64 * PIMAGE_THUNK_DATA64;typedef struct _IMAGE_THUNK_DATA32 { union { DWORD ForwarderString; // PBYTE DWORD Function; // PDWORD DWORD Ordinal; DWORD AddressOfData; // PIMAGE_IMPORT_BY_NAME } u1;} IMAGE_THUNK_DATA32;typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32; |
FirstThunk指向的IAT表,IAT表中的结构体跟INT表中的结构体完全相同(ps:文件加载前)
其原因跟THUNK结构体中的类型有关,可以看到该结构体实际上内部是一个union包着4个参数,长度为DWORD,只需要看后面两个,Ordinal和AddressOfData
- Ordinal为序号,通过序号去查找函数位置
- AddressOfData为函数名称的地址,通过导入表中函数的导出名称去查找
在程序加载前,对于每个导入表的导出函数而言,是无法知道其是否可以通过函数名称进行查找的,所以此时才有了INT和IAT
查找IAT表有两种办法,一个是通过DataDirectory[13],第二个是通过导入表中的FirstThunk偏移找到IAT表的起始位置
文件加载后:
INT中的数据不变,IAT中的值替换成函数地址,函数地址通过INT或者IAT进行替换
在查找时,如果文件不存在
Relocation Directory
重定位表关乎到程序能否正常运行,该表的解析很有意思
通过Data Directory[IMAGE_DIRECTORY_ENTRY_BASERELOC]定位到第一张表的指针位置,同样需要通过RVAToRAW拿到文件中存放该表的位置
Relocation结构体:
1 2 3 4 5 6 | typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock;// WORD TypeOffset[1];} IMAGE_BASE_RELOCATION;typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; |
既然是表,就说明不止有这一个结构体,这里的索引也是同样的,通过当前的指针来索引下一个表的位置,最后通过结构体全0来判断该表的结束:
1 | VirtualAddress == 0 && SizeOfBlock == 0 |
SizeOfBlock表明指向的Relocation结构体的实际大小,该大小是包括已知两个参数的大小:所以实际的大小应该为SizeOfBlock - 8,在32位程序中,块大小为2的12次方,也就是4096,所以只需要12位就可以遍历整个块,根据字节对齐需要,编译器对程序进行分配时不会直接分12位,而是会分16位,也就是2个字节,所以对于该2字节,低12位为偏移,高4位为填充,当高4位的值为3时,说明该地址需要被重定位,如果为其他值,则说明该2字节的值可作为填充数据处理

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | this->pRelocation = (PIMAGE_BASE_RELOCATION) & (((PBYTE)lpBuffer)[this->RVAToRAW((this->pDataDirectory + 5)->VirtualAddress)]); PIMAGE_BASE_RELOCATION pTmpRelocation = pe->pRelocation;while (pTmpRelocation->VirtualAddress != 0 && pTmpRelocation->SizeOfBlock != 0) { PWORD pBlock = (PWORD)(pTmpRelocation + 1); cout << "VirtualAddr: " << pTmpRelocation->VirtualAddress << " Size: " << pTmpRelocation->SizeOfBlock; int items = (pTmpRelocation->SizeOfBlock - 8) / 2; cout << " Items: " << items << endl; for (int i = 0; i < items ; i++) { cout << "Item: " << * pBlock << " RVA: " << pTmpRelocation->VirtualAddress + ( * pBlock & 0x7F) << endl; pBlock++; } cout << endl; pTmpRelocation = (PIMAGE_BASE_RELOCATION)((PBYTE)pTmpRelocation + pTmpRelocation->SizeOfBlock);} |
打印结果:

通过这几张表的解析也可以发现PE的特点,由于程序的大小不确定,所以无法静态or动态的分配空间给各种表,所以通过自索引的方式来实现表的遍历,实现了程序的可扩展性,这确实是一种好的方式,这个结构的优点也一直延续至今
Resource Directory
该表分成三层进行解析
First Layer:
第一层为资源样式,最后两个参数之和决定资源数:
DWORD resourceNum = NumberOfNamedEntries + NumberOfIdEntries
1 2 3 4 5 6 7 8 9 | 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; |
在该结构体后紧跟的就是第二层,resourceNum决定后面跟着多少个_IMAGE_RESOURCE_DIRECTORY_ENTRY
PIMAGE_RESOURCE_DIRECTORY_ENTRY = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(PIMAGE_RESOURCE_DIRECTORY + 1)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY { union { struct { DWORD NameOffset:31; DWORD NameIsString:1; } DUMMYSTRUCTNAME; DWORD Name; WORD Id; } DUMMYUNIONNAME; union { DWORD OffsetToData; struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; } DUMMYSTRUCTNAME2; } DUMMYUNIONNAME2;} IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY; |
该结构体为两个2联合体,第一个union中的NameIsString表示该资源是否通过字符串进行索引,该值为1时说明通过字符串进行索引,说明该资源为开发者资源,该值为0说明通过Id进行索引,说明该资源为系统资源,第二个union中的DataIsDirectory表示该数据是否是一个目录,值为1说明该数据为目录
Windows预定义了一些资源,位于winuser.h,一般只用到前面的11个资源,预定义的资源都是通过ID进行索引
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /* * 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)#define DIFFERENCE 11#define RT_GROUP_CURSOR MAKEINTRESOURCE((ULONG_PTR)(RT_CURSOR) + DIFFERENCE)#define RT_GROUP_ICON MAKEINTRESOURCE((ULONG_PTR)(RT_ICON) + DIFFERENCE)#define RT_VERSION MAKEINTRESOURCE(16)#define RT_DLGINCLUDE MAKEINTRESOURCE(17)#if(WINVER >= 0x0400)#define RT_PLUGPLAY MAKEINTRESOURCE(19)#define RT_VXD MAKEINTRESOURCE(20)#define RT_ANICURSOR MAKEINTRESOURCE(21)#define RT_ANIICON MAKEINTRESOURCE(22)#endif /* WINVER >= 0x0400 */#define RT_HTML MAKEINTRESOURCE(23)#ifdef RC_INVOKED#define RT_MANIFEST 24#define CREATEPROCESS_MANIFEST_RESOURCE_ID 1#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID 2#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID 3#define ISOLATIONPOLICY_MANIFEST_RESOURCE_ID 4#define ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID 5#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID 1 /* inclusive */#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID 16 /* inclusive */#else /* RC_INVOKED */#define RT_MANIFEST MAKEINTRESOURCE(24)#define CREATEPROCESS_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1)#define ISOLATIONAWARE_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(2)#define ISOLATIONAWARE_NOSTATICIMPORT_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(3)#define ISOLATIONPOLICY_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(4)#define ISOLATIONPOLICY_BROWSER_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(5)#define MINIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE( 1 /*inclusive*/)#define MAXIMUM_RESERVED_MANIFEST_RESOURCE_ID MAKEINTRESOURCE(16 /*inclusive*/)#endif /* RC_INVOKED */#endif /* !NORESOURCE */ |
关于资源表的结构可以理解为资源管理器,在文件夹中即可以创建文件也可以创建文件夹,所以在解析该结构的时候可以通过递归去做解析
参考文章
[1]PE整体结构体:https://docs.microsoft.com/en-us/windows/win32/debug/pe-format





