01 PE文件的两种状态

首先,我们回顾一下上篇中的PE文件的主要结构如下图;

在这里插入图片描述

然后,我们从 细节上来了解一下PE文件结构;

1.DOS部分

DOS部分包含了两部分,第一部分是IMAGE_DOS_HEADER结构体,我们称其为DOS MZ文件头,装了vs2013的话这个结构体在C:\Program Files (x86)\Microsoft SDKs\Windows\v7.1A\Include\WinNT.h (注:其他PE结构体也都在这个文件里)中定义了,其他版本可以类比目录结构进行查找,或者直接在电脑上搜索,其结构体(其大小为64个字节,可以自己数一下,也可以写个程序测一下,同样可以看下面结构体中给的偏移)如下,为了方便使用我们在前面加上了偏移:
在这里插入图片描述
下面我们用winhex打开记事本notepad.exe这个程序来看一下DOS MZ头部长什么样,如下图:
在这里插入图片描述
接下来是DOS块,这段数据长度是不确定的,主要给链接器用,在Windows下这块被删掉也不影响程序运行,虽然大小不固定,但是也有办法来确定其大小,我们从IMAGE_DOS_HEADER结构体上可以看出,其最后一个成员指向了PE头部,那么就可以知道DOS Stub部分就在DOS头部到PE头部标记之间,如下图():
在这里插入图片描述

2.PE文件头

PE文件头由三部分组成,先从结构体来看一下它长啥样:
在这里插入图片描述
我们再看看IMAGE_FILE_HEADER结构体,如下:

在这里插入图片描述我们看一下Signature和FileHeader部分的二进制:
在这里插入图片描述
接下来我们看看IMAGE_OPTIONAL_HEADER32结构体,其大小不是固定的,一般32位大小为0x00E0,64位为0x00F0,其结构体如下:

typedef struct _IMAGE_OPTIONAL_HEADER {
0x18h      WORD Magic;			//标志字,(ROM映像0107h),,普通可执行					//文件(010Bh)
0x1Ah      BYTE MajorLinkerVersion;		//连接程序主版本号
0x1Bh      BYTE MinorLinkerVersion;		//连接程序福版本号
0x1Ch      DWORD SizeOfCode;			//所有代码区块的总大小
0x20h      DWORD SizeOfInitializedData;		//所有已初始化数据的总大小
0x24h      DWORD SizeOfUninitializedData;		//所有未初始化数据的总大小
0x28h      DWORD AddressOfEntryPoint;		//程序执行入口RVA
0x2Ch      DWORD BaseOfCode;			//代码区块起始RVA
0x30h      DWORD BaseOfData;			//数据区块起始RVA
//以下属于NT结构增加的领域
0x34h      DWORD ImageBase;		//程序首选装载地址
0x38h      DWORD SectionAlignment;		//内存中区块的对齐值大小
0x3Ch      DWORD FileAlignment;		//文件中区块的对齐值大小
0x40h      WORD MajorOperatingSystemVersion;	//要求操作系统最低主版本号
0x42h      WORD MinorOperatingSystemVersion;	//要求操作系统的最低福版本号
0x44h      WORD MajorImageVersion;		//镜像主版本号
0x46h      WORD MinorImageVersion;		//镜像福版本号
0x48h      WORD MajorSubsystemVersion;	//最低子系统主版本号
0x4Ah      WORD MinorSubsystemVersion;	//最低子系统福版本号
0x4Ch      DWORD Win32VersionValue;		//保留,必须为0(没有被病毒感染时)
0x50h      DWORD SizeOfImage;		//映像装入内存的总尺寸(内存对齐					//的倍数)
0x54h      DWORD SizeOfHeaders;		//所有头加区块表的大小
0x58h      DWORD CheckSum;			//映像校验和
0x5Ch      WORD Subsystem;			//可执行文件期望的子系统
0x5Eh	   WORD DllCharacteristics;	//DllMain何时被调用
0x60h      DWORD SizeOfStackReserve;//初始化时栈的大小
0x64h      DWORD SizeOfStackCommit; //初始化时实际提交栈的大小
0x68h      DWORD SizeOfHeapReserve; //初始化时保留的堆大小
0x6Ch      DWORD SizeOfHeapCommit; //初始化时实际提交栈的大小
0x70h      DWORD LoaderFlags;	//与调试有关,默认为0
0x74h      DWORD NumberOfRvaAndSizes;//下边数据目录的项数
0x78h  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];//数据目录表
} IMAGE_OPTIONAL_HEADER32,*PIMAGE_OPTIONAL_HEADER32;

