Windows PE 文件头解析
2022-5-5 15:2:15 Author: www.secpulse.com(查看原文) 阅读量:41 收藏

0x01 PE文件基本介绍

PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(来自百度百科)。包括DOS头、PE头、节表、导入表和导出表。

本文主要介绍DOS头、NT头、标准PE头、可选PE头和节表。

在介绍前先了解几个概念:

虚拟地址(Virtual Address,VA):在windows系统中,PE文件被系统加载器映射到内存中。每个程序都有自己的虚拟空间,这个虚拟空间的内存地址称为虚拟地址。

相对虚拟地址(Relative Virtual Address,RVA):RVA是PE文件被装在到内存中,某个数据位置相对于装入地址的偏移量。假设一个程序从400000h处装入,代码开始与401000h,于是RVA = 401000h - 400000h,为1000h

虚拟地址(RV)=  基地址(ImageBase)+  相对虚拟地址(RVA)

有关ImageBase的内容后面会介绍到。

下图为PE文件的基本结构:

a3448a4bea9d8af7f610ed3a855f4252.png

0x02 DOS头

本文使用32位程序做演示。

DOS头(IMAGE_DOS_HEADER)的大小为40H(64字节)

DOS头的基本结构如下:

struct _IMAGE_DOS_HEADER { 
0x00 WORD e_magic; *       //5A 4D         MZ标记,用来判断是否为可执行文件
0x02 WORD e_cblp;          //00 90
0x04 WORD e_cp;            //00 03
0x06 WORD e_crlc;          //00 00
0x08 WORD e_cparhdr;       //00 04
0x0a WORD e_minalloc;      //00 00
0x0c WORD e_maxalloc;      //FF FF
0x0e WORD e_ss;            //00 00
0x10 WORD e_sp;            //00 B8
0x12 WORD e_csum;          //00 00
0x14 WORD e_ip;            //00 00
0x16 WORD e_cs;            //00 00
0x18 WORD e_lfarlc;        //00 40
0x1a WORD e_ovno;          //00 00
0x1c WORD e_res[4];        //00 00 00 00
0x24 WORD e_oemid;         //00 00
0x26 WORD e_oeminfo;       //00 00
0x28 WORD e_res2[10];      //00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x3c DWORD e_lfanew; *     //00 00 00 E8    PE头相对于文件的偏移,用于定位PE文件
};

在DOS头中比较重要的两个参数:

e_magic:在DOS头开始的位置,大小为2字节,存储的内容一般为5A 4D,也就是'MZ'的十六进制,'MZ'是MS-DOS的创建者之一Mark Zbikowski名字的缩写,通常用来判断文件是否为可执行文件。

e_lfanew:在DOS头结束的位置,大小为4字节,是PE头相对于文件的偏移,用于定位PE文件头的位置。

075e24a7fc194f011f9f6e6fa4a2c5e9.png

可以通过e_lfanew中存储的数据找到PE文件头的位置,图中e_lfanew存储的内容为00 00 00 F0(小端存储),所以可以定位到PE文件头的位置在F0H。

e_lfanew到PE文件头的这段空间,是由编译器生成,用来存储一些程序运行的信息,可以随意更改,对程序没有影响。

0x03 PE文件头

PE文件头是PE相关结构NT映像头的简称,其中包含许多PE装载器能用到的重要字段,在NT头中除了存放了4个字节的PE文件头标识以外还有标准PE头和可选PE头。

NT头(IMAGE_NT_HEADERS)的基本结构如下:

struct _IMAGE_NT_HEADERS {
0x00 DWORD Signature;                          //00 00 45 50  PE文件标识,PE文件头的开始
0x04 _IMAGE_FILE_HEADER FileHeader;            //标准PE头
0x18 _IMAGE_OPTIONAL_HEADER OptionalHeader;    //可选PE头
};

可以看到在NT头的开始处是一个32位的标志信息,PE,DOS头中的e_lfanew指向该位置

1446f80c63b4b973f2bb247d3f1cf02a.png

1、标准PE头

标准PE头(IMAGE_FILE_HEADER)的大小为12H(20字节)

标准PE头的基本结构如下:

struct _IMAGE_FILE_HEADER {
0x00 WORD Machine; *               //01 4C         程序运行的cpu型号:0x0任何处理器   386即后续处理器
0x02 WORD NumberOfSections; *      //00 04         文件中存在的节的总数,如果要新增节或者合并节,就需要修改这个值
0x04 DWORD TimeDateStamp; *        //4B 67 34 C3   时间戳:文件创建的时间(编译器填写)
0x08 DWORD PointerToSymbolTable;   //00 00 00 00   指向符号表(用于调试)
0x0c DWORD NumberOfSymbols;        //00 00 00 00   符号表中符号的个数(用于调试)
0x10 WORD SizeOfOptionalHeader; *  //00 E0         可选PE头的大小,32位PE文件默认0xE0,64位PE文件默认0xF0
0x12 WORD Characteristics; *       //01 22         文件属性  每一位的含义不同
};

在标准PE头中比较重要的几个参数:

Machine:存储了程序运行的cpu型号,2字节大小,一般0x14C遇见的比较多,这是在网上找到的winnt.h中各个值对应的信息。

#define IMAGE_FILE_MACHINE_UNKNOWN           0
#define IMAGE_FILE_MACHINE_TARGET_HOST       0x0001  // Useful for indicating we want to interact with the host and not a WoW guest.
#define IMAGE_FILE_MACHINE_I386              0x014c  // Intel 386.
#define IMAGE_FILE_MACHINE_R3000             0x0162  // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000             0x0166  // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000            0x0168  // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2         0x0169  // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA             0x0184  // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3               0x01a2  // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP            0x01a3
#define IMAGE_FILE_MACHINE_SH3E              0x01a4  // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4               0x01a6  // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5               0x01a8  // SH5
#define IMAGE_FILE_MACHINE_ARM               0x01c0  // ARM Little-Endian
#define IMAGE_FILE_MACHINE_THUMB             0x01c2  // ARM Thumb/Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_ARMNT             0x01c4  // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33              0x01d3
#define IMAGE_FILE_MACHINE_POWERPC           0x01F0  // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP         0x01f1
#define IMAGE_FILE_MACHINE_IA64              0x0200  // Intel 64
#define IMAGE_FILE_MACHINE_MIPS16            0x0266  // MIPS
#define IMAGE_FILE_MACHINE_ALPHA64           0x0284  // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU           0x0366  // MIPS
#define IMAGE_FILE_MACHINE_MIPSFPU16         0x0466  // MIPS
#define IMAGE_FILE_MACHINE_AXP64             IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE           0x0520  // Infineon
#define IMAGE_FILE_MACHINE_CEF               0x0CEF
#define IMAGE_FILE_MACHINE_EBC               0x0EBC  // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64             0x8664  // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R              0x9041  // M32R little-endian
#define IMAGE_FILE_MACHINE_ARM64             0xAA64  // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE               0xC0EE

NumberOfSections:存储了文件中节的总数,2字节大小,不同的可执行文件节的总数是不一样的,可以通过NumberOfSections得知当前文件节的总数,对节的内容进行遍历,如果要增加或者删除节,也是修改此参数。

TimeDateStamp:存储了文件创建的时间,4字节大小,是由编译器写入的。

SizeOfOptionalHeader:存储了可选PE头的大小,2字节大小,可选PE头的大小一般是不固定的,通常情况下32位程序为0xE0,64位程序为0xF0,此值可以自定义。

Characteristics:存放了文件属性,2字节大小,它的每一位的含义都不同,当前程序的值为0x0122,所以换算成二进制就是0000 0001 0010 0010,对应的属性如下图所示

7483c26aa1146b5a476c764b392441ff.png

标准PE头在程序中的位置:

863dfb2883ae1b317d47fbd9e0714797.png

2、可选PE头

在标准PE头后面就是可选PE头(IMAGE_OPTIONAL_HEADER),32位程序和64位程序可选PE头大小分别是0xE0和0xF0,在内容上也略有不同。

32位可选PE头的基本结构如下:

