![](/style/images/good.png)
![](/style/images/bad.png)
PE结构-PE文件的两种状态
source link: https://antipassion.github.io/2021/11/16/PE%E7%BB%93%E6%9E%84-PE%E6%96%87%E4%BB%B6%E7%9A%84%E4%B8%A4%E7%A7%8D%E7%8A%B6%E6%80%81/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
本文的是学习复现自吾爱破解dalao lyl610abc的PE文件解析系列,非本人原创,属于学习笔记(大佬yyds!)。
PE文件的两种状态
PE文件处于磁盘中与处于内存中时,两者的结构会稍微发生改变。
一个PE文件可以分为两种状态:
运行态: 当一个PE文件被打开时,PE文件的相关数据将会被装载
到内存中,根据文件对齐以及内存对齐中的区别,文件的大小以以及结构会发生相应的改变
非运行态: 当一个PE文件尚未运行时,其数据存储在磁盘中
时的一种状态
思维脑图
lyl610abc师傅太强了
PE文件整体结构表
判断文件类型是否为PE格式文件
判断流程如下:
- 判断前两个字节是否为4D 5A(MZ)
- 找到3Ch
- 根据3Ch位置的2字节大小数据跳转偏移至其中,查看是否为50 45 00 00(PE..)
利用msf生成的exe马进行实例分析
使用十六进制编辑器打开,判断文件格式类型
可以看到,00处起始位置为4D 5A,表示DOS头开头;3Ch处位置是1801 表示PE头文件偏移位置;利用Ctrl+G跳转至118,可以看到标准PE头50 45,此文件可以判断为是PE文件
对应表格数据为
地址 说明 值 ASCII 0 DOS MZ头的第一个成员 4D 5A MZ 3C 指出PE头文件偏移位置C 118
F0 PE文件头标志 50 45 00 00 PE..
由此,我们可以发现以PE文件头标志为开始的PE头
根据PE结构可知,整个PE头是:PE文件头标志+标准PE头+扩展PE头
的标准,长度大小为4+20+224=248字节
根据3Ch处的118
后面所占的字节地址为:0x118+4+20+224=280+4+20+224=448=0xE0
由此可知整个PE头所占范围为0x118~0xE0
PE文件头标志和标准PE头:
0x118+2+20=200+24=0x130
扩展PE头
根据先前得到的标准PE头结束后,紧挨着的224个字节便是扩展PE头所占空间
标准pe头结束位置:0x130
扩展PE头所占空间:224字节
所以扩展PE头所占区域为: 0x130+224=304+224=528=0x210
由此可知,自0xE0开始到0x1C0结束,都是扩展PE头位置
块表
先前拿到的扩展PE头地址后16个字节为空字节,跳过,拿到第一个块表地址
从先前拿到的块表头地址继续往后看40个字节(块表大小) 拿到第二个块表的首地址
第一个块表头地址: 0x210
块表大小为 40字节
0x210+40 = 0x238
第一块表范围: 0x210~0x238
从0x238开始便是第二个块表
第一个块表: .text
第二个块表: .rdata
0x238+0x28=0x260 ,块表范围为0x238~0x260
第三个块表: .data
0x260+0x28=0x288,块表范围为0x260~0x288
第四块表 .sxdata
0x288+0x28=0x2B0,块表范围为0x288~0x2B0
第五块表 .rsrc
0x2B0+0x28=0x2D8,块表范围为0x2B0~0x2D8
第六块表 .reloc
0x2D8+0x28=0x300,块表范围为0x2D8~0x300.
汇总块表
块表后的空隙
块表后跟着的按理应该是块,但在块表后和块之前,多出了一段空间
此处为300~400
存储时不存在空隙的,被称为连续存储
但在块表以及块之间是可能存在空隙的,这个空隙里面一般被填充为编译器插入的数据(也可能不存在)
这段间隙的修改并不会导致程序的不可运行,因此可以拿来写入自己想要的代码来对程序进行修改
为什么会存在这段间隙呢?
- 这段间隙是由于块表与块之间没有进行连续存储
- 这段长度的存在与否以及长度,取决于块的起始位置
- 而块的其实位置则根据
扩展PE头中的成员决定
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
结构体中的SizeOfHeaders成员(DWORD类型占4个字节)
SizeOfHeaders的含义是3个头按文件对齐后的大小
:(DOS头+PE头+块表)大小加起来后进行文件对齐
后得到的大小
自DOS头开始至块表结束,头大小和为0x248
,此处想要拿到SizeOfHeaders成员的值,需要了解文件对齐
的概念并对其进行对齐处理。
什么是文件对齐?
文件对齐的标准通过扩展PE头中定义的FileAlignment(DWORD类型 占4字节)
文件对齐的要求就是SizeOfHeaders必须为FileAlignment的整数倍
通过以上概念,我们可以理解,由于扩展PE头FileAlignment成员的限制,要求SizeOfHeaders必须为它的整数倍(不足则填充);要想知道文件对齐的标准是多少,我们需要拿到FileAlignment的值
由于前面已知扩展PE头开始地址为:0xE0
FileAlignment(DWORD 4字节)地址为:0xE0+36 = 0x104
FileAlignment为 00 02 00 00 即为 0x200
前面拿到的头大小和为0x248,显然并不是0x200的整数倍
其整数倍,也就是SizeOfHeader的大小应为: (0x248/200+1)*200=400
SizeOfHeaders大小
再通过扩展PE头成员值的方式验证计算是否正确
先前拿到的FileAlignment地址为0x104,再往后24字节,便是SizeOfHeaders存放地址
0x104+24 = 0x11C
SizeOfHeaders:na
拿到00 04 00 00 即 0x400,是FileAlignment的整数倍
可以看到,块表与块之间的间隙自0x400结束
为什么需要文件对齐?
跟内存对齐一样,都是为了使执行时的效率更高,方便内存与磁盘进行交换数据更有效率。
块结构
块的起始地址由块表中的PointerToRawData决定,第一个块
的起始地址则有上面的SizeofHeaders决定
块部分存储的是数据,如何存储由块表进行决定
块表在C中的定义
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData; //块的大小
DWORD PointerToRawData; //块在磁盘中的偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
块的起始地址
找到结构体中的PointerToRawData成员(DWORD类型占4个字节)
PointerToRawData的含义为该块在磁盘文件中的偏移
前面拿到第一个块表的首地址0x210
从0x210开始往后找20字节,0x210+20=0x224,拿到PointerToRawData的地址,跳转过去拿到4字节的值(磁盘文件中的偏移)
00 04 00 00 即 0x400
与SizeOfHeaders得到的一致,前面说过,块的起始地址由块表中的PointerToRawData决定,而第一个块的首地址由SizeOfHeaders决定,此处一致,验证了上文的说法。
块的大小
SizeOfRawData为块的大小(文件对齐后)
SizeOfRawData(Dword 4字节)就在PointerToRawData前面
所以其在块表中的地址为PointerToRawData-4=0x224-4=0x220
SizeOfRawData:
00 72 09 00 即为 0x97200
块的大小与前面三个头(DOS+PE+块表)的大小一样,都需要满足文件对齐
先前拿到的FileAlignment为0x200 块的大小SizeOfRawData大小为0x97200满足整数倍条件,满足文件对齐。
块的结束地址(下一个块的起始地址)
块的结束地址为 块的起始地址+块的大小
即 0x400+0x97200 = 0x97600
可以看到,第一个块与第二个块之间存在空隙
,这段空隙是由于在文件中补足文件对齐产生的。
在非运行态下:
- DOS首部和PE文件头与块表连续存储,中间没有空隙
- 块表与块之间存在
文件对齐
的影响,可能会存在间隙 - 块与块之间也可能由于
文件对其
的影响产生间隙
相关数据结构成员
记录下各个结构的起始以及结束位置方便接下来与运行态比较
运行态,指PE文件装载入内存时,PE结构文件的状态
加载运行态PE文件
1.启动PE文件
2.使用16进制编辑器 工具->打开主内存->选定PE程序
分析运行态的PE文件
此时Dos头的偏移起始地址为00820000,结束地址为008200F0
跟进偏移,到达PE头标志起始地址008200F0,可见头标志起始地址和标准PE头
PE头标志和标准头
起始为008200F0
结束为00820108
紧随其后,便是扩展PE头,占据224字节单位
起始:00820108
结束:008201E8
块表
块表起始位置:008201E8
块表结束位置:008202AF
块表后的空隙
根据前面的信息来看,在块表前的结构在运行态以及废运行态出了起始地址不同之外,其他的并无不同
在内存中,块表与块的间隙大小与文件对齐FileAlignment无关,是由内存对齐
所决定的,内存对齐的机制与文件对齐类似,都是需为SectionAlignment所规定的的值的整数倍。
内存对齐的属性是由_IMAGE_OPTIONAL_HEADER
构造体中的成员SectionAlignment
决定的,同时,此成员其实就在FileAlignment
(文件对齐)成员的上面
扩展PE头在c中的定义
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
//
// NT additional fields.
//
DWORD ImageBase;
DWORD SectionAlignment; //<--- 内存对齐
DWORD FileAlignment; //<--- 文件对齐
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders; //<--- 决定块的起始位置
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
利用扩展PE头中的SectionAlignment值验证理论是否正确:
从扩展PE头首地址00820108开始数32(1word 2bytes 7dword =1 + 2 + 28)个字节,到820128处,拿到SectionAlignment值
00 10 00 00 即 0x1000
即内存对齐的基数为0x1000
同时,在内存中的PE文件块结构,头地址与SizeofHeaders值无关,因为SizeOfHeaders是文件对齐(FileAlignMent)专用
块
在非运行态中,块的起始位置由PointerToRawData决定,且PointerToRawData必须为FileAlignment的整数倍
但在运行态中,块的起始位置则并不由PointerToRawData决定,PointerToRawData和SizeOfHeaders一样都为文件对齐专用
运行态块存储涉及点较多,此处只对第一个块的起始地址,结束地址以及大小做计算
块的起始地址
第一个块的起始地址,取决于(DOS头+PE头+块表)总大小的和进行内存对齐后的结果。
三大头加起来的地址为8202AF为止,根据SectionAlignment的值1000h计算,需要进行补齐操作,补齐至821000达到1000的整数倍
可以看到第一个块的首地址已经发现:
首地址:821000
块表的结束地址=块表首地址+块大小,而块大小通过块表当中的SizeOfRawData可以拿到,此处再放出c中的块表定义
#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
2
DWORD PointerToRawData; //<--- 块在磁盘文件中的偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
根据块表定义可以发现,SizeOfRawData
在块表中可以通过块表首地址+16字节(8x1bytes + 2DWORD ) = 8201E8 + 16 = 8201F8
拿到SizeOfRawData大小 00 92 19 00 即 199200
由此可知结束地址:821000 + 199200 = 9BA200 + E00(补0内存对齐1000)
拿到块大小第二种方法
块的大小=块的结束地址-块的起始地址=0x59B000-0x401000=0x19A000(满足内存对齐)
运行态时,块的大小满足内存对齐,而非先前的文件对齐
在运行态中:
- DOS头、PE文件头、块表连续存储,中间没有空隙
- 运行态中,块表与块之间存储可能会有空隙
- 空隙的大小与SectionAlignMent有关,而非FileAlignMent
- 块与块之间也有可能会因
内存对齐
而产生空隙
相关数据结构成员:
各结构的起始和结束位置:
运行态与非运行态相同点
相同
无论是运行态,还是非运行态,DOS头、PE头、块表均为连续存储,中间没有填充间隙
第一个块表首地址都三个头的大小影响,紧随着对齐后的三个头
块与块之间也需要进行对齐
运行态与非运行态的起始地址不同
在非运行态中,块表与块之间、块与块之间的间隙由文件对齐(FileAlignment)
产生
在运行态中,块表与块之间、块与块之间的间隙由内存对齐(SectionAlignment)
产生
非运行态与运行态映射图
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK