首页 PE文件格式入门 word版 (完美排版)

PE文件格式入门 word版 (完美排版)

举报
开通vip

PE文件格式入门 word版 (完美排版)其实PE文件就是指Windows里的DLL与EXE文件,PE的意思就是 Portable Executable,即可移植的执行体。 PE文件格式入门 By: QQ1462433585 说明: 几年前学习PE文件格式的时候,看了几个比较流行的PE文件教程,发现这些教程要么不够详细,要么太深奥,要么干脆就是纯英文的,所以就想把这几个教程好好整理综合一下,就得到了这篇文章。 其中大部分内容都是从已有教程中直接复制过来的,并不是本人原创(只可惜当时没有注明出处)。我所做的工作就是综合了这些中文教程,把英文教程里的英文说明翻...

PE文件格式入门 word版 (完美排版)
其实PE文件就是指Windows里的DLL与EXE文件,PE的意思就是 Portable Executable,即可移植的执行体。 PE文件格式入门 By: QQ1462433585 说明: 几年前学习PE文件格式的时候,看了几个比较流行的PE文件教程,发现这些教程要么不够详细,要么太深奥,要么干脆就是纯英文的,所以就想把这几个教程好好整理综合一下,就得到了这篇文章。 其中大部分 内容 财务内部控制制度的内容财务内部控制制度的内容人员招聘与配置的内容项目成本控制的内容消防安全演练内容 都是从已有教程中直接复制过来的,并不是本人原创(只可惜当时没有注明出处)。我所做的工作就是综合了这些中文教程,把英文教程里的英文说明翻译了一下,调整好了格式,排好了版,有几张图片里面自己加上了说明,还又画了几张图,比如输入表结构图、输出表机构图、重定位表结构图等。 因为当时也是初学PE格式,文中肯定也会有不少的错误,大家看的时候不要盲信,对有疑问的地方多查查资料。 本人QQ:1462433585 另外附上那时候看过的一些相关教程和文章的名字,文中的内容应该是从这些教程中复制过来的。 《深度探索Win32可执行文件格式》 姜庆东译 《加密解密 技术内幕》PE格式学习 总结 初级经济法重点总结下载党员个人总结TXt高中句型全总结.doc高中句型全总结.doc理论力学知识点总结pdf 《【NB5免杀】PE结构图解》 《PE头结构 》 《PE文件区段表详解—精通PE文件格式》 《体验修改PE文件的乐趣》 《学习笔记之PE文件结构导入表》 《手工构造PE文件》 - 看雪软件安全论坛 《手工构造典型PE文件》 《也谈PE重定位表》 - 看雪软件安全论坛 《再写手工打造可执行程序》 - 看雪软件安全论坛 《关于重定位表》 - CSDN博客频道 Code Space 《PE文件重定位表详解》 - CSDN博客频道matianlong_080的专栏 《PE 重定位表学习》 - justnow0506的日志 - 网易博客 《PE重定位表学习手记》 目录 TOC \o "1-2" \h \u 第一节 PE文件格式一览 1 第二节 深入了解PE文件与文件头 4 第三节 PE header(PE头) 7 第四节 File Header (文件头) 9 4.1 文件头 9 4.2 PE头移动 11 4.3修改PE头 11 第五节 Optional Header(可选头) 12 5.1 可选头 12 5.2 数据目录 14 5.3 相对虚拟地址RVA 15 5.3 对齐值 17 第六节 Section Table(区段表) 18 第七节 Import Table(输入表、引入表或者导入表) 21 7.1 输入表结构 21 7.2 输入表绑定 29 第八节 Export Table(输出表或叫引出表、导出表) 32 第九节 重定位表 38 第十节 关于区段 42 第十一节 几个小知识 44 一、PE相关名词解释如下 44 二、文件中大量的空白 44 三、高存高低存低原则 45 四、DOS MZ Header 中的 MZ 46 第一节 PE文件格式一览 其实PE文件就是指Windows里的DLL与EXE文件,PE的意思就是 Portable Executable(可移植的执行体)。它是 Win32环境自身所带的执行体文件格式。它的一些特性继承自 Unix的 Coff (common object file format)文件格式。"portable executable"(可移植的执行体)意味着此文件格式是跨win32平台的,即使Windows运行在非Intel的CPU上,任何win32平台的PE装载器都能识别和使用该文件格式。当然,移植到不同的CPU上PE执行体必然得有一些改变。所有win32执行体 (除了VxD和16位的Dll)都使用PE文件格式,包括NT的内核模式驱动程序(kernel mode drivers)。我们现今组成 Windows 大家庭的主要成员就是 PE 文件了,里面包括 EXE、DLL、OCX、SYS 等一切最有价值的文件都是 PE 文件格式。因而研究PE文件格式给了我们洞悉Windows结构的良机。 PE文件总的来说是由dos文件头、dos加载模块、PE头、区段表与区段5部分构成。其实,如果在纯Windows环境下运行,dos文件头、dos加载模块根本是用不上的,加上两个dos相关的结构完全是为了兼容性问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 。 图一 为了方便观察与理解,我们可以通过观察图二大体了解PE文件的结构。 图二 如上图可知,整个程序就是以dos文件头“MZ”开始的,接下来就是dos加载模块“This program cannot be run in dos mode”,几乎每个Windows程序的前面都是这样一些信息! 下面有一个以字母“PE”为开头部分,这就是大名鼎鼎的PE头了,PE头的标准大小为248个字节,由图可见,里面有一个画了横线标记的问号与左面的十六进制信息“E0”相对应,这便是PE头中可选头体积的描述标记,十六进制的“E0”等于十进制的“224”,这是PE头中可选头的大小(一般都是E0,即224字节),再加上PE标识符(4个字节)和文件头(20个字节),正好是248个字节。 再往下就是以“.text”、“ .data”与“.rsrc”组成的区段表了。区段表也称节表,它的作用就相当于一本书中的目录,你想看哪一章哪一节,只要按着目录标注的页数去找就可以,PE文件的区段表也是起同样的作用,但是区段表除此之外还包含有各个区段的读写权限信息。而图中的“.text”、“ .data”与“.rsrc”则是这个程序里的区段名称,也称为“节”。由此可见这个程序是由“.text”、“ .data”与“.rsrc”这3个区段组成的,如下图。 图三 其实通过区段名称就可以大体猜出来这个节里包含什么信息,在整个程序中能起到什么作用等等。 第二节 深入了解PE文件与文件头 下面让我们抽象的了解一下PE文件的构成: dos MZ header < 方法 快递客服问题件处理详细方法山木方法pdf计算方法pdf华与华方法下载八字理论方法下载 将这些节映射到内存,一同赋上区段表里指定的节属性。 4PE文件映射入内存后,PE装载器将处理PE文件中类似 import table(输入表)逻辑部分。 5跳转到OEP 开始执行程序。 第三节 PE header(PE头) 如何才能校验指定文件是否为一有效PE文件呢? 这个问题很难回答,完全取决于想要的精准程度。你可以检验PE文件格式里的各个数据结构,或者仅校验一些关键数据结构。大多数情况下,没有必要校验文件里的每一个数据结构,只要一些关键数据结构有效,我们就认为是有效的PE文件了。下面我们就来实现前面的假设。 我们要验证的重要数据结构就是 PE header。从编程角度看,PE header 实际就是一个 IMAGE_NT_HEADERS 结构。定义如下: typedef struct _IMAGE_NT_HEADERS { DWORD Signature; //PE标识符,共4个字节,此字段被设置为0X00004550,ASCII码 是“PE00” IMAGE_FILE_HEADER FileHeader; //文件头结构,共20个字节 IMAGE_OPTIONAL_HEADER OptionalHeader; //可选头结构,共224个字节 } IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS,PeHeader; <1>Signature:PE标识符,DWORD类型,4个字节,值为50h,45h,00h,00h(PE\0\0)。如果Signature域值等于"PE\0\0",那么就是有效的PE文件。 <2>FileHeader:即文件头,20个字节。该结构域包含了关于PE文件物理分布的信息,比如节数目、文件执行机器等。 <3>OptionalHeader:即可选头,一般为224个字节(可以通过更改文件头中表示大小的变量来改变它的大小)。该结构域包含了关于PE文件逻辑分布的信息,虽然域名有"可选"字样,但实际上本结构总是存在的。 我们目的很明确。如果IMAGE_NT_HEADERS的signature域值等于"PE\0\0",那么就是有效的PE文件。实际上,为了比较方便,Microsoft已定义了常量IMAGE_NT_SIGNATURE供我们使用。 IMAGE_DOS_SIGNATURE equ 5A4Dh IMAGE_OS2_SIGNATURE equ 454Eh IMAGE_OS2_SIGNATURE_LE equ 454Ch IMAGE_VXD_SIGNATURE equ 454Ch IMAGE_NT_SIGNATURE equ 4550h 接下来的问题是: 如何定位 PE header? 答案很简单: DOS MZ header 已经包含了指向 PE header 的文件偏移量。DOS MZ header 又定义成结构 IMAGE_ DOS_HEADER 。查询windows.inc,我们知道 IMAGE_DOS_HEADER 结构的e_lfanew成员就是指向 PE header 的文件偏移量。 现在将所有步骤总结如下: 1)​ 首先检验文件头部第一个字的值是否等于 IMAGE_DOS_SIGNATURE,是则 DOS MZ header 有效。 2)​ 一旦 证明 住所证明下载场所使用证明下载诊断证明下载住所证明下载爱问住所证明下载爱问 文件的 DOS header 有效后,就可用e_lfanew来定位 PE header 了。 3)​ 比较 PE header 的第一个字的值是否等于IMAGE_NT_HEADER。如果前后两个值都匹配,那我们就认为该文件是一个有效的PE文件。 第四节 File Header (文件头) 4.1 文件头 File Header(IMAGE_FILE_HEADER结构)包含在PE Header(IMAGE_NT_ HEADERS)里面,其结构定义: typedef struct _IMAGE_FILE_HEADER { WORD Machine; //该文件运行所要求的CPU,Intel 80386以上处理器必须为4C 01 WORD NumberOfSections; //文件中区段的数目 DWORD TimeDateStamp; //文件创建日期和时间,免杀时可随便改 DWORD PointerToSymbolTable;// 指向符号表,用于调试,免杀时可随便改 DWORD NumberOfSymbols; // 符号表中的符号数量,用于调试,免杀时可随便改 WORD SizeOfOptionalHeader; //可选头的大小 WORD Characteristics; //文件的执行属性,该字段很重要,EXE 文件此值一般为 010Fh } IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER; IMAGE_FILE_HEADER 结构成员含义: 结构成员 含义 Machine 该文件运行所要求的CPU。对于Intel平台,该值是14Ch,对于MIPS R3000平台为162h,对于MIPS R4000平台为166h,对于Alpha AXP平台为184h,对于Power PC平台为1F0h。更改这个值会使程序不能正确执行。看来除了禁止程序执行之外,本域对我们来说用处不大。 NumberOfSections 这是一个很重要的字段,用来确定文件中区段的数目。如果我们要在文件中增加或删除一个区段,就需要修改这个值。 TimeDateStamp 文件创建日期和时间。其格式是自从1969年12 月31 日4:00 P.M. 之后的总秒数。据计算,0xFFFFFFFFh是136.19251950152207001522 070015221年。将这个值翻译为易读的字符串的方法是用_ctime函数(它是时区敏感型的),另一个对此字段计算有用的函数是gmtime。 PointerToSymbolTable 用于调试。 NumberOfSymbols 用于调试。 SizeOfOptionalHeader 指示紧随本结构之后的OptionalHeader结构大小,必须为有效值。 Characteristics 此值为文件的执行属性。它影响系统对文件的装入方式,比如,位13为1时,表示这是一个DLL文件,系统将使用调用dll入口函数的方式调用文件入口,如果为0,就是普通的可执行文件,系统直接跳到入口处执行。EXE 文件此值一般为 010Fh,DLL文件此值一般为 210Eh。各位的含义见下图 简言之,只有三个域对我们有一些用: Machine, NumberOfSections 和 Characteristics。通常不会改变 Machine 和Characteristics 的值,但如果要遍历区段表就得使用 NumberOfSections。为了更好阐述 NumberOfSections 的用处,这里简要介绍一下区段表。 区段表是一个IMAGE_SECTION_HEADER结构的数组,每个结构包含一个节的信息。该数组中成员的个数由 File Header (IMAGE_FILE_HEADER) 结构中 NumberOfSections 域的域值来定。区段表中的成员是IMAGE_SECTION_ HEADER 结构,IMAGE_SECTION_ HEADER 结构的长度固定,长40个字节。整个Section Table 的长度不固定,等于 NumberOf Sections*sizeof(IMAGE_ SECTION_HEADER),即NumberOf Sections*40字节。因此如果程序有3个节,这个数组就有3个成员。 我们需要NumberOfSections值来了解该数组中到底有几个成员。也许你会想检测结构中的全0成员起到同样效果。Windows确实采用了这种方法。为了证明这一点,可以增加NumberOfSections的值,Windows仍然可以正常执行文件。据我们的观察,Windows读取NumberOfSections的值然后检查区段表里的每个结构,如果找到一个全0结构就结束搜索,否则一直处理完NumberOfSections指定数目的结构。 为什么我们不能忽略NumberOfSections的值? 有2个原因: 1)PE说明中没有指定区段表必须以全0结构结束,这样可能会出现最后的那个数组成员与节1之间没有全0结构的情形。 2)如果此PE文件进行了bound imports(即输入表绑定),那么在区段表最后一个成员之后紧接着的不是全0结构,而是进行输入表绑定时所使用的dll文件的版本信息。(输入表绑定见7.2节) 因此你仍然需要NumberOfSections。 4.2 PE头移动 因为在windows中DOS Stub部分根本没用,所以我们可以讲PE头往上移动,占用DOS Stub的位置,PE头原先的位置空出来的地方都用00填充。这时候要修改两个位置的数据: 1、表示PE头位置的偏移量为3C、3D、3E、3F四个字节处的数据为PE头现在的新位置; 2、关键字“PE”之后的第19、20个字节处的数据,也就是文件头结构中的SizeOfOptionalHeader域的值,因为我们把PE头往上移动了,系统在可选头之后紧挨着可选头的地方找不到区段表就会报错无法运行,因此我们要把刚才移动之后在可选头和区段表之间空出的区域包含到可选头中,把新的可选头的大小写到SizeOfOptionalHeader中(也就是关键字“PE”之后的第19、20个字节处)。 PE头甚至可以移动到DOS文件头的空间里,关键是要让PE头中一个没用的DWORD字段正好占用掉偏移量为3C、3D、3E、3F四个字节,然后把这里写上PE头新的位置,更改SizeOfOptionalHeader域的值就可以了。 如果不想修改可选头的大小可以把区段表一块往上移动,区段表空出来的地方用00填充。 移动PE头之后的程序用OD载入会提示不是有效的WIN32程序,点击确定之后会来到系统领空,这可以作为简单的反调试。此时打开内存窗口,在程序的PE头上下断,F9运行,又断在系统领空,Alt+F9,程序就运行起来了。 4.3修改PE头 通过修改PE头可以使OD载入时提示错误,并且在内存窗口中只能看到PE头,有两种方法: 方法一、手动移动PE头 方法二、修改数据目录的项数。可以手动的修改,但是要改动的地方比较多,较简单的方法是用LordPE的PE编辑器载入程序,其中的RVA数及大小就是指的数据目录的项数,因为数据目录的项数自NT系统发布以来一直是16,用十六进制表示就是10H,所以一般这一项总为10,可以把这个数改大,如果改小了就会导致程序无法运行。 第五节 Optional Header(可选头) 5.1 可选头 Optional Header 结构是 IMAGE_NT_HEADERS 中的最后成员。包含了PE文件的逻辑分布信息。该结构共有31个域,一些是很关键,另一些不太常用。其结构定义: typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. WORD Magic; //幻数,用于描述次PE文件的映像类型,ROM映像为 0107h,普通可执行 映像 010Bh;PE32+则是 020Bh。 BYTE MajorLinkerVersion; //链接程序的主版本号,免杀时可随便改 BYTE MinorLinkerVersion; //链接程序的次版本号,免杀时可随便改 DWORD SizeOfCode; //★所有含代码的区段的总大小,改变此值不影响运行,免杀可随便改 DWORD SizeOfInitializedData; //所有含已初始化数据的区段的总大小,免杀时可随便改 DWORD SizeOfUninitializedData;// 所有含未初始化数据的区段的总大小,免杀时可随便改 DWORD AddressOfEntryPoint; //★程序开始执行的入口地址,这是一个RVA(相对虚拟地址) DWORD BaseOfCode; //代码段的起始RVA,典型的代码段名有“.text”、“.code”、 “AUTO”等,这个值不重要,因为在区段表中还有起相同作 用的值,免杀时可随便改 DWORD BaseOfData; //数据段的起始RVA,典型的数据节名有“.data”、“.idata”、 “DATA”等,这个值不重要,因为在区段表中还有起相同作 用的值,免杀时可随便改 // // NT additional fields. DWORD ImageBase; //★可执行文件默认装入的基地址,一般为00400000,这个值不要更改 DWORD SectionAlignment; //★内存中块的对齐值(默认的块对齐值为1000H,即CPU的页 尺寸4KB字节) DWORD FileAlignment; // ★文件中块的对齐值(默认值为200H或1000H字节,为了保证 块总是从磁盘的扇区开始的,这个值必须是2的幂,并且最小 为 200h) WORD MajorOperatingSystemVersion;//要求操作系统的最低版本号的主版本号,免杀时可随 便改 WORD MinorOperatingSystemVersion;//要求操作系统的最低版本号的次版本号,免杀时可随 便改 WORD MajorImageVersion; //该可执行文件的主版本号,免杀时可随便改 WORD MinorImageVersion; //该可执行文件的次版本号,免杀时可随便改 WORD MajorSubsystemVersion; //★要求最低之子系统版本的主版本号,值为0004h WORD MinorSubsystemVersion;//★要求最低之子系统版本的次版本号,一般情况下都为0 DWORD Win32VersionValue; //保留字,免杀时可随便改 DWORD SizeOfImage; //★映像装入内存后的总尺寸,它是所有头和节经过节对齐处 理后的大小 DWORD SizeOfHeaders; //★所有头部和区段表的大小(包括对齐填入的00),等于文件 尺寸减去文件中所有节的尺寸,也等于第一个区段的文件 偏移量 DWORD CheckSum; //CRC检验和,只在sys文件中有用,免杀时可随便改 WORD Subsystem; //★程序使用的用户接口子系统,0表示未知,1表示不需要子系统 (如驱动程序),2表示图形接口子系统(GUI),3表示字符子系 统(CUI),5表示OS/2 字符子系统,7表示POSIX 字符子系统, 8为保留,9表示Windows CE 图形界面,WIN32程序就是2 WORD DllCharacteristics; //DLLmain函数何时被调用,默认为0,对EXE文件免杀时可 随便改 DWORD SizeOfStackReserve; //初始化时堆栈大小,免杀时可随便改 DWORD SizeOfStackCommit; //初始化时实际提交的堆栈大小,免杀时可随便改 DWORD SizeOfHeapReserve; //初始化时保留的堆大小,免杀时可随便改 DWORD SizeOfHeapCommit; //初始化时实际提交的堆大小,免杀时可随便改 DWORD LoaderFlags; //与调试有关,默认为0,免杀时可随便改 DWORD NumberOfRvaAndSizes;//★数据目录的项数,此字段自NT系统发布以来一直是16 IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //★数据目录表 } IMAGE_OPTIONAL_HEADER, *PIMAGE_OPTIONAL_HEADER; IMAGE_OPTIONAL_HEADER 结构几个比较重要的成员含义: 成员 含义 SizeOfCode AddressOfEntryPoint PE装载器准备运行的PE文件的第一个指令的RVA。通常会落在 .text section.此域适用于 exe 或 dll。OEP ImageBase PE文件的优先装载地址。比如,如果该值是400000h,PE装载器将尝试把文件装到虚拟地址空间的400000h处。字眼"优先"表示若该地址区域已被其他模块占用,那PE装载器会选用其他空闲地址。 SectionAlignment 内存中节对齐的粒度。例如,如果该值是4096 (1000h),那么每节的起始地址必须是4096的倍数。若第一节从401000h开始且大小是10个字节,则下一节必定从402000h开始,即使401000h和402000h之间还有很多空间没被使用。 FileAlignment 文件中节对齐的粒度。例如,如果该值是(200h),,那么每节的起始地址必须是512的倍数。若第一节从文件偏移量200h开始且大小是10个字节,则下一节必定位于偏移量400h: 即使偏移量512和1024之间还有很多空间没被使用/定义。 MajorSubsystemVersion MinorSubsystemVersion win32子系统版本。若PE文件是专门为Win32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感。MajorSubsystemVersion的值是0004H SizeOfImage 内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。 SizeOfHeaders 所有头+区段表的大小(包括对齐填入的00),也就等于文件尺寸减去文件中所有节的尺寸,也等于PE文件第一个区段的文件偏移量。 Subsystem NT用来识别PE文件属于哪个子系统。 对于大多数Win32程序,只有两类值: Windows GUI 和 Windows CUI (控制台)。 DataDirectory IMAGE_DATA_DIRECTORY结构数组。每个结构给出一个重要数据结构的RVA,比如引入地址表等。 5.2 数据目录 所谓数据目录就是指可选头中的data directory字段,这个字段可以说是可选头中最重要的字段之一,它是由16个相同的IMAGE_DATA_DIRECTORY结构组成的结构数组。虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的区段中的,但是这些处于各个区段中的数据按照用途可以分为输入表、输出表、资源、重定位表等数据块,这16个IMAGE_DATA_DIRECTORY结构就是用来定位不同用途的数据块的。我们知道,区段表可以看作是PE文件各区段的根目录,同样的, data directory 是存储在这些区段里的逻辑元素的根目录。 明确点说,输入表、输出表等重要数据结构是按照它们各自不同的属性放置在对应属性的区段里的,它们的位置和尺寸信息就保存在data directory (数据目录)中。 数据目录中每个成员都包含了一个重要数据结构的信息,如表5.1所示。 表5.1 成员的索引号 Info inside 0 Export symbols(导出表) 1 Import symbols(导入表) 2 Resources(资源目录) 3 Exception(异常目录) 4 Security(安全目录) 5 Base relocation(重定位基本表) 6 Debug(调试目录) 7 Copyright string(版权信息) 8 Unknown(机器值) 9 Thread local storage (TLS)(TLS目录) 10 Load configuration(载入配值目录) 11 Bound Import(绑定输入) 12 Import Address Table(IAT表,但不是必需 的,修改这个值不影响IAT表) 13 Delay Import 14 COM descriptor 15 未使用 上面那些金色显示的是常用的。 data directory 的每个成员都是一个 IMAGE_DATA_DIRECTORY 结构,其定义如下所示: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //4个字节,该数据结构的相对虚拟地址(RVA) DWORD isize; //4个字节,该数据结构的大小 } IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY; VirtualAddress 实际上是该数据结构的相对虚拟地址(RVA)。比如,如果该结构是关于Import symbols(输入表)的,该域就包含指向IMAGE_IMPORT_ DESCRIPTOR 数组(即输入表)的RVA。 isize 含有VirtualAddress所指向数据结构的字节数。 在PE文件中寻找特定的数据时就是从这些IMAGE_DATA_DIRECTORY结构开始的,比如要存取资源,那么必须要从第3个IMAGE_DATA_DIRECTORY结构(索引为2)中得到资源数据块的位置和大小。同理,如果要查看PE文件导入了哪些DLL文件的哪些API函数,那就必须首先从第2个IMAGE_DATA_DIRECTORY结构得到输入表的位置和大小。 5.3 相对虚拟地址RVA RVA是相对虚拟地址(Relative Virtual Address)的缩写,顾名思义,它是一个“相对”地址,PE文件的各种数据结构中涉及到地址的字段大部分都是以RVA表示的。准确地说,RVA就是当PE文件被装载到内存中后,某个数据的位置相对于MZ头的偏移量。 文件映射到内存: 在可执行程序运行之前,PE加载器将把PE文件加载到进程空间的内存中去,并且初始化每个区段(比如对齐、赋上属性等)。那么加载到内存中的哪个地址去呢?这将由IMAGE_OPTIONAL_HEADER32结构中的 ImageBase的值指出加载的起始地址(又叫基址)。这个值通常是00400000h,那么PE文件的首地址“00000”就被映射到内存地址“00400000”处,而相对于文件开头偏移10个字节的地址为“00010”,被映射到内存后的偏移也应该是10个字节,映射后的地址应该为“00400010”。如果某个区段中的某个数据载入内存后被装入到了0040xxxxh处,那么这个数据的RVA就是(0040xxxxh- 00400000h)=xxxxh,反过来说,将RVA的值加上文件被装载的基地址,就可以找到数据在内存中的实际地址。 PE文件中出现RVA的概念是因为PE的内存映像和磁盘文件映像是不同的,同一数据相对于文件头的偏移量在内存中和在磁盘文件中可能是不同的,为了提高效率,PE文件头中使用的都是内存映像中的偏移量,也就是RVA。 RVA是指内存中,不是指文件中。是指相对于载入点的偏移而不是一个内存地址,只有RVA加上载入点的地址,才是一个实际的内存地址。 RVA仅仅是对于身处于区段中的数据而言的,对于文件头和节表来说无所谓 RVA和文件偏移,因为它们在被映射到内存中后不管是大小还是偏移都不会有任何改变。 为什么PE文件格式要用到RVA呢? 这是为了减少PE装载器的负担。因为每个模块都有可能被重载到任何虚拟地址空间,如果让PE装载器修正每个重定位项,这肯定是个梦魇。相反,如果所有重定位项都使用RVA,那么PE装载器就不必操心那些东西了: 它只要将整个模块重定位到新的起始VA。这就象相对路径和绝对路径的概念: RVA类似相对路径,VA就像绝对路径。 在PE文件中大多数地址多是RVAs 而RVAs只有当PE文件被PE装载器装入内存后才有意义。如果直接将文件映射到内存而不是通过PE装载器载入,则不能直接使用那些RVAs。必须先将那些RVAs转换成文件偏移量。 代码的文件虚拟偏移地址和文件物理偏移地址的计算公式如下: 假如代码的虚拟地址VA = 00401325,ImageBase = 00400000 (基地址), 某代码所在区段的RVA:VOffset=00001000,区段的物理地址ROffset=00000400 则: 代码的物理偏移地址 = 代码的虚拟RVA -本区段的RVA+本区段的物理偏移 =(代码的虚拟VA-ImageBase)- VOffset + ROffset = (00401325 - 00400000)- 00001000 + 00000400 = 725 代码的虚拟RVA = 代码的物理偏移 - 本区段的物理偏移 + 本区段的RVA。 5.3 对齐值 文件对齐值分在内存中的对齐值和在文件中的对齐值。PE文件中的节无论是在磁盘中或内存映像中都是按照一定单位进行对齐的。 那么对齐的具体情况是什么样的呢? 假如在内存中的对齐单位是1000h,即4 Kb,装入内存时的基址是00400000h,那么Windows将PE文件装入内存时先把内存中的空间从地址00400000h开始,每4KB作为一页,然后把PE文件的DOS头、dos加载模块、PE头、区段表按顺序逐个字节的装载到从00400000h开始的第1页,载入之后用0把第1页填满,接着把各个区段装载到各区段所要求的RVA处(RVA一般也都是该文件对齐单位1000h的倍数),本页未填满的都用0填充。 在文件中的对齐也是类似的,比如DOS头、dos加载模块、PE头、区段表共占用了260H字节,文件中的对齐值是200H,所以第一个区段只能从400H开始。 第六节 Section Table(区段表) 区段表其实就是紧挨着 PE header 的一个IMAGE_SECTION_HEADER结构数组,IMAGE_SECTION_ HEADER 结构的长度固定,长40个字节。该数组成员的数目由 file header (IMAGE_FILE_HEADER) 结构中 NumberOfSections 域的域值来决定。整个Section Table 的长度不固定,等于 NumberOfSections *sizeof(IMAGE_SECTION_HEADER),即NumberOf Sections*40字节,后面还要紧跟一个全0结构表示区段表的结束(当此PE文件进行了输入表绑定之后,在区段表之后没有全0结构来表示区段表的结束,此时只能依靠文件头中NumberOfSections来判断区段表是否结束)。区段表结构后面就是各个区段。IMAGE_SECTION_HEADER结构定义如下: typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //★区段名称,不超过8个字节,如".text", 可随便改 union { DWORD PhysicalAddress; // 物理地址,基本不用这个 DWORD VirtualSize; // ★区段在内存中实际使用的大小,就是区块数据在内存 中没有对齐处理前的大小,免杀时可以填00 } Misc; DWORD VirtualAddress; // ★本区段在内存中的RVA DWORD SizeOfRawData; // ★本区段在文件中经对齐后的物理大小 DWORD PointerToRawData; // ★本区段在文件中的偏移量 DWORD PointerToRelocations; // 重定位的偏移,在OBJ文件中使用,免杀时可随便改 DWORD PointerToLinenumbers; // 行号表的偏移(用于调试),免杀时可随便改 WORD NumberOfRelocations; // 重定位项数目,在OBJ文件中使用,免杀时可随便改 WORD NumberOfLinenumbers; // 符号表中行号的数目,免杀时可随便改 DWORD Characteristics; // ★区段属性,可参考LordPE,可以更改,但不能乱改 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER; 结构中某些字段是供COFF格式的OBJ文件使用的,对可执行文件来说不代表任何意义,在分析的时候可以不予理会,几个比较重要的成员含义见表6.1. 表6.1 成员 含义 Name1 区段名。事实上本域的名称是"name",只是"name"已被MASM用作关键字,所以我们只能用"Name1"代替。这里的区段名长度不超过8字节,不足8字节用0补齐。区段名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。另外要注意,不能有同名的区段。 VirtualAddress 本区段被装载到内存中的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。 SizeOfRawData 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注:假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。 PointerToRawData 这是本区段在文件中的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 Characteristics 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。 现在我们已知晓IMAGE_SECTION_HEADER结构,再来模拟一下PE装载器的工作吧: 1)​ 读取IMAGE_FILE_HEADER的NumberOfSections域,知道文件的区段的数目。 2)​ SizeOfHeaders域值作为区段表的文件偏移量,并以此定位区段表。 3)​ 遍历整个结构数组检查各成员值。 4)​ 对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于区段起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。 5)​ 遍历整个数组,直至所有区段都已处理完毕。 注意我们并没有使用区段名: 这其实并不重要。 遍历区段表的步骤: 1)​ PE文件有效性校验。 2)​ 定位到 PE header 的起始地址。 3)​ 从 file header 的 NumberOfSections域获取区段数。 4)​ 通过两种方法定位区段表: ImageBase+SizeOfHeaders或者PE heade的起始地址+ PE header结构大小。 (区段表紧随PE header)。如果不是使用文件映射的方法,可以用SetFilePointer 直接将文件指针定位到区段表。区段表的文件偏移量存放在SizeOfHeaders域里。(SizeOfHeaders 是 IMAGE_OPTIONAL_ HEADER 的结构成员) 5)​ 处理每个IMAGE_SECTION_HEADER结构。 试画映象文件的全图如下: 每个数据目录都会指向其中的一个节(几个数据目录有可能指向同一个节,而且也可能有的节没有数据目录指向它们)。 第七节 Import Table(输入表、引入表或者导入表) 7.1 输入表结构 首先,你得了解什么是引入函数。一个引入函数是被某模块调用的但又不在调用者模块中的函数,因而命名为"import(引入)"。引入函数实际位于一个或者更多的DLL里。调用者模块里只保留一些函数信息,包括函数名及其驻留的DLL名等等。但是对于这些硬盘上静态的PE文件来说,在自己被映射到内存之前,无法得知这些导入函数会在哪个地方出现。只有当文件被装入内存后,Windows才会将相应的DLL文件装入,并将导入函数与DLL中的实际函数地址联系起来,这便是“动态链接”的概念,也就是为什么DLL文件会被称为“动态链接库文件”的原因。说了这么一大堆,归根结底就是要大家明白,动态链接就是由“导入表”来完成的,输入表中保存的正是函数名和其驻留的DLL名等动态链接所必需的信息。 反汇编的时候我们可以看到那种通过调用输入函数的指令: CALL 内存地址1 //调用输入函数 …… …… 内存地址1上 JMP DWORD PTR [内存地址XXXXXXXX] 可以看到对输入函数的调用就是对某地址的调用,而调用的这个地址显然是位于程序自身模块而不是在DLL模块中的,实际上,这是由编译器在程序所有代码的后面自动加上的JMP DWORD PTR [XXXXXXXX]类型的指令,这个指令是一个间接寻址的跳转指令,XXXXXXXX地址中存放的才是真正的输入函数的地址。而XXXXXXXX地址所在的位置就是IAT表,PE装载器在将文件装载入内存的时候会根据输入表的内容把输入函数的地址填写到它里面。 那么PE装载器是如何得到输入表的地址呢?它并不是通过各个区段名就可以得到各个结构的偏移,因为区段是通过各个属性来定义的。什么意思?就是比如我们的某些数据拥有可读可写等属性,则归纳为一个区段。一个区段中的数据仅仅是属性相同,并不代表他们的用途相同, 比如输入表和输出表等有可能和只读常量放在一个区段中,但是它们的用途是不相同的。所以区段名只是一个简单的定义、标识而已。 现在,我们怎样才能找到输入表呢? 到optional header(可选头)结构中最后一个成员 data directory(数据目录)寻求吧。由表5.1我们知道,数据目录中第2项就是关于输入表的,索引号为1。 下面就是如何找寻PE文件中重要数据结构的一般方法: 1)​ 从 DOS header 定位到 PE header。 2)​ 从 optional header 读取 data directory 的地址。 3)​ IMAGE_DATA_DIRECTORY 结构尺寸乘上找寻结构的索引号: 比如你要找寻import symbols的位置信息,必须用IMAGE_DATA_DIRECTORY 结构尺寸(8 bytes)乘上1(import symbols在data directory中的索引号)。 4)​ 将上面的结果加上data directory地址,我们就得到包含所查询数据结构信息的 IMAGE_DATA_DIRECTORY 结构项。 现在我们开始真正讨论输入表了。输入函数的引入见图7.1和图7.2。 图7.1 引入简图 引入详图见下页图7.2 data directory数组第二项成员的VirtualAddress包含输入表地址。VirtualAddress+镜像基址=内存地址,由内存地址我们就可以算出输入表的文件物理偏移地址。输入表实际上是一个 IMAGE_IMPORT_DESCRIPTOR 结构数组。每个结构包含PE文件引入函数的一个相关DLL的信息。这个结构长度为20个字节,所以这个数组每个成员长度为20字节。成员的个数是该文件需要的动态链接库.dll文件的个数加上1(因为该数组以一个全0的成员结尾)。这样,输入表的长度=20*(动态链接库.dll文件的个数+1)。比如,如果该PE文件从9个不同的DLL中引入函数,那么这个数组就有10个成员,输入表的长度就是200字节。下面详细研究其成员的结构组成: typedef struct _IMAGE_IMPORT_DESCRIPTOR { union { DWORD Characteristics; DWORD OriginalFirstThunk; //指向IMAGE_THUNK_DATA结构数组的一 个RVA,这个结构数组的每一个成员都 包含一个导入函数的信息 } DUMMYUNIONNAME; DWORD TimeDateStamp; //当该字段为FFFFFFFF时,表示在文件被装入内存之前 IAT表中存放的就是输入函数的真实虚拟地址,当该字 段为00000000时,在文件被装入内存之前IAT表中存 放的内容跟HNT表相同,输入函数的真实虚拟地址要 等PE文件被装入内存之后才被系统填入IAT表中 DWORD ForwarderChain; //第一个被转向的API的索引 DWORD Name; //一个指向一串以00结尾的的ASCII字符的RVA, 该串字 符是该dll文件的名称; DWORD FirstThunk; //指向输入地址表(IAT),IAT是一个IMAGE_THUNK_ DATA结构数组,PE装入之后IAT表中是调用的输入函 数在内存中的虚拟地址 } IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR; 这个结构共有5个字段。各字段含义如下: OriginalFirstThunk:(在WINNT.H中Characteristics这个叫法已经不对了)这里实际上保存着一个RVA,这个RVA指向一个IMAGE_THUNK_DATA结构数组,这个数组可以叫做输入查询表(即HNT表),每个数组元素,或者叫一个表项,保存着一个指向函数名的RVA或者保存着一个函数的序号。 TimeDateStamp:当可执行文件不与被输入的dll进行绑定时,此字段为0,否则为ffffffff ForwarderChain:(待查) Name:一个RVA,这个RVA指向一个以ASCII空字符结束的字符串,这个字符串就是本结构对应的dll文件的名字。 FirstThunk:一个RVA,这个RVA指向一个DWORD数组,这个数组可以叫输入地址表(IAT)。如果绑定了的话(即TimeDateStamp的值为FFFFFFFF),这个数组的每个元素,就是一个输入函数的入口地址;如果没有绑定(即TimeDateStamp的值为0),这个数组的值与OriginalFir
本文档为【PE文件格式入门 word版 (完美排版)】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_360971
暂无简介~
格式:doc
大小:4MB
软件:Word
页数:49
分类:互联网
上传时间:2014-02-15
浏览量:55