初步了解PE文件格式(上)


      今天这篇博客只对PE文件进行大致的分析,使用一个例子并结合一个简单的PE文件格式图来说明PE文件格式,因为初学,里面有不正确的地方还望大家指正。

1.PE文件结构

      首先说一下什么是PE格式吧,虽然从网上搜一下会有很多定义,就我的理解来说,PE是Microsoft为了让程序在Windows上可移植而做的一种文件格式规定。就拿我们每个人都不陌生的exe程序来说吧,它就是一个PE文件,在我们的印象中,只要是Windows操作系统,都能执行exe程序,这就是Microsoft做的让程序在Windows平台上实现移植的功能,这个移植能力的实现是因为规定了exe程序的格式,Windows在执行exe程序的时候,PE文件加载器会按照约定加载exe程序,所以程序就正常地运行起来了。我这么来说是不是对PE文件不那么抽象了,比如像EXE,DLL,SYS这种格式的文件就是PE格式文件,这些文件都是我们平时见到过或者使用过的。如果大家了解图片格式,比如BMP图片,那这个PE文件格式就更好理解了,都是按照一定的规范生成的,在BMP图片中文件头部信息记录了这个文件的大小、创建日期、宽度和高度等等信息,PE文件也是这样,只不过比BMP文件格式更复杂。
      说了这么多废话下面进入正题,纸上得来终觉浅,觉知此事要躬行,如果不亲手用一个工具去看PE文件,那么理解PE文件格式还是有点困难的,因此在看PE文件的时候最好使用WinHex这个工具,另外还需要一个辅助工具就是LoadPE,LoadPE这个工具是来验证我们分析的PE文件是否是正确的。下面我们就来分析一下Windows 7中的记事本程序notepad,按照网上给的PE格式图我们一边分析一边通过LoadPE来验证。
      首先来看一下PE文件的基本结构

初步了解PE文件格式(上)

这个图是我根据我的理解画出来的,PE文件基本由以下四个部分组成,DOS头部、PE文件头、节表和节,如果有了一个整体的PE文件结构,我觉得对于理解PE文件格式就轻松一些,同时记忆也深刻些,下面就一一讲解每个部分。
      首先我们用WinHex打开notepad程序如下图,打开后看到16进制格式编码,后面会按照上面的PE结构图分部分来讲解。
初步了解PE文件格式(上)

2.DOS文件头

初步了解PE文件格式(上)

      上图就是notepad程序DOS文件头的16进制编码部分,DOS文件头包含两部分,一部分是DOS文件头的标志也就是以ASCII码为“MZ”字母开头的部分,简称MZ文件头,另外一部分就是DOS程序,这部分是当程序在DOS环境下运行时调用的,具体就不详细讲解了,因为我也不懂。在这个部分我们比较关注的有两个地方,一个就是前面讲的MZ标志,另外一个就是从文件头偏移3CH(16进制)的地方有4个字节,这4个字节是一个地址,指向PE文件头。
      上图中最开始的两个字节4D5A就是我们说的MZ头,看到对应右边的ASCII码是字母MZ,从头开始偏移3CH的位置的4个字节图中显示的是E0000000,这里是一个地址,对了这里要说明一下,从WinHex看到的数据是E0000000,实际是要倒过来看000000E0,这里涉及到数据存储的大小端问题,这里就不展开说明了,记住后面的数据都是从后往前读就可以了。我们找到000000E0位置,看到50450000这4个字节,对应ASCII码是字母PE,这里就进入了PE头部了。

3.PE文件头

初步了解PE文件格式(上)

      上图就是PE部分(除了最后8个标红的字节),紧接着DOS部分,从50450000这个4个字节开始,PE文件头占用F8H(248)个字节,总共分为三个部分,PE标志、PE文件头和PE可选头部,其中PE标志占4个字节,PE文件头占20个字节,PE可选头部占224个字节。
      首先我们看到PE标志,DOS部分我们也讲到了这个标志,占用4个字节。接着是PE文件头了,PE文件头包含块的数目、文件创建日期和文件信息标志等信息,这里就不细讲了。再接着就是PE可选头部,这里面包含信息比较多,我只挑一些重要的讲一下,首先是两个字节的标志位,一般常值为10BH,然后就是4个字节找到图中的89360000(第三行标红的块),这是程序入口RVA地址,RVA(Relative Virtual Address)就是相对虚拟地址,这个地址与我们前面说的3CH偏移值不一样,前面的那个3CH偏移值指的是从PE文件开始的偏移值,而这里的地址是程序加载到内存以后相对于加载的基址的偏移值(这里很绕,如果没有搞明白也没关系,后面我会着重将明白什么是RVA)。这里又涉及到文件对齐和内存对齐的问题,说简单点就是我们代码存储在硬盘里,硬盘里是512字节一个扇区,那么每次硬盘读写数据都是512个字节的读,如果读写的数据是300个字节,那么仍然占用512个字节的空间,这就是文件对齐。内存对齐同理,假设CPU访问内存每次读写的都是4096个字节,那这样的话从硬盘上读取代码加载到内存里就要进行转换,我们用WinHex打开的notepad程序显示的仅仅是在硬盘里存的代码,如果加载到内存,内存空间就不是我们看到的这样了。
      我们继续讲下一个比较关注的地方,就是内存块对齐值,上图中的00100000,表示内存块对齐值,而00020000表示文件对齐值(我都用红色标出来了)。这个就是前面我提到内存对齐和文件对齐的单位,将内存块对齐值换算成十进制就是4096,文件对齐值换算成十进制就是512。再下面一个重要的是映像大小,上图中的00000300就是,映像大小就是PE文件加载到内存后占用内存的大小,我们换算一下,是192KB,我们再看一下notepad.exe的文件大小是176KB,所以一般文件加载到内存后都会占用更多的空间。上面说到的一些信息我们用LoadPE打开看一下,如下图,
初步了解PE文件格式(上)

在图中,我把重要的几个标出来了,与我们前面使用WinHex分析的是一致的。
      剩下两个重要的,就是输出表RVA值和输入表RVA值,在图中就是紧挨着的两个各自占用8字节的红色块。每个都是前4个字节表示RVA值,后4个字节表示大小值。所谓的输入表和输出表,简单点来说就是程序是否有导出函数、类等,是否需要导入函数,就像我们使用DLL需要导入函数,那这个DLL信息就可以在导入表中找到。这里导出表全为0,因为我们的notepad程序不需要导出函数,所以为空。那既然有了导入表RVA值,我们想知道notepad程序到底调用了哪些DLL,怎么来在WinHex中找到导入表信息呢,在后面的博客我们再讲,这篇写的有点长了,中间断断续续,写博客真的很考验一个人,文章中有问题的地方请大家指正!