Win32 PE文件格式简要总结

PE文件的内容分为不同的块/节(Section),块中包含代码或数据,各个块按页边界对齐,块没有大小限制,是一个连续结构。
PE文件不是作为一个整体被载入内存的而是分节加载的。PE装载器不会对PE文件头作特殊处理,但装载各个块的时候会根据块的属性做不同的处理:
1. 内存页的属性
对于磁盘来说,所有的页都是按照磁盘映射文件函数指定的属性设置的,但是在装载可执行文件的时候,与块对应的内存页属性要按照块的属性来设置,所以,在同属于一个模块的内存页中,从不同块映射过来的内存页的属性是不同的。
2. 块的偏移地址
块的起始地址在磁盘文件中是按照IMAGE_OPTIONAL_HEADER32结构的 FileAlignment 字段的值进行对齐的,而当被加载道内存中时是按照同一结构中的SectionAlignment字段的值设置对齐的,两者的值可能不同。所以一个块表被装载到内存后相对于文件头的偏移地址和磁盘中的偏移地址可能是不同的。(块表事实上就是相同属性数据的组合,当块表再入到内存中的时候,相同一个快比爱所对应的内存页都将被赋予相同的页属性,事实上Windows 系统对内存属性的设置是以页为单位的进行的,块表在内存的对齐的单位必须是一个页的大小或者是一个页的正整数倍)。在磁盘中没有这个限制,因为磁盘中排放的是以空间为主导,在磁盘知识存放,不是使用,所以不用设置那么详细的属性。
3. 块表的尺寸
主要有2个方面:
对齐:磁盘映像和内存映像中块表对齐存储单位的不同而导致了长度不同;
初始化:某些数据没有初始化,那么没有必要为其在磁盘中浪费空间资源,但是在内存中不同,程序一旦运行,之前没有初始化的数据便有可能要被赋值初始化,那么就必须为他们留下空间。
4. 不进行映射的块表
有些块表并不需要映射到内存中,例如 .reloc 块表,重定位数据对于文件的执行代码是透明的,它只是提供Windows装载器使用执行代码根本不会去访问他们,所以没有必要将他们映射到物理内存中。

各种地址概念

虚拟地址(VA):
每个进程都有自己独立的4G虚拟内存空间,对应的地址为VA
基址:
PE映象文件被装入内存的地址,在windows中也就是hModule的值,同时也指向PE头
相对虚拟地址 RVA(Relative Virtual Addresses):
PE文件可以被加载到进程地址空间的任何位置;RVA是相对于基址的偏移,即VA=基址+RVA
文件偏移地址:
和内存无关,指距离文件头的偏移

重定位

1.每个PE文件可以定义一个默认的基址,但当它被装载时该基址有可能已经被其他模块占用
2.链接器生成PE文件时会假设指令中用到的地址都基于默认的基地址
3.假如该模块被迫加载到非默认的基址上,此时就需要根据重定位表进行地址的修复
*.由于一般exe是第一个加载的,所以一般exe不需要重定位表,但不代表所有exe都没有重定位表

常见section

.text:虽然叫text,但实际上它主要保存编译后的源码指令,是只读字段
.data:保存数据,对应初始化后的非const的全局变量变量或者局部static变量
.rdata:保存只读数据,对应C语言中的const变量
.bss:未初始化的非const全局变量和局部static变量
.reloc:重定位段。如果加载PE文件失败,将基于此段进行重新调整
.rsrc:资源

PE头

通过GetModuleHandle得到的HMODULE实际上就指向IMAGE_DOS_HEADER,也就是PE文件头的特殊标记MZ。通过IMAGE_DOS_HEADER可以进一步获取到IMAGE_FILE_HEADER,并依此访问各个节的数据。相关代码网上一搜就有,不过要注意32位与64位平台的差异。

导入表, 导出表

导入表记录一个exe/dll所用到的其他模块导出的函数;导出表则记录了导出符号的信息;日常开发中常用的工具exescope/dumpbin/depends等就是从导入表/导出表中获取的相应信息。
exe很少有导出表;大多数dll都有导出表;某些仅用于存放资源的dll可以没有导出表;
可以根据PE结构解析出已经被加载到内存中的某个模块的导出表的地址并对其进行修改以达到hook某个函数的目的,这就是IAT Hook。

应用

为什么要学习PE格式,学了这个可以做什么?列举几个例子:

  1. IAT hook
  2. chrome_elf
    chrome_elf模块为chrome浏览器提供以下支持:初始化crashpad(dump抓取);拦截第三方模块注入;
    为了确保抓取到某些早期逻辑导致的崩溃,也为了拦住在早期就注入到进程里的第三方模块,chrome_elf的执行非常早,早于一般程序的入口点winMain,也早于全局对象的初始化,它基于隐式链接动态库的DllMain函数来实现。为了确保它在程序启动的最早时机被执行,编译chrome.exe之后会由某个脚本修改chrome.exe的导入表,将chrome_elf.dll置于第一个,因为隐式加载dll的顺序是按导入表的顺序执行的,这样就可以确保chrome_elf.dll被第一个加载到进程里,并首先运行chrome_elf.dll里的DllMain。
  3. 注入exe
    这个主要为某些病毒程序所用,将其源码插入到正常程序中,或者将其dll插入到exe导入表中,达到跟随正常程序启动的目的;

你可能感兴趣的:(Win32 PE文件格式简要总结)