关闭

关闭

封号提示

内容

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

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

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

上传者: w39493032 2014-02-15 评分 5 0 221 30 1003 暂无简介 简介 举报

简介:本文档为《PE文件格式入门 word版 (完美排版)doc》,可适用于IT/计算机领域,主题内容包含其实PE文件就是指Windows里的DLL与EXE文件PE的意思就是PortableExecutable即可移植的执行体。PE文件格式入门By:QQ符等。

其实PE文件就是指Windows里的DLL与EXE文件PE的意思就是PortableExecutable即可移植的执行体。PE文件格式入门By:QQ说明:几年前学习PE文件格式的时候看了几个比较流行的PE文件教程发现这些教程要么不够详细要么太深奥要么干脆就是纯英文的所以就想把这几个教程好好整理综合一下就得到了这篇文章。其中大部分内容都是从已有教程中直接复制过来的并不是本人原创(只可惜当时没有注明出处)。我所做的工作就是综合了这些中文教程把英文教程里的英文说明翻译了一下调整好了格式排好了版有几张图片里面自己加上了说明还又画了几张图比如输入表结构图、输出表机构图、重定位表结构图等。因为当时也是初学PE格式文中肯定也会有不少的错误大家看的时候不要盲信对有疑问的地方多查查资料。本人QQ:另外附上那时候看过的一些相关教程和文章的名字文中的内容应该是从这些教程中复制过来的。《深度探索Win可执行文件格式》姜庆东译《加密解密技术内幕》PE格式学习总结《【NB免杀】PE结构图解》《PE头结构》《PE文件区段表详解精通PE文件格式》《体验修改PE文件的乐趣》《学习笔记之PE文件结构导入表》《手工构造PE文件》看雪软件安全论坛《手工构造典型PE文件》《也谈PE重定位表》看雪软件安全论坛《再写手工打造可执行程序》看雪软件安全论坛《关于重定位表》CSDN博客频道CodeSpace《PE文件重定位表详解》CSDN博客频道matianlong的专栏《PE重定位表学习》justnow的日志网易博客《PE重定位表学习手记》目录TOCo""hu第一节PE文件格式一览第二节深入了解PE文件与文件头第三节PEheader(PE头)第四节FileHeader(文件头)文件头PE头移动修改PE头第五节OptionalHeader(可选头)可选头数据目录相对虚拟地址RVA对齐值第六节SectionTable(区段表)第七节ImportTable(输入表、引入表或者导入表)输入表结构输入表绑定第八节ExportTable(输出表或叫引出表、导出表)第九节重定位表第十节关于区段第十一节几个小知识一、PE相关名词解释如下二、文件中大量的空白三、高存高低存低原则四、DOSMZHeader中的MZ第一节PE文件格式一览其实PE文件就是指Windows里的DLL与EXE文件PE的意思就是PortableExecutable(可移植的执行体)。它是Win环境自身所带的执行体文件格式。它的一些特性继承自Unix的Coff(commonobjectfileformat)文件格式。"portableexecutable"(可移植的执行体)意味着此文件格式是跨win平台的即使Windows运行在非Intel的CPU上任何win平台的PE装载器都能识别和使用该文件格式。当然移植到不同的CPU上PE执行体必然得有一些改变。所有win执行体(除了VxD和位的Dll)都使用PE文件格式包括NT的内核模式驱动程序(kernelmodedrivers)。我们现今组成Windows大家庭的主要成员就是PE文件了里面包括EXE、DLL、OCX、SYS等一切最有价值的文件都是PE文件格式。因而研究PE文件格式给了我们洞悉Windows结构的良机。PE文件总的来说是由dos文件头、dos加载模块、PE头、区段表与区段部分构成。其实如果在纯Windows环境下运行dos文件头、dos加载模块根本是用不上的加上两个dos相关的结构完全是为了兼容性问题。图一为了方便观察与理解我们可以通过观察图二大体了解PE文件的结构。图二如上图可知整个程序就是以dos文件头“MZ”开始的接下来就是dos加载模块“Thisprogramcannotberunindosmode”几乎每个Windows程序的前面都是这样一些信息!下面有一个以字母“PE”为开头部分这就是大名鼎鼎的PE头了PE头的标准大小为个字节由图可见里面有一个画了横线标记的问号与左面的十六进制信息“E”相对应这便是PE头中可选头体积的描述标记十六进制的“E”等于十进制的“”这是PE头中可选头的大小(一般都是E即字节)再加上PE标识符(个字节)和文件头(个字节)正好是个字节。再往下就是以“text”、“data”与“rsrc”组成的区段表了。区段表也称节表它的作用就相当于一本书中的目录你想看哪一章哪一节只要按着目录标注的页数去找就可以PE文件的区段表也是起同样的作用但是区段表除此之外还包含有各个区段的读写权限信息。而图中的“text”、“data”与“rsrc”则是这个程序里的区段名称也称为“节”。由此可见这个程序是由“text”、“data”与“rsrc”这个区段组成的如下图。图三其实通过区段名称就可以大体猜出来这个节里包含什么信息在整个程序中能起到什么作用等等。第二节深入了解PE文件与文件头下面让我们抽象的了解一下PE文件的构成:dosMZheader<<dos头dosstub<<dos加载模块PEheader<<PE头Sectiontable<<区段表Section<<节Section<<节Section<<Sectionn<<节ndosMZheader:也称之dos文件头亦或dosMZ文件头它是一段以关键字MZ为开头的数据实际上是一个IMAGEDOSSIGNATURE结构共有个字节如下图所示有了它一旦程序在DOS下执行DOS就能识别出这是有效的执行体然后运行紧随MZheader之后的DOSstub。偏移量C处包含着PE头的起始位置信息。偏移量C、D、E、F四个字节的数据的倒序排列所组成的地址就是PE头的起始物理地址(倒序的原因是根据高存高低存低的原则)。dosstub:这个区块是以一段“Thisprogramcannotberunindosmode”为标志当运行环境不匹配时则弹出这句话对于WIN位的操作系统来讲存在的意义不大完全可以删除但是删除之后可能无法进行动态调试。此段的标准大小为字节。PEheader:这就是需要我们着重研究的PE头了PEheader是PE相关结构IMAGENTHEADERS的简称其中包含了许多PE装载器用到的重要域。执行体在支持PE文件结构的操作系统中执行时PE装载器将从DOSMZheader中找到PEheader的起始偏移量因而跳过了DOSstub直接定位到真正的文件头PEheader。PEheader是一段以关键字“PE”为开头的数据默认大小字节。我们沿着PE头开始标志符“PE”往下找在关键字“PE”之后的第、个字节处的数据的倒序就是代表PE头中可选头的大小一般为。sections:PE文件的真正内容划分成块称之为sections(区段或节)。每节是一块拥有共同属性的数据比如代码数据、读写等。我们可以把PE文件想象成一逻辑磁盘PEheader是磁盘的boot扇区而sections就是各种文件每种文件自然就有不同属性如只读、系统、隐藏、文档等等。值得我们注意的是:节的划分是基于各组数据的共同属性而不是逻辑概念。重要的不是数据代码是如何使用的如果PE文件中的数据代码拥有相同属性它们就能被归入同一节中。之所以这样做是因为Win中可以对每个内存页分别指定可执行、可读写等属性在PE文件中将同样属性的数据分类放在一起是为了统一描述这些数据装入内存后的页面属性。所以不必关心节中类似于"data","code"或其他的逻辑概念:如果数据和代码拥有相同属性它们就可以被归入同一个节中。注意:节名称仅仅是个区别不同节的符号而已类似"data","code"的命名只为了便于识别惟有节的属性设置决定了节的特性和功能。如果某块数据想赋为只读属性就可以将该块数据放入属性为只读的节中当PE装载器映射节内容时它会检查相关节属性并置对应内存块为指定属性。sectiontable:如果我们将PE文件格式视为一逻辑磁盘PEheader是boot扇区而sections是各种文件但我们仍缺乏足够信息来定位磁盘上的不同文件譬如什么是PE文件格式中等价于目录的东东?别急那就是PEheader接下来的数组结构sectiontable(区段表或者叫区段表)。每个结构包含对应节的属性、文件偏移量、虚拟偏移量等。如果PE文件里有个节那么此结构数组内就有个成员。因此我们便可以把区段表视为逻辑磁盘中的根目录每个数组成员等价于根目录中的目录项。以上就是PE文件格式的物理分布下面将总结一下装载PE文件的主要步骤:当PE文件被执行PE装载器检查DOSMZheader里的PEheader偏移量。如果找到则跳转到PEheader。PE装载器检查PEheader的有效性并获取信息(比如OEP)。如果有效就跳转到PEheader的尾部。紧跟PEheader的是区段表。PE装载器读取其中的节信息并采用文件映射方法将这些节映射到内存一同赋上区段表里指定的节属性。PE文件映射入内存后PE装载器将处理PE文件中类似importtable(输入表)逻辑部分。跳转到OEP开始执行程序。第三节PEheader(PE头)如何才能校验指定文件是否为一有效PE文件呢这个问题很难回答完全取决于想要的精准程度。你可以检验PE文件格式里的各个数据结构或者仅校验一些关键数据结构。大多数情况下没有必要校验文件里的每一个数据结构只要一些关键数据结构有效我们就认为是有效的PE文件了。下面我们就来实现前面的假设。我们要验证的重要数据结构就是PEheader。从编程角度看PEheader实际就是一个IMAGENTHEADERS结构。定义如下:typedefstructIMAGENTHEADERS{DWORDSignaturePE标识符共个字节此字段被设置为XASCII码是“PE”IMAGEFILEHEADERFileHeader文件头结构共个字节IMAGEOPTIONALHEADEROptionalHeader可选头结构共个字节}IMAGENTHEADERS,*PIMAGENTHEADERS,PeHeader<>Signature:PE标识符DWORD类型个字节值为hhhh(PE)。如果Signature域值等于"PE"那么就是有效的PE文件。<>FileHeader:即文件头个字节。该结构域包含了关于PE文件物理分布的信息比如节数目、文件执行机器等。<>OptionalHeader:即可选头一般为个字节(可以通过更改文件头中表示大小的变量来改变它的大小)。该结构域包含了关于PE文件逻辑分布的信息虽然域名有"可选"字样但实际上本结构总是存在的。我们目的很明确。如果IMAGENTHEADERS的signature域值等于"PE"那么就是有效的PE文件。实际上为了比较方便Microsoft已定义了常量IMAGENTSIGNATURE供我们使用。IMAGEDOSSIGNATUREequADhIMAGEOSSIGNATUREequEhIMAGEOSSIGNATURELEequChIMAGEVXDSIGNATUREequChIMAGENTSIGNATUREequh接下来的问题是:如何定位PEheader答案很简单:DOSMZheader已经包含了指向PEheader的文件偏移量。DOSMZheader又定义成结构IMAGEDOSHEADER。查询windowsinc我们知道IMAGEDOSHEADER结构的elfanew成员就是指向PEheader的文件偏移量。现在将所有步骤总结如下:)​ 首先检验文件头部第一个字的值是否等于IMAGEDOSSIGNATURE是则DOSMZheader有效。)​ 一旦证明文件的DOSheader有效后就可用elfanew来定位PEheader了。)​ 比较PEheader的第一个字的值是否等于IMAGENTHEADER。如果前后两个值都匹配那我们就认为该文件是一个有效的PE文件。第四节FileHeader(文件头)文件头FileHeader(IMAGEFILEHEADER结构)包含在PEHeader(IMAGENTHEADERS)里面其结构定义:typedefstructIMAGEFILEHEADER{WORDMachine该文件运行所要求的CPUIntel以上处理器必须为CWORDNumberOfSections文件中区段的数目DWORDTimeDateStamp文件创建日期和时间免杀时可随便改DWORDPointerToSymbolTable指向符号表用于调试免杀时可随便改DWORDNumberOfSymbols符号表中的符号数量用于调试免杀时可随便改WORDSizeOfOptionalHeader可选头的大小WORDCharacteristics文件的执行属性该字段很重要EXE文件此值一般为Fh}IMAGEFILEHEADER,*PIMAGEFILEHEADERIMAGEFILEHEADER结构成员含义:结构成员含义Machine该文件运行所要求的CPU。对于Intel平台该值是Ch对于MIPSR平台为h对于MIPSR平台为h对于AlphaAXP平台为h对于PowerPC平台为Fh。更改这个值会使程序不能正确执行。看来除了禁止程序执行之外本域对我们来说用处不大。NumberOfSections这是一个很重要的字段用来确定文件中区段的数目。如果我们要在文件中增加或删除一个区段就需要修改这个值。TimeDateStamp文件创建日期和时间。其格式是自从年月日:PM之后的总秒数。据计算xFFFFFFFFh是年。将这个值翻译为易读的字符串的方法是用ctime函数(它是时区敏感型的)另一个对此字段计算有用的函数是gmtime。PointerToSymbolTable用于调试。NumberOfSymbols用于调试。SizeOfOptionalHeader指示紧随本结构之后的OptionalHeader结构大小必须为有效值。Characteristics此值为文件的执行属性。它影响系统对文件的装入方式比如位为时表示这是一个DLL文件系统将使用调用dll入口函数的方式调用文件入口如果为就是普通的可执行文件系统直接跳到入口处执行。EXE文件此值一般为FhDLL文件此值一般为Eh。各位的含义见下图简言之只有三个域对我们有一些用:Machine,NumberOfSections和Characteristics。通常不会改变Machine和Characteristics的值但如果要遍历区段表就得使用NumberOfSections。为了更好阐述NumberOfSections的用处这里简要介绍一下区段表。区段表是一个IMAGESECTIONHEADER结构的数组每个结构包含一个节的信息。该数组中成员的个数由FileHeader(IMAGEFILEHEADER)结构中NumberOfSections域的域值来定。区段表中的成员是IMAGESECTIONHEADER结构IMAGESECTIONHEADER结构的长度固定长个字节。整个SectionTable的长度不固定等于NumberOfSections*sizeof(IMAGESECTIONHEADER)即NumberOfSections*字节。因此如果程序有个节这个数组就有个成员。我们需要NumberOfSections值来了解该数组中到底有几个成员。也许你会想检测结构中的全成员起到同样效果。Windows确实采用了这种方法。为了证明这一点可以增加NumberOfSections的值Windows仍然可以正常执行文件。据我们的观察Windows读取NumberOfSections的值然后检查区段表里的每个结构如果找到一个全结构就结束搜索否则一直处理完NumberOfSections指定数目的结构。为什么我们不能忽略NumberOfSections的值有个原因:)PE说明中没有指定区段表必须以全结构结束这样可能会出现最后的那个数组成员与节之间没有全结构的情形。)如果此PE文件进行了boundimports(即输入表绑定)那么在区段表最后一个成员之后紧接着的不是全结构而是进行输入表绑定时所使用的dll文件的版本信息。(输入表绑定见节)因此你仍然需要NumberOfSections。PE头移动因为在windows中DOSStub部分根本没用所以我们可以讲PE头往上移动占用DOSStub的位置PE头原先的位置空出来的地方都用填充。这时候要修改两个位置的数据:、表示PE头位置的偏移量为C、D、E、F四个字节处的数据为PE头现在的新位置、关键字“PE”之后的第、个字节处的数据也就是文件头结构中的SizeOfOptionalHeader域的值因为我们把PE头往上移动了系统在可选头之后紧挨着可选头的地方找不到区段表就会报错无法运行因此我们要把刚才移动之后在可选头和区段表之间空出的区域包含到可选头中把新的可选头的大小写到SizeOfOptionalHeader中(也就是关键字“PE”之后的第、个字节处)。PE头甚至可以移动到DOS文件头的空间里关键是要让PE头中一个没用的DWORD字段正好占用掉偏移量为C、D、E、F四个字节然后把这里写上PE头新的位置更改SizeOfOptionalHeader域的值就可以了。如果不想修改可选头的大小可以把区段表一块往上移动区段表空出来的地方用填充。移动PE头之后的程序用OD载入会提示不是有效的WIN程序点击确定之后会来到系统领空这可以作为简单的反调试。此时打开内存窗口在程序的PE头上下断F运行又断在系统领空AltF程序就运行起来了。修改PE头通过修改PE头可以使OD载入时提示错误并且在内存窗口中只能看到PE头有两种方法:方法一、手动移动PE头方法二、修改数据目录的项数。可以手动的修改但是要改动的地方比较多较简单的方法是用LordPE的PE编辑器载入程序其中的RVA数及大小就是指的数据目录的项数因为数据目录的项数自NT系统发布以来一直是用十六进制表示就是H所以一般这一项总为可以把这个数改大如果改小了就会导致程序无法运行。第五节OptionalHeader(可选头)可选头OptionalHeader结构是IMAGENTHEADERS中的最后成员。包含了PE文件的逻辑分布信息。该结构共有个域一些是很关键另一些不太常用。其结构定义:typedefstructIMAGEOPTIONALHEADER{StandardfieldsWORDMagic幻数用于描述次PE文件的映像类型ROM映像为h普通可执行映像BhPE则是Bh。BYTEMajorLinkerVersion链接程序的主版本号免杀时可随便改BYTEMinorLinkerVersion链接程序的次版本号免杀时可随便改DWORDSizeOfCode所有含代码的区段的总大小改变此值不影响运行免杀可随便改DWORDSizeOfInitializedData所有含已初始化数据的区段的总大小免杀时可随便改DWORDSizeOfUninitializedData所有含未初始化数据的区段的总大小免杀时可随便改DWORDAddressOfEntryPoint程序开始执行的入口地址这是一个RVA(相对虚拟地址)DWORDBaseOfCode代码段的起始RVA典型的代码段名有“text”、“code”、“AUTO”等这个值不重要因为在区段表中还有起相同作用的值免杀时可随便改DWORDBaseOfData数据段的起始RVA典型的数据节名有“data”、“idata”、“DATA”等这个值不重要因为在区段表中还有起相同作用的值免杀时可随便改NTadditionalfieldsDWORDImageBase可执行文件默认装入的基地址一般为这个值不要更改DWORDSectionAlignment内存中块的对齐值(默认的块对齐值为H即CPU的页尺寸KB字节)DWORDFileAlignment文件中块的对齐值(默认值为H或H字节为了保证块总是从磁盘的扇区开始的这个值必须是的幂并且最小为h)WORDMajorOperatingSystemVersion要求操作系统的最低版本号的主版本号免杀时可随便改WORDMinorOperatingSystemVersion要求操作系统的最低版本号的次版本号免杀时可随便改WORDMajorImageVersion该可执行文件的主版本号免杀时可随便改WORDMinorImageVersion该可执行文件的次版本号免杀时可随便改WORDMajorSubsystemVersion要求最低之子系统版本的主版本号值为hWORDMinorSubsystemVersion要求最低之子系统版本的次版本号一般情况下都为DWORDWinVersionValue保留字免杀时可随便改DWORDSizeOfImage映像装入内存后的总尺寸它是所有头和节经过节对齐处理后的大小DWORDSizeOfHeaders所有头部和区段表的大小(包括对齐填入的)等于文件尺寸减去文件中所有节的尺寸也等于第一个区段的文件偏移量DWORDCheckSumCRC检验和只在sys文件中有用免杀时可随便改WORDSubsystem程序使用的用户接口子系统表示未知表示不需要子系统(如驱动程序)表示图形接口子系统(GUI)表示字符子系统(CUI)表示OS字符子系统表示POSIX字符子系统为保留表示WindowsCE图形界面WIN程序就是WORDDllCharacteristicsDLLmain函数何时被调用默认为对EXE文件免杀时可随便改DWORDSizeOfStackReserve初始化时堆栈大小免杀时可随便改DWORDSizeOfStackCommit初始化时实际提交的堆栈大小免杀时可随便改DWORDSizeOfHeapReserve初始化时保留的堆大小免杀时可随便改DWORDSizeOfHeapCommit初始化时实际提交的堆大小免杀时可随便改DWORDLoaderFlags与调试有关默认为免杀时可随便改DWORDNumberOfRvaAndSizes数据目录的项数此字段自NT系统发布以来一直是IMAGEDATADIRECTORYDataDirectoryIMAGENUMBEROFDIRECTORYENTRIES数据目录表}IMAGEOPTIONALHEADER,*PIMAGEOPTIONALHEADERIMAGEOPTIONALHEADER结构几个比较重要的成员含义:成员含义SizeOfCodeAddressOfEntryPointPE装载器准备运行的PE文件的第一个指令的RVA。通常会落在textsection此域适用于exe或dll。OEPImageBasePE文件的优先装载地址。比如如果该值是hPE装载器将尝试把文件装到虚拟地址空间的h处。字眼"优先"表示若该地址区域已被其他模块占用那PE装载器会选用其他空闲地址。SectionAlignment内存中节对齐的粒度。例如如果该值是(h)那么每节的起始地址必须是的倍数。若第一节从h开始且大小是个字节则下一节必定从h开始即使h和h之间还有很多空间没被使用。FileAlignment文件中节对齐的粒度。例如如果该值是(h),那么每节的起始地址必须是的倍数。若第一节从文件偏移量h开始且大小是个字节则下一节必定位于偏移量h:即使偏移量和之间还有很多空间没被使用定义。MajorSubsystemVersionMinorSubsystemVersionwin子系统版本。若PE文件是专门为Win设计的该子系统版本必定是否则对话框不会有维立体感。MajorSubsystemVersion的值是HSizeOfImage内存中整个PE映像体的尺寸。它是所有头和节经过节对齐处理后的大小。SizeOfHeaders所有头区段表的大小(包括对齐填入的)也就等于文件尺寸减去文件中所有节的尺寸也等于PE文件第一个区段的文件偏移量。SubsystemNT用来识别PE文件属于哪个子系统。对于大多数Win程序只有两类值:WindowsGUI和WindowsCUI(控制台)。DataDirectoryIMAGEDATADIRECTORY结构数组。每个结构给出一个重要数据结构的RVA比如引入地址表等。数据目录所谓数据目录就是指可选头中的datadirectory字段这个字段可以说是可选头中最重要的字段之一它是由个相同的IMAGEDATADIRECTORY结构组成的结构数组。虽然PE文件中的数据是按照装入内存后的页属性归类而被放在不同的区段中的但是这些处于各个区段中的数据按照用途可以分为输入表、输出表、资源、重定位表等数据块这个IMAGEDATADIRECTORY结构就是用来定位不同用途的数据块的。我们知道区段表可以看作是PE文件各区段的根目录同样的datadirectory是存储在这些区段里的逻辑元素的根目录。明确点说输入表、输出表等重要数据结构是按照它们各自不同的属性放置在对应属性的区段里的它们的位置和尺寸信息就保存在datadirectory(数据目录)中。数据目录中每个成员都包含了一个重要数据结构的信息如表所示。表成员的索引号InfoinsideExportsymbols(导出表)Importsymbols(导入表)Resources(资源目录)Exception(异常目录)Security(安全目录)Baserelocation(重定位基本表)Debug(调试目录)Copyrightstring(版权信息)Unknown(机器值)Threadlocalstorage(TLS)(TLS目录)Loadconfiguration(载入配值目录)BoundImport(绑定输入)ImportAddressTable(IAT表但不是必需的修改这个值不影响IAT表)DelayImportCOMdescriptor未使用上面那些金色显示的是常用的。datadirectory的每个成员都是一个IMAGEDATADIRECTORY结构其定义如下所示:typedefstructIMAGEDATADIRECTORY{DWORDVirtualAddress个字节该数据结构的相对虚拟地址(RVA)DWORDisize个字节该数据结构的大小}IMAGEDATADIRECTORY,*PIMAGEDATADIRECTORYVirtualAddress实际上是该数据结构的相对虚拟地址(RVA)。比如如果该结构是关于Importsymbols(输入表)的该域就包含指向IMAGEIMPORTDESCRIPTOR数组(即输入表)的RVA。isize含有VirtualAddress所指向数据结构的字节数。在PE文件中寻找特定的数据时就是从这些IMAGEDATADIRECTORY结构开始的比如要存取资源那么必须要从第个IMAGEDATADIRECTORY结构(索引为)中得到资源数据块的位置和大小。同理如果要查看PE文件导入了哪些DLL文件的哪些API函数那就必须首先从第个IMAGEDATADIRECTORY结构得到输入表的位置和大小。相对虚拟地址RVARVA是相对虚拟地址(RelativeVirtualAddress)的缩写顾名思义它是一个“相对”地址PE文件的各种数据结构中涉及到地址的字段大部分都是以RVA表示的。准确地说RVA就是当PE文件被装载到内存中后某个数据的位置相对于MZ头的偏移量。文件映射到内存:在可执行程序运行之前PE加载器将把PE文件加载到进程空间的内存中去并且初始化每个区段(比如对齐、赋上属性等)。那么加载到内存中的哪个地址去呢?这将由IMAGEOPTIONALHEADER结构中的ImageBase的值指出加载的起始地址(又叫基址)。这个值通常是h那么PE文件的首地址“”就被映射到内存地址“”处而相对于文件开头偏移个字节的地址为“”被映射到内存后的偏移也应该是个字节映射后的地址应该为“”。如果某个区段中的某个数据载入内存后被装入到了xxxxh处那么这个数据的RVA就是(xxxxhh)=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=ImageBase=(基地址)某代码所在区段的RVA:VOffset=区段的物理地址ROffset=则:代码的物理偏移地址=代码的虚拟RVA本区段的RVA本区段的物理偏移=(代码的虚拟VAImageBase)VOffsetROffset=()=代码的虚拟RVA=代码的物理偏移本区段的物理偏移本区段的RVA。对齐值文件对齐值分在内存中的对齐值和在文件中的对齐值。PE文件中的节无论是在磁盘中或内存映像中都是按照一定单位进行对齐的。那么对齐的具体情况是什么样的呢假如在内存中的对齐单位是h即Kb装入内存时的基址是h那么Windows将PE文件装入内存时先把内存中的空间从地址h开始每KB作为一页然后把PE文件的DOS头、dos加载模块、PE头、区段表按顺序逐个字节的装载到从h开始的第页载入之后用把第页填满接着把各个区段装载到各区段所要求的RVA处(RVA一般也都是该文件对齐单位h的倍数)本页未填满的都用填充。在文件中的对齐也是类似的比如DOS头、dos加载模块、PE头、区段表共占用了H字节文件中的对齐值是H所以第一个区段只能从H开始。第六节SectionTable(区段表)区段表其实就是紧挨着PEheader的一个IMAGESECTIONHEADER结构数组IMAGESECTIONHEADER结构的长度固定长个字节。该数组成员的数目由fileheader(IMAGEFILEHEADER)结构中NumberOfSections域的域值来决定。整个SectionTable的长度不固定等于NumberOfSections*sizeof(IMAGESECTIONHEADER)即NumberOfSections*字节后面还要紧跟一个全结构表示区段表的结束(当此PE文件进行了输入表绑定之后在区段表之后没有全结构来表示区段表的结束此时只能依靠文件头中NumberOfSections来判断区段表是否结束)。区段表结构后面就是各个区段。IMAGESECTIONHEADER结构定义如下:typedefstructIMAGESECTIONHEADER{BYTENameIMAGESIZEOFSHORTNAME区段名称不超过个字节如"text"可随便改union{DWORDPhysicalAddress物理地址基本不用这个DWORDVirtualSize区段在内存中实际使用的大小就是区块数据在内存中没有对齐处理前的大小免杀时可以填}MiscDWORDVirtualAddress本区段在内存中的RVADWORDSizeOfRawData本区段在文件中经对齐后的物理大小DWORDPointerToRawData本区段在文件中的偏移量DWORDPointerToRelocations重定位的偏移在OBJ文件中使用免杀时可随便改DWORDPointerToLinenumbers行号表的偏移(用于调试)免杀时可随便改WORDNumberOfRelocations重定位项数目在OBJ文件中使用免杀时可随便改WORDNumberOfLinenumbers符号表中行号的数目免杀时可随便改DWORDCharacteristics区段属性可参考LordPE可以更改但不能乱改}IMAGESECTIONHEADER,*PIMAGESECTIONHEADER结构中某些字段是供COFF格式的OBJ文件使用的对可执行文件来说不代表任何意义在分析的时候可以不予理会几个比较重要的成员含义见表表成员含义Name区段名。事实上本域的名称是"name"只是"name"已被MASM用作关键字所以我们只能用"Name"代替。这里的区段名长度不超过字节不足字节用补齐。区段名仅仅是个标记而已我们选择任何名字甚至空着也行注意这里不用结束。命名不是一个ASCIIZ字符串所以不用结尾。另外要注意不能有同名的区段。VirtualAddress本区段被装载到内存中的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值因此如果域值是h而PE文件装在地址h处那么本节就被载到h。SizeOfRawData经过文件对齐处理后节尺寸PE装载器提取本域值了解需映射入内存的节字节数。(译者注:假设一个文件的文件对齐尺寸是x如果前面的VirtualSize域指示本节长度是x字节则本域值为x表示本节是x字节长)。PointerToRawData这是本区段在文件中的偏移量PE装载器通过本域值找到节数据在文件中的位置。Characteristics包含标记以指示节属性比如节是否含有可执行代码、初始化数据、未初始数据是否可写、可读等。现在我们已知晓IMAGESECTIONHEADER结构再来模拟一下PE装载器的工作吧:)​ 读取IMAGEFILEHEADER的NumberOfSections域知道文件的区段的数目。)​ SizeOfHeaders域值作为区段表的文件偏移量并以此定位区段表。)​ 遍历整个结构数组检查各成员值。)​ 对于每个结构我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于区段起始的虚拟地址。然后就准备把节映射进内存并根据Characteristics域值设置属性。)​ 遍历整个数组直至所有区段都已处理完毕。注意我们并没有使用区段名:这其实并不重要。遍历区段表的步骤:)​ PE文件有效性校验。)​ 定位到PEheader的起始地址。)​ 从fileheader的NumberOfSections域获取区段数。)​ 通过两种方法定位区段表:ImageBaseSizeOfHeaders或者PEheade的起始地址PEheader结构大小。(区段表紧随PEheader)。如果不是使用文件映射的方法可以用SetFilePointer直接将文件指针定位到区段表。区段表的文件偏移量存放在SizeOfHeaders域里。(SizeOfHeaders是IMAGEOPTIONALHEADER的结构成员))​ 处理每个IMAGESECTIONHEADER结构。试画映象文件的全图如下:每个数据目录都会指向其中的一个节(几个数据目录有可能指向同一个节而且也可能有的节没有数据目录指向它们)。第七节ImportTable(输入表、引入表或者导入表)输入表结构首先你得了解什么是引入函数。一个引入函数是被某模块调用的但又不在调用者模块中的函数因而命名为"import(引入)"。引入函数实际位于一个或者更多的DLL里。调用者模块里只保留一些函数信息包括函数名及其驻留的DLL名等等。但是对于这些硬盘上静态的PE文件来说在自己被映射到内存之前无法得知这些导入函数会在哪个地方出现。只有当文件被装入内存后Windows才会将相应的DLL文件装入并将导入函数与DLL中的实际函数地址联系起来这便是“动态链接”的概念也就是为什么DLL文件会被称为“动态链接库文件”的原因。说了这么一大堆归根结底就是要大家明白动态链接就是由“导入表”来完成的输入表中保存的正是函数名和其驻留的DLL名等动态链接所必需的信息。反汇编的时候我们可以看到那种通过调用输入函数的指令:CALL内存地址调用输入函数…………内存地址上JMPDWORDPTR内存地址XXXXXXXX可以看到对输入函数的调用就是对某地址的调用而调用的这个地址显然是位于程序自身模块而不是在DLL模块中的实际上这是由编译器在程序所有代码的后面自动加上的JMPDWORDPTRXXXXXXXX类型的指令这个指令是一个间接寻址的跳转指令XXXXXXXX地址中存放的才是真正的输入函数的地址。而XXXXXXXX地址所在的位置就是IAT表PE装载器在将文件装载入内存的时候会根据输入表的内容把输入函数的地址填写到它里面。那么PE装载器是如何得到输入表的地址呢?它并不是通过各个区段名就可以得到各个结构的偏移因为区段是通过各个属性来定义的。什么意思?就是比如我们的某些数据拥有可读可写等属性则归纳为一个区段。一个区段中的数据仅仅是属性相同并不代表他们的用途相同,比如输入表和输出表等有可能和只读常量放在一个区段中但是它们的用途是不相同的。所以区段名只是一个简单的定义、标识而已。现在我们怎样才能找到输入表呢到optionalheader(可选头)结构中最后一个成员datadirectory(数据目录)寻求吧。由表我们知道数据目录中第项就是关于输入表的索引号为。下面就是如何找寻PE文件中重要数据结构的一般方法:)​ 从DOSheader定位到PEheader。)​ 从optionalheader读取datadirectory的地址。)​ IMAGEDATADIRECTORY结构尺寸乘上找寻结构的索引号:比如你要找寻importsymbols的位置信息必须用IMAGEDATADIRECTORY结构尺寸(bytes)乘上(importsymbols在datadirectory中的索引号)。)​ 将上面的结果加上datadirectory地址我们就得到包含所查询数据结构信息的IMAGEDATADIRECTORY结构项。现在我们开始真正讨论输入表了。输入函数的引入见图和图。图引入简图引入详图见下页图datadirectory数组第二项成员的VirtualAddress包含输入表地址。VirtualAddress镜像基址=内存地址由内存地址我们就可以算出输入表的文件物理偏移地址。输入表实际上是一个IMAGEIMPORTDESCRIPTOR结构数组。每个结构包含PE文件引入函数的一个相关DLL的信息。这个结构长度为个字节所以这个数组每个成员长度为字节。成员的个数是该文件需要的动态链接库dll文件的个数加上(因为该数组以一个全的成员结尾)。这样输入表的长度=*(动态链接库dll文件的个数)。比如如果该PE文件从个不同的DLL中引入函数那么这个数组就有个成员输入表的长度就是字节。下面详细研究其成员的结构组成:typedefstructIMAGEIMPORTDESCRIPTOR{union{DWORDCharacteristicsDWORDOriginalFirstThunk指向IMAGETHUNKDATA结构数组的一个RVA这个结构数组的每一个成员都包含一个导入函数的信息}DUMMYUNIONNAMEDWORDTimeDateStamp当该字段为FFFFFFFF时表示在文件被装入内存之前IAT表中存放的就是输入函数的真实虚拟地址当该字段为时在文件被装入内存之前IAT表中存放的内容跟HNT表相同输入函数的真实虚拟地址要等PE文件被装入内存之后才被系统填入IAT表中DWORDForwarderChain第一个被转向的API的索引DWORDName一个指向一串以结尾的的ASCII字符的RVA,该串字符是该dll文件的名称DWORDFirstThunk指向输入地址表(IAT)IAT是一个IMAGETHUNKDATA结构数组PE装入之后IAT表中是调用的输入函数在内存中的虚拟地址}IMAGEIMPORTDESCRIPTOR,*PIMAGEIMPORTDESCRIPTOR这个结构共有个字段。各字段含义如下:OriginalFirstThunk:(在WINNTH中Characteristics这个叫法已经不对了)这里实际上保存着一个RVA这个RVA指向一个IMAGETHUNKDATA结构数组这个数组可以叫做输入查询表(即HNT表)每个数组元素或者叫一个表项保存着一个指向函数名的RVA或者保存着一个函数的序号。TimeDateStamp:当可执行文件不与被输入的dll进行绑定时此字段为否则为ffffffffForwarderChain:(待查)Name:一个RVA这个RVA指向一个以ASCII空字符结束的字符串这个字符串就是本结构对应的dll文件的名字。FirstThunk:一个RVA,这个RVA指向一个DWORD数组这个数组可以叫输入地址表(IAT)。如果绑定了的话(即TimeDateStamp的值为FFFFFFFF)这个数组的每个元素就是一个输入函数的入口地址如果没有绑定(即TimeDateStamp的值为)这个数组的值与OriginalFir

精彩专题

职业精品

上传我的资料

热门资料

资料评价:

/ 49
所需积分:2 立即下载

意见
反馈

返回
顶部

Q