windows下的可执行文件格式详解内容摘要:

的值。 它首先决定每个段需要多少字节,并且最后将页面总数向上取整至最接近的 SectionAlignment 边界,然后总数就是每个段个别需求之和了。 SizeOfHeaders。 这个域表示文件中有多少空间用来保存所有的文件头部,包括 MSDOS 头部、PE 文件头部、 PE 可选头部以及 PE 段 头部。 文件中所有的段实体就开始于这个位置。 CheckSum。 校验和是用来在装载时验证可执行文件的,它是由链接器设置并检验的。 由于创建这些校验和的算法是私有信息,所以在此不进行讨论。 Subsystem。 用于标识该可执行文件目标子系统的域。 每个可能的子系统取值列于 的IMAGE_OPTIONAL_HEADER 结构之后。 DllCharacteristics。 用来表示一个 DLL 映像是否为进程和线程的初始化及终止包含入口点的标记。 SizeOfStackReserve、 SizeOfStackCommit、 SizeOfHeapReserve、 SizeOfHeapCommit。 这些域控制要保留的地址空间数量,并且负责栈和默认堆的申请。 在默认情况下,栈和堆都拥有 1 个页面的申请值以及 16 个页面的保留值。 这些值可以使用链接器开关 STACKSIZE:与 HEAPSIZE:来设置。 LoaderFlags。 告知装载器是否在装载时中止和调试,或者默认地正常运行。 NumberOfRvaAndSizes。 这个域标识了接下来的 DataDirectory 数组。 请 注意它被用来标识这个数组,而不是数组中的各个入口数字,这一点非常重要。 DataDirectory。 数据目录表示文件中其它可执行信息重要组成部分的位置。 它事实上就是一个IMAGE_DATA_DIRECTORY 结构的数组,位于可选头部结构的末尾。 当前的 PE 文件格式定义了 16 种可能的数据目录,这之中的 11 种现在在使用中。 数据目录 之中所定义的数据目录为: // // 目录入口 // 导出目录 define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // 导入目录 define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // 资源目录 define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // 异常目录 define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // 安全目录 define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // 重定位基本表 define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // 调 试目录 define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // 描述字串 define IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // 机器值( MIPS GP) define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // TLS 目录 define IMAGE_DIRECTORY_ENTRY_TLS 9 // 载入配置目录 define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 基本上 ,每个数据目录都是一个被定义为 IMAGE_DATA_DIRECTORY 的结构。 虽然数据目录入口本身是相同的,但是每个特定的目录种类却是完全唯一的。 每个数据目录的定义在本文的以后部分被描述为 “预定义段 ”。 // typedef struct _IMAGE_DATA_DIRECTORY { ULONG VirtualAddress。 ULONG Size。 } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY。 每个数据目录入口指定了该目录的尺寸 和相对虚拟地址。 如果你要定义一个特定的目录的话,就需要从可选头部中的数据目录数组中决定相对的地址,然后使用虚拟地址来决定该目录位于哪个段中。 一旦你决定了哪个段包含了该目录,该段的段头部就会被用于查找数据目录的精确文件偏移量位置。 所以要获得一个数据目录的话,那么首先你需要了解段的概念。 我在下面会对其进行描述,这个讨论之后还有一个有关如何定位数据目录的示例。 PE 文件段 PE 文件规范由目前为止定义的那些头部以及一个名为 “段 ”的一般对象组成。 段包含了文件的内容,包括代码、数据、资源以及其它 可执行信息,每个段都有一个头部和一个实体(原始数据)。 我将在下面描述段头部的有关信息,但是段实体则缺少一个严格的文件结构。 因此,它们几乎可以被链接器按任何的方法组织,只要它的头部填充了足够能够解释数据的信息。 段头部 PE 文件格式中,所有的段头部位于可选头部之后。 每个段头部为 40 个字节长,并且没有任何的填充信息。 段头部被定义为以下的结构: // define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { UCHAR Name[IMAGE_SIZEOF_SHORT_NAME]。 union { ULONG PhysicalAddress。 ULONG VirtualSize。 } Misc。 ULONG VirtualAddress。 ULONG SizeOfRawData。 ULONG PointerToRawData。 ULONG PointerToRelocations。 ULONG PointerToLinenumbers。 USHORT NumberOfRelocations。 USHORT NumberOfLinenumbers。 ULONG Characteristics。 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER。 你如何才能获得一个特定段的段头部信息。 既然段头部是被连续的组织起来的,而且没有一个特定的顺序,那么段头部必须由名称来定位。 以下的函数示范了如何从一个给定了段名称的 PE 映像文件中获得一个段头部: // BOOL WINAPI GetSectionHdrByName(LPVOID lpFile, IMAGE_SECTION_HEADER *sh, char *szSection) { PIMAGE_SECTION_HEADER psh。 int nSections = NumOfSections (lpFile)。 int i。 if ((psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile)) != NULL) { /* 由名称查找段 */ for (i = 0。 i nSections。 i++) { if (!strcmp(pshName, szSection)) { /* 向头部复制数据 */ CopyMemory((LPVOID)sh, (LPVOID)psh, sizeof(IMAGE_SECTION_HEADER))。 return TRUE。 } else psh++。 } } return FALSE。 } 这个函数通过 SECHDROFFSET 宏将第一个段头部定位,然后它开始在所有段中循环,并将要寻找的段名称和每个段的名称相比较,直到找到了正确的那一个为止。 当找到了段的时候,函数将内存映像文件的数据复制到传入函数的结 构中,然后 IMAGE_SECTION_HEADER 结构的各域就能够被直接存取了。 段头部的域 Name。 每个段都有一个 8 字符长的名称域,并且第一个字符必须是一个句点。 PhysicalAddress 或 VirtualSize。 第二个域是一个 union 域,现在已不使用了。 VirtualAddress。 这个域标识了进程地址空间中要装载这个段的虚拟地址。 实际的地址由将这个域的值加上可选头部结构中的 ImageBase 虚拟地址得到。 切记,如果这个映像文件是一个 DLL,那么这个DLL 就不一定会装载到 ImageBase 要求的位置。 所以一旦这个文件被装载进入了一个进程,实际的ImageBase 值应该通过使用 GetModuleHandle 来检验。 SizeOfRawData。 这个域表示了相对 FileAlignment 的段实体尺寸。 文件中实际的段实体尺寸将少于或等于 FileAlignment 的整倍数。 一旦映像被装载进入了一个进程的地址空间,段实体的尺寸将会变得少于或等于 FileAlignment 的整倍数。 PointerToRawData。 这是一个文件中段实体位置的偏移量。 PointerToRelocations、 PointerToLinenumbers、 NumberOfRelocations、 NumberOfLinenumbers。 这些域在 PE 格式中不使用。 Characteristics。 定义了段的特征。 这些值可以在 及本光盘(译注: MSDN 的光盘)的PE 格式规范中找到。 值 定义 0x00000020 代码段 0x00000040 已初始化数据段 0x00000080 未初始化数据段 0x04000000 该 段数据不能被缓存 0x08000000 该段不能被分页 0x10000000 共享段 0x20200000 可执行段 0x40000000 可读段 0x80000000 可写段 定位数据目录 数据目录存在于它们相应的数据段中。 典型地来说,数据目录是段实体中的第一个结构,但不是必需的。 由于这个缘故,如果你需要定位一个指定的数据目录的话,就需要从段头部和可选头部中获得信息。 为了让这个过程简单一点,我编写了以下的函数来定位任何一个在 之中定义的数据目录。 // LPVOID WINAPI ImageDirectoryOffset(LPVOID lpFile, DWORD dwIMAGE_DIRECTORY) { PIMAGE_OPTIONAL_HEADER poh。 PIMAGE_SECTION_HEADER psh。 int nSections = NumOfSections(lpFile)。 int i = 0。 LPVOID VAImageDir。 /* 必须为 0 到 (NumberOfRvaAndSizes1)之间 */ if (dwIMAGE_DIRECTORY = pohNumberOfRvaAndSizes) return NULL。 /* 获得可选头部和段头部的偏移量 */ poh = (PIMAGE_OPTIONAL_HEADER)OPTHDROFFSET(lpFile)。 psh = (PIMAGE_SECTION_HEADER)SECHDROFFSET(lpFile)。 /* 定位映像目录的相对虚拟地址 */ VAImageDir = (LPVOID)pohDataDirectory [dwIMAGE_DIRECTORY].VirtualAddress。 /* 定位包含映像目录的段 */ while (i++ nSections) { if (pshVirtualAddress = (DWORD)VAImageDir amp。 amp。 pshVirtualAddress + pshSizeOfRawData (DWORD)VAImageDir) break。 psh++。 } if (i nSections) return NULL。 /* 返回映像导入目录的偏移量 */ return (LPVOID)(((int)lpFile + (int)VAImageDir. pshVirtualAddress) + (int)pshPointerToRawData)。 } 该函数首先确认被请求的数据目录入口数字,然后它分别获取指向可选头部和第一个段头部的两个指针。 它从可选头部决定数据目录的虚拟地址,然后它使用这个值来 决定数据目录定位在哪个段实体之中。 如果适当的段实体已经被标识了,那么数据目录特定的位置就可以通过将它的相对虚拟地址转换为文件中地址的方法来找到 PE 文件格式详解 (下 ) 作。
阅读剩余 0%
本站所有文章资讯、展示的图片素材等内容均为注册用户上传(部分报媒/平媒内容转载自网络合作媒体),仅供学习参考。 用户通过本站上传、发布的任何内容的知识产权归属用户或原始著作权人所有。如有侵犯您的版权,请联系我们反馈本站将在三个工作日内改正。