PE头详细分析


目录

  • PE头详细分析
    • 0x00 前言
    • 0x01 PE文件介绍
    • 0x02 PE头详细分析
      • DOS头解析
      • NT头解析
      • 标准PE头解析
      • 可选PE头解析
        • 可选PE头结构
        • 基址
        • 代码段地址
        • 数据段地址
        • OEP程序入口点
    • 0x03 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文件被加载到内存中的时候(相当于一个拉伸的过程),把数据给拉长了。
不过块中的数据还是一样的,我们可以看到最开头的快就是PE头的数据,接下来是节表等其他块的数据,这些我们会在后续文章中介绍。
PE头详细分析
文章图片

0x02 PE头详细分析 PE主要由3部分构造,(1)、DOS头 (2)、NT头(标准PE头、可选PE头)。
参考图:(OpenRCE.org网站上的PE格式图.pdf)
PE头详细分析
文章图片

DOS头解析
DOS头中的数据如下,其中带*号的是比较重要的数据,DOS头大小为:64字节。
//--> DOS头(_IMAGE_DOS_HEADER ) <-- struct _IMAGE_DOS_HEADER { WORDe_magic; //*DOS头魔数 Magic* WORDe_cblp; //[Bytes on last page] WORDe_cp; //[Pages in file] WORDe_crlc; //[Relocations] WORDe_cparhdr; //[Size of header] WORDe_minalloc; //[Minium memory] WORDe_maxalloc; //[Maxium Memory] WORDe_ss; //[Inital SS value] WORDe_sp; //[Inital SP value] WORDe_csum; //[Checksum] WORDe_ip; //[Inital IP value] WORDe_cs; //[Inital CS value] WORDe_lfarlc; //[Table offset] WORDe_ovno; //[Overlay number] WORDe_res[4]; //[Reserved words] WORDe_oemid; //[OEM id] WORDe_oeminfo; //[OEM infomation] WORDe_res2[10]; //[Reserved words] DWORD e_lfanew; //*NT头地址* } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

PE头详细分析
文章图片

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头详细分析
文章图片

PE头详细分析
文章图片

可选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头数据最多,不过这是好事对我们有用的数据也很多,比如下面标红的都是平时在逆向或者脱壳、破解中比较有用的信息。
PE头详细分析
文章图片

基址 首先第一个魔数0x010B我们就不说了,主要应该是为了区别32位和64位程序吧。
接着我们看程序基地址0x01000000,这个基地址的意思就是程序加载到内存时候PE文件所在的位置,Windows他会为每个程序分配一个虚拟的4GB空间。
这里有的读者会问那为什么基址非要那么大,为什么不是0呢?因为Windows他有一段内存是用来保护用的,平时我们在写C++代码时候比如:我们引用了一个NULL(空指针)其指向的内存地址就是0的时候,程序就会崩溃就会报错!,没错这就是Windows为了保护程序设计的。
我们也可以用Winhex打开加载在内存中Notepad.exe的数据,可以发现其首地址就是程序基址。
PE头详细分析
文章图片

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

我们可以用radare2套件中的rasm2来将十六进制转换成汇编代码看看。
rasm2 -a x86 -b 32 -d "十六进制" #-a 代表平台 x86架构平台 #-b 位数32位 #-d 解码解析成汇编

PE头详细分析
文章图片

数据段地址 接下来看数据段的地址0x009000,数据段主要存放数据,比如字符串等数据,在下图中能看到存放了Notepad字符串。
PE头详细分析
文章图片

OEP程序入口点 OEP是可选PE头结构体中第7个成员AddressOfEntryPoint的数据,顾名思义指的是程序开始执行的第一行代码位置。
由于程序被加载到内存,所以我们还需要加上基址才是真实的程序入口点。
即:基址+OEP = 0x01000000 + 0x739D = 0x0100739D
PE头详细分析
文章图片

我们可以用工具将其转换成汇编,看看第一行汇编代码是什么?
PE头详细分析
文章图片

我们也可以利用OD来加载程序,OD加载程序默认会自动加载到OEP处。所以可以来验证下我们找的位置对不对。
PE头详细分析
文章图片

好了可选头PE内容介绍到这里就结束了,其中可选PE头还有最有一个结构体数据_IMAGE_DATA_DIRECTORY,这个暂时就先不介绍了,留在后面介绍其他内容的时候在详解。
0x03 PE头解析工具编写 知道了PE头结构后,代码写起来也是很方便,而且微软有自带的PE头结构体,我们可以直接open()文件后直接读取内容到结构体解析即可。
/******************************************************* * * 学习滴水逆向 PE结构分析代码练习 * * 海东老师 Bilibili:滴水逆向三期 *********************************************************///头文件定义 #include #include #include #include using namespace std; //--------------------------PE结构---------------------------------- //--> DOS头(_IMAGE_DOS_HEADER ) <-- struct _IMAGE_DOS_HEADER_2 { WORDe_magic; //*DOS头魔数 Magic* WORDe_cblp; //[Bytes on last page] WORDe_cp; //[Pages in file] WORDe_crlc; //[Relocations] WORDe_cparhdr; //[Size of header] WORDe_minalloc; //[Minium memory] WORDe_maxalloc; //[Maxium Memory] WORDe_ss; //[Inital SS value] WORDe_sp; //[Inital SP value] WORDe_csum; //[Checksum] WORDe_ip; //[Inital IP value] WORDe_cs; //[Inital CS value] WORDe_lfarlc; //[Table offset] WORDe_ovno; //[Overlay number] WORDe_res[4]; //[Reserved words] WORDe_oemid; //[OEM id] WORDe_oeminfo; //[OEM infomation] WORDe_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); structtm 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
【PE头详细分析】PE头详细分析
文章图片

    推荐阅读