作者:杨浩 | 来源:互联网 | 2023-09-15 14:12
接上一篇的win32下PE文件分析之DOS头(一)win32中PE的NT: NT头是PE文件中标准PE头和可选PE头的总体称谓,还包含一个PE标识.下面
接上一篇的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);
}
注释掉其他解析部分,运行结果如下图:
(五).说明:
解析过程中最为麻烦的是计算偏移,多算几次,多看下结构图就好了.都是那么过来的.