zoukankan      html  css  js  c++  java
  • Windows Pe 第三章 PE头文件(下)

    3.5  数据结构字段详解

    3.5.1  PEIMAGE_NT_HEADER的字段

        1.IMAGE_NT_HEADER.Signature

        +0000h,双字。PE文件标识,被定义为00004550h。也就是“P”“E”加上两个0,这也是PE这个称呼的由来。如果更改其中任何一个字节,操作系统就无法把该文件识别为正确的PE文件。通过修改这个字段,会导致PE文件在32位系统中加载失败,但由于这个文件的其他部分(特别是DOS头)还没有被破坏,系统还是可以识别出其为DOS系统下的可执行程序,并通过调用纯DOS环境来运行DOS Stub中的程序代码。

    如果你确认操纵系统中的某个PE文件携带病毒,并且开机后会被加载进内存运行,最简单的处理办法是通过WindowsPE盘启动系统。在系统中找到病毒文件,使用记事本简单地修改其中任何一个字符,保存文件,重新开机后即可防止病毒文件被加载。

        2.IMAGE_NT_HEADER.FileHeader

        +0004h,结构。该结构指向IMAGE_FILE_HEADER,由于PE扩展自通用COFF规范,所以,该字段在官方文档中被称为标准COFF头。

        3.IMAGE_NT_HEADER.OptionalHeader

        +0018h,结构。该结构指向IMAGE_OPTIONAL_HEADER32Windows操作系统可执行文件的大部分特性均在这个结构里呈现。因为符合COLL规范的”.obj”目标文件中该部分并不存在,所以该部分被称为OptionalHeader(可选的头部信息,简称 可选头),它是操作系统映象文件所独有的头部信息。

    可选头又分为两部分,前10个字段原属于COFF,用来加载和运行一个可执行文件;后21个字段则是通过连接器加载的。他们作为PE扩展部分,用于描述可执行文件的一些信息,供PE加载器加载使用。

    3.5.2  标准PEIMAGE_FILE_HEADER的字段

        4.IMAGE_FILE_HEADER.Machine

        +0004h,单字。用来指定PE文件运行的平台。由于Windows最初设计为可以运行在IntelSunDecIBM等多种硬件平台上,或者能模拟这些平台的软件环境中,而不同的硬件平台其指令的机器码不相同,因此为不同平台编译的EXE文件是无法通用的。假设将运行在Intel 386机器上的PE文件该字段设置为01f0h,既指定平台为IBM POWER PC(小尾方式),则系统会有下图所示提示:

     


        5.IMAGE_FILE_HEADER.NumberOfSections

        +0006h,单字。文件中存在的节的总数(XP中可以有0个节),数值不能小于1,也不能超过96。如果将该值设置为0,则系统装载时会提示不是有效的Win32程序。如果想在PE中增加或删除节,必须改变此处的值。

    另外,这个值既不能比实际内存中多,也不能少,否则装载时会发生错误。

        6.IMAGE_FILE_HEADER.TimeDateStamp

        +0008h,双字。编译器创建此文件时的时间戳。低32位存放的值是自19701100:00时开始到创建时间为止的总秒数。

        该数值可以所以修改而不会影响程序的运行。所以,有的连接器在这里填入固定的值,有的则随意写入任何值,这对用户创建的文件并没有实际的意义。另外,这个时间与操作系统文件属性里看到的三个时间(创建时间、修改时间、访问时间)也没有任何联系。

        7.IMAGE_FILE_HEADER.PointerToSymbolTable

        +0010h,双字。COFF符号表的文件偏移。如果不存在COFF符号表,此值为0。对于映象文件来说,此值为0,因为微软已经不赞成在PE中使用COFF调试信息了。

        8.IMAGE_FILE_HEADER.NumberOfSymbols

    +0010h,双字。符号表中元素数目。对于映象文件来说,此值为0,主要用于调试。

        9.IMAGE_FILE_HEADER.SizeOfOptionalHeader

        +0014h,单字。指定结构IMAGE_OPTIONAL_HEADER32的长度,默认情况下这个值等于00e0h;如果是64PE文件,该结构的默认大小为00F0h

        用户可以自己定义这个值的大小,不过需要注意两点:

        1)改完之后,需要自行将文件中IMAGE_OPTIONAL_HEADER32的大小扩充为你指定的值(一般以0补足)。

        2)扩充完以后,要维持文件中的对齐特性(比如在HelloWorld.exe中,此处增加了8个自节后,一定在后面相应地删除8个字节,以保证.txt节起始位置处于0400h)。

        10.IMAGE_FILE_HEADER.Characteristics

        +0016h,单字。文件属性标识字段,它的不同数据位蒂尼了不同文件属性,具体内容见表3-3。这是一个很重要的字段,不同的定义将影响系统对文件的装入方式。比如,当位131时,表示这是一个DLL文件,那么系统将使用调用DLL入口函数的方式执行文件入口函数;当13位为0时,表示这是一个普通的可执行文件,系统将直接跳到入口处执行。对于普通的可执行PE文件来说,这个字段一般是010fh(我刚刚看的我电脑上是0103),而对于DLL文件来说,这个字段值一般是210eh。(我电脑上是2102

        如表3-3所示,当第0位为1时,表明此文件不包含基址重定位信息,因此必须将其加载到文件头指定的机智地址字段位置。如果进程空间此处的位置被占用,加载器就会报错。在程序运行前如果发现文件中存在可重定位信息,连接器会执行移除可执行文件的重定位信息操作。

     

     当第1位为1时,表明此映象文件是合法的,可以运行。如果未设置此标志,表明出现了连接器错误。

    当第7位为1时,表示文件小尾方式,既内存中,最低有效位LSB位于最高有效位MSB的前面,与第15位的大尾方式一样,都不赞成使用该标志,最好将其设置为0

    当第10位为1时,如果此映象文件可在移动存储介质上,那么加载器将完全加载它并把它复制到内存交换文件中。

    当第11位为1时,如果此映象文件在网络上,那么加载器也将完全加载并把它复制到内存交换文件中。

    当第13位为1时,表明此映象文件是动态链接库(DLL.这样的文件总被认为是可执行文件,尽管它们不能直接运行。

    可执行文件的标识设置为010fh,既第0 1 2 3 8违背设置为1,表示该文件为可执行文件,不包含重定位信息,不包含符号和行号信息,文件只在32位平台上运行。

    3.5.3  扩展PEIMAGE_OPTIONAL_HEADER32字段

        11.IMAGE_OPTIONAL_HEADER32.Magic

        +0018h,单字。魔术字,说明文件类型,如果为010BH,则表示该文件为PE32;如果为0107H,则表示文件为ROM影像;如果为020BH,则表示该文件为PE32+,既64位下的PE文件。

        12.IMAGE_OPTIONAL_HEADER32.MajorLinkerVersion

        13.IMAGE_OPTIONAL_HEADER32.MinorLinkerVersion

        +001ah,单字。这两个字段都是字节型,指定连接器版本号,对执行没有任何影响。

        14.IMAGE_OPTIONAL_HEADER32.SizeOfCode

        +001ch,双字。所有代码节的总和(以字节计算),该大小事基于文件对齐后的大小,而非内存对齐后的大小。稍后还会介绍一个字段SIzeOfImage,它是基于内存对齐后的大小。需要注意一点:判断某个节是否包含代码的方法不是根据节的属性中是否有IMAGE_SCN_MEM_EXECUTE标志,而是根据节中是否含有IMAGE_SCN_CNT_CODE标志。

        15.IMAGE_OPTIONAL_HEADER32.SizeOflnitializedData

        +20h,双字。所有包含已经初始化的节的总大小。

        16.IMAGE_OPTIONAL_HEADER32.SizeOfUninitializedData

        +0024h,双字。所有包含未初始化的节的总大小。这些数据被定义在文件中不占用空间;但在被加载到内存以后,PE加在程序应该为这些数据分配适当的虚拟地址空间。

        17.IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint

        +0028h,双字。在Windows中,可执行程序运行在虚拟地址空间中,由于4GB空间对于程序是唯一的,所以这里的虚拟空间可以简单地理解为真实地址(我们暂且忘记物理内存地址概念,这样就不需要理解页面调度机制了)。该字段的值是一个RVA,它记录了启动代码距离该PE加载后其实位置到底有多少个字节。

    如果在一个可执行文件中附加了一段自己的代码,并且想让这段代码首先被执行,一般都要修改这里的值使之指向自己的代码的位置。对于一般程序映象来说,他就是启动地址;对于设备驱动程序来说,他是初始化函数地址。入口点对于DLL来说是可选的,如果不存在入口点,这个字段必须设置为0

    (许多病毒程序、加载程序、补丁程序都会劫持这里的值,是指指向其他拥堵的代码地址。)

        18.IMAGE_OPTIONAL_HEADER32.BaseOfCode

       +002CH,双字。代码节的其实RVA,表示映象被加载进内存时执行代码节的开头相对于映象基址的偏移地址。一般情况下,代码节紧跟在PE头部后面,节的名称通常为”.text”。

        19.IMAGE_OPTIONAL_HEADER32.BaseOfData

        +0030h,双字。数据节的其实RVA,表示映象被加载进内存时数据节的开头相对于映象基地址的偏移地址。一般情况下,数据节位于文件末尾,节的名称通常称为”.data”。

    20.IMAGE_OPTIONAL_HEADER32.imageBase

        +0031h,双字。该字段指出了PE映象的优先装入地址。也就是在IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint中说的程序被加载到内存后的起始VA.那么为什么要设置这个地址呢?因为链接器在产生可执行文件的时候,是对应这个地址来生成机器码的。如果操作系统也是按照这个地址加载机器码到内存中,那么执行中的许多重定位信息就不需要修改了,这样运行速度就会更快一些。

        前面说过,对于EXE文件来说,每个文件使用的都是独立的虚拟地址空间,所以,优先装入的地址通常不会被其他模块占据。也就是说,EXE文件总是能按照这个地址装入,这就意味着装入后的EXE文件不需要进行重定位了,例如,在HelloWorld.exe中就看不到重定位信息。

        在链接的时候,可以使用参数-base”来指定优先装入的地址,如果不指定,那么连接器默认装入EXE地址就是0x00400000。而对于DLL文件来说,它默认优先转入地址则是0x10000000。如果一个进程用到了多个DLL文件,其装入地址可能会发生冲突。PE加载器会调整其中的地址。使所有的DLL文件都能被正确装入、所以,不要错误地认为内存中动态链接库的基地址和其文件头字段IMAGE_OPTIONAL_HEADER32.imagrBase指定的完全一样。

        可以自己定义这个值,但取值有限制:第一,取值不能超出边界,既取的值必须在进程地址空间中;第二,该值必须是64KB的整数倍。

        21.IMAGE_OPTIONAL_HEADER32.SectionAlignment

        +0038h,双字。内存中节的对齐粒度,该资源指定了节被装入内存后的对齐单位。为什么要对齐?不对齐的数据可以节省空间,但没有规律,而且要在运行时需要从磁盘文件调入内存分也是容易导致效率低下。大家应该学习过汇编,知道为什么在16位汇编里取数时要从偶地址开始吗?(取一个字从偶地址开始,只需要一个CPU周期可以去到;而从奇地址取一个字节,则需要两个CPU周期。)其实,对其和它是一个道理,内存中的数据存取以页面为单位。Win32的页面大小事4KB,所以Win32PE中节的内存对齐粒度一般都选择4KB(01000h)

        SectionAlignment必须大于或等于FileAlignment。当它小于系统页面大小时,必须保证SectionAlinmentFileAlignment相等。

       22.IMAGE_OPTIONAL_HEADER32.FileAlignment

        +003ch,双字。文件中节的对齐粒度。文件中的节对齐并不是提高程序本身的执行效率,同样也是为了从磁盘加载的效率。WindowsXP用来阻止硬盘的所有文件系统都是基于簇(分配单元)的,(每个簇包含几个扇区的大小)。扇区是磁盘物理存取的最小单位。簇越大,磁盘存储信息容量就越大,但存取所花的时间也越长。通常情况下,Windows会选择使用512字节的簇大小(一个物理扇区的大小)来格式化分区,最大可以达到4KB。在本书的例子中,文件对齐粒度选择了512字节(200h)。

    查看簇大小:

     

        23.IMAGE_OPTIONAL_HEADER32.MajorOperatingSystemVersion

        24.IMAGE_OPTIONAL_HEADER32.MinorOperationgSystemVersion

        +0040h2324标注的两个字段都位单字,共计为双字。表示操作系统的版本号,分为主版本号和次版本号两部分。

        25.IMAGE_OPTIONAL_HEADER32.MajorlmageVersion

        26.IMAGE_OPTIONAL_HEADER32.MinorlmageVersion

        +0044h,双字。本PE文件影像的版本号。

        27.IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion

        28.IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion

        +0044h,双字。本PE文件影响的版本号。

        27.IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion

        28.IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion

        +0048,双字。运行所需要的子系统的版本号。

        29.IMAGE_OPTIONAL_HEADER32.Win32VersionValue

        +004ch,双字。子系统版本的值,保留,必须0.否则将导致运行失败。

        30.IMAGE_OPTIONAL_HEADER32.SizeOfimage

        +0050h,双字。内存中整个PE文件的映射尺寸。已加载到内充中的HelloWorld.exe为例,HelloWorld.exe中文件头占用了1000h字节,三个节各占用1000h,所以文件在内存中占用的空间总大小为04000h。该值可以比实际的值大,但不能比他小,而且必须保证该值是SectionAlignment的整数倍。

        31.IMAGE_OPTIONAL_HEADER32.SizeOfHeaders

    +0054h,双字。所有头+节表按照文件对齐粒度对齐后的大小,HellpWorld.exe中的值为0400h.PE文件中,该部分数据是严格按照200h对齐的,如果不对齐,系统加载时会提示出错。

        32.IMAGE_OPTIONAL_HEADER32.CheckSum

        +0058h,双字。校验和,在大多数的PE文件中,该值是0,但在一些内核模式的驱动程序和系统DLL中,该值则是必须存在且正确的,比如kernel32.dllPE的校验和是0011E97eh

    我本机上的不是。

     

        Windows系统目录下有一个动态链接库IMAGEHELP.DLL,它是Win32中专门用来操作PE文件的函数库,这里的函数CheckSumMappedFile就是用来计算文件头校验和的,对于整个PE文件也有一个校验和函数MapFileAndCheckSum。该动态间接库中还包含其他一些常用的函数,可以通过小工具PEinfo输出并查看。关于校验和的具体计算方法,可参照3.7接种关于PE文件头编程的部分。

        33.IMAGE_OPTIONAL_HEADER32.Subsystem

        +005ch,单字。指定使用界面的子系统,它的取值如表3-4所示。这个字段决定了系统如何为程序连接初始的界面,连接时使用参数-subsystem:xxx先选指定的就是这个字段的值,如果将子系统指定为Windows命令行交互模式(CUI,那么系统会自动为程序建立一个控制台窗口;如果是GUI,窗口程序代码必须由用户自己建立。


    对于上面,我手动测试了两件事:

    1cui程序我直接给成gui了,就是把

        

    2改成3了,结果发现黑色界面消失了,但是进程还在。

    2)又找到一个界面的程序,C++写的,然后把3改成2了,结果是既弹出了主程序界面,又多弹出了一个cui的黑色界面(开始是想该C#写的一个程序。结果二进制编辑的那个软件一直加载不了那个文件。)

    MASM32link程序的链接开关-subsystem的常见选项如3-5所示。

     

        34.IMAGE_OPTIONAL_HEADER32.DLLCharacteristics

        +005eh,单字。DLL文件属性。它是一个标志集,不是针对DLL文件的,而是针对所有PE文件的。

     


        该字段定义了PE文件装载时的一些特性,第十章会提供一个使用该标志的例子。

        35.IMAGE_OPTIONAL_HEADER32.SizeOfStackReserve

        +0060h,双字。初始化时八六栈的大小。该字段表示为初始化线程的栈而保留的虚拟内存数量,然而并不是留出的所有虚拟内存都可以做栈(真正的栈大小由下一个字段SizeOfStackCommit决定)。该字段的默认值为0x100000(1M)

    ,如果调用API函数CreateThread时,把NULL当做当做传入的参数,那么创建出来的栈大小也是1MB

        36.IMAGE_OPTIONAL_HEADER32.SizeOfStackCommit

        +0064h,双字。初始化时实际提交的栈大小。

        37.IMAGE_OPTIONAL_HEADER32.SizeOfHeapReserve

        +0068h,双字。初始化是保留的堆大小。用来保留给初始进程堆使用的虚拟内存,这个堆的句柄可以通过调用GetProcessHeap函数获得。每一个进程至少会有一个默认的进程堆,该堆在进程启动时被创建,而且在进程的生命期中韵苑不会被删除。默认为1MB

        38.IMAGE_OPTIONAL_HEADER32.SizeOfHeapCommit

        +006ch,双字。初始化实际提交的堆大小,在进程初始化时设定的堆所占用的内存空间。默认值为1页。

        39.IMAGE_OPTIONAL_HEADER32.LoaderFlags

        +0074h,双字。加载标记。

        40.IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSize

        +0074h,双字。定义数据目录结构的数量,一般为00000010h,16个。该值由字段SizeOfOptionalHeaders决定,实际应用中可取2-16的值。

        41.IMAGE_OPTIONAL_HEADER32.DataDirectory

        +0078h,结构。由16IMAGE_DATA_DIRECTORY结构线性排列而成,用于定义PE中的16种不同类别的数据所在的位置和大小。前面已经对这部分做过说明,后面将会对这些数据进行详细描述。一下是对这16种数据的说明:


     

    3.5.4  数据目录项IMAGE_DATA_DIRECTORY的字段

        42.IMAGE_DATA_DIRECTORY.VirtualAddress

        +0000h,双字。这个字段记录了特定类型数据的起始RVA。当然,针对不同的数据结构,该字段包含的数据含义并不一样,有的数据甚至还不是RVA(如属性证书数据中该字段的值表示的是FOA)

        43.IMAGE_DATA_DIRECTORY.isize

        +0004h,双字。该字段记录了特定类型的数据的长度。

    3.5.5 节表项 IMAGE_SECTION_HEADER的字段

        44.IMAGE_SECTION_HEADER.Name1

        +0000h,8字节。该字段一共8个字节,一般情况下是以一个””结尾的ASCII码字符串来标示节的名称,内容可以自行定义。

        该名称并不遵循Ansi字符串必须””结尾的规律,如果不易””结尾,系统依然会认为它是一个字符串,但会根据8个字节长度对其进行截断处理。

        45.IMAGE_SECTION_HEADER.Misc

        +0008h,双字。该字段是一个union型数据,这是节数据在没有对齐前的真是尺寸,不过很多PE文件里该节值并不准确。

        46.IMAGE_SECTION_HEADER.VirtualAddress

        +000ch,双字。节区的RVA地址。

        47.IMAGE_SECTION_HEADER.SizeOfRawData

        +0010h,双字。节在文件中对齐后的尺寸。在HelloWorld.exe中,数据量不大的节,其大小一般为200h

        48.IMAGE_SECTION_HEADER.PointerToRawData

        +0014h,双字。节区其实数据在文件中的偏移。

     

        49.IMAGE_SECTION_HEADER.PointerToRelocations

        +0018h,双字。在.obj文件中使用,指向重定位表的指针。

        50.IMAGE_SECTION_HEADER.PointerToLinenumbers

        +001ch,双字。行号表的位置(供调试用)。

        51.IMAGE_SECTION_HEADER.NumberOfRelocations

        +0020h,单子。重定位表的个数(在OBJ文件中使用)。

        52.IMAGE_SECTION_HEADER.NumberOfLinenumbers

        +0022h,单子。行号表中行号的数量。

        53.IMAGE_SECTION_HEADER.Characteristics

        +0024h,双字。节的属性。这个字段很重要,这是节的属性标志字段,其中不同的数据位代表了不同的属性,具体的定义如表3-7所示,这些数据位的组合表述了内存中一个节所拥有的访问属性。

     

    3.5.6  解析HelloWorld程序的字节码

        这一节是吧exe用二进制工具打开,然后把上面的所有字段都找到然后在图片上标记出来。这个不总结了,比较简单。可以用一个可以看PE结构的程序,去找到每个段的地址,然后去看对应的内容就行了。

    最后还有一个提示:


    3.6PE内存映像

    exe加载到OD里,然后 查看->内存 ,看其内存分配。

     

    文件字节码在内存中的大致分布 PE头文件+代码+输入表+数据


    书中的HelloWorld的文件-内存关系是

     

    我自己编写的Helloworld略有不同,.text段是2000h

     

         由上面可以知道,头+节表 部分的数据没有任何更改,多出部分0填充。这个对于这个节,

    其对齐的方式则是由数据结构中的字段IMAGE_ OPTIONAL_HEADER32.FileAlignmentIMAGE_OPTIONAL_HEADER32.Section Alignment分别定义。

  • 相关阅读:
    进销存管理软件,企业ERP如何上线准备,一张图让你明白
    检测行业ERP信息系统(检测行业管理软件)构建,大家猜猜看花了多少银子开发!
    商会协会会员管理系统
    给广大程序员的一封信!尤其是想换行销售的程序员们
    如何利用极致业务基础平台构建一个通用企业ERP之二十多界面显示
    如何利用极致业务基础平台构建一个通用企业ERP之十九过滤器的功能介绍(2)
    快速开发平台如何让开发周期变成原来的十分之一,有图有真相!告别码农的机会!告别公司亏损时代!
    如何利用极致业务基础平台构建一个通用企业ERP之十八如何调用存储过程介绍
    创建新文件(包括上级文件夹),获取外置SD卡的根目录
    c++匿名函数精简写法
  • 原文地址:https://www.cnblogs.com/csnd/p/12062240.html
Copyright © 2011-2022 走看看