0x00 前言

最近我在学习Linux PWN相关知识的时候,也是在看《程序员的自我修养(装载->链接->库)》这本书的时候,接触到了可执行文件格式,COFF、ELF、PE,所以也找了Bilibili上海东老师的《滴水逆向三期》视频中的PE课程在学习,刚看完PE头这节课。在此做个笔记也算是整理学习的结果 并且分享给大家,共同学习,如有错误欢迎指正。

0x01 PE文件介绍

PE文件是Windows上的可执行文件,就是我们鼠标双击就能运行的程序,当然也有双击不能运行的程序。其中.exe.dll.sys这些都是PE文件,那么读者可能有个疑问,我双击.txt的文件也是能直接打开运行的啊?Emmmmm….这个其实他是用Notepad记事本加载并打开的,而PE可执行文件他是经过系统加载并运行的。

image-20211102102833590

PE文件是分块存储的。在图中我们可以看出数据被加载到内存后会不一样,其中PE文件被加载到内存中的时候(相当于一个拉伸的过程),把数据给拉长了。

不过块中的数据还是一样的,我们可以看到最开头的快就是PE头的数据,接下来是节表等其他块的数据,这些我们会在后续文章中介绍。

image-20211102111812462

0x02 PE头详细分析

PE主要由3部分构造,(1)、DOS头 (2)、NT头(标准PE头、可选PE头)。

参考图:(OpenRCE.org网站上的PE格式图.pdf)

image-20211102112750039

DOS头解析

