PE头详细分析
PE头详细分析
0x00 前言
最近我在学习Linux PWN相关知识的时候,也是在看《程序员的自我修养(装载->链接->库)》这本书的时候,接触到了可执行文件格式,COFF、ELF、PE,所以也找了Bilibili上海东老师的《滴水逆向三期》视频中的PE课程在学习,刚看完PE头这节课。在此做个笔记也算是整理学习的结果 并且分享给大家,共同学习,如有错误欢迎指正。
0x01 PE文件介绍
PE文件是Windows上的可执行文件,就是我们鼠标双击就能运行的程序,当然也有双击不能运行的程序。其中.exe
、.dll
、.sys
这些都是PE文件,那么读者可能有个疑问,我双击.txt的文件也是能直接打开运行的啊?Emmmmm....这个其实他是用Notepad记事本加载并打开的,而PE可执行文件他是经过系统加载并运行的。
PE文件是分块存储的。在图中我们可以看出数据被加载到内存后会不一样,其中PE文件被加载到内存中的时候(相当于一个拉伸的过程),把数据给拉长了。
不过块中的数据还是一样的,我们可以看到最开头的快就是PE头的数据,接下来是节表等其他块的数据,这些我们会在后续文章中介绍。
0x02 PE头详细分析
PE主要由3部分构造,(1)、DOS头 (2)、NT头(标准PE头、可选PE头)。
参考图:(OpenRCE.org网站上的PE格式图.pdf)
DOS头解析
DOS头中的数据如下,其中带*号的是比较重要的数据,DOS头大小为:64字节。
//--> 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;
NT头解析
NT头主要由3部分构成,标记、标准PE头、可选PE头。其中NT头魔数就是PE
字符串,可见上图。
//--> 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文件、重定位信息是否被移去、是否为系统文件等等。
//--> 标准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数据
解析后
可选PE头解析
可选PE头结构
//--> 可选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头数据最多,不过这是好事对我们有用的数据也很多,比如下面标红的都是平时在逆向或者脱壳、破解中比较有用的信息。
基址
首先第一个魔数0x010B我们就不说了,主要应该是为了区别32位和64位程序吧。
接着我们看程序基地址0x01000000
,这个基地址的意思就是程序加载到内存时候PE文件所在的位置,Windows他会为每个程序分配一个虚拟的4GB空间。
这里有的读者会问那为什么基址非要那么大,为什么不是0呢?因为Windows他有一段内存是用来保护用的,平时我们在写C++代码时候比如:我们引用了一个NULL(空指针)其指向的内存地址就是0的时候,程序就会崩溃就会报错!,没错这就是Windows为了保护程序设计的。
我们也可以用Winhex打开加载在内存中Notepad.exe的数据,可以发现其首地址就是程序基址。
代码段地址
接着我们来看代码段的地址0x001000
,也就是PE文件在这地方开始存的都是程序代码,当然在底层被转为了汇编代码。
我们可以用radare2套件中的rasm2来将十六进制转换成汇编代码看看。
rasm2 -a x86 -b 32 -d "十六进制"
#-a 代表平台 x86架构平台
#-b 位数 32位
#-d 解码 解析成汇编
数据段地址
接下来看数据段的地址0x009000
,数据段主要存放数据,比如字符串等数据,在下图中能看到存放了Notepad字符串。
OEP程序入口点
OEP是可选PE头结构体中第7个成员AddressOfEntryPoint
的数据,顾名思义指的是程序开始执行的第一行代码位置。
由于程序被加载到内存,所以我们还需要加上基址
才是真实的程序入口点。
即:基址
+OEP
= 0x01000000 + 0x739D = 0x0100739D
。
我们可以用工具将其转换成汇编,看看第一行汇编代码是什么?
我们也可以利用OD来加载程序,OD加载程序默认会自动加载到OEP处。所以可以来验证下我们找的位置对不对。
好了可选头PE内容介绍到这里就结束了,其中可选PE头还有最有一个结构体数据_IMAGE_DATA_DIRECTORY
,这个暂时就先不介绍了,留在后面介绍其他内容的时候在详解。
0x03 PE头解析工具编写
知道了PE头结构后,代码写起来也是很方便,而且微软有自带的PE头结构体,我们可以直接open()文件后直接读取内容到结构体解析即可。
/*******************************************************
*
* 学习滴水逆向 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(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;
}
最后欢迎大家加群:1145528880
本文来自博客园,作者:VxerLee,转载请注明原文链接:https://www.cnblogs.com/VxerLee/p/15498560.html 专注逆向、网络安全 ——VxerLee