接上一篇的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); }
注释掉其他解析部分,运行结果如下图:
(五).说明:
解析过程中最为麻烦的是计算偏移,多算几次,多看下结构图就好了.都是那么过来的.
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。