千家信息网

win32下PE文件分析之NT头

发表于:2025-01-19 作者:千家信息网编辑
千家信息网最后更新 2025年01月19日,接上一篇的win32下PE文件分析之DOS头(一)win32中PE的NT:NT头是PE文件中标准PE头和可选PE头的总体称谓,还包含一个PE标识.下面是它在Visual C++ 6.0中WINNT.h
千家信息网最后更新 2025年01月19日win32下PE文件分析之NT头

接上一篇的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);                                                 }

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


(五).说明:

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




0