struct _IMAGE_OPTIONAL_HEADER32 {
0x00 WORD Magic; *                      //01 0B         说明文件是ROM镜像(0107h),还是普通的可执行的镜像(010Bh)
0x02 BYTE MajorLinkerVersion;           //0A            链接程序的主版本号
0x03 BYTE MinorLinkerVersion;           //00            链接程序的次版本号
0x04 DWORD SizeOfCode; *                //00 06 22 00   所有代码节的和,必须为FileAlignment的整数倍,编译器填写
0x08 DWORD SizeOfInitializedData; *     //00 03 16 00   已初始化数据大小的和,必须为FileAlignment的整数倍
0x0c DWORD SizeOfUninitializedData; *   //00 00 00 00   未初始化数据大小的和,必须为FileAlignment的整数倍
0x10 DWORD AddressOfEntryPoint; *       //00 05 8C 77   程序入口
0x14 DWORD BaseOfCode; *                //00 00 10 00   代码开始的基址,编译器填写
0x18 DWORD BaseOfData; *                //00 06 40 00   数据开始的基址,编译器填写
0x1c DWORD ImageBase; *                 //01 00 00 00   内存镜像基址
0x20 DWORD SectionAlignment; *          //00 00 10 00   内存对齐 
0x24 DWORD FileAlignment; *             //00 00 02 00   文件对齐
0x28 WORD MajorOperatingSystemVersion;  //00 06         要求操作系统的最低版本号的主版本号
0x2a WORD MinorOperatingSystemVersion;  //00 01         要求操作系统的最低版本号的次版本号
0x2c WORD MajorImageVersion;            //00 06         该可执行文件的主版本号
0x2e WORD MinorImageVersion;            //00 01         该可执行文件的次版本号
0x30 WORD MajorSubsystemVersion;        //00 05         要求最低子系统版本的主版本号
0x32 WORD MinorSubsystemVersion;        //00 00         要求最低子系统版本的次版本号
0x34 DWORD Win32VersionValue;           //00 00 00 00   从来不用的字段,通常设置为0
0x38 DWORD SizeOfImage; *               //00 09 70 00   内存中整个PE文件的映射的尺寸
0x3c DWORD SizeOfHeaders; *             //00 00 04 00   所有头+节表按照文件对齐后的大小,必须是正确的,否则加载会出错
0x40 DWORD CheckSum; *                  //00 08 F0 A9   校验和,用来判断文件是否被修改
0x44 WORD Subsystem;                    //00 02         表明可执行文件所期望的子系统的枚举值
0x46 WORD DllCharacteristics;           //81 40         DllMain()函数何时被调用,默认为0
0x48 DWORD SizeOfStackReserve; *        //00 04 00 00   初始化时保留的堆栈大小
0x4c DWORD SizeOfStackCommit; *         //00 00 20 00   初始化时实际提交的大小
0x50 DWORD SizeOfHeapReserve; *         //00 10 00 00   初始化时保留的堆的大小
0x54 DWORD SizeOfHeapCommit; *          //00 00 10 00   初始化时实际提交的大小
0x58 DWORD LoaderFlags;                 //00 00 00 00   与调试有关,默认为0
0x5c DWORD NumberOfRvaAndSizes; *       //00 00 00 10   目录项数目
0x60 _IMAGE_DATA_DIRECTORY DataDirectory[16];
};

在32位程序中如下图所示:

IMAGE_OPTIONAL_HEADER64IMAGE_OPTIONAL_HEADER32不同的是IMAGE_OPTIONAL_HEADER64没有BaseOfData参数,并且ImageBase、SizeOfStackReserve、SizeOfStackCommit、SizeOfHeapReserve、SizeOfHeapCommit的大小为ULONGLONG,也就是64位8个字节。

64位可选PE头的基本结构如下:

