pe文件格式:PE文件格式(2)

  下面个成员是'PhysicalAddress'和'VirtualSize'32位联合体.在目标文件该地址是内容被重定位地址在可执行文件内是内容尺寸实际上该域好像没有被使用链接器填入尺寸有链接器填入地址链接器填入0

  下个成员是'VirtualAddress',32位保存当节数据加载入内存时RVA

  然后是32位'SizeOfRawData',是 4舍 5入到下个FileAlignment'倍数大小

  下个是'PoerToRawData',32位它是从文件起始到节数据偏移量如果是0,节数据不包含在文件内在加载时被确定

  然后是'PoerToRelocations'32位和'PoerToLinenumbers'32位'NumberOfRelocations'16位'NumberOfLinenumbers'16位.所有这些信息仅仅用于目标文件可执行文件有个特殊基准重定位目录如果存在行号信息般包含在特殊目调试段或其他

  最后个是32位'Characteristics',它是组标志描述如何处理节内存

  bit5(IMAGE_SCN_CNT_CODE)置1节内包含可执行代码  

  bit6(IMAGE_SCN_CNT_INITIALIZED_DATA)置1节内包含数据在执行前是确定  

  bit7(IMAGE_SCN_CNT_UNINITIALIZED_DATA)置1本节包含未数据执行前即将被化为0般是BSS.

  bit9(IMAGE_SCN_LNK_INFO)置1节内不包含映象数据除了注释描述或者其他文档外个目标文件部分可能是针对链接器信息比如哪个库被需要

  bit11(IMAGE_SCN_LNK_REMOVE)置1在可执行文件链接后作为文件部分数据被清除

  bit12(IMAGE_SCN_LNK_COMDAT)置1节包含公共块数据是某个顺序打包

  bit15(IMAGE_SCN_MEM_FARDATA)置1不确定

  bit17(IMAGE_SCN_MEM_PURGEABLE)置1数据是可清除

  bit18(IMAGE_SCN_MEM_LOCKED)置1节不可以在内存内移动

  bit19(IMAGE_SCN_MEM_PRELOAD)置1节必须在执行开始前调入

  Bits20to23指定对齐般是库文件对象对齐

  bit24(IMAGE_SCN_LNK_NRELOC_OVFL)置1节包含扩展重定位

  bit25(IMAGE_SCN_MEM_DISCARDABLE)置1进程开始后节数据不再需要

  bit26(IMAGE_SCN_MEM_NOT_CACHED)置1数据不得缓存Cache

  bit27(IMAGE_SCN_MEM_NOT_PAGED)置1数据不得交换出去

  bit28(IMAGE_SCN_MEM_SHARED)置1数据在所有映象例程内共享如DLL化数据

  bit29(IMAGE_SCN_MEM_EXECUTE)置1进程得到“执行”访问节内存

  bit30(IMAGE_SCN_MEM_READ)置1进程得到“读出”访问节内存

  bit31(IMAGE_SCN_MEM_WRITE)置1进程得到“写入”访问节内存

  在节头后面我们看到节自身他们在文件内按照'FileAlignment'字节数对齐即在可选头和每个节后面将添加0节按照他们RVAs排序.当加载到RAM,节对齐按照SectionAlignment

  例如:个可选头在偏移量981处结束文件对齐为512个节起始于1024你可以借助'PoerToRawData'或者'VirtualAddress'找到节不必用对齐找节(节头哪里去了?此段有问题!!)

  +-------------------+
  |DOS-stub     |
  +-------------------+
  |file-header   |
  +-------------------+
  |optionalheader |
  |----------|
  |         |----------------+
  |datadirectories |        |
  |         |        |
  |(RVAstodirec-  |-------------+ |
  |toriesinsections)|      | |
  |         |---------+ | |目录在节内相对地址
  |         |    | | |
  +-------------------+    | | |
  |         |-----+ | | |
  |sectionheaders |  | | | |
  |(RVAstosection |--+ | | | |节边界相对地址
  | borders)    | | | | | |
  +-------------------+<-+ | | | |
  |         |  |<-+ | |
  |sectiondata1  |  |   | |节数据
  |         |  |<-----+ |
  +-------------------+<----+     |
  |         |        |
  |sectiondata2  |        |
  |         |<--------------+
  +-------------------+
  每个节都有个节头每个数据目录将指向个节(几个数据目录可能指向同个节)节可能没有数据目录指向它

  节原始数据Sections'rawdata

  ------------------

  概要-------

  所有节调入内存后按照'SectionAlignment'对齐FileAlignment'是节在文件内对齐字节数节由节头内项目来描述可以通过'PoerToRawData'在文件内找到节在内存内通过'VirtualAddress'找到节长度是'SizeOfRawData'.

  根据他们包含内容有几种节般至少有个数据目录指向内容保存在个节内

  代码节codesection

  ------------

  本节至少含有个标志位'IMAGE_SCN_CNT_CODE','IMAGE_SCN_MEM_EXECUTE'and

  'IMAGE_SCN_MEM_READ'集合并且“可选头”成员'AddressOfEntryPo'指向这个节内某个地方“可选头”成员'BaseOfCode'将指向这个节开始如果把非代码放在代码的前也有可能指向后面某个地方般除了可执行代码没有其他东西般只有个代码节名字如:".text",".code","AUTO"

  数据节datasection

  ------------

  本节包含化过静态变量例如"i=5;".他含有这些位'IMAGE_SCN_CNT_INITIALIZED_DATA','IMAGE_SCN_MEM_READ'及'IMAGE_SCN_MEM_WRITE'等.有链接器把常量数据放在没有可写标志位节内如果部分数据是共享或者有其他特性节将包含更多特征位集般在'BaseOfData'到'BaseOfData'+'SizeOfInitializedData'范围内.典型名字如:'.data','.idata','DATA'

  bsssection

  -----------

  还有未数据例如"k;"该节'PoerToRawData'为0表明其内容不在文件内特征位'IMAGE_SCN_CNT_UNINITIALIZED_DATA'指明所有内容必须在加载时间置0这意味着有节头但没有节在文件内该节被加载器创建并且包含全0字节其长度是'SizeOfUninitializedData'.典型名字如'.bss','BSS'

  有节数据没有被数据目录指向其内容和结果被编译器支持不是被链接器支持堆栈段和堆段不是可执行文件但是被加载器创建其大小为optionalheader内stacksize和heapsize

  .

  版权copyright

  ---------

  开始于个简单目录'IMAGE_DIRECTORY_ENTRY_COPYRIGHT'.其内容是个版权或者个非0结尾象"Gonkulatorcontrolapplication,copyright(c)1848Hugendubel&Cie".

  该串被使用命令行或者描述文件提供给链接器该串不是必须可以被舍弃他不是可写实际上不必访问他链接器将找出是否有个可以舍弃不可写创建个名字为'.descr'然后把串填入该段让版权目录指针指向他该节特征字'IMAGE_SCN_CNT_INITIALIZED_DATA'必须置1

  输出符号exportedsymbols

  ----------------

  下面个简单事情是输出目录'IMAGE_DIRECTORY_ENTRY_EXPORT'.该目录是DLL内典型目录他包括输出入口点输出对象地址等该节必须是化数据和可读不可以是可废弃进程可能在运行时间"GetProcAddress"找出入口该节般称为'.edata'.它般被并入其他节

