1. 前述

在 NT 头结束后,紧接着就是区块表,区块表包含每个块在映象中的信息,分别指向不同的区块实体。

2. 区块表

区块表是一个 IMAGE_SECTION_HEADER 结构数组,这个结构包含区块的信息,比如位置、长度、属性等,区块的数目是由 NT 头中的文件头里的 NumberOfSections 给出。以下为 IMAGE_SECTION_HEADER 结构:

在上述图中,有两个字段比较重要,分别为 VirtualAddress、PointerToRawData,这两个字段用于将相对虚拟地址或虚拟地址转换为文件偏移地址,以下为 RVA 转 FOA 函数:

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
// RVA 转 FOA
DWORD RVAtoFOA(DWORD dwRva)
{
// 获取区段表的数量
DWORD dwCounts = g_NtHeader->FileHeader.NumberOfSections;

// 获取区段表数组的首元素
auto Sections = IMAGE_FIRST_SECTION(g_NtHeader);

// 遍历所有的区段表找到符合要求的区段
for (DWORD i = 0; i < dwCounts; ++i)
{
// 要求:RVA >= 区段的首地址 并且 RVA < 区段的结尾的地址
if (dwRva >= Sections[i].VirtualAddress &&
dwRva < (Sections[i].VirtualAddress + Sections[i].SizeOfRawData))
{
// FOA = VA - ImageBase - (所在区段的 RVA - 所在区段的 FOA)
// FOA = RVA - 所在区段的 RVA + 所在区段的 FOA
return dwRva - Sections[i].VirtualAddress + Sections[i].PointerToRawData;
}
}

// 如果找不到就返回 -1
return -1;
}

计算公式为:FOA = VA - ImageBase - (所在区段的 RVA - 所在区段的 FOA)FOA = RVA - 所在区段的 RVA + 所在区段的 FOA

在上述代码中,有一个为 IMAGE_FIRST_SECTION,我们来看下它的定义,如下:

其实 IMAGE_FIRST_SECTION 为一个宏,它主要由三部分相加组成,作用是获取到第一个区段的首地址,参数为 NT 头。你可以把这个首地址理解成数组名,数组的首地址。在获取到了地址后,下面的 for 循环遍历所有的区段表找到符合要求的区段。这三部分内容具体如下:

IMAGE_NT_HEADERS 的起始地址
IMAGE_OPTIONAL_HEADER32 (PE 扩展头)在 IMAGE_NT_HEADERS 中的偏移
IMAGE_OPTIONAL_HEADER32 的大小

其中后两个加起来的大小恰好就是 IMAGE_NT_HEADERS 的大小,再跟第一个相加就得到区段表的地址了。看到这你可以会问,为什么不直接加上 IMAGE_NT_HEADERS 的大小呢?因为 IMAGE_OPTIONAL_HEADER32 大小不固定,32 位下该值为 0x00E0H,64 位下该值为 0x00F0H,并且用户还可以自定义其大小。

3. 额外说明

扩展头大小是由文件头中 SizeOfOptionalHeader 字段给出,FIELD_OFFSET 这个是给出 OptionalHeaderIMAGE_NT_HEADERS 结构中的偏移,如下:


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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include <windows.h>
#include <iostream>
#include <iomanip>
#include <time.h>

DWORD OpenPEFile(const char *);
BOOL IsPEFile(DWORD);
void ReadFileHeader();
void ReadOptionalHeader();
DWORD RVAtoFOA(DWORD);

DWORD g_FileAddr = 0;
PIMAGE_DOS_HEADER g_DosHeader = nullptr;
PIMAGE_NT_HEADERS g_NtHeader = nullptr;

int main()
{
// 获取文件地址
g_FileAddr = OpenPEFile("KDebugger.exe");

// 判断文件是否是一个有效的 PE 文件
if (!IsPEFile(g_FileAddr))
{
std::cout << "这不是一个有效的 PE 文件" << std::endl;
system("pause");
exit(EXIT_SUCCESS);
}

std::cout << "--------------------------------------" << std::endl;

// 解析文件头
ReadFileHeader();

std::cout << "--------------------------------------" << std::endl;

// 解析扩展头
ReadOptionalHeader();

system("pause");

return 0;
}

// 打开一个 PE 文件,并获取它的文件缓冲区内容返回
DWORD OpenPEFile(const char * pFilePath)
{
// 获取文件句柄
HANDLE hFile = CreateFileA(
pFilePath,
GENERIC_ALL,
NULL,
NULL,
OPEN_EXISTING,
NULL,
NULL
);

// 判断获取文件句柄是否成功
if (hFile == INVALID_HANDLE_VALUE)
{
std::cout << "打开文件失败!" << std::endl;
system("pause");
exit(EXIT_SUCCESS);
}

// 得到文件大小
DWORD dwFileSize = GetFileSize(hFile, NULL);

// 申请空间
BYTE * pFileBuffer = new BYTE[dwFileSize];

// 将文件内容读入到缓冲区
DWORD dwRealRead = 0;
ReadFile(hFile, pFileBuffer, dwFileSize, &dwRealRead, NULL);

// 关闭文件句柄
CloseHandle(hFile);

// 返回文件缓冲区
return (DWORD)pFileBuffer;
}