struct _IMAGE_OPTIONAL_HEADER {
0x00 WORD Magic; *                      //02 0B         说明文件是ROM镜像(0107h),还是普通的可执行的镜像(010Bh)
0x02 BYTE MajorLinkerVersion;           //0B            链接程序的主版本号
0x03 BYTE MinorLinkerVersion;           //00            链接程序的次版本号
0x04 DWORD SizeOfCode; *                //00 01 82 00   所有代码节的和,必须为FileAlignment的整数倍,编译器填写
0x08 DWORD SizeOfInitializedData; *     //00 01 EE 00   已初始化数据大小的和,必须为FileAlignment的整数倍
0x0c DWORD SizeOfUninitializedData; *   //00 00 00 00   未初始化数据大小的和,必须为FileAlignment的整数倍
0x10 DWORD AddressOfEntryPoint; *       //00 00 2C 80   程序入口
0x14 DWORD BaseOfCode; *                //00 00 10 00   代码开始的基址,编译器填写
0x1c ULONGLONG ImageBase; *             //00 00 00 01 40 00 00 00  内存镜像基址
0x20 DWORD SectionAlignment; *          //00 00 10 00   内存对齐 
0x24 DWORD FileAlignment; *             //00 00 02 00   文件对齐
0x28 WORD MajorOperatingSystemVersion;  //00 06         要求操作系统的最低版本号的主版本号
0x2a WORD MinorOperatingSystemVersion;  //00 03         要求操作系统的最低版本号的次版本号
0x2c WORD MajorImageVersion;            //00 06         该可执行文件的主版本号
0x2e WORD MinorImageVersion;            //00 03         该可执行文件的次版本号
0x30 WORD MajorSubsystemVersion;        //00 06         要求最低子系统版本的主版本号
0x32 WORD MinorSubsystemVersion;        //00 03         要求最低子系统版本的次版本号
0x34 DWORD Win32VersionValue;           //00 00 00 00   从来不用的字段,通常设置为0
0x38 DWORD SizeOfImage; *               //00 03 B0 00   内存中整个PE文件的映射的尺寸
0x3c DWORD SizeOfHeaders; *             //00 00 04 00   所有头+节表按照文件对齐后的大小,必须是正确的,否则加载会出错
0x40 DWORD CheckSum; *                  //00 03 CE 33   校验和,用来判断文件是否被修改
0x44 WORD Subsystem;                    //00 02         表明可执行文件所期望的子系统的枚举值
0x46 WORD DllCharacteristics;           //C1 60         DllMain()函数何时被调用,默认为0
0x48 ULONGLONG SizeOfStackReserve; *    //00 00 00 00 00 08 00 00  初始化时保留的堆栈大小
0x50 ULONGLONG SizeOfStackCommit; *     //00 00 00 00 00 01 10 00  初始化时实际提交的大小
0x58 ULONGLONG SizeOfHeapReserve; *     //00 00 00 00 00 10 00 00  初始化时保留的堆的大小
0x60 ULONGLONG SizeOfHeapCommit; *      //00 00 00 00 00 00 10 00  初始化时实际提交的大小
0x68 DWORD LoaderFlags;                 //00 00 00 00   与调试有关,默认为0
0x6C DWORD NumberOfRvaAndSizes; *       //00 00 00 10   目录项数目
0x70 _IMAGE_DATA_DIRECTORY DataDirectory[16];
};

在64位程序中如下图所示:

在32位程序中可选PE头比较重要的参数有以下几个:

Magic:说明文件是ROM镜像(0107h),还是普通的可执行的镜像(010Bh),一般来说32位程序是010Bh,64位程序是020Bh。

SizeOfCode:存储了所有代码节的和,他必须是FileAlignment文件对齐的整数倍,此程序文件对齐的大小是200H,所以SizeOfCode的大小为1000H,为200H的整数倍,由编译器填写。

SizeOfInitializedData:存储已经初始化数据块的大小,即在编译的时候所构成的块的大小(不包括代码段),此值也为文件对齐的整数倍,由编译器填写。

SizeOfUninitializedData:未初始化数据块的大小,装载程序要在虚拟地址空间中为这些数据约定空间,一般存在.bss节中,为文件对齐的整数倍,由编译器填写。

AddressOfEntryPoint:可选PE头中最重要的一个参数,也就是我们通常说的OEP,是当前程序的入口位置,该地址是一个相对虚拟地址,指向了程序执行的第一条代码,如果程序被加壳,那么这个地址就会被修改,通常在使用OD进行动态调试的时候,OD首次停留的位置就是AddressOfEntryPoint。