IMAGE_EXPORT_DIRECTORYSTRUCT
 Characteristics     DWORD   ?
 TimeDateStamp      DWORD   ?
 MajorVersion       WORD   ?
 MinorVersion       WORD   ?
 nName          DWORD   ?
 nBase          DWORD   ?
 NumberOfFunctions    DWORD   ?
 NumberOfNames      DWORD   ?
 AddressOfFunctions    DWORD   ?
 AddressOfNames      DWORD   ?
 AddressOfNameOrdinals  DWORD   ?
IMAGE_EXPORT_DIRECTORYENDS
  输出表结构('IMAGE_EXPORT_DIRECTORY')包含个头和输出数据即符号名序号及到入口点偏移

  首先个32位'Characteristics'般为0然后是32位'TimeDateStamp',输出表创建时间并非总是有效有些链接器置0然后是2个16位版本信息MajorVersion和MinorVersion般置0.

  下面是32位'Name'这是个指向以0结尾RVA名字是必须防止DLL文件改名

  然后是32位'Base'.下个是32位输出项目总数'NumberOfFunctions'.除了序数外也可能以名字输出下面个是32位输出名字总数'NumberOfNames'.在多数情况每个输出项有个确切名字将以名字使用它个项目可能有多个名字或者没有名字这种情况只能够以序号来访问不赞成只以序号输出带来维护问题

  下面32位是AddressOfFunctions是输出项目列表RVA它指向具有'NumberOfFunctions'个数元素每个成员是输出或变量RVA该列表有两个怪事输出RVA可能是0他没有被使用第 2如果RVA指向包含输出目录这是个转发输出转发输出是个指向另个文件输出指针这样就使用另个文件内被指向输出输出序号是AddressOfFunctions索引+上面提到BASE值多数情况下'Base'=1,意思是第个输出序号为1第 2个是2

  下面是指向其成员为指向符号名32位RVA'AddressOfNames',和个指向16位序数32位RVAAddressOfNameOrdinals'.两个都含有'NumberOfNames'个元素.符号名可能全部丢失所以'AddressOfNames'是0.否则被指向并行运行'AddressOfNames'包含指向0结尾输出名RVAs名字都按照字母顺序保存便于高效检索名字根据PE规范标准AddressOfNameOrdinals含有相应名字序数但实际上含有AddressOfFunctions索引序数=BASE+INDEX

  上述3表示意图:

  AddressOfFunctions 地址指向个地址
     |
     |
     v
  exportedRVAwithordinal'Base' //带有序号输出RVA
  exportedRVAwithordinal'Base'+1
  ...
  exportedRVAwithordinal'Base'+'NumberOfFunctions'-1
  AddressOfNames         AddressOfNameOrdinals
     |               |
     |               |
     v               v
  RVAtofirstname     <->Indexofexportforfirstname
  RVAtosecondname     <->Indexofexportforsecondname
  ...              ...
  RVAtoname'NumberOfNames'<->Indexofexportforname'NumberOfNames'
  名字地址指向名字RVA        “名字序数地址”指向名字序数

  要按照序数找出输出符号:减去'Base'得到索引跟随AddressOfFunctionsRVA找到输出用索引定位输出符号RVA所在元素如果没有指向输出节你就完成任务了否则如果指向个描述输出DLL名字和序数你必须查找被转发输出项

  要按照名字找出输出符号沿着AddressOfNames'RVA找到指向包含输出名字RVAs其中每个RVA指向个名字搜索名字使用名字索引在AddressOfNameOrdinals得到和相应名字对应16位数(按照PE规范标准此数值为输出序号需要减去BASE得到索引)根据经验这就是索引不是序号不必减去BASE直接使用该索引在'AddressOfFunctions'找出输出RVA或者是个指向转发串

  输入表importedsymbols

  ----------------

  当编译器发现在其他可执行文件(般是DLL)内它将不知道这个情况简单输出符号指令其地址由链接器添上链接器使用输入库查询哪个DLL哪个符号被引入为所有引入符号产生STUB每个由个跳转指令构成STUB是真正目标这些跳转指令真正跳到个从地址表内取出地址在复杂应用中当"__declspec(dllimport)"被使用时候编译器知道被引入并输出个对输入表内地址无论如何DLL内地址总是必须来自输出DLL输出目录地址被提供给加载器加载器知道哪个符号需要查找通过搜索输入目录修正地址下面是个例子:

  个带有/不带有声明__declspec(dllimport)如下:

  源:

    symbol(char*);
    __declspec(dllimport)symbol2(char*);
    voidfoo(void)
    {
      i=symbol("bar");
      j=symbol2("baz");
    }
  汇编:

    ...
    call_symbol        ;无declspec(dllimport)
    ...
    call[__imp__symbol2]    ;有declspec(dllimport)
    ...
  第种情况没有声明编译器不知道'_symbol'在个DLL于是链接器必须提供此不在那里它将提供个STUB对应输入符号作为个间接跳转所有输入STUB集合称作转换区或跳板跳到那里为是跳到另个地方该跳板典型位于代码区不是输入目录部分每个STUB是个跳转到DLL实际JUMP跳板区看起来如下:

  _symbol:    jmp [__imp__symbol]
  _other_symbol: jmp [__imp__other__symbol]
  ...
  这就是意味着如果你使用不带声明输入符号链接器就产生包含间接跳转跳板区如果指定了声明"__declspec(dllimport)",编译器就自己做这个工作跳板区是不必要它也意味着如果你引入变量或其他东西你必须指定"__declspec(dllimport)",带有JMPSTUB只适合于不适合于变量等

  在任何情况下符号地址存在于个地址'__imp_x'.所有这些地址组成所谓输入地址表通过DLL引入库提供给链接器输入地址表如下:

 __imp__symbol: 0xBEFFA100
 __imp__symbol2: 0x40100
 __imp__symbol3: 0x300100
 ...
  输入地址表是数据目录部分被IMAGE_DIRECTORY_ENTRY_IAT目录指针指向些链接器不设置这个目录项照样工作显然加载器可以不使用目录IMAGE_DIRECTORY_ENTRY_IAT就可以解决问题链接器不知道这个表内地址链接器插入哑元(即RVAs)在加载时加载器用输出DLL输出目录修补这些哑元注意这是C规范标准有其他编译环境不需要输入库他们需要产生输入地址表C编译器倾向于使用输入库方便于它们链接器

  我们要看个输入目录如何建立于是加载器才可以使用

  输入目录必须驻留在个节该节是和可读输入目录是个IMAGE_IMPORT_DESCRIPTORs每个DLL都使用个元素该列表中止于个全0元素

  个IMAGE_IMPORT_DESCRIPTOR是个具有如下元素结构:

IMAGE_IMPORT_DESCRIPTORSTRUCT
  union
    Characteristicsdd   ?
    OriginalFirstThunkdd ?
 ends
  TimeDateStampdd  ?
  ForwarderChaindd ?
  Name1dd      ?
  FirstThunkdd   ?
IMAGE_IMPORT_DESCRIPTORENDS
  OriginalFirstThunk32位RVA指向个以0结尾IMAGE_THUNK_DATAs每个成员描述个输入

  TimeDateStamp 32位时间戳

  ForwarderChain输入个转发链32位索引

  Name指向DLL名字32位RVA    

  FirstThunk指向IMAGE_THUNK_DATAs32位RVA它是输入地址表部分将发生变化Thunk意思就是查找虚实变换意思

IMAGE_THUNK_DATA32STRUCT
  unionu1
    ForwarderStringdd ?
    Functiondd    ?
    Ordinaldd     ?
    AddressOfDatadd  ?
  ends
IMAGE_THUNK_DATA32ENDS
以下是VC98WINNT.h内定义:
typedefstruct_IMAGE_THUNK_DATA64{
  union{
    PBYTE ForwarderString;
    PDWORDFunction;
    ULONGLONGOrdinal;
    PIMAGE_IMPORT_BY_NAME AddressOfData;
  }u1;
}IMAGE_THUNK_DATA64;
typedefIMAGE_THUNK_DATA64*PIMAGE_THUNK_DATA64;
  于是每个IMAGE_IMPORT_DESCRIPTOR成员给出输出DLL名字2个RVAs指向IMAGE_THUNK_DATAs,最后个成员全部填充0表示结束每个IMAGE_THUNK_DATA成员有个指向IMAGE_IMPORT_BY_NAMERVA用以描述输入有趣是这些并行运行他们指向相同

  IMAGE_IMPORT_BY_NAMEs(按名输入).

  这是IMAGE_IMPORT_DESCRIPTOR必须内容:

  OriginalFirstThunk   FirstThunk
      |          |
      |          |
      |          |
      V          V
      0-->  func1  <--0
      1-->  func2  <--1
      2-->  func3  <--2
      3-->  foo   <--3
      4-->  mumpitz <--4
      5-->  knuff  <--5
      6-->0      0<--6   /*最后RVA是0*/
