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

这里的宏定义就是它们的一些魔术字的默认值。

IMAGE_FILE_HEADER

定义如下

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文件的属性。

_IMAGE_OPTIONAL_HEADER

然后这个扩展/可选头就比较讲究了,它有很多的字段。

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 {
//
// 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;
  • 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");
}

运行结果