DOS头中的数据如下,其中带*号的是比较重要的数据,DOS头大小为:64字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//--> DOS头(_IMAGE_DOS_HEADER ) <--
struct _IMAGE_DOS_HEADER
{
WORD e_magic; //*DOS头魔数 Magic*
WORD e_cblp; //[Bytes on last page]
WORD e_cp; //[Pages in file]
WORD e_crlc; //[Relocations]
WORD e_cparhdr; //[Size of header]
WORD e_minalloc;//[Minium memory]
WORD e_maxalloc;//[Maxium Memory]
WORD e_ss; //[Inital SS value]
WORD e_sp; //[Inital SP value]
WORD e_csum; //[Checksum]
WORD e_ip; //[Inital IP value]
WORD e_cs; //[Inital CS value]
WORD e_lfarlc; //[Table offset]
WORD e_ovno; //[Overlay number]
WORD e_res[4]; //[Reserved words]
WORD e_oemid; //[OEM id]
WORD e_oeminfo; //[OEM infomation]
WORD e_res2[10];//[Reserved words]
DWORD e_lfanew; //*NT头地址*
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

image-20211102113315651

NT头解析

NT头主要由3部分构成,标记、标准PE头、可选PE头。其中NT头魔数就是PE字符串,可见上图。

1
2
3
4
5
6
7
//--> NT头(_IMAGE_NT_HEADERS) <--
struct _IMAGE_NT_HREADERS
{
DWORD Signature;//*NT头魔数
_IMAGE_FILE_HEADER FileHeader;//标准PE头
_IMAGE_OPTIONAL_HEADER OptionalHeader;//可选PE头
}IMAGE_NT_HREADERS,*PIMAGE_NT_HREADERS;

标准PE头解析

标志PE头的固定大小是20字节,其中我们可以看见里面有区段数目的数据。

还有比较关注的点是时间戳,这个时间戳我们可以用文章https://www.cnblogs.com/17bdw/p/6412158.html中的方法转换成文件创建时间

最后特征的数据也要关注下,因为可以用他来判断PE文件的许多特征信息,比如是否为DLL文件、重定位信息是否被移去、是否为系统文件等等。

1
2
3
4
5
6
7
8
9
10
11
//--> 标准PE头(_IMAGE_FILE_HEADER) <--
struct _IMAGE_FILE_HEADER
{
WORD Machine;//*运行平台
WORD NumberOfSections;//*区段数目
DWORD TimeDateStamp;//*时间戳
DWORD PointerToSymbolTable;//[Pointer to COFF]
DWORD NumberOfSymbols;//[COFF table size]
WORD SizeOfOptionalHeader;//*可选PE头大小
WORD Characteristics;//*特征
}IMAGE_FILE_HEADER,*PIMAGE_FILE_HEADER;

HEX数据

image-20211102115557549

解析后

image-20211102114951403

image-20211102115137714

image-20211102114847003

可选PE头解析

可选PE头结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
//--> 可选PE头(_IMAGE_OPTIONAL_HEADER) <--
struct _IMAGE_OPTIONAL_HEADER
{
WORD Magic;//[可选PE头魔数]
BYTE MajorLinkerVersion;//[主链接器版本]
BYTE MinorLinkerVersion;//[副链接器版本]
DWORD SizeOfCode;//[代码段大小]
DWORD SizeOfInitializedData;//[初始化数据大小]
DWORD SizeOfUninitializedData;//[未初始化数据大小]
DWORD AddressOfEntryPoint;//*[程序入口点]
DWORD BaseOfCode;//*[代码段地址]
DWORD BaseOfData;//*[数据段地址]
DWORD ImageBase;// *[加载到内存的开始地址] -> 一般好像都是0x400000
DWORD SectionAlignment;//*[内存页对其大小]
DWORD FileAlignment;//[文件对其大小]
WORD MajorOperatingSystemVersion;//*[操作系统的主版本号]
WORD MinjorOperatingSystemVersion;//*[操作系统的次版本号]
WORD MajorImageVersion;//[程序主版本号]
WORD MinorImageVersion;//[程序次版本号]
WORD MajorSubsystemVersion;//[子系统主版本号]
WORD MinorSubsystemVersion;//[子系统次版本号]
DWORD Win32VersionValue;//[默认保留]
DWORD SizeOfImage;//*[加载到内存映像的大小]
DWORD SizeOfHeaders;//[DOS头 PE头 节头组合大小]
DWORD CheckSum;//*[获取加载到内存映像的hash]
WORD Subsystem;//[运行此映像所需要的子系统名称]
WORD DllCharacteristics;//[DLL映像的特征]
DWORD SizeOfStackReserve;//*[获取保留堆栈的大小]
DWORD SizeOfStackCommit;//*[获取要提交堆栈的大小]
DWORD SizeOfHeapReserve;//*[获取保留堆空间的大小]
DWORD SizeOfHeapCommit;//*[获取要提交的本地堆空间大小]
DWORD LoaderFlags;//[之前保留的成员]
DWORD NumberOfRvaAndSizes;//*[获取 PEHeader 剩余部分中数据目录项的数目 |位置和大小]
_IMAGE_DATA_DIRECTORY DataDirectory[16];//[指向数据目录中的第一个 IMAGE_DATA_DIRECTORY 结构的指针。]
}IMAGE_OPTIONAL_HEADER,*PIMAGE_OPTIONAL_HEADER;

可以看出可选PE头数据最多,不过这是好事对我们有用的数据也很多,比如下面标红的都是平时在逆向或者脱壳、破解中比较有用的信息。

image-20211102120641106

基址

首先第一个魔数0x010B我们就不说了,主要应该是为了区别32位和64位程序吧。

接着我们看程序基地址0x01000000,这个基地址的意思就是程序加载到内存时候PE文件所在的位置,Windows他会为每个程序分配一个虚拟的4GB空间。

这里有的读者会问那为什么基址非要那么大,为什么不是0呢?因为Windows他有一段内存是用来保护用的,平时我们在写C++代码时候比如:我们引用了一个NULL(空指针)其指向的内存地址就是0的时候,程序就会崩溃就会报错!,没错这就是Windows为了保护程序设计的。

我们也可以用Winhex打开加载在内存中Notepad.exe的数据,可以发现其首地址就是程序基址。

image-20211102121506217

代码段地址

接着我们来看代码段的地址0x001000,也就是PE文件在这地方开始存的都是程序代码,当然在底层被转为了汇编代码。

image-20211102122000641

我们可以用radare2套件中的rasm2来将十六进制转换成汇编代码看看。

1
2
3
4
rasm2 -a x86 -b 32 -d "十六进制"
#-a 代表平台 x86架构平台
#-b 位数 32位
#-d 解码 解析成汇编

image-20211102122324910

数据段地址

接下来看数据段的地址0x009000,数据段主要存放数据,比如字符串等数据,在下图中能看到存放了Notepad字符串。

image-20211102132149727

OEP程序入口点

OEP是可选PE头结构体中第7个成员AddressOfEntryPoint的数据,顾名思义指的是程序开始执行的第一行代码位置。

由于程序被加载到内存,所以我们还需要加上基址才是真实的程序入口点。

即:基址+OEP = 0x01000000 + 0x739D = 0x0100739D

image-20211102132902638

我们可以用工具将其转换成汇编,看看第一行汇编代码是什么?

image-20211102133015445

我们也可以利用OD来加载程序,OD加载程序默认会自动加载到OEP处。所以可以来验证下我们找的位置对不对。

image-20211102133152033

好了可选头PE内容介绍到这里就结束了,其中可选PE头还有最有一个结构体数据_IMAGE_DATA_DIRECTORY,这个暂时就先不介绍了,留在后面介绍其他内容的时候在详解。

0x03 PE头解析工具编写

知道了PE头结构后,代码写起来也是很方便,而且微软有自带的PE头结构体,我们可以直接open()文件后直接读取内容到结构体解析即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
/*******************************************************
*
* 学习滴水逆向 PE结构分析代码练习
*
* 海东老师 Bilibili:滴水逆向三期
*********************************************************/

//头文件定义
#include <stdio.h>
#include <string.h>
#include <iostream>
#include <Windows.h>
using namespace std;

//--------------------------PE结构----------------------------------
//--> DOS头(_IMAGE_DOS_HEADER ) <--
struct _IMAGE_DOS_HEADER_2
{
WORD e_magic; //*DOS头魔数 Magic*
WORD e_cblp; //[Bytes on last page]
WORD e_cp; //[Pages in file]
WORD e_crlc; //[Relocations]
WORD e_cparhdr; //[Size of header]
WORD e_minalloc;//[Minium memory]
WORD e_maxalloc;//[Maxium Memory]
WORD e_ss; //[Inital SS value]
WORD e_sp; //[Inital SP value]
WORD e_csum; //[Checksum]
WORD e_ip; //[Inital IP value]
WORD e_cs; //[Inital CS value]
WORD e_lfarlc; //[Table offset]
WORD e_ovno; //[Overlay number]
WORD e_res[4]; //[Reserved words]
WORD e_oemid; //[OEM id]
WORD e_oeminfo; //[OEM infomation]
WORD e_res2[10];//[Reserved words]
DWORD e_lfanew; //*PE文件头地址*
} IMAGE_DOS_HEADER_2, *PIMAGE_DOS_HEADER_2;
//--> NT头(_IMAGE_NT_HEADERS) <--
struct _IMAGE_NT_HREADERS_2
{
DWORD Signature;
_IMAGE_FILE_HEADER FileHeader;
_IMAGE_OPTIONAL_HEADER OptionalHeader;
}IMAGE_NT_HREADERS_2,*PIMAGE_NT_HREADERS_2;
//--> 标准PE头(_IMAGE_FILE_HEADER) <--
struct _IMAGE_FILE_HEADER_2
{
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
}IMAGE_FILE_HEADER_2,*PIMAGE_FILE_HEADER_2;
//--> 可选PE头(_IMAGE_OPTIONAL_HEADER) <--
struct _IMAGE_OPTIONAL_HEADER_2
{
WORD Magic;//[可选PE头魔数]
BYTE MajorLinkerVersion;//[主链接器版本]
BYTE MinorLinkerVersion;//[副链接器版本]
DWORD SizeOfCode;//[代码段大小]
DWORD SizeOfInitializedData;//[初始化数据大小]
DWORD SizeOfUninitializedData;//[未初始化数据大小]
DWORD AddressOfEntryPoint;//*[程序入口点]
DWORD BaseOfCode;//*[代码段地址]
DWORD BaseOfData;//*[数据段地址]
DWORD ImageBase;// *[加载到内存的开始地址] -> 一般好像都是0x400000
DWORD SectionAlignment;//*[内存页对其大小]
DWORD FileAlignment;//[文件对其大小]
WORD MajorOperatingSystemVersion;//*[操作系统的主版本号]
WORD MinjorOperatingSystemVersion;//*[操作系统的次版本号]
WORD MajorImageVersion;//[程序主版本号]
WORD MinorImageVersion;//[程序次版本号]
WORD MajorSubsystemVersion;//[子系统主版本号]
WORD MinorSubsystemVersion;//[子系统次版本号]
DWORD Win32VersionValue;//[默认保留]
DWORD SizeOfImage;//*[加载到内存映像的大小]
DWORD SizeOfHeaders;//[DOS头 PE头 节头组合大小]
DWORD CheckSum;//*[获取加载到内存映像的hash]
WORD Subsystem;//[运行此映像所需要的子系统名称]
WORD DllCharacteristics;//[DLL映像的特征]
DWORD SizeOfStackReserve;//*[获取保留堆栈的大小]
DWORD SizeOfStackCommit;//*[获取要提交堆栈的大小]
DWORD SizeOfHeapReserve;//*[获取保留堆空间的大小]
DWORD SizeOfHeapCommit;//*[获取要提交的本地堆空间大小]
DWORD LoaderFlags;//[之前保留的成员]
DWORD NumberOfRvaAndSizes;//*[获取 PEHeader 剩余部分中数据目录项的数目 |位置和大小]
_IMAGE_DATA_DIRECTORY DataDirectory[16];//[指向数据目录中的第一个 IMAGE_DATA_DIRECTORY 结构的指针。]
}IMAGE_OPTIONAL_HEADER_2,*PIMAGE_OPTIONAL_HEADER_2;
//-----------------------------------------------------------------


int main(int args,char *argv[])
{
if (args < 2)
{
printf("参数有误,请按照如下格式调用本程序!\n");
printf("PEAnysis.exe 程序名.exe\n");
return 0;
}
//初始化可有颜色终端Handle
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
//
printf("===================PE Anysis-PE文件分析程序工具!==========================\n\n");
//FILE* fp = fopen("C:\\Users\\gelin\\Desktop\\123456.exe", "rb");
FILE* fp = fopen(argv[1], "rb");
if (fp != NULL)
{
//读取DOS头
fread(&IMAGE_DOS_HEADER_2, sizeof(IMAGE_DOS_HEADER_2), 1, fp);
//跳转到NT头
fseek(fp, IMAGE_DOS_HEADER_2.e_lfanew, 0);
//读取NT头
fread(&IMAGE_NT_HREADERS_2, sizeof(IMAGE_NT_HREADERS_2), 1, fp);

printf("---------------PE头数据----------------\n");
//输出DOS头信息
cout << "--> DOS头(_IMAGE_DOS_HEADER ) <--" << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
char szMagic[3] = { 0 };
memcpy(szMagic, &IMAGE_DOS_HEADER_2.e_magic, 2);
printf("*DOS头魔数:0x%x|%s\n", IMAGE_DOS_HEADER_2.e_magic, szMagic);
SetConsoleTextAttribute(handle, 0x07);
printf("[Bytes on last page]:0x%x\n", IMAGE_DOS_HEADER_2.e_cblp);
printf("[Pages in file]:0x%x\n", IMAGE_DOS_HEADER_2.e_cp);
printf("[Relocations]:0x%x\n", IMAGE_DOS_HEADER_2.e_crlc);
printf("[Size of header]:0x%x\n", IMAGE_DOS_HEADER_2.e_cparhdr);
printf("[Minium memory]:0x%x\n", IMAGE_DOS_HEADER_2.e_minalloc);
printf("[Maxium Memory]:0x%x\n", IMAGE_DOS_HEADER_2.e_maxalloc);
printf("[Inital SS value]:0x%x\n", IMAGE_DOS_HEADER_2.e_ss);
printf("[Inital SP value]:0x%x\n", IMAGE_DOS_HEADER_2.e_sp);
printf("[Checksum]:0x%x\n", IMAGE_DOS_HEADER_2.e_csum);
printf("[Inital IP value]:0x%x\n", IMAGE_DOS_HEADER_2.e_ip);
printf("[Inital CS value]:0x%x\n", IMAGE_DOS_HEADER_2.e_cs);
printf("[Table offset]:0x%x\n", IMAGE_DOS_HEADER_2.e_lfarlc);
printf("[Overlay number]:0x%x\n", IMAGE_DOS_HEADER_2.e_ovno);
printf("[Reserved words]:", IMAGE_DOS_HEADER_2.e_res);
for (size_t i = 0; i < 4; i++)
{
printf("0x%x, ", IMAGE_DOS_HEADER_2.e_res[0]);
}
cout << endl;
printf("[OEM id]:0x%x\n", IMAGE_DOS_HEADER_2.e_oemid);
printf("[OEM infomation]:0x%x\n", IMAGE_DOS_HEADER_2.e_oeminfo);
printf("[Reserved words]:", IMAGE_DOS_HEADER_2.e_res2);
for (size_t i = 0; i < 10; i++)
{
printf("0x%x, ", IMAGE_DOS_HEADER_2.e_res2[0]);
}
cout << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*PE文件头地址:0x%x\n", IMAGE_DOS_HEADER_2.e_lfanew);
SetConsoleTextAttribute(handle, 0x07);
cout << "DOS头大小:" << sizeof(IMAGE_DOS_HEADER_2) << endl;
cout << endl;

//输出标准PE头信息
cout << "--> 标准PE头(_IMAGE_FILE_HEADER) <--" << endl;
char szNTSignature[3] = { 0 };
memcpy(szNTSignature, &IMAGE_NT_HREADERS_2.Signature, 2);
printf("[NT头标识]:%s\n", szNTSignature);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[运行平台]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.Machine);
printf("*[节数量]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections);
printf("*[时间戳]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.TimeDateStamp);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_GREEN);
struct tm test_gmtime_s;
errno_t err = gmtime_s(&test_gmtime_s, (time_t*)&IMAGE_NT_HREADERS_2.FileHeader.TimeDateStamp);
printf(" 文件创建时间:%d年%d月%d日 %02d时:%02d分:%02d秒(周%d)\n", test_gmtime_s.tm_year + 1900, test_gmtime_s.tm_mon, test_gmtime_s.tm_mday,
test_gmtime_s.tm_hour + 8, test_gmtime_s.tm_min, test_gmtime_s.tm_sec, test_gmtime_s.tm_wday);
SetConsoleTextAttribute(handle, 0x07);
printf("[Pointer to COFF]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.PointerToSymbolTable);
printf("[COFF table size]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.NumberOfSections);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[可选头大小]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.SizeOfOptionalHeader);
printf("*[特征/特性]:0x%x\n", IMAGE_NT_HREADERS_2.FileHeader.Characteristics);
SetConsoleTextAttribute(handle, 0x07);
cout << "标准PE头大小:" << sizeof(IMAGE_NT_HREADERS_2.FileHeader) << endl;
cout << endl;

//输出可选PE头信息
cout << "--> 可选PE头(_IMAGE_OPTIONAL_HEADER) <--" << endl;
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[程序内存入口点]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.AddressOfEntryPoint + IMAGE_NT_HREADERS_2.OptionalHeader.ImageBase);
printf("*[可选PE头魔数]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Magic);
printf("*[主链接器版本]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorLinkerVersion);
printf("*[副链接器版本]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorLinkerVersion);
printf("*[代码段大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfCode);
printf("*[初始化数据大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfInitializedData);
printf("*[未初始化数据大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfUninitializedData);
printf("*[代码段地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.BaseOfCode);
printf("*[数据段地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.BaseOfData);
printf("*[PE文件基地址]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.ImageBase);
printf("*[程序入口点]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.AddressOfEntryPoint);
SetConsoleTextAttribute(handle, 0x07);
printf("[内存对其大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SectionAlignment);
printf("[文件对其大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.FileAlignment);
printf("[操作系统的主版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorOperatingSystemVersion);
printf("[操作系统的次版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorOperatingSystemVersion);
printf("[程序主版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorImageVersion);
printf("[程序次版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorImageVersion);
printf("[子系统主版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MajorSubsystemVersion);
printf("[子系统次版本号]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.MinorSubsystemVersion);
printf("[Win32版本值]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Win32VersionValue);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[内存映像大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfImage);
printf("*[DOS|PE|节头大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeaders);
printf("*[内存映像hash]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.CheckSum);
SetConsoleTextAttribute(handle, 0x07);
printf("[程序可以运行的系统]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.Subsystem);
SetConsoleTextAttribute(handle, FOREGROUND_INTENSITY | FOREGROUND_RED);
printf("*[DLL映像的特征]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.DllCharacteristics);
printf("*[获取保留堆栈的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfStackReserve);
printf("*[获取要提交堆栈的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfStackCommit);
printf("*[获取保留堆空间的大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeapReserve);
printf("*[获取要提交的本地堆空间大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.SizeOfHeapCommit);
SetConsoleTextAttribute(handle, 0x07);
printf("[加载标志(已废弃)]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.LoaderFlags);
printf("[获取PEHeader剩余部分数据,位置和大小]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.NumberOfRvaAndSizes);
printf("[指向IMAGE_DATA_DIRECTORY结构指针]:0x%x\n", IMAGE_NT_HREADERS_2.OptionalHeader.DataDirectory);
cout << "可选PE头大小:" << sizeof(IMAGE_NT_HREADERS_2.OptionalHeader) << endl;
cout << endl;
printf("---------------节表数据----------------\n");

printf("===========================================================================\n\n");

}
else
{
printf("文件打开失败,请检查是否被占用!\n");
return 0;
}


int x;
cin >> x;

return 0;
}

PE分析

最后欢迎大家加群:1145528880

Pwn菜鸡学习小分队群聊二维码