Windows
PE 文件结构图
PEB 结构
32位及64位TEB、PEB详解
x86/x64 PE文件头详解
本文档使用 MrDoc 发布
-
+
首页
x86/x64 PE文件头详解
## 这篇也是暂时的,后面会有大量补充。 # 基础概念 ## 地址 - ImageBase 基地址 - VA 虚拟地址 - 进程基地址+相对虚拟内存地址 - 32 位程序中,PE 虚拟地址空间是 4G(0x0000 0000 ~ 0xffff ffff) - 低 2G(0x0000 0000 ~ 0x7fff ffff) 是用户空间,高 2G(0x8000 0000 ~ 0xffff ffff)是内核空间 - 部分情况是低 3G(0x0000 0000 ~ 0xbfff ffff) 是用户空间,高 1G(0xc000 0000 ~ 0xffff ffff) 是内核空间 - RVA 相对虚拟内存地址 - RVA = VA - ImageBase - **RVA一定是相对某个模块的** - 模块加载时会有个基地址,不同模块基地址不同 - FOA offset 文件偏移地址,文件中使用 offset 表示位置,内存用 VA 表示 - 和内存无关,指某个位置距离文件头的偏移 - 特殊地址 - 不是从内存头或者某个模块的基地址开始计算,而是从某个特定位置开始 - 少见,但是在资源表里就出现过这种地址 ## 数据目录 - 除了代码和数据段,还包含了一些其它的数据,如导入导出表,图标资源等。**数据目录**的作用是记录所有可能的数据类型。目前已定义的有 15 种,包括导出表、导入表、资源表、异常表、属性证书表、重定位表、调试数据、Architecture、Global Ptr、线程局部存储、加载配置表、绑定导入表、IAT、延迟导入表和 CLR 运行时头部。 ## 节 - 程序对数据和代码分离,为了稳定和安全性,系统一般会对不同用途的数据给予不同的访问权限。如代码段不允许修改,数据段不允许执行等。**节**就是存放不同类型数据的地方,不同的节有不同的访问权限,不同的节有不同权限。有些数据类型不同,属于不同的数据目录,但是访问属性相同也会被合并到同一节中。 ## 对齐 ### 内存对齐 - 由于 Windows 操作系统对内存属性的设置以页为单位,通常情况节在内存中的对齐单位必须至少是一个页的大小。**32 位系统值是 4KB(1000h),64 位操作系统值是 8KB (2000h)**。 ### 文件对齐 - 节在磁盘文件中的对齐尺寸没有那么严格。为了提高磁盘利用率,通常情况节的文件对齐单位小于内存对齐单位:通常会以一个物理扇区的大小作为对齐粒度的值,即 **512 字节,十六进制表示为 200h**。系统允许节在内存和文件中的对齐尺寸不一致。造成了 PE 在文件中和在内存中的大小也会不一致。通常情况下,PE 在内存中的尺寸要比在文件中的尺寸大。用户可以自己定义这些对齐的值(如果内存对齐被定义为小于操作系统页的大小,则文件对齐和内存对齐的值必须一致)。 ### 数据资源对齐 - 资源文件中,资源字节码部分一般要求以双字(4 个字节)方式对齐 ### Unicode 字符串 ``` typedef struct _UNICODE_STRING { WORD Length; // 字符串长度(字节数) WORD MaximumLength; // 字符串缓冲区长度(字节数) WORD * Buffer; // 字符串缓冲区 } UNICODE_STRING, *PUNICODE_STRING; ``` - Unicode 字符串结尾是 '\0',也就是 0x00 00,ASCII 结尾的'\0'是 0x00。 ## PE 计算 ### FOA 和 RVA 的转换 - PE 文件头和 PE 内存映像的文件头大小都是一样的,他们受对齐粒度不同的映像;节的数据在内存和磁盘文件的大小是不一样的,**节表项记录了内存映像中节的其实RVA,也同样记录了本节在文件中的起始偏移。**节在内存中是线性排列的,如果找到下一个节的内存起始RVA就能知道上一个节的大小。 - RVA 转 FOA 步骤: 1. 判断指定的 RVA 落在哪个节内。(数据目录项中的第一个成员是 RVA,如果落在两个相连的 RVA 内表示的就是在前一个数据目录项对应的节中) 2. 求出该节的起始 RVA0 = IMAGE_SECTION_HEADER.VirtualAddress。 3. 求出偏移量 offset1 = RVA - RVA0。 4. 求出该 RVA 相对磁盘文件头的偏移 - FOA = IMAGE_SECTION_HEADER.PointerToRawData + offset1 - FOA 转 RVA(一般不会这样): 1. 同理,判断 FOA 落在哪个节中。(节表项中有个是物理偏移)。 2. 求出文件节的起始, FOA0 = IMAGE_SECTION_HEADER.PointerToRawData。 3. 求出偏移量 offset1 = FOA - FOA0 4. 求出该 FOA 相对于 ImageBase 的偏移 - RVA = IMAGE_SECTION_HEADER.VirtualAddress + offset1 ### PE 校验和计算 1. 将文件头部的字段 IMAGE OPTIONAL HEADER32.CheckSum 清 0。 2. 以 WORD 为单位对数据块进行带进位的累加,大于 WORD 部分自动溢出 3. 将累加和加上文件的长度。 # PE 文件结构 ## 结构划分 - PE 结构 - PE 文件头部 - **IMAGE_DOS_HEADER**-DOS 头-64BYTE - **DOS MZ 头** - +00h-**`e_magic`**-WORD(2Byte):DOS可执行文件标记 4Dh 5Ah 写作MZ - 其它成员不需要看 - +3Ch-**`e_lfanew`**-LONG(4Byte):指向PE的NT头,实际上保存的是 PE 头相对于文件头的偏移地址 - **DOS Stub**-DOS 存根:在DOS环境下运行,一般用来显示该程序不能在 DOS 环境下运行 - **IMAGE_NT_HEADER**-NT 头:DOS MZ基地址+IMAGE_DOS_HEADER.e_lfanew - +00h-**Signature**-PE 签名-DWORD(4Byte):50h 45h 00h 00h 写作'PE\0\0'。修改该字段会导致文件在系统加载失败。 - +04h-**`IMAGE_FILE_HEADER`**-标准 PE 头-20Byte。由于PE扩展自通用COFF规范所以,该字段在官方文档中被称为标准COFF头。 - +04h-**Machine**-运行平台-WORD(2Byte) 1. **0x0 适用于任何类型处理器** 2. 0x1d3 Matsushita AM33处理器 3. **0x8664 x64处理器**:二进制0x64 0x86,写作"d." 4. 0x1c0 ARM小尾处理器 5. 0xebc EFI字节码处理器 6. **0x14c Intel 386或后继处理器及其兼容处理器**:二进制0x4c 0x01(小端存储),写作"L." 7. 0x200 Intel Itanium处理器 8. 0x9041 Mitsubishi M32R小尾处理器 9. 0x266 MIPS16处理器 10. 0x366 带FPU的MIPS处理器 11. 0x466 带FPU的MIPS16处理器 12. 0x1f0 Power PC小尾处理器 13. 0x1f1 带符点运算支持的Power PC处理器 14. 0x166 MIPS小尾处理器 15. 0x1a2 Hitachi SH3处理器 16. 0x1a3 Hitachi SH3 DSP处理器 17. 0x1a6 Hitachi SH4处理器 18. 0x1a8 Hitachi SH5处理器 19. 0x1c2 ARM或Thumb处理器 20. 0x169 MIPS小尾WCE v2处理器 - +06h-**`NumberOfSections`**-文件的区块数目-WORD(2Byte),和实际如果不同运行会报错。可以有0个节,但是xp中数值不能小于1(即使节数量为0也要写成1),也不能大于96。如果写成0则会提示不是有效的win32程序。**这个值必须和实际内存中的节数量相同。** - +08h-**TimeDateStamp**-文件创建日期和时间-DWORD(4Byte):这是个UNIX时间戳,没影响 - +0Ch-**PointerToSymbolTable**-指向符号表-DWORD(4Byte):主要用于调试。COFF 符号表的文件偏移。如果不存在COFF符号表,则为0。 - +10h-**NumberOfSymbols**-符号表中符号个数-DWORD(4Byte):主要用于调试。一般为0。 - +14h-**`SizeOfOptionalHeader`**-IMAGE_OPTIONAL_HEADER32结构大小-WORD(2Byte):扩展头结构的长度 - **32 位默认为 00E0H(224个字节)** - **64 位默认为 00F0H(240个字节)** - 可以扩展 1. 扩展后需要自行将 IMAGE_OPTIONAL_HEADER大小扩展成指定的值(一般以0补充) 2. 扩展完成后,维持文件对其(举例(具体根据实际看):如此处增加8个字节,一定要在后面删除响应的8个字节,确保.text节起始位置处于0x0400h处) - +16h-**`Characteristics`**-文件属性-WORD(2Byte) - 判断 DLL 或 EXE - 0 为1表明此文件不包含基址重定位信息,因此必须被加载到其首选基地址上。如果基地址不可用,加载器会报错。 - **1 文件是可执行(合法)的。看起来有点多此一举,但又不能少。未设置表明链接器错误** - 2 保留,必须为0。不存在行信息 - 3 保留。不存在符号信息 - 4 保留,必须为0。调整工作集 - **5 应用程序可以处理大于2GB的地址。** - 6 保留,必须为0。 - 7 小尾方式 - **8 机器类型基于32位体系结构。只在 32 位运行(来自:权威指南)** - 9 调试信息已经从此镜像文件中移除。不包含调试信息(来自:权威指南) - 10 不能从可移动盘运行,如果此镜像文件在可移动介质上,完全加载它并把它复制到交换文件中。几乎不用。不能从可移动盘运行(来自:权威指南) - 11 不能从网络运行,如果此镜像文件在网络介质上,完全加载它并把它复制到交换文件中。几乎不用。不能从网络运行(来自:权威指南) - 12 此镜像文件是系统文件(如驱动文件),而不是用户程序。不能直接运行 - **13 此镜像文件是动态链接库(DLL)。1 表示 dll,0 表示可执行** - 14 此文件只能运行于单处理器机器上。 - 15 大尾方式 - **对于普通 32 位可执行 PE 文件来说,这个字段的值一般为 010Fh(0,1,2,3,8位),0102h(1,8位)。而对于 DLL 文件来说,这个字段一般是210Eh(1,2,3,8,13)、64位dll是2022h(1,2,13)。普通64位可执行PE文件位 0022h(1和5位)。** - +18h-**IMAGE_OPTIONAL_HEADER32**-x32 PE可选头(扩展头)-**`224BYTE`**:定义PE文件属性。可执行文件的大部分特性在此定义。 - +18h-**IMAGE_OPTIONAL_HEADER64**-x64 PE可选头(扩展头)-**`240BYTE`**:64 位大小不一样 - +18h-**`Magic`**-Magic 标志字-WORD(2Byte):0x010B(32位,文件中0Bh 01h),0x020B(64位,文件中 0Bh 02h),0x0107(ROM镜像,文件中07h 01h)。系统根据这个判断多少位 - +1Ah-**MajorLinkerVersion**-链接器主版本号-BYTE(1Byte) - +1Bh-**MinorLinkerVersion**-链接器次版本号-BYTE(1Byte):这两个对执行没有任何影响。 - +1Ch-**SizeOfCode**-所有含代码节的总字节大小-DWORD(4Byte):多个节计算总和,基于文件对齐的大小,后面有个SizeOfImage是基于内存对齐的大小。 - +20h-**SizeOfInitializedData**-所有含已初始化数据的节的总大小-DWORD(4Byte) - +24h-**SizeOfUninitializedData**-所有含未初始化数据的节的大小-DWORD(4Byte):这些数据未初始化,在文件中不占用空间。但加载入内存后,会被分配虚拟空间。 - +28h-**`AddressOfEntryPoint`**-程序执行入口 RVA-DWORD(4Byte):如果在可执行文件上加了一段代码并且想先被执行,只需要将这个入口地址指向附加的代码就可以了。DLL 一般是填充 0。该字段的值是一个RVA,它记录了启动代码距离该PE加载后的**起始位置**到底有多少个字节。 - +2Ch-**BaseOfCode**-代码节的起始 `RVA`-DWORD(4Byte):表示映像被载入内存时代码节相对于映像基址(ImageBase)的偏移地址。一般来说,**代码节紧跟PE头后面**,名称通常为".text" - +30h-**BaseOfData**-数据节的起始 `RVA`-DWORD(4Byte)-**PE32+(64 位PE)该字段被删除:****数据节一般位于文件末尾**,节名称通常为".data"。PE32+(64 位PE)该字段被删除。 - +34h-**`ImageBase`**-文件的优先装入地址-DWORD(4Byte)-**PE32+(64 位PE)QWORD(8Byte):**PE32+类型变为ULONGLONG 8 字节,为了适应增大的进程虚拟内存。文件被执行的时候,系统优先将文件装到指定的地址中。只有指定的地址被其它模块占用,文件才能被装入到其它地址中。在链接的时候可以指定优先装入地址。不指定的话,EXE 文件默认优先装入地址为 00400000h,DLL 的默认优先装入地址为 10000000h。限制 1:取值不能超出进程的地址空间。限制 2:该值必须是 64KB 的整数倍。ImageBase是PE文件在内存中的加载基址,而AddressOfEntryPoint是PE文件中可执行代码的起始地址。ImageBase用于确定文件在内存中的布局,而AddressOfEntryPoint用于确定程序执行的起始点。 - +38h-**`SectionAlignment`**-内存中的区块的对齐大小-DWORD(4Byte):**一般是0x1000H(4KB)(Win32的页面大小是4KB),64位`有的是`2000h(8KB)。资源字节码部分一般按照双字(4字节)对齐。**每个节被装入的地址必定是本字段指定数的整数倍。SectionAlignment 必须大于或等于FileAlignment。当它小于系统页面大小时,必须保证SectionAlignment 和FileAlignment 相等。有两个办法确定: 1. 查看簇及扇区大小 fsutil fsinfo ntfsinfo d: 2. 创建 10 字节大小文件,查看文件属性,占用空间显示的就是簇大小 - +3Ch-**`FileAlignment`**-文件中的区块的对齐大小-DWORD(4Byte):一般是 0x200H(512BYTE) - +40h-**MajorOperatingSystemVersion**-要求操作系统最低版本号的主版本-WORD(2Byte) - +42h-**MinorOperatingSystemVersion**-要求操作系统最低版本号的次版本-WORD(2Byte) - +44h-**MajorImageVersion**-可运行于操作系统的主版本号(PE文件映像版本号)-WORD(2Byte) - +46h-**MinorImageVersion**-可运行于操作系统的次版本号(PE文件映像版本号)-WORD(2Byte) - +48h-**MajorSubsystemVersion**-要求最低子系统版本的主版本号-WORD(2Byte) - +4Ah-**MinorSubsystemVersion**-要求最低子系统版本的次版本号-WORD(2Byte) - +4Ch-**Win32VersionValue**-未用-DWORD(4Byte):不为 0 会显示应用程序正常初始化失败 - +50h-**`SizeOfImage`**-映像装入内存后的总尺寸-DWORD(4Byte):可以比实际大,但是不能比实际小;**必须是 SectionAlignment 整数倍** - +54h-**SizeOfHeaders**-所有头+节表按照FileAlignment对齐后的尺寸大小-DWORD(4Byte):**该部分严格按照 200h 对齐,不对其系统加载会出错。** - +58h-**CheckSum**-映像的校验和-DWORD(4Byte):通常作为判断这段数据是否被非法修改的依据;大多数 PE 文件为 0,少数内核模式的驱动文件和系统 DLL 中,该值必须存在且正确。Windows系统目录下有一个动态链接库IMAGEHLP.DLL,它是Win32中专门用来操作 PE 文件的函数库,这里面的函数 CheckSumMappedFile 就是用来计算文件头校验和的,对于整个PE文件也有一个校验和函数 MapFileAndCheckSum。该动态链接库中还包括其他一些常用的函数,可以通过小工具 PEInfo 输出并查看。 - +5Ch-**`Subsystem`**-可执行文件期望的子系统-WORD(2Byte) - 0 未知子系统 - 1 设备驱动程序和Native Windows进程(.sys) - **2 Windows图形用户界面(GUI)子系统(Windows图形用户界面)** - **3 Windows字符模式(CUI)子系统(控制台)** - 7 Posix字符模式子系统 - 9 Windows CE - 10 可扩展固件接口(EFI)应用程序 - 11 带引导服务的EFI驱动程序 - 12 带运行时服务的EFI驱动程序 - 13 EFI ROM镜像 - 14 XBOX - +5Eh-**DllCharacteristics**-DLL文件特性,默认为 0-WORD(2Byte):PE32 kernel32.dll 0X210Eh ; PE32+ kernel32.dll 0X2022h - 0 保留,必须为0。 - 1 保留,必须为0。 - 2 保留,必须为0。 - 3 保留,必须为0。 - 4 官方文档缺失 - 5 官方文档缺失 - 6 DLL可以在加载时被重定位。 - 7 强制进行代码完整性校验。 - 8 镜像兼容于DEP。 - 9 可以隔离,但并不隔离此镜像。 - 10 不使用结构化异常(SEH)处理。 - 11 不绑定映像。 - 12 保留,必须为0。 - 13 WDM驱动程序。 - 14 保留,必须为0 - 15 可以用于终端服务器。 - +60h-**SizeOfStackReserve**-初始化时的栈大小-DWORD(4Byte)-**64位QWORD(8Byte)**:为初始线程的栈而保留的虚拟内存数量(不是所有虚拟内存都能做栈,真正的由下个字段决定)。默认值是0x100000(1MB),调用 CreateThread 时,NULL 当作传入参数,则创建出来的栈大小也为 1MB - +64h-+68h(64位)-**SizeOfStackCommit**-初始化时实际提交的栈大小-DWORD(4Byte)-**64位QWORD(8Byte)**:保证初始线程实际占用内存大小。 - +68h-+70h(64位)-**SizeOfHeapReserve**-初始化时保留的堆大小-DWORD(4Byte)-**64位QWORD(8Byte)**:保留给初始进程堆使用的虚拟内存,可以通过 GetProcessHeap()获得。每个进程至少一个默认进程堆。默认值 1MB。 - +6Ch-+78h(64位)-**SizeOfHeapCommit**-初始化时实际提交的堆大小-DWORD(4Byte)-**64位QWORD(8Byte)**:进程初始化时设定的堆所占用空间大小 - +70h-+80h(64位)-**LoaderFlags**-与调试有关-DWORD(4Byte):一般为 0 - +74h-+84h(64位)-**NumberOfRvaAndSizes**-下边数据目录的项数-DWORD(4Byte):一般是0x00000010H(即16个),由 PE 文件头的 SizeOfOptionalHeaders 决定,实际可以取2~16的值。 - +78h-+88h(64位)-**DataDirectory\[16]**-数据目录表-_IMAGE_DATA_DIRECTORY\[16](16*8=128Byte) - +78h-+88h(64位)-**IMAGE_DIRECTORY_ENTRY_EXPORT**-导出表-_IMAGE_DATA_DIRECTORY(8Byte):导出表地址和大小;节名称一般为`.edata`,包含了导出函数和资源等。 - +80h-+90h(64位)-**IMAGE_DIRECTORY_ENTRY_IMPORT**-导入表-_IMAGE_DATA_DIRECTORY(8Byte):导入表地址和大小;节名称一般为`.idata` - +88h-+98h(64位)-**IMAGE_DIRECTORY_ENTRY_RESOURCE**-资源表-_IMAGE_DATA_DIRECTORY(8Byte):资源表地址和大小;节名称一般为`.rsrc`;多层的二叉排列树 - +90h-+A0h(64位)-**IMAGE_DIRECTORY_ENTRY_EXCEPTION**-异常表-_IMAGE_DATA_DIRECTORY(8Byte):异常表地址和大小;节名称一般为`.pdata`;用于异常处理的函数表项组成的数组。表项目必须按照函数地址排序;函数表项必须符合特定的目标平台。该部分数据主要用于基于表的异常处理,适用于除了x86之外所有类型的CPU。 - +98h-+A8h(64位)-**IMAGE_DIRECTORY_ENTRY_SECURITY**-安全(属性证书数据)-_IMAGE_DATA_DIRECTORY(8Byte):属性证书数据地址和大小;**类似于PE文件的校验或者MD5码,通过属性证书可以判断PE文件是否被非法修改过。**这个字段保存**文件偏移**,不是RVA。因为不作为映像一部分映射入内存。由一组连续的按八进制字(从任意字节边界开始的16个连续字节)边界对齐的属性证书表项组成,每个属性证书表项指向 WIN_CERTIFICATE 结构。此结构在WinTrust.H文件中。该数据并不作为映像的一部分被映射到内存,因此,DataDirectory.Certificate_VirtualAddress 字段**是文件偏移,而不是RVA**。DataDirectory.Certificate VirtualAddress 字段给出了属性证书表中第一个属性证书表项在文件中的偏移。对于后续的属性证书表项,可以通过将当前属性证书表项的文件偏移加上**WINCERTIFICATE.dwLength**字段的值,并将结果**向上舍入为8个字节的倍数**来访问。后续的属性证书表项可以一直以这种方式访问,直到这些**WINCERTIFICATE.dwLength字段(已经向上舍人为8字节的倍数)的和**等于可选文件头中的**DataDirectory.Certificate Size**的值。如果上述的值最后不等于Size字段的值,要么是属性证书表被破坏了,要么是Size域被修改了 - +A0h-+B0h(64位)-**IMAGE_DIRECTORY_ENTRY_BASERELOC**-重定位表-_IMAGE_DATA_DIRECTORY(8Byte):基地址重定位表地址和大小;节名称一般为`.reloc` - +A8h-+B8h(64位)-**IMAGE_DIRECTORY_ENTRY_DEBUG**-调试信息-_IMAGE_DATA_DIRECTORY(8Byte):调试信息地址和大小;节名称一般为`.debug`;指向IMAGE_DEBUG_DIRECTORY结构数组 - +B0h-+C0h(64位)-**IMAGE_DIRECTORY_ENTRY_ARCHITECTURE(也有说法:_COPYRIGHT)**-版权信息-_IMAGE_DATA_DIRECTORY(8Byte):预留为0; - +B8h-+C8h(64位)-**IMAGE_DIRECTORY_ENTRY_GLOBALPTR**-在IA-64架构上用作全局指针,x86未使用-_IMAGE_DATA_DIRECTORY(8Byte):指向全局指针寄存器的值 - +C0h-+D0h(64位)-**IMAGE_DIRECTORY_ENTRY_TLS**-TheadLocal Storage线程局部存储地址-_IMAGE_DATA_DIRECTORY(8Byte):线程局部存储地址和大小;通常命名为`.tls`;指向线程本地存储(Thread Local Storage,TLS),只指向一个地址 - +C8h-+D8h(64位)-**IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG**-加载配置表-_IMAGE_DATA_DIRECTORY(8Byte):加载配置表地址和大小;用于包含保留的SEH技术;加载配置表包含了一系列与程序加载和运行时的配置参数和选项相关的信息。它提供了更多高级的配置选项,例如堆栈保护、安全检查、动态基址随机化等。这些配置参数可以影响程序在运行时的行为和性能。 - +D0h-+Eh(64位)-**IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT**-绑定导入表-_IMAGE_DATA_DIRECTORY(8Byte):绑定导入表地址和大小;提高PE加载效率。允许将程序依赖的外部函数或符号与特定的导入模块进行绑定,以避免在运行时动态解析导入函数地址的开销;简单理解:当PE文件被加载到内存时,加载器会先检查导人表,然后把需要加载的DLL载入到地址空间中。加载器还有一项比较重要的工作是根据导入信息的描述使用动态链接库里输入函数的实际地址去替换IAT表的内容,这个步骤会花去一部分时间。但是,如果程序员(或者链接器)可以完全知道函数的地址,就可以直接把数组中的元素替换为地址,这能节省相当多的时间。这种方法就称为绑定(Binding)。 - +D8h-+F8h(64位)-**IMAGE_DIRECTORY_ENTRY_IAT**-导入函数地址表-_IMAGE_DATA_DIRECTORY(8Byte):延迟导入表地址和大小;IAT是导入地址表的英文缩写。准确地讲,它是导入表的一部分,这个双字数组里定义了所有导入函数的VA,程序可以直接通过跳转指令跳转到该VA处执行。 - +E0h-+F0h(64位)-**IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT**-延迟导入表-_IMAGE_DATA_DIRECTORY(8Byte):CLR运行时头部数据地址和大小;为了给“应用程序直到首次调用某个DLL中的函数或数据时才加载这个DLL(即延迟加载)”这种行为提供一种统一的访问机制。 - +E8h-+F8h(64位)-**IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR**-CLR运行时头部数据-_IMAGE_DATA_DIRECTORY(8Byte):系统保留;**PE32+已废除,被填充为0**;通常命名为`.cormeta`;是.NET框架重要组成部分,所有基于.NET框架开发的程序,其初始化部分都是通过访问这部分定义而实现的。PE加载时将通过该结构加载代码托管机制需要的所有动态链接库文件,并完成与CLR有关的一些其他操作。 - +F0h-+100h(64位)-**未使用**-_IMAGE_DATA_DIRECTORY(8Byte): - PE 数据区 - +F8h-+108h(64位)-IMAGE_SECTION_HEADER-节表:由许多节表项(IMAGE_SECTION_HEADER)组成,参考数据结构-PE 数据区-节表项。 - 节内容 ## 数据结构 ### PE 可选头-数据目录表-_IMAGE_DATA_DIRECTORY(8Byte) ``` // 8Byte typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // 数据的起始 RVA DWORD Size; // 数据块的长度 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; ``` ### PE 可选头-数据目录表-IMAGE_DIRECTORY_ENTRY_SECURITY(安全表)-WIN_CERTIFICATE ``` typedef struct _WIN_CERTIFICATE { DWORD dwLength; // 证书长度 WORD wRevision; // 证书版本号 WORD wCertificateType; // 证书类型 BYTE bCertificate[ANYSIZE_ARRAY]; // 证书内容 } WIN_CERTIFICATE, *LPWIN_CERTIFICATE; ``` ### PE 数据区-节表项 ``` // 0x28(40Byte) typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 0x00 8个字节节名,以'\0'结尾,如果不是以'\0'结尾,系统自动截取8个字节。 union { DWORD PhysicalAddress; // 0x08 File address.一般这个用不到 DWORD VirtualSize; // 加载到内存中时节的总大小,以字节为单位。是该节在没有对齐前的真实尺寸,可以不准确。如果此值大于SizeOfRawData成员,则该部分将填充0。 } Misc; DWORD VirtualAddress; // 0x0c 重要!节区在内存中的偏移地址,加上ImageBase才是真正的地址。 DWORD SizeOfRawData; // 0x10 在文件中对齐的尺寸。这个数值等于VirtualSize字段的值按照PE可选头的FileAlignment的值对齐以后的大小。 // 如果此值小于VirtualSize成员,则该部分的其余部分将填充为0。如果该节仅包含未初始化的数据,则成员为0。 DWORD PointerToRawData; // 0x14 重要!本节在文件中的偏移量(相对于文件头)。此值必须是IMAGE_OPTIONAL_HEADER的FileAlignment的倍数。如果一个节只包含未初始化的数据,则此成员为零 DWORD PointerToRelocations; // 0x18 在OBJ文件中使用,指向重定位表指针。如果没有重新定位表,则此值为零。 DWORD PointerToLinenumbers; // 0x1c 行号表的位置(供调试使用),如果没有则为0。 WORD NumberOfRelocations; // 0x20 在OBJ文件中使用,重定位表个数。对于可执行文件来说,这个值为0。 WORD NumberOfLinenumbers; // 0x22 行号表中行号的数量 (不太明白) DWORD Characteristics; // 0x24 节的属性。该区块的属性。该字段是按位来指出区块的属性(如代码/数据/可读/可写等)的标志。 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; /* 代码节的属性一般为60000020h,也就是可执行、可读和“节中包含代码段”, 数据节的属性一般为C0000040h,也就是可读、可写和“包含已初始化数据”, 常量节的属性为40000040h,也就是可读和“包含已初始化数据”, 补丁节:E0000020,可读、可写、可执行。 Characteristics 的每一个 bit 所对应的属性如下: 0 已经废除 1 已经废除 2 已经废除 3 已经废除 4 已经废除 5 此节包含可执行代码。代码段才用“.text” 6 此节包含已初始化的数据。“.data” 7 此节包含未初始化的数据。“.bss” 8 已经废除 9 已经废除 10 已经废除 11 已经废除 12 已经废除 13 已经废除 14 已经废除 15 此节包含通过全局指针(GP)来引用的数据。 16 已经废除 17 已经废除 18 已经废除 19 已经废除 20 已经废除 21 已经废除 22 已经废除 23 已经废除 24 此节包含扩展的重定位信息。 25 此节可以在需要时被丢弃。 26 此节不能被缓存。 27 此节不能被交换到页面文件中。 28 此节可以在内存中共享。 29 此节可以作为代码执行。 30 此节可读。(几乎都设置此节) 31 此节可写。 */ ``` - 可以看到,用到的主要有5,6,7,8,25,26,27,28,29,30,31 | 数据位 | 常量符号 | 位为1时的含义 | | :----: | :----: | :----: | | 5 | IMAGE_SCN_CNT_CODE 或 0000 0020h | 节中包含代码 | | 6 | IMAGE_SCN_CNT_INITIALICED_DATA 或 0000 0040h | 节中包含已初始化数据 | | 7 | IMAGE_SCN_CNT_UNINITIALIZED_DATA 或 0000 0080h | 节中包含未初始化数据 | | 8 | IMAGE_SCN_LNK_OTHER 或 0000 0100h | 保留供将来使用 | | 25 | IMAGE_SCN_MEM_DISCARDABLE 或 0200 0000h | 节中的数据在进程开始以后将会被丢弃,如.reloc | | 26 | IMAGE_SCN_MEM_NOT_CACHED 或 0400 0000h | 节中的数据不会经过缓存 | | 27 | IMAGE_SCN_MEM_NET_PAGED 或 0800 0000h | 节中的数据不会被交换到磁盘 | | 28 | IMAGE_SCN_MEM_SHARED 或 1000 0000h | 表示节中的数据将被不同的进程所共享 | | 29 | IMAGE_SCN_MEM_EXECUTE 或 2000 0000h | 映射到内存后的页面包含可执行属性 | | 30 | IMAGE_SCN_MEM_READ 或 4000 0000h | 映射到内存后的页面包含可读属性 | | 31 | IMAGE_SCN_MEM_WRITE 或 8000 0000h | 映射到内存后的页面包含可写属性 | - 注意,判断节是否包含代码的方式不是根据节的属性是否包含IMAGE_SCN_MEM_EXECUTE标志,而是判断节的属性是否包含`IMAGE_SCN_CNT_CODE`标志。 - 代码节一般是:6000 0020h(可执行,可读,节中包含代码),数据节:c000 0040h(可读,可写,包含已初始化数据),常量节:4000 0040h(可读,包含已初始化数据),资源节一般和常量节相同。 - 节属性的定义不一定必须是这些值。当PE文件被压缩工具压缩以后,包含代码的节往往被同时设置成具有可执行、可读和可写属性,因为解压部分需要将解压后的代码回写到代码段中。 #### 常见区块名称及意义 - 有些数据类型不同,分别属于不同的数据目录。但是**由于其访问属性相同,便被归类到同一个区块中。** - 注意,`判断节是否包含代码的方式不是根据节的属性是否包含IMAGE_SCN_MEM_EXECUTE标志,而是判断节的属性是否包含IMAGE_SCN_CNT_CODE标志。` - **.text** - 默认的块区代码,它的内容全是指令代码,链接器吧所有目标文件的.text块链接成一个大的.text块,如果使用的是Borland C++,其链接器将产生的代码存于名为CODE的区块中。 - **.data** - 默认的读/写区块,全局变量、静态变量一般放在这里 - **.rdata** - 默认的只读数据区块,至少有两种情况下要用到.rdata,一是在Microsoft的链接器产生的EXE文件中,用于存放调试目录;二是用于存放说明字符 - **.idata** - 包含其它外来DLL的函数及数据信息,即我们所说的输入表。将.idata区块合并到另外一个区块现在已成管理,典型的.rdata区块。 - **.edata** - 输出表。当创建要给输出API或者数据的可执行文件时,链接器会创建一个.EXP文件,这个.EXP文件就包含了一个.edata区块,其会被加入到最后的可执行文件中。与.data区块一样,.edata也经常被发现合并到了.text或.tdata的区块中。 - **.didata** - 延迟输入文件名表 - **.reloc** - 重定位表信息 - **.rsrc** - 资源 - **.tls** - 线程的本地存储器 - **.xdata** - 异常处理表 - **.rsrc** - 资源,包含模块的全部资源,如图标、菜单、位图登、这个区块是只读的,无论如何它不应该被命名为.rsrc以外的其它任何名字,也不能被合并到其它区块中。 - **.bss** - 未初始化资源。很少使用,取而代之的是执行文件的.data区块的Virtual Siz被扩展到足够大的空间用来装下未初始化的资源。 - **.tls** - TLS的意思是线程局部存储器,用于支持通过\__declspec(thread)声明的线程局部存储变量的数据。这包括数据的初始化值,也包括运行时所需要的额外变量。 - **.reloc** - 可执行文件的基址重定位。基址重定位一般仅是DLL文件才需要的。在Release模式,链接器并不给EXE文件加上基址重定位,重定位可以在链接时候通过/FIXED开关去掉。 - **.sdata** - 可通过全局指针相对寻址的“短”可读/写数据,用于IA-64和其它使用全局指针寄存器体系上。IA-64上的正常大小的全局变量位于这个节中。 - **.srdata** - 可通过全局指针相对寻址的“短”只读数据。用于IA-64和其它使用全局指针寄存器的体系上。 - **.pdata** - 异常表。只在64 位可执行文件中存在。包含一个IMAGE_RUNTIME_FUNCTION-ENTRY类型的数据结构数组,这个结果是特定于CPU的,数据目录中的IMAGE_DIRECTORY_ENTRY_EXCEPTION指向它。用于基础表的异常处理的体系,比如IA-64。唯一一个不使用基于表的异常处理的体系是x86 - **.debug$S** - OBJ文件中的Codeview格式的符号。这是一个可变长的Codeview格式符号记录流。 - **.debug$T** - OBJ文件中Vodeview格式的类型记录。这是一个可变长的Codeview格式类型记录流。 - **.debug$P** - 当使用预编译头时会出现在OBJ文件中 - **.drectve** - 只用于OBJ文件,包含一些链接器指令。这些指令是一些能被传递到链接器命令行的ASCII字符串。例如:-defaultlib:LIBC 多个指令间用空格隔开。 - **.CRT** - C运行期制度数据 - **.didat** - 延迟加载的导入数据。可在用于非Release模式创建的可执行文件中找到它。而在Release模式,延迟加载数据会被合并到其它节中。
别卷了
2024年8月28日 11:39
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码