BaseOfCode:代码开始的基址,在内存中,代码段通常在PE文件头之后,数据段开始之前,此值通常由编译器填写。

BaseOfData:数据开始的基址,数据段通常在内存的末尾,在64位程序中没有该参数,此值通常由编译器编写。

ImageBase:内存的镜像地址,也称基地址,是文件在内存中的首选装入地址,如果文件需要在内存中执行的话,会首先使用ImageBase中存放的地址,如果地址被占用,文件会被装入到其他地址中,因为直接装入这个地址不需要进行重定位,所以速度会很快,如果当前地址被占用就需要重定位后装入其他的地址,相对来说速度就会慢一些。

SectionAlignment:程序被装入内存后的对齐大小,通常为1000H。

FileAlignment:程序在没有被装入内存前文件对齐的大小,通常为200H或者1000H,在为1000H的时候文件对齐和内存对齐相同,会加快程序的运行速度。为200H时程序装载到内存中需要进行拉伸操作,把对齐大小拉伸到1000H,这样做相对来说速度会慢一些,但是在磁盘中存储会节省空间。

SizeOfImage:是程序在装入内存后的整个PE文件在内存中的映射尺寸,指的就是装入文件从ImageBase到最后一个块的大小,可以比实际的值大,但必须是SectionAlignment内存对齐的整数倍。

SizeOfHeaders:是DOS头,PE文件头和节表的总大小,该值必须是正确的,否则程序无法运行。

CheckSum:映像的校验和,可以用来检查文件是否被更改。

SizeOfStackReserve:初始化时保留的堆栈大小。

SizeOfStackCommit:初始化时实际提交的大小。

SizeOfHeapReserve:初始化时保留的堆的大小。

SizeOfHeapCommit:初始化时实际提交的大小。

NumberOfRvaAndSizes:数据目录的项数。

DataDirectory:数据目录表,由数个IMAGE_DATA_DIRECTORY结构组成,指向输出表、输入表、资源块等数据,如下图所示

fe40e5d34bab113ccf8f413d0697eaea.png

0x04 节表

节表大小为24H(40字节)

节表不止一个,可能有多个,节表的数量存在标准PE头中的NumberOfSections属性,虽然节表有多个,但是每个节表中的结构是相同的。

节表的基本结构如下:

typedef struct _IMAGE_SECTION_HEADER {
0x00 BYTE Name[IMAGE_SIZEOF_SHORT_NAME];     //00 00 00 74 78 65 74 2E 8字节,节表的名字,一般情况下""来结束,内容可以自己定义
union {  
	0x08 DWORD PhysicalAddress; 
	0x08 DWORD VirtualSize;                    
} Misc;                                      //00 01 80 6C   双字,是该节在没有对齐前的真是尺寸,内容可以不准确
0x0c DWORD VirtualAddress;                   //00 00 10 00   节区在内存中的偏移地址
0x10 DWORD SizeOfRawData;                    //00 01 82 00   节在文件中对齐后的尺寸
0x14 DWORD PointerToRawData;                 //00 00 04 00   节区在文件中的偏移
0x18 DWORD PointerToRelocations;             //00 00 00 00   在exe文件中无意义
0x1c DWORD PointerToLinenumbers;             //00 00 00 00   在exe文件中无意义
0x20 WORD NumberOfRelocations;               //00 00         在exe文件中无意义
0x22 WORD NumberOfLinenumbers;               //00 00         该节在行号表中的行号数
0x24 DWORD Characteristics;                  //60 00 00 20   节的属性
};

Name:存储节表的名字,一般情况下‘.’开始,""来结束,内容可以自定义,所以并不能完全靠Name参数来判断节表中的内容。

VirtualSize:实际使用的节的大小,也就是对齐前的节的大小。如果VirtualSize的值大SizeOfRawData,那么SizeOfRawData表示来自可执行文件初始化数据的大小,与VirtualSize相差的字节用0填充。