接下来我们在二进制中找到它,如下:
在这里插入图片描述

3.节表

节表非常重要,我们程序真正的数据存放到一个一个的节中,我们有多少节,每个节从哪里开始,哪里结束,里面存放的是什么数据,都是由节表来记录的,同样我们先来看一下其结构体长什么样,如图:
在这里插入图片描述
接下来我们看看二进制中节表是什么样,一个节表40个字节,如图:
在这里插入图片描述
从上图可以看到它有五个节。

4.节

那我们找到了前三个部分,而且前三个部分都是连续的,那第四部分的节从哪找呢?这时我们需要了解一个IMAGE_OPTIONAL_HEADER32字段 SizeOfHeaders,这个字段存的就是DOS头部PE头部和节表大小相加后按文件对齐值对齐后的大小,比如他们相加是0x354个字节,但是这个字段里存的一定不会是0x354这个数据,因为它是按文件对齐后的大小。那么什么是文件对齐值?
同样在扩展头结构中有一个FileAlignment字段用来存放文件对齐值,其可能值有可能是0x200或0x1000,如果这个值是0x200这SizeOfHeaders中存的就会是0x400,如果FileAlignment值是0x1000,那么SizeOfHeaders中存的就会是0x1000,其思想类似C中结构体对齐,以空间换时间。
接下来我们到分析的文件中找找这个值,FileAlignment由上面给的结构体,我们已经标识出其相对PE标识的偏移值是0x3C,如图:

在这里插入图片描述
从上图PE标识处偏移三行,再偏移C字节找到FileAlignment,从其值可以看到,这个值就是0x200(看不懂了解一下大小端),那么SizeOfHeaders字段的值一定是0x200的整数倍,就是以0x200的整数倍向上取整,接下来我们找一下SizeOfHeaders的值,从上面结构体中我们可以看出,相对PE标识偏移0x54,如图:
在这里插入图片描述
可以看出其大小时0x400,就是0x200的整数倍。那么中间如果有空闲的地方有啥用,其实没啥用,哪些空白你想怎么改怎么改;第一个节开始的地方就是紧接着头大小后面的部分;同样这种文件对齐也适用于节,当大小不满足的时候在后面填0对齐。

最后,我们来讲解本篇的主题

通过上面的讲解,我们再来看下面的图就一目了然了:

在这里插入图片描述
从图中,我们可以看出,一个PE文件,在硬盘中的状态和在内存中状态是不一样的,在文件中要考虑文件对齐,在内存中要考虑内存对齐,接下来我们将notepad.exe运行起来,用winhex点击工具,然后选择在内存中打开,选择notepad.exe代开,然后我们就可以对比其有什么不同了,我们先来看看其二进制,左边为文件中的notepad.exe,又边为内存中的,如图:
在这里插入图片描述
在这里插入图片描述
我们可以看到除了偏移外,其值都是一样的,那么后面也都是一样的吗?
不一样,在文件中第一个节是从0x400开始的,如图:
在这里插入图片描述
在内存中,第一个节从起始位置偏移0x1000处开始:
在这里插入图片描述
为什么没有从0x400处开始,原因是由内存对齐值决定的,在扩展头中我们找一下内存对齐字段SectionAlignment,如下图:
在这里插入图片描述
从这里我们可以看出它的值是0x1000,现在我们清楚内存中为什么偏移了0x1000了。

附录

提供一个打印各个结构体大小的C++程序:

扫描二维码关注公众号,回复: 4800861 查看本文章
#include <windows.h>
#include <winNT.h>
#include <iostream>

using namespace std;

int main()
{
	cout << "IMAGE_DOS_HEADER:    " << sizeof(IMAGE_DOS_HEADER) << endl;
	cout << "IMAGE_NT_HEADERS32:    " << sizeof(IMAGE_NT_HEADERS32) << endl;
	cout << "IMAGE_FILE_HEADER:    " << sizeof(IMAGE_FILE_HEADER) << endl;
	cout << "IMAGE_OPTIONAL_HEADER32:    " << sizeof(IMAGE_OPTIONAL_HEADER32) << endl;
	cout << "IMAGE_SECTION_HEADER:    " << sizeof(IMAGE_SECTION_HEADER) << endl;
	getchar();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/lifeshave/article/details/85919952