PE学习——PE基本概念
PE基础
PE文件结构
从本章开始就要学习PE相关的内容。
可执行文件
在了解PE之前,我们需要知道什么是可执行文件,从字面理解可执行文件就是可以由操作系统进行加载执行的文件。
Windows平台下的可执行文件的格式,我们称之为PE(Portable Executable)文件结构;Linux平台下的可执行文件格式,我们称之为ELF(Executable and Linking Format)文件结构。
仔细的人可能会发现PE的全称是Portable Executable,其中文意思就是便携的可执行,而ELF的全称Executable and Linking Format就是可执行可链接格式,那么两者之间的差距就出现了,Windows平台下的PE文件结构是便携的,也就表示其在Windows下是通用兼容的,例如你在Windows7下的可执行文件也可以在Windows8、10系统下运行,而Linux则不一样,不同内核编译的可执行文件在不同内核的环境下是无法使用的。
在这些领域下会用到PE文件格式:
-
病毒和反病毒;
-
外挂和反外挂;
-
加壳和脱壳(保护与破解);
-
无源码的情况下修改功能、汉化软件...
识别PE文件
你想要识别一个文件是不是PE文件,或者说是不是一个可执行文件,可以根据PE指纹来识别:首先你需要找到一个可以以16进制打开PE文件的工具(010 Editor),然后找到一个PE文件,用该工具打开PE文件,在PE文件的开始位置有一个0x5A4D(十进制:MZ),接着在0x003C位置向后有一个0x100,接着我们再去寻找0x100位置就会出现一个0x4550(十进制:PE),那么当你用这个方法可以顺利的走通整个流程找到PE,就表示这是一个PE文件,同样这也是一个PE指纹:
如上示例中我使用的是exe后缀的文件,但即使不是exe后缀的文件,例如.sys、.dll后缀的文件,实际上你通过这种方式会发现它们也是PE文件,所以我们不要只看后缀名来认定是不是PE文件,而要具体去看文件中的指纹。
PE文件的整体结构
如上所述中我们可以了解到通过PE指纹的方式识别PE文件,但是我又是如何知道这是否是一个PE文件的呢?这是因为PE文件结构有一个规范和定义,如下图所示就是PE文件的整体结构:
如上图所示众可以发现PE文件有很多结构,其结构格式图可以见附件:PE格式图.pdf(看上去很多,但不需要害怕,一步一步学下去还是非常容易理解的)
PE文件的两种状态
主要结构体
上文中,我们了解了PE文件的整体结构,我们可以看见其中有很多结构体:
这几个主要结构体分别对应的宽度如下所示:
结构体 |
宽度(字节) |
IMAGE_DOS_HEADER |
64 |
IMAGE_FILE_HEADER |
20 |
IMAGE_OPTIONAL_HEADER32 |
224 |
IMAGE_SECTION_HEADER |
40 |
这些结构体你都可以在Microsoft Visual Studio\VC98\Include\WINNT.H头文件中看见。
文件分析
这些结构体的具体细节,在之后的章节会详细了解,现在我们只需要按照PE文件的整体结构来看一个PE文件(使用010 Editor打开文件)。
DOS部分
首先来看一下DOS部分,首先是DOS MZ文件头IMAGE_DOS_HEADER结构,这个结构占64字节,文件前四行就是了(类似010 Editor这种编辑器,单行都是16字节):
接着是DOS块,这个大小是不固定的,但是在上文中,我们了解到可以根据某个值定位到PE文件头,我们可以先找到PE文件头,这样夹在他们之间的就是DOS块了,在这里就是IMAGE_DOS_HEADER结构体的e_lfanew成员,如上图所示这里对应的值是0xF8:
所以如下图所示中,绿色框标记的部分就是DOS块:
PE文件头
接着来看PE文件头,其第一个是PE文件头标志,这里占4字节,也就是上文图中所示的0x4550(PE标识是不能修改的),所以在这不赘述了;PE文件头第二部分就是PE文件表头IMAGE_FILE_HEADER结构,这个结构占20字节,我们也称之为标准PE头:
继续看PE文件头的第三个部分PE文件表头可选部分,我们也称之为扩展PE头,其就是IMAGE_OPTIONAL_HEADER32结构,默认情况下它在32位下是224字节,在64位下是240字节,你也可以通过IMAGE_FILE_HEADER结构的成员去获取/修改扩展PE头的宽度:
在这里也就对应着如下图中的0xF0(十进制240,因为当前系统和文件都是64位的):
也就表示在这里扩展PE头的宽度就是240字节:
扩展PE头之所以数据宽度较大,是因为其有一个成员是结构体数组:
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES
16
这个成员的宽度就是16个IMAGE_DATA_DIRECTORY结构体的宽度。
节表、节数据
节表很重要,其决定节数据的相关属性,而节数据是我们真正存储数据的地方,其数量和节表是对应的。
节表就是N个IMAGE_SECTION_HEADER结构体组成的,该结构体数据宽度是40字节:
我们可以在该PE文件中看一下有多少个IMAGE_SECTION_HEADER结构体,如下图用不同颜色标记的就是每个节,其实通过编辑器右边的内容你就可以大致知道每个节的表示什么类型了:
在当前PE文件中我们可以知道有4个节表,那也就表示在文件中存储数据的也就有4个部分,在节表之后的就是编译器的插入的数据,而编译器又是如何知道从哪开始插入数据呢?这实际上取决于一个扩展PE头的一个成员SizeOfHeaders:
该成员用来表示DOS头、PE头与节表加起来按照文件对齐以后的大小。这个真正的大小实际上取决于另外一个成员FileAlignment,SizeOfHeaders存储的数值一定是FileAlignment的整数倍,默认情况下该成员的值为0x200。
假设当前DOS头、PE头与节表加起来的宽度为302,而成员FileAlignment的值为200,这时候成员SizeOfHeaders的值按FileAlignment的值进行文件对齐就应该是400,而之所以需要文件对齐是为了提高执行效率,这是一个牺牲空间换时间的一种策略,我们可以在当前PE文件中查看这两个成员:
这两个成员刚好与我们假设的值是一样的,所以这里DOS头、PE头与节表加起来按照文件对齐以后的大小就是400,但这样确实比实际大小要多出一些空间,这些空间默认会用0x00填充,但也有可能这些空间会被编译器插入一些信息,接着在400地址之后的就是节数据了。
静动态差异
PE文件在运行前(静态,存储在磁盘上)和运行时(动态,运行在内存中)的格式是有差异的,这种差异对于我们理解PE文件是如何执行的来说很重要。
我们在之前的文件分析过程中实际上所看到的是静态的内容,其大小是要根据FileAlignment的值进行文件对齐的,但是在运行时则整体按照扩展PE头的成员SectionAlignment的值进行内存对齐,默认情况下该值为0x1000:
我们可以实际观察一下在内存中的PE文件,首先打开记事本,然后在Winhex中这样选择:
然后找到对应的扩展PE头的成员SectionAlignment的值,这里就是默认的0x1000: