PE第二课——PE头解析
课堂笔记 PE 头我们能在 010 editor 打开exe后面可以很明显的看到有一个 PE
这就是 PE 头所在的位置。上节课我们也提到了一个 MS-DOS头的最后一个字段 e_lfanew 其实就是我们 PE 头所在的位置,就是这个 PE 文件的字节地址。我们可以看到最后是 0x80,而 PE 头也是在 0x80 的地方开始的。
PE头 PE 头分为标准 PE 头(必要)和扩展 PE 头(不必要)。
PE 头结构在 visual studio 中的结构体为 _IMAGE_NT_HEADERS。
它的定义为
1 2 3 4 5 typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
这里的 Signature 就是 5045,也就是我们看到的 PE。
IMAGE_FILE_HEADER 就是标准 NT 头
IMAGE_OPTIONAL_HEADER32 是可选、扩展头。
有一个宏定义叫 IMAGE_NT_SIGNATURE
这里的宏定义就是它们的一些魔术字的默认值。
定义如下
1 2 3 4 5 6 7 8 9 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;
Machine:代表了这个程序运行的架构,这里也有宏来表示它的架构
NumberOfSections:表示了区段数量,常见的有:.text .code——代码段、.data——可读写的段、.rdata——只读数据段、.idata——导入数据段、.edata——导出数据段、.rsrc——资源段、.bss——未初始化数据段、.reloc——可重定向数据段。
TimeDateStamp:文件创建时间的时间戳。
PointerToSymbolTable:符号表指针,目前用的不多了。
NumberOfSymbols:符号表数量
SizeOfOptionalHeader:计算扩展头的大小。
Characteristic:PE文件的属性。
然后这个扩展/可选头就比较讲究了,它有很多的字段。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 typedef struct _IMAGE_OPTIONAL_HEADER { WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; 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;
MAGIC:魔术头
MajorLinkerVersion:链接器主版本号
MinorLinkerVersion:链接器次版本号
SizeOfCode:后面某个段的大小
SizeOfInitializedData:初始化的大小
SizeOfUninitializedData:未初始化的大小
AddressOfEntryPoint:程序执行入口的RVA(Relative Virtual Address)相对虚拟内存地址的偏移量,FOA:文件偏移地址。它并不指向 main 或者 dllmain 函数,而是指向了运行时库的一段代码,运行时库来调用 main 或 dllmain 函数。
BaseOfCode:代码段的 RVA,一般为 0x1000
BaseOfData:数据段的 RVA,通常在代码段后面。
ImageBase:文件首选装入地址
SectionAlignment:段对齐大小
FileAlignment:文件对齐的大小
MajorSystemVersion:操作系统主版本号
MinorSystemVersion:操作系统次版本号
MajorImageVersion:文件主版本号
MinorImageVersion:文件次版本号
MajorSubSystemVersion:最低操作系统主版本号
MinorSubSystemVersion:最低操作系统次版本号
Win32Version:保留值,必须为0
SizeOfImage:印象文件装入内存的总大小
SizeOfHeader:MS-DOS头和这个头+区段表的总大小。
CheckSum:文件校验和,可执行文件不校验,dll,sys必须校验。
SubSystem:所期望子系统的值,宏:IMAGE_SUBSYSTEM_NATIVE
DllCharacteristics:动态链接库所需要的
SizeOfStackReserve:为线程保存的堆栈大小
SizeOfStackCommit: 栈初始化内存大小,默认4KB
SizeOfHeapReserve:为堆保存的大小
SizeOfHeapCommit:堆初始化大小,默认4KB
LoaderFlags:与调试相关内容,默认为 0。
NumberOfRvaAndSizes:数据目录成员的数量,默认为 16.
代码 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <windows.h> #include <stdio.h> int main () { FILE* fd = fopen ("C:\\Users\\xia0ji233\\Desktop\\Home\\C++\\test.exe" , "rb" ); char * buffer = (char *)malloc (0x10000 ); if (buffer == NULL ) { perror ("malloc fail" ); exit (0 ); } if (fd == NULL ) { perror ("NO such File" ); exit (0 ); } fread (buffer, 1 , 0x10000 , fd); PIMAGE_DOS_HEADER pheader = (PIMAGE_DOS_HEADER)buffer; printf ("MS-DOS INFO:\n" ); printf ("MAGIC HEADER: " ); fwrite ((char *)&pheader->e_magic, 2 , 1 , stdout); putchar (10 ); printf ("PE OFFSET:%x\n" , pheader->e_lfanew); printf ("PE INFO:\n" ); PIMAGE_NT_HEADERS ReadNTHeaders=(PIMAGE_NT_HEADERS)(&(buffer[pheader->e_lfanew])); printf ("PE Magic Header:" ); fwrite (&ReadNTHeaders->Signature, 2 , 1 , stdout); putchar (10 ); printf ("Standard Header info:" ); printf ("Platform:" ); switch (ReadNTHeaders->FileHeader.Machine) { case IMAGE_FILE_MACHINE_I386: printf ("I386" ); break ; case IMAGE_FILE_MACHINE_IA64: printf ("Intel 64" ); break ; case IMAGE_FILE_MACHINE_AMD64: printf ("AMD 64" ); break ; default : printf ("UNKnown Platform" ); break ; } putchar (10 ); printf ("Optional PE Header:" ); printf ("ImageBase:%08x\n" , ReadNTHeaders->OptionalHeader.ImageBase); system ("pause" ); }
运行结果