接上一篇的win32下PE文件分析之DOS头

(一)win32中PE的NT:

    NT头是PE文件中标准PE头和可选PE头的总体称谓,还包含一个PE标识.下面是它在Visual C++ 6.0中WINNT.h中的定义:

typedef struct _IMAGE_NT_HEADERS64 {    DWORD Signature;    IMAGE_FILE_HEADER FileHeader;    IMAGE_OPTIONAL_HEADER64 OptionalHeader;} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;typedef struct _IMAGE_NT_HEADERS {    DWORD Signature;                        //PE标识    IMAGE_FILE_HEADER FileHeader;           //标准PE头(也称文件头)    IMAGE_OPTIONAL_HEADER32 OptionalHeader; //可选PE头} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

    第一个是64bit的NT头定义,第二个是32bit的.这里只探讨32bit的.标准PE头也叫文件头,这不重要,知道是那么个东西就行了,个人不太喜欢动不动就用高端名词,高端名词主要是为了严谨而取出来的,但是很多时候很晦涩,通俗易懂更易让人接受.

(二).NT头中的Signature:

    这就是一个PE标识,说明这是PE的开始位置.它在PE文件中的偏移由DOS头中的最后一个成员e_lfanew决定,上一节解析了它的值为:0xE0,如图:

(三).NT头中的标准PE头:

    (1).NT头中的标准PE头数据宽度是0x14个字节,在Visual C++ 6.0中的结构定义如下:

typedef struct _IMAGE_FILE_HEADER {    WORD    Machine;    WORD    NumberOfSections;    DWORD   TimeDateStamp;    DWORD   PointerToSymbolTable;    DWORD   NumberOfSymbols;    WORD    SizeOfOptionalHeader;    WORD    Characteristics;} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

    (2).代码的文件结构如下图:每个解析头函数定义分别放在不同的头文件中,方便逐个头结构的观察,为了可以多熟悉即便各个头的数据结构,每个函数中都重新从头开始解析了一遍,这样效率会降低.后面如果有空,会提供优化了的代码.(优化思路:在解析一开始,就将各个头的地址放进一个unsigned long*的全局数组里面,这样在后面是用的时候就直接调用数组里的地址,而不用每次都重新定义DOS头结构再依计算出各个结构的偏移.)

 

    (3).解析文件头的代码如下(代码中都只是输出部分重要的数据,博客中代码列数的限制,注释一行放不下,不美观):

    file.h

void Output_File(void* buffer){	void* buf = buffer;	//计算偏移时有用	IMAGE_DOS_HEADER* pdos = (IMAGE_DOS_HEADER*)buf;			//pnt存放NT头的地址.	IMAGE_NT_HEADERS32* pnt = (IMAGE_NT_HEADERS32*)((unsigned char*)buf + pdos->e_lfanew);		//pfile存放NT头中标准PE头结构所在地址.	IMAGE_FILE_HEADER* pfile = (IMAGE_FILE_HEADER*)&pnt->FileHeader;		printf("\nNT Header:\n");	//PE标识,值与PE的ascii码一一对应	printf("PE:	%#X\n", pnt->Signature);		printf("File Header:\n");	//输出程序能在哪种CPU平台上运行.	printf("Machine:	%#X\n", pfile->Machine);	//输出PE文件中节的数量	printf("NumberOfSec:	%#X\n", pfile->NumberOfSections);	//时间戳,文件的创建时间,一般有编译器填充,修改后不会影响程序运行	printf("TimeStamp:	%#X\n", pfile->TimeDateStamp);		//可选PE头的大小(32bit默认是0xE0,64bit默认是0xF0)		printf("SizeOfOpHdr:	%#X\n", pfile->SizeOfOptionalHeader);	//该文件的属性(标识给文件的类型,如是exe还是dll或其他)		printf("Characteristics:	%#X\n", pfile->Characteristics);	}

    注释掉其他头的解析:运行结果如下:

(四).NT头中的可选PE头:

    可选PE头结构挺复杂,也最重要,32bit的和64bit的有点不同.在Visual C++ 6.0中winnt.h中定义如下:

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;

    解析文件头的代码如下:

void Output_Optional(void* buffer){	void* buf = buffer;	//计算偏移时使用	IMAGE_DOS_HEADER* pdos = (IMAGE_DOS_HEADER*)buf;		//pop中存放可选PE头的起始位置, 0x4是NT头中PE表示,0x14是NT头中的标准PE头,根据结构计算出可选PE头的偏移	IMAGE_OPTIONAL_HEADER32* pop = (IMAGE_OPTIONAL_HEADER32*)((unsigned char*)buf + pdos->e_lfanew + 0x4 + 0x14);	printf("Optional PE Header:\n");	//说明文件的类型,010B为32bit的PE文件,020B为64bit的PE文件	printf("Magic:				%#X\n", pop->Magic);	//所有代码节的和,大小必须是FileAlignment的整数倍,有编译器填充,修改无影响.	printf("SizeOfCode:			%#X\n",pop->SizeOfCode);		//已经初始化数据大小的和.	printf("SizeOfinitializedData:		%#X\n",pop->SizeOfInitializedData);	//未初始化数据大小的和.	printf("SizeOfuninitializedData:	%#X\n",pop->SizeOfUninitializedData);		//程序入口OEP	printf("AddressOfEntryPoint:		%#X\n",pop->AddressOfEntryPoint);	//内存镜像基址	printf("ImageBase:			%#X\n", pop->ImageBase);			//内存对齐				printf("SectionAlignment:		%#X\n", pop->SectionAlignment);		//文件对齐	printf("FileAlignment:			%#X\n", pop->FileAlignment);	//内存中整个PE文件的映射大小,可比实际的大,必须为内存对齐的整数倍				printf("SizeOfImage:			%#X\n", pop->SizeOfImage);	//所有头+节表文件对齐后的大小,严格按照文件对齐.	printf("SizeOfHeaders:			%#X\n", pop->SizeOfHeaders);	//校验和,用来判断文件是否被篡改.比如系统的一些dll加载时会用到.				printf("CheckSum:			%#X\n", pop->CheckSum);							}

    注释掉其他解析部分,运行结果如下图:

(五).说明:

    解析过程中最为麻烦的是计算偏移,多算几次,多看下结构图就好了.都是那么过来的.