第一部分:DOS头部 所有PE文件都必须以DOS MZ Header开头 16进制的 DOS MZ Header 部分加壳软件会处理DOS MZ Header 4D5A90000300000004000000FFFF0000B800000000000000400000000000000000000000000000000000000000000000000000000000000000000000C80000000E1FBA0E00B409CD21B8014CCD21546869732070726F6772616D2063616E6E6F742062652072756E20696E20444F53206D6F64652E0D0D0A2400000000000000 第二部分:DOS stub 它其实是一个有效的EXE,在不支持PE的系统中,它将简单的提示一个错误 我在几个软件上做了测试,这部分可以用00填充,不影响正常的使用 第三部分:PE Header PE Header是PE相关结构IMAGE_NT_HEADERS的简称 执行体在支持PE文件结构的操作系统中执行时,PE装载器将从DOS MZ Header中找到PE Header的起始偏移量。因而跳过了DOS stub直接定位到真正的文件PE Header。 注:从3C的位置开始定义,从低位到高位的写法 各区段的名字只是一个代号,不会影响我们正常的使用 各种编译器有自己特有的区段名,记住这些能够有帮助与我们的工作 第四部分: Section table(节表/块表) 每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。 我们可以把节表视为逻辑磁盘中的根目录 装载PE文件的主要步骤 1. 当PE文件被执行,PE装载器检查DOS MZ Header里的PE Header偏移量,如果找到就跳转到PE Heardr。 2. PE装载器检查PE Header的有效性,如果有效就跳转到PE Header的尾部 3. 下面紧跟PE Heardr的是节表。PE装载器读取其中的节信息,并采用文件映射方法将这些节映射到内存,同时附上节表里指定的节属性。 4. PE文件映射入内存后,PE装载器将处理PE文件中类似Import Tabel(引入表)逻辑部分。 名词解释: 1.入口点(Entry Point) PE文件在执行时都要一个所谓的入口点,通常程序在执行的第一行代码的地址应该就是这个值。 2.文件偏移地址(File Offset) 所谓文件偏移地址(File Offset)就是我们把PE文件储存在磁盘上时,各数据的地址,也叫做物理地址(RAW Offset),它是从PE文件的第一个字节 十六进制工具所显示的地址就是我们这里所讲的文件偏移地址了 3.虚拟地址(Virtual Address,VA). 虚拟地址的产生主要是因为Windows程序运行在386保护模式下,因此程序访问存储器所使用的逻辑地址就称为虚拟地址,也就是我们通常讲的内存偏移地址(Memory Offset) 4.基地址(Image Base) 是指文件执行时被映射到指定内存地址中的初始内存地址,这个值是有PE文件本身设定的。 这是PE文件希望被加载到的虚拟空间地址,比如400000h,除非有其它模块已经占用了这里的空间,否则加载程序将会尝试将PE加载到00400000h处。 这个基地址在生成文件是可以设置成其它的 5. 相对虚拟地址(Relative Virtual Address,RVA) 是内存中相对于PE文件装入地址(基地址)的偏移量 00400000, 00401000 相对虚拟地址=00401000-00400000 PE Signature字段,也就是PE头了 在一个有效的PE文件里,Signature字段被设置为00004550h,ASCII字符是“PE00” 2-2-4-4-4-2-2这样的结构 分别是: 1.运行平台 2.文件区块的数目(Section Tabel紧跟在此结构的后面) 3.文件创建日期和事件 4.指向符号表(用于调试) 5.符号表中的符号个数(用于调试) 6.IMAGE_OPTIONAL_HEADER32结构大小(紧跟在此后面) 其大小依赖于是32位还是64位文件,对于32位PE文件,这个通常是00E0h;对于64位PE32+文件,这个是00F0h。不管怎么样,这些是要求的最小值,较大的值可能也会出现。 7.文件属性 IMAGE_FILE_HAEDER结构完了之后紧接着的就是IMAGE_OPTIONAL_HEADER32结构 IMAGE_OPTIONAL_HEADER32结构96字节 现在就是IMAGE_DATA_DIRECTORY非常重要 里面包含了输出表、输入表和资源等重要数据的定位 每个成员占8个字节,分别指向相关的结构 前四个字节为地址,后4个字节为大小 从上往下,分别是: 导出表 导入表 资源 例外表 安全证书 重定位表 调试信息 版本号 全局指针 TLS表 加载构造表 绑定表 IAT表 延迟导入表 COM 保留 紧跟着的就是区块表了,它是一个IMAGE_SECTIN_HEADER结构数组。每个结构包含了它所关联区块的信息,入位置、长度、属性; 该数组的数目由IMAGE_FILE_HAEDER中的第2个参数指出 每一个IMAGE_SECTIN_HEADER结构体,有40个字节组成 分别是: 1块名,多数块名以一个“.”开始,这个不是必须的。值得注意的是,如果块名超过8个字节,则没有最后的终止标志“NULL”字节 2指出实际的、被使用的区块的大小,是区块在没有对齐处理前的实际大小(物理大小) 3该块装载到内存中的RVA。(虚拟地址) 4该块在磁盘文件中所占的大小。在可执行文件中,该字段包含经过FileAlignment调整后的长度。例如,指定FileAlignment的大小为200h,如果实际长度为19AH个字节,这个块应保存的长度为200h个字节。(虚拟大小) 5该块在磁盘文件中的偏移。(物理位置) 6这部分在EXE中无意义。 7行号表在文件中的偏移值。 8这部分在EXE中无意义 9该块在行号表中的行号数目 10块属性。如可读、可写等 PE文件一般至少有两个区块:一个是代码块,另一个是数据块 以下是微软定义的区块意义,大家可以做一个参考 输入表 可执行文件使用来自于其它DLL的代码或数据时,称为输入。Windows加载器的工作之一就是定位所有被输入的函数和数据,并让正在被装入的文件可以使用那些地址。 这个过程是通过PE文件的输入表(Import Tabel,检查IT,也叫导入表)来完成的,输入表中保存的是函数名和驻留的DLL名等动态链接所需的信息。 输入表在软件外壳技术上的地位非常重要。 输入函数是怎么调用的呢 输入函数的代码位于相关的DLL文件中,在调用者程序中只保留相关函数信息,如函数名,DLL文件名等。 对于磁盘上的PE文件来说,它无法得知这些输入函数在内存中的地址。只有PE文件被装入内存后,Windows加载器才将相关DLL装入,并将调用输入函数的指令和函数实际所处的地址联系起来。 当应用程序调用一个DLL的代码和数据时,那它正在隐含连接到DLL 另一种是运行期的连接,这意味着必须确定目标DLL已经被加载,然后寻找API的地址,这几乎总是通过调用LoadLibrary个GetProcAddress来完成的。 在PE文件中有一组数据结构,它们分别对应着每个被输入的DLL。每一这样的结构都给出了被输入的DLL的名称并指向一组函数指针。 这组函数指针被称为输入地址表(IAT) 每一个被引入的API函数在IAT里都有自己保留的位置,在那里它将被Windows加载器写入输入函数的地址。 一旦模块被装入,IAT中包含所要调用输入函数的地址。 绝对偏移地址=RAV-区段的物理地址-虚拟地址
|