基本常识
- EXE和DLL文件PE格式相同,只用了一个字段标识出是哪种文件
- 64位的PE32+没有添加新的结构,只是将32位字段扩展成64位
- 32bit:4D5A
- 64bit:4D5A90
- PE格式定义在头文件winnt.h中
- Image Format
PE基本概念
- PE使用一个平面地址空间(线型),代码和数据放在一起,文件内容被分割为不同的区块,块中包含代码或数据。各个区块按照页边界来对齐,区块没有大小限制,是连续结构。每个块在内存中有自己的属性。
- PE文件不是作为单一内存映射文件被装入内存的,应该由windows加载器(PE装载器)遍历PE文件,决定文件哪一部分被映射(比如调试信息不会被映射),映射方式是将文件较高偏移位置映射到较高的内存地址中。当磁盘文件装入内存,磁盘上的数据结构布局和内存中的数据结构布局一致,但数据之间的相对位置可能改变。
- 在磁盘中以为200h个bytes一页,在内存中以【32bit:4K(1000h),64bit:8K】为一页,加载到内存需要对齐,以0为填充。
- 基地址:PE结构头地址。PE文件加载到内存中就称为一个模块(module),映射文件的起始地址称为句柄(handle),即基地址。
- 用GetModuleHandle(LPCTSTR lpModuleNmae)获取名称对应的句柄指针
- 偏移地址:文件存储在磁盘中时,各个区块对头(0开始数)的相对偏移
- 虚拟地址(VA):内存中区块的地址,32位(2^32)中最大为4G(4x1024x1024x1024=4x2^30=2^32)
- 相对虚拟地址(RVA):VA-ImageBase
MS-DOS头部 400000
- 每个PE文件是以一个DOS程序开始的
- PE文件的第一个字节起始于MS-DOS头部,称作:IMAGE_DOS_HEADER(结构体)
- 重点记忆:e_magic(5A4D:MZ) 、e_lfanew(32位指针,指向PE头)
IMAGE_DOS_HEADER STRUCT
{
+0h WORD e_magic //Magic DOS signature MZ(4Dh 5Ah) DOS可执行文件标记
+2h WORD e_cblp//Bytes on last page of file
+4h WORD e_cp//Pages in file
+6h WORD e_crlc//Relocations
+8h WORD e_cparhdr //Size of header in paragraphs
+0ah WORD e_minalloc //Minimun extra paragraphs needs
+0ch WORD e_maxalloc //Maximun extra paragraphs needs
+0eh WORD e_ss //intial(relative)SS value DOS代码的初始化堆栈SS
+10h WORD e_sp //intial SP value DOS代码的初始化堆栈指针SP
+12h WORD e_csum //Checksum
+14h WORD e_ip // intial IP value DOS代码的初始化指令入口[指针IP]
+16h WORD e_cs //intial(relative)CS value DOS代码的初始堆栈入口
+18h WORD e_lfarlc //File Address of relocation table
+1ah WORD e_ovno // Overlay number
+1ch WORD e_res[4] //Reserved words
+24h WORD e_oemid // OEM identifier(for e_oeminfo)
+26h WORD e_oeminfo // OEM information;e_oemid specific
+29h WORD e_res2[10] // Reserved words
+3ch DWORD e_lfanew //Offset to start of PE header 指向PE文件头
} IMAGE_DOS_HEADER ENDS
PE文件头 4000E0
- PE Header是PE相关结构NT映像头(IMAGE_NT_HEADER)的简称,包含PE装载器用到的重要字段。e_lfanew+基地址即得到指向PE Header的指针。(PNTHeader = ImageBase + dosHeader ->e_lfanew) ```cpp typedef struct _IMAGE_NT_HEADERS { +00h DWORD Signature; // 固定为 0x00004550 根据小端存储为:”PE..” +04h IMAGE_FILE_HEADER // FileHeader结构体; +18h IMAGE_OPTIONAL_HEADER32 //OptionalHeader;(大小在FileHeader结构体中) } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER { +04h WORD Machine; // 运行平台 +06h WORD NumberOfSections; // 文件的区块数目(区块表紧跟在IMAGE_NT_HEADER) +08h DWORD TimeDateStamp; // 文件创建日期和时间 +0Ch DWORD PointerToSymbolTable; // 指向符号表(主要用于调试)【没啥用,可用来放攻击载核】 +10h DWORD NumberOfSymbols; // 符号表中符号个数(同上)【没啥用,可用来放攻击载核】 +14h WORD SizeOfOptionalHeader; // IMAGE_OPTIONAL_HEADER32 结构大小 +16h WORD Characteristics; // 文件属性 } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

- Signature(是否是有效PE文件:**4550**)
- **IMAGE_FILE_HEADER**
- Machine
| 宏定义 | 平台及相关意义 | 数值 |
| --- | --- | --- |
| IMAGE_FILE_MACHINE_I386 | x86、Intel 386 | 0x014c |
| **IMAGE_FILE_MACHINE_IA64** | Intel Itanium、Intel 64 | 0x0200 |
| **IMAGE_FILE_MACHINE_AMD64** | x64、AMD64 (K8) | 0x8664 |
- NumberOfSections:文件的区块数目(区块表紧跟在IMAGE_NT_HEADER)
- TimeDateStamp: 文件创建日期和时间(使用**_ctime**或**gmtime**转换成可读)
- ** SizeOfOptionalHeader**:IMAGE_OPTIONAL_HEADER32 结构大小,**正常为0xE0,否则可能是恶意代码!!**
- Characteristics:pe文件属性信息,有选择的通过几个值运算(**或**)得到
```cpp
#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 // 符号被移除
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Agressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // 程序能处理大于2G的地址
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#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 // 文件只能运行在单处理器上
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.
- IMAGE_OPTIONAL_HEADER32(!!)
- AddressOfEntryPoint:程序执行入口RVA,AddressOfEntryPoint指向的位置开始执行(病毒利用,最后指回真正入口点),dll文件填充0
- ImageBase:程序的首选装载地址,如果被占用由装载器重新指定(exe默认4000000h,dll默认10000000h)
- SectionAlignment:指定了节被装入内存后的对齐单位,每个节被装入的地址必定是本字段指定数值的整数倍。(1000h)
- FileAlignment:节存储在磁盘文件中的对齐单位。(200h)
- DataDirectory(!!):数据目录表,由16个相同的IMAGE_DATA_DIRECTORY结构组成。里面包含每个数据块的起始RVA和长度。在PE文件中寻找特定数据时从这些IMAGE_DATA_DIRECTORY结构开始查找。
- [0]:IMAGE_DIRECTORY_ENTRY_EXPORT 导出表
- [1]:IMAGE_DIRECTORY_ENTRY_IMPORT 导入表
- [2]:IMAGE_DIRECTORY_ENTRY_RESOURCE 资源
- [5]:IMAGE_DIRECTORY_ENTRY_BASERELOC 重定位表
```cpp
typedef struct _IMAGE_OPTIONAL_HEADER
{
//
// Standard fields.
// +18h WORD Magic; // 标志字, ROM 映像(0107h),普通可执行文件(010Bh) +1Ah BYTE MajorLinkerVersion; // 链接程序的主版本号 +1Bh BYTE MinorLinkerVersion; // 链接程序的次版本号 +1Ch DWORD SizeOfCode; // 所有含代码的节的总大小 +20h DWORD SizeOfInitializedData; // 所有含已初始化数据的节的总大小 +24h DWORD SizeOfUninitializedData; // 所有含未初始化数据的节的大小 +28h DWORD AddressOfEntryPoint; // 程序执行入口RVA +2Ch DWORD BaseOfCode; // 代码的区块的起始RVA +30h DWORD BaseOfData; // 数据的区块的起始RVA // // NT additional fields. 以下是属于NT结构增加的领域。 // +34h DWORD ImageBase; // 程序的首选装载地址 +38h DWORD SectionAlignment; // 内存中的区块的对齐大小 +3Ch DWORD FileAlignment; // 文件中的区块的对齐大小 +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; // DllMain()函数何时被调用,默认为 0 +60h DWORD SizeOfStackReserve; // 初始化时的栈大小 +64h DWORD SizeOfStackCommit; // 初始化时实际提交的栈大小 +68h DWORD SizeOfHeapReserve; // 初始化时保留的堆大小 +6Ch DWORD SizeOfHeapCommit; // 初始化时实际提交的堆大小 +70h DWORD LoaderFlags; // 与调试有关,默认为 0 +74h DWORD NumberOfRvaAndSizes; // 下边数据目录的项数,这个字段自Windows NT 发布以来一直是16 +78h IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表 } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; /指向某个数据的相对虚拟地址 RAV 偏移0x00/ DWORD Size; /某个数据块的大小 偏移0x04/ } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
<a name="vuU0T"></a>
## 区块表(节表) 400160
- **PE文件到内存的映射**:执行PE时,windows不会在一开始就将整个文件读入内存。windows加载器在装载时仅仅建立好虚拟地址和PE文件之间的映射关系。当执行到某个内存页中的指令或访问某一页中的数据时,页面才会从磁盘提交到物理内存。
- 装载DOS头、PE头和区块表时不作任何处理,装载区块时按照对应属性做不同处理。
- 内存页的属性
- 节的偏移地址
- 节的尺寸
- 不进行映射的节:.reloc,重定位动态链接库的地址
- **节事实上就是相同属性数据的集合**,当节被载入内存后,相同的一个节对应的内存页都将被赋予相同的页属性,节在内存中的对齐单位必须至少是一个页的大小。
- **节表紧挨PE文件头**,PE文件中所有节的属性都被定义在节表中,节表由一系列的**IMAGE_SECTION_HEADER**结构组成,每个结构描述一个节,排列顺序和节的排列顺序相同。最后以一个空的IMAGE_SECTION_HEADER结构作为结束。**IMAGE_FILE_HEADER->NumberOfSections指定节表总数(节数+1)**。
- **IMAGE_SECTION_HEADER(40【28h】个字节)**
- Name:一般占8个字节,习惯以'.'开头(不是必须),前面带'$'的区块在载入时会被链接器合并($A在$B之前)。不能有重名区块。(**恶意代码常增加区块保存自己的内容,特殊区块名也常作为文件是否被感染的标记**)
- 从PE文件中读取需要的区块时不能以名称作为标准,正确方法时按照**IMAGE_OPTIONAL_HEADER32结构中的数据目录字段**结合定位。
- VirtualSize:区块数据在没有对齐处理前的实际大小
- **VirtualAddress**:区块载入到内存中的RVA地址,按照内存页来对齐(SectionAlignment的整数倍),第一个默认为1000h(x86),x64中为2000h
- **SizeOfRawData**:该区块在磁盘中所占大小(按照FileAlignment对齐后的结果)
- **PointerToRawData**:区块在磁盘中的偏移(用工具打开看到的),从文件头算起
- **Characteristics**:按位指明区块的属性(**or操作**)
- IMAGE_SCN_CNT_CODE(0x00000020):包含代码,常与 0x10000000一起设置。
- IMAGE_SCN_MEM_SHARED(0x10000000):该区块为共享区块
- IMAGE_SCN_MEM_DISCARDABLE(0x02000000):该区块可被丢弃,因为当它一旦被装入后,进程就不在需要它了,典型的如重定位区块。(.reloc只在载入的时候有用)
- IMAGE_SCN_MEM_EXECUTE(0x20000000):可执行
- IMAGE_SCN_MEM_READ(0x40000000):可读
- IMAGE_SCN_MEM_WRITE(0x80000000):可写
```cpp
typedef struct _IMAGE_SECTION_HEADER
{
+0h BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节表名称,如“.text”
//IMAGE_SIZEOF_SHORT_NAME=8
union //二选其一
+8h {
DWORD PhysicalAddress; // 物理地址
DWORD VirtualSize; // 真实长度,这两个值是一个联合结构,可以使用其中的任何一个,一般是取后一个
} Misc;
+0ch DWORD VirtualAddress; // 节区的 RVA 地址
+10h DWORD SizeOfRawData; // 在文件中对齐后的尺寸
+14h DWORD PointerToRawData; // 在文件中的偏移量
+18h DWORD PointerToRelocations; // 在OBJ文件中使用,重定位的偏移
+1ch DWORD PointerToLinenumbers; // 行号表的偏移(供调试使用地)
+1eh WORD NumberOfRelocations; // 在OBJ文件中使用,重定位项数目
+20h WORD NumberOfLinenumbers; // 行号表中行号的数目
+24h DWORD Characteristics; // 节属性如可读,可写,可执行等
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
- 微软默认的区块名称
- 修改数据存放的区块:#pragma data_msg(“FC_data”),把数据放在FC_data
- 合并区块:链接会合并有相似、一致性的属性
- 区块的对齐值
- 打乱区块可以免杀
- 在对齐填充的00h里可以填充恶意代码(先执行恶意代码->再执行PE文件),恶意代码可以拆分
- RVA和文件偏移的转换(换算内存->物理地址)
- RVA:当PE文件被装载到内存后,某个数据位置相对于文件头的偏移量(文件被装载的基地址)。
- 步骤一:循环扫描区块表,得出每个区块在内存中的起始RVA(IMAGE_SECTION_HEADER -> VirtualAddress),根据区块的大小(IMAGE_SECTION_HEADER -> SizeOfRawData)算出区块的结束RVA,判断目标是否在该区块中;
- 步骤二:定位目标RVA所在区块后,用目标RVA减去区块的起始RVA(IMAGE_SECTION_HEADER -> VirtualAddress),得到偏移量RVA2;
- 步骤三:在区块表中获取该区块在文件中所处的偏移地址RVA3(IMAGE_SECTION_HEADER->PointerToRawData),RVA2+RVA3得到了目标在文件中的偏移地址
输入表(导入表)
- 一个区块中的数据是因为属性相同而放在一起的,与具体用途无关。因此 导入导出表也可能和只读数据放在一个区块里。
- 动态链接:调用程序仅在导入表中保留相关函数信息(dll名和函数名),在磁盘上的PE文件无法得知导入函数在内存中的地址,只有PE文件被装入内存后,windows加载器才将相关dll装入,并将调用导入函数的指令和其目前实际在内存中的地址联系起来。动态链接是通过PE文件中定义的导入表完成的。
- 示例:静态反汇编,获取导入函数地址(MessageBox)
- 反汇编中找到导入函数的虚拟地址(VA),转换成物理地址,这个物理地址处存放的是导入函数信息的地址,在这个地址处发现【导入函数名.dll名】
- 导入表工作机制
- 导入表结构
- IMAGE_OPTIONAL_HEADER->DataDirectory的第二个成员指向导入表
- 导入表以一个IMAGE_IMPORT_DESCRIPTOR(IID)的数组开始,每个被PE文件链接进来的DLL文件都分别对应一个IID数组结构,最后以一个全为NULL(0)的IID作为IID数组的结束标志。
- IMAGE_IMPORT_DESCRIPTOR
- OriginalFirstThunk和FirstThunk都是类型为IMAGE_THUNK_DATA(联合类型,DWORD大小)的数组。
- IMAGE_THUNK_DATA
- 当IMAGE_THUNK_DATA的值最高位为1时,表示函数以序号方式输入,这时低31位(16位WORD)被看成是函数序号。
- 当IMAGE_THUNK_DATA的值最高位为0时,表示函数以函数名方式输入,这时值是一个RVA,指向一个IMAGE_IMPORT_BY_NAME结构。
- IMAGE_IMPORT_BY_NAME
- 为什么两个指针同时指向?【一个放名称、一个放地址】
- OriginalFirstThunk指向的,不能被改写(INT:Import Name Table)
- FirstThunk指向的数组是由PE装载器重写的【核心操作】(IAT),PE装载器首先搜索OriginalFirstThunk,索引到IMAGE_IMPORT_BY_NAME,找到每个数组中每个函数的名称(或序号),将该函数的地址就放到FirstThunk指向的IAT。
- 为什么两个指针同时指向?【一个放名称、一个放地址】
- 导入表结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {//一般取第二个值
DWORD Characteristics; // 0 for terminating null import descriptor
DWORD OriginalFirstThunk;
};
DWORD TimeDateStamp; //当可执行文件不与被导入的DLL进行绑定时,此字段为0
DWORD ForwarderChain; //第一个被转向的API索引
DWORD Name; //指向被导入的DLL 名称
DWORD FirstThunk; //指向输入地址表(IAT)RVA,IAT是一个IMAGE_THUNK_DATA结构的数组
}
struct _IMAGE_THUNK_DATA32{
union {
DWORD ForwarderString;
DWORD Function; //被输入的函数的内存地址
DWORD Ordinal; //高位为1则被输入的API的序数值
DWORD AddressOfData;//高位为0则指向IMAGE_IMPORT_BY_NAME 结构体二
}u1;
}IMAGE_THUNK_DATA32;
//IMAGE_THUNK_DATA64与IMAGE_THUNK_DATA32的区别,仅仅是把DWORD换成了64位整数。
struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;//指出函数在所在的dll的输出表中的序号
BYTE Name[1];//指出要输入的函数的函数名
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
- EXE的导入表和dll的导出表共同配合确定导入函数的实际装载地址。
输出表(导出表)
- 包含函数名称,输出序数等,序数是代表dll中某个函数的16位数字,在dll文件中是独一无二的(不提倡通过序数索引,容易发生改变)。
- IMAGE_OPTIONAL_HEADER->DataDirectory的第一个成员指向导出表
- 导入表以一个IMAGE_EXPORT_DESCRIPTOR(IED)的数组开始
- Name:记载动态链接库(dll)的真实名称,不容易被修改
- Base:基数,加上序数就是函数地址数组的索引值
- NumberOfNames(病毒常用)
- 可以利用DEF伪造dll导出函数的实际个数(PE结构查看器bug:其实不存在的函数地址都显示为0)
IMAGE_EXPORT_DIRECTORY STRUCT【导出表,共40字节】
{
+00 h DWORD Characteristics ; 未使用,总是定义为0
+04 h DWORD TimeDateStamp ; 文件生成时间
+08 h WORD MajorVersion ; 未使用,总是定义为0
+0A h WORD MinorVersion ; 未使用,总是定义为0
+0C h DWORD Name ; 模块的真实名称
+10 h DWORD Base ; 基数,加上序数就是函数地址数组的索引值
+14 h DWORD NumberOfFunctions ; 导出函数的总数
+18 h DWORD NumberOfNames ; 以名称方式导出的函数的总数
+1C h DWORD AddressOfFunctions ; 指向输出函数地址的RVA
+20 h DWORD AddressOfNames ; 指向输出函数名字的RVA
+24 h DWORD AddressOfNameOrdinals ; 指向输出函数序号的RVA(挂钩名称RVA和地址RVA,相当于数组序号)
};IMAGE_EXPORT_DIRECTORY ENDS
从序号查找函数入口地址
- 定位到PE文件头
- 从IMAGE_OPTIONAL_HEADER32结构中取出数据目录表(IMAGE_DATADIRECTORY),从第一个IMAGE_DATADIRECTORY中的到导出表的RVA
- 从导出表的Base字段得到起始序号
- 需要查找的导出序号 - 起始序号 = 函数在入口地址表中的索引
- 检查索引是否大于NumberofFunctions字段的值,如果大于说明序号无效
- 用索引在AddressofFunctions字段指向的导出函数入口地址表中取出相应的项目,这就是函数入口地址的RVA,当函数被装入内存时,这个RVA + 模块实际装入的基地值 —> 函数真正的入口地址。
从函数名称查找函数入口地址
- 定位到PE文件头
- 从IMAGE_OPTIONAL_HEADER32结构中取出数据目录表(IMAGE_DATADIRECTORY),从第一个IMAGE_DATADIRECTORY中的到导出表的RVA
- 从NumberOfNames字段得到函数总数,以这个数作为循环次数构造一个循环
- 从AddressOfNames字段指向得到的函数名称地址表的第一项开始,在循环中将每个函数与待查函数名进行比较,如果符合记下索引(3)
- 在AddressOfNameOrdinals中以同样的索引取出数组项的值(3->x)
- 以x+base为索引,在AddressOfFunction中获取的RVA就是函数真正的入口地址。
基址重定位
- 重定位:原来的地址被其他程序占用,需要基址重定位。凡是涉及到直接寻址的指令都需要进行重定位处理。将直接寻址指令中的双字地址加上模块的实际装入地址与模块建议装入地址之差。
- 系统对于一条指令进行重定位需要哪些信息?
- 需要修正的地址(x->x+(b-a))
- 建议装入的地址(a)
- 实际装入的地址(b)
- 重定位需要的信息哪些要保存在重定位表中?
- 建议->PE头中定义了
- 实际->只有装载器知道
- 只保存需要进行重定位修正的代码的地址(RVA)
- 基址重定位结构表(IMAGE_BASE_RELOCATION),表位于.reloc区块
- VirtualAddress:Base Relocation Table的RVA(需要重定位的数据的基址,偏移在TypeOffset数组中)
- SizeOfBlock:Base Relocation Table的大小
- TypeOffset:数组,每项大小为两个字节
- 高4位:重定位类型
- 低12位:重定位地址(+VirtualAddress = 需要重定位的代码地址) ```cpp typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
VirtualAddress:页起始地址RVA。 SizeOfBlock:表示该分组保存了几项重定位项。 TypeOffset:这个域有两个含义,大家都知道,页内偏移用12位就可以表示,剩下的高4位用来表示重定位的类型。而事实上,Windows只用了一种类型IMAGE_REL_BASED_HIGHLOW 数值是 3。
<a name="fVT0L"></a>
## 资源
- 资源结构
- 类似于磁盘结构目录
- 资源类系 -> 资源ID -> 资源代码页

- **IMAGE_DATA_DIRECTORY**的第三项指向资源区块
- 资源目录结构中每一个节点都是由**IMAGE_RESOURCE_DIRECTORY(深灰)+n个IMAGE_RESOURCE_DIRECTORY_ENTRY(浅灰)**结构组成的。
- IMAGE_RESOURCE_DIRECTORY:个数

- **IMAGE_RESOURCE_DIRECTORY**
- NumberOfNamedEntries:以名称命名的入口数量
- NumberOfIdEntries:以ID命名的入口数量
```cpp
typedef struct _IMAGE_RESOURCE_DIRECTORY
{
DWORD Characteristics; //理论上为资源的属性,不过事实上总是0
DWORD TimeDateStamp; //资源的产生时刻
WORD MajorVersion; //理论上为资源的版本,不过事实上总是0
WORD MinorVersion
WORD NumberOfNamedEntries; //以名称(字符串)命名的入口数量
WORD NumberOfIdEntries; //以ID(整型数字)命名的入口数量
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
- IMAGE_RESOURCE_DIRECTORY_ENTRY(8个字节,包含2个字段)
- Name:在不同层目录中含义不同(第一层:资源类型,第二层:资源名称,第三层:代码页编号)
- 最高位为0: 字段的值做ID使用
- 最高位为1: 字段的低位作为指针使用,指向IMAGE_RESOURCE_DIR_DTRING_U结构 ,指针是从.rdata开始的地方算起的偏移量(不是RVA!!!)
- OffsetToData(指针)
- 最高位为0: 指向IMAGE_RESOURCE_DATA_ENTRY结构
- 最高位为1: 低位数据指向下一层目录块的起始地址,指针是从.rdata开始的地方算起的偏移量(不是RVA!!!)
- Name:在不同层目录中含义不同(第一层:资源类型,第二层:资源名称,第三层:代码页编号)
- IMAGE_RESOURCE_DATA_ENTRY(资源数据真正的位置和描述)
- OffsetToData:资源数据的RVA
```cpp
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
}; DWORD Name; //资源/语言类型 WORD Id; //资源数字ID }; union { DWORD OffsetToData; //数据偏移地址 struct {DWORD NameOffset : 31; //资源名偏移
DWORD NameIsString : 1; //资源名为字符串
}; }; } IMAGE_RESOURCE_DIRECTORY_ENTRY, *PIMAGE_RESOURCE_DIRECTORY_ENTRY;DWORD OffsetToDirectory : 31; //子目录偏移地址
DWORD DataIsDirectory : 1; //数据为目录
- OffsetToData:资源数据的RVA
```cpp
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
typedef struct _IMAGE_RESOURCE_DIR_STRING_U { WORD Length;//字符串长度 WCHAR NameString[1];//字符串数组 } IMAGE_RESOURCE_DIR_STRING_U, *PIMAGE_RESOURCE_DIR_STRING_U;
typedef struct _IMAGE_RESOURCE_DATA_ENTRY { DWORD OffsetToData;//资源数据的RVA DWORD Size;//资源数据的长度 DWORD CodePage;//代码页 DWORD Reserved;//保留字段 } IMAGE_RESOURCE_DATA_ENTRY, *PIMAGE_RESOURCE_DATA_ENTRY; ``` accepted