IMAGE_IMPORT_BY_NAMESTRUCT
  Hdw  ?
  Name1db  ?
IMAGE_IMPORT_BY_NAMEENDS
  中间名字是要讨论IMAGE_IMPORT_BY_NAMEs.每个包括个16位h个没有确定字节数nameh个输出DLL名字表(见前面输出目录)索引索引处名字被测试如果不匹配则用 2进制搜索来找到名字链接器只是简单置1或者其他任意数字只是导致第次搜索企图失败然后强迫使用 2进制搜索名字

  如果想从DLL"knurr"查找输入"foo"信息,首先数据目录内找到IMAGE_DIRECTORY_ENTRY_IMPORT项得到RVA,在rawsectiondata找到那个地址现在我们有了IMAGE_IMPORT_DESCRIPTORs.通过检查由NAME指向得到和DLL"knurr"有关成员当你发现了正确IMAGE_IMPORT_DESCRIPTOR,沿着它OriginalFirstThunk得到个指针指向个IMAGE_THUNK_DATAs;检查每个RVAs找到"foo".

  为什么有两个指针列表指向IMAGE_IMPORT_BY_NAMEs?运行时不需要输入需要是地址这里是地址表再次出现地方加载器在每个涉及DLL输出目录内查找输入符号使用DLL入口点线性地址替换掉FirstThunk列表内IMAGE_THUNK_DATA元素记住带有标号"__imp__symbol地址列表、被数据目录项

  IMAGE_DIRECTORY_ENTRY_IAT指向输入地址表也确切地被'FirstThunk'指向.(假如输入来自几个区别DLL输入地址表由所有DLL'FirstThunk'构成目录项IMAGE_DIRECTORY_ENTRY_IAT可能丢失输入依然工作很好'OriginalFirstThunk'保持不变可以通过它查找输入名原始列表

  现在输入已经用正确线性地址打了补丁如下所示:

  OriginalFirstThunk   FirstThunk
      |          |
      |          |
      |          |
      V          V
      0-->  func1    0--> exportedfunc1
      1-->  func2    1--> exportedfunc2
      2-->  func3    2--> exportedfunc3
      3-->  foo     3--> exportedfoo
      4-->  mumpitz   4--> exportedmumpitz
      5-->  knuff    5--> exportedknuff
      6-->0      0<--6
  这是最简单情况基本结构下面学习输入目录

Tags:  pe文件 pe格式详解 pe格式 pe文件格式

延伸阅读

最新评论

发表评论