VirtualAddress:存储节区在内存中的偏移地址,需要加上ImageBase才是真实的地址。

SizeOfRawData:节在文件中的尺寸,也就是该节在磁盘中所占用的空间。

PointerToRawData:节区在文件中的偏移。

Characteristics:节的属性,通过属性表里的值相加得到。

9f1a4ebbb644711176733cd47592353e.png

本例中节的属性值为60 00 00 20H,也就是包含可执行代码+该块可执行+该块可读。

节表在程序中如下图所示:

6ea3c9e6e68b796998b515d059ca0ff0.png

0x05 简单的32位PE头解析器编写

//程序打印DOS头,PE头和所有的节表,代码比较简单,主要是便于理解DOS头,PE头和所有的节表
#include "stdafx.h"
#include "malloc.h"
#include "windows.h"

LPVOID ReadPEFile(LPSTR lpszFile){
	FILE* pFile = NULL; 
	DWORD FileSize = 0;
	LPVOID pFileBuffer = NULL;

	pFile = fopen(lpszFile,"rb");
	if(!pFile){
		printf("无法打开exe文件");
		return NULL;
	}

	fseek(pFile,0,2);
	FileSize = ftell(pFile);
	fseek(pFile,0,0);

	pFileBuffer = malloc(FileSize);
	if(!pFileBuffer){
		printf("初始化空间失败");
		fclose(pFile);
		return NULL;
	}

	size_t n = fread(pFileBuffer,FileSize,1,pFile);
	if(!n){
		printf("读取数据失败");
		free(pFileBuffer);
		fclose(pFile);
		return NULL;
	}

	fclose(pFile);
	return pFileBuffer;

}

VOID PrintSectionHeader(){
	LPVOID pFileBuffer = NULL;
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pPEHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSeationHeader = NULL;
	int SectionsCounts = 0;

	pFileBuffer = ReadPEFile("xxxxx/notepad.exe");
	if(!pFileBuffer){
		printf("读取文件失败");
		return ;
	}

	if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE){
		printf("不是有效的MZ标志\n");
		free(pFileBuffer);
		return ;
	} 

	pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;

	if(*((PWORD)((DWORD)pFileBuffer + pDosHeader -> e_lfanew)) != IMAGE_NT_SIGNATURE){
		printf("不是有效的PE标志\n");
		free(pFileBuffer);
		return ;
	}

	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader -> e_lfanew);
	printf("****************NT头***************\n");
	printf("NT:%x\n\n",pNTHeader -> Signature);
	pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
	printf("****************PE头***************\n");
	printf("PE:%x\n",pPEHeader -> Machine);
	printf("节的数量:%x\n",pPEHeader -> NumberOfSections);
	printf("可选PE头的大小:%x\n",pPEHeader -> SizeOfOptionalHeader);
	SectionsCounts = pPEHeader -> NumberOfSections;
	printf("节表数:%x\n\n",SectionsCounts);
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER);
	printf("****************OPTION_PE头***************\n");
	printf("OPTION_PE: %x\n\n",pOptionHeader -> Magic);
	pSeationHeader = (PIMAGE_SECTION_HEADER)(((DWORD)pOptionHeader) + pPEHeader -> SizeOfOptionalHeader);
	printf("****************节表***************\n");

	for (int i = 0; i < SectionsCounts; i++,pSeationHeader++){
		printf("节表%d名字:%s\n",(i + 1),pSeationHeader);
		printf("Misc:%x\n",pSeationHeader -> Misc);
		printf("VirtualAddress:%x\n",pSeationHeader -> VirtualAddress);
		printf("SizeOfRawData:%x\n",pSeationHeader -> SizeOfRawData);
		printf("PointerToRawData:%x\n",pSeationHeader -> PointerToRawData);
		printf("Characteristics:%x\n\n",pSeationHeader -> Characteristics);
	}
	free(pFileBuffer);
}

int main(int argc, char* argv[])
{
	PrintSectionHeader();
	getchar();
	return 0;
}

本文作者:TideSec

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/178258.html


文章来源: https://www.secpulse.com/archives/178258.html
如有侵权请联系:admin#unsafe.sh