// 判断文件是否为有效的 PE 的文件
BOOL IsPEFile(DWORD dwBuffer)
{
// 获取 DOS 头
g_DosHeader = (PIMAGE_DOS_HEADER)dwBuffer;

// 判断该文件的 DOS 头的第一个字段是否为 IMAGE_DOS_SIGNATRUE
if (g_DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
{
// 如果不等于,则立即返回 FALSE
return FALSE;
}

// 获取 NT 头
g_NtHeader = (PIMAGE_NT_HEADERS)(dwBuffer + g_DosHeader->e_lfanew);

// 判断该文件的 NT 头的第一个字段是否为 IMAGE_NT_SIGNATRUE
if (g_NtHeader->Signature != IMAGE_NT_SIGNATURE)
{
// 如果不等于,则立马返回 FALSE
return FALSE;
}

// 当上面全部通过后,则说明为一个有效的 PE 文件,返回 TRUE
return TRUE;
}

// 解析文件头
void ReadFileHeader()
{
// 获取文件头
auto FileHeader = g_NtHeader->FileHeader;

// 进行时间转换
struct tm FileCreateTime;
errno_t err = gmtime_s(&FileCreateTime, (time_t *)&FileHeader.TimeDateStamp);

std::cout << "文件头解析" << std::endl;
std::cout << "运行平台:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.Machine << std::endl;
std::cout << "文件区块数目:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.NumberOfSections <<std::endl;

if (err)
{
std::cout << "无效的参数" << std::endl;
}
else
{
std::cout << "文件创建日期和时间:" << std::dec << FileCreateTime.tm_year + 1900 << "-"
<< FileCreateTime.tm_mon + 1 << "-"
<< FileCreateTime.tm_mday << " "
<< FileCreateTime.tm_hour + 8 << ":"
<< FileCreateTime.tm_min << ":"
<< FileCreateTime.tm_sec << std::endl;
}

std::cout << "指向符号表:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.PointerToSymbolTable << std::endl;
std::cout << "符号表中符号个数:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.NumberOfSymbols << std::endl;
std::cout << "IMAGE_OPTIONAL_HEADER 结构大小:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.SizeOfOptionalHeader << std::endl;
std::cout << "文件属性:0x" << std::hex << std::setfill('0') << std::setw(8) << FileHeader.Characteristics << std::endl;
}

// 解析扩展头
void ReadOptionalHeader()
{
// 获取扩展头
auto OptionalHeader = g_NtHeader->OptionalHeader;

std::cout << "扩展头解析" << std::endl;
std::cout << "标志:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.Magic << std::endl;
std::cout << "链接器主版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << (int)OptionalHeader.MajorLinkerVersion << std::endl;
std::cout << "链接器次版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << (int)OptionalHeader.MinorLinkerVersion << std::endl;
std::cout << "所有含有代码区块的总大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfCode << std::endl;
std::cout << "所有初始化区块大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfInitializedData << std::endl;
std::cout << "所有未初始化区块大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfUninitializedData << std::endl;
std::cout << "程序执行入口 RVA:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.AddressOfEntryPoint << std::endl;
std::cout << "代码区块起始 RVA:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.BaseOfCode << std::endl;
std::cout << "数据区块起始 RVA:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.BaseOfData << std::endl;
std::cout << "程序默认装入基地址:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.ImageBase << std::endl;
std::cout << "内存中区块的对齐值:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SectionAlignment << std::endl;
std::cout << "文件中区块的对齐值:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.FileAlignment << std::endl;
std::cout << "操作系统主版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MajorOperatingSystemVersion << std::endl;
std::cout << "操作系统次版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MinorOperatingSystemVersion << std::endl;
std::cout << "用户自定义主版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MajorImageVersion << std::endl;
std::cout << "用户自定义次版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MinorImageVersion << std::endl;
std::cout << "所需子系统主版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MajorSubsystemVersion << std::endl;
std::cout << "所需子系统次版本号:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.MinorSubsystemVersion << std::endl;
std::cout << "映像装入内存后的总尺寸:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfImage << std::endl;
std::cout << "Dos头、PE头、区块表总大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfHeaders << std::endl;
std::cout << "映像检验和:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.CheckSum<< std::endl;
std::cout << "文件子系统:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.Subsystem << std::endl;
std::cout << "显示DLL特性旗标:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.DllCharacteristics << std::endl;
std::cout << "初始化堆栈大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfStackReserve << std::endl;
std::cout << "初始化实际提交堆栈大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfStackCommit << std::endl;
std::cout << "初始化保留堆栈大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfHeapReserve << std::endl;
std::cout << "初始化实际保留堆栈大小:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.SizeOfHeapCommit << std::endl;
std::cout << "数据目录表项数:0x" << std::hex << std::setfill('0') << std::setw(8) << OptionalHeader.NumberOfRvaAndSizes << std::endl;

std::cout << "**************************************" << std::endl;
std::cout << "目录表" << std::endl;
std::cout << "RVA\t\t大小" << std::endl;

for (int i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; ++i)
{
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(8)
<< OptionalHeader.DataDirectory[i].VirtualAddress << "\t";
std::cout << "0x" << std::hex << std::setfill('0') << std::setw(8)
<< OptionalHeader.DataDirectory[i].Size << std::endl;
}
}

// RVA 转 FOA
DWORD RVAtoFOA(DWORD dwRva)
{
// 获取区段表的数量
DWORD dwCounts = g_NtHeader->FileHeader.NumberOfSections;

// 获取区段表数组的首元素
auto Sections = IMAGE_FIRST_SECTION(g_NtHeader);

// 遍历所有的区段表找到符合要求的区段
for (DWORD i = 0; i < dwCounts; ++i)
{
// 要求:RVA >= 区段的首地址 并且 RVA < 区段的结尾的地址
if (dwRva >= Sections[i].VirtualAddress &&
dwRva < (Sections[i].VirtualAddress + Sections[i].SizeOfRawData))
{
// FOA = VA - ImageBase - (所在区段的 RVA - 所在区段的 FOA)
// FOA = RVA - 所在区段的 RVA + 所在区段的 FOA
return dwRva - Sections[i].VirtualAddress + Sections[i].PointerToRawData;
}
}

// 如果找不到就返回 -1
return -1;
}

留言

⬆︎TOP