0%

PE结构-VA与FOA的转化

VA转FOA

相应名词概念

对应结构体成员 英文全称 含义
VA _IMAGE_SECTION_HEADER.VirtualAddress Virtual Address 在内存中的虚拟地址
RVA _IMAGE_SECTION_HEADER.VirtualAddress Relative Virtual Address 相对虚拟地址
FOA _IMAGE_SECTION_HEADER.PointerToRawData File Offset Address 文件偏移地址

为什么需要学习VA与FOA之间的转换

此处需要引入一个问题:如何改变一个全局变量的初始值

  • 如果一个全局变量有初始值,那么他的初试值一定是存储于PE文件中的。
  • 如果一个全局变量没有初始值,那么在PE文件中就没有存储它的位置,只有当PE文件加载到内存中时,才会给它分配空间

修改程序的数据时,如果不懂的如何转换VA与FOA,我们在修改内存全局变量的时候就只能通过CE等辅助工具进行搜索修改了。

全局变量初始值Demo

利用c语言输出全局变量的值与地址。

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int global = 0x610;
int main(int argc, char* argv[])
{
//输出全局变量地址
printf("address:%X\n", &global);
//输出全局变量的值
printf("value:0x%X\n", global);
//暂停一下,防止窗口运行完自动关闭
getchar();
return 0;
}

image-20211014123921284

可以看到程序将全局变量地址与值打印到了dos窗口之上

修改全局变量的初始值

注意,在调试的时候,要选择低版本windows 如windows 2008,高版本windows启用随机内存地址保护,不利于我们调试,一开始踩了这个大坑,又空耗了1个多小时….

此时我们可以看到在运行时,全局变量的地址为0A425A30

此时我们如果使用16进制编辑器去寻找这个地址,我们是无法发现全局变量的。

这是由于PE文件在载入内存中时的地址是虚拟地址(VA)

VA = ImageBase + RVA

也就是说虚拟地址 = PE文件载入内存中后的基地址 + 相对虚拟地址(相对于基地址看齐)

而基地址是ImageBase字段的值,这点我们可以从PE头中获知

于是我们可以计算得出 RVA = VA - ImageBase

而其在PE文件中的地址为FOA(文件偏移地址)

最终问题就也就变成了 RVA与FOA的转换

通俗点来讲,就是说假设我们通过运行程序拿到RVA,想要获知FOA,就需要通过一定手段利用RVA来获知FOA,从而定位到我们想要的一个函数或变量的地址

VA到FOA转换流程

1.得到RVA的值: RVA = VA - ImageBase

2.判断RVA是否在PE文件头内

  • 如果在,则FOA=RVA
  • 如果不在,判断RVA在哪个节(区块),差值 = RVA - 区块.virtualAddress(RVA), FOA = 区块.PointerToRawData + 差值

image-20211014114852057

根据流程转换

1.拿到RVA的值: RVA = VA - ImageBase

首先拿到ImageBase值

利用StudyPE来查看

image-20211014124137197

显然,ImageBase的值为00400000

于是可以算出 RVA = VA - ImageBase = 425a30h - 400000h = 25A30h

2.判断RVA是否位于PE文件头之内

可以看到PE文件头的地址在F7h处结尾,而25A30h远远大于,因此不在pe文件头处

image-20211014125244591

很显然,1F600已经超出了PE文件的最大范围,这证明我们定位的RVA ≠FOA

3.判断RVA属于那一节

根据RVA > = 区块.VirtualAddress

RVA < 区块.VirtualAddress + 当前节内存对齐后

1
2
3
RVA>=节.VirtualAddress

RVA<节.VirtualAddress + 当前节内存对齐后的大小=节.VirtualAddress +[(Max{节.Misc,节.SizeOfRawData})÷SectionAlignment]向上取整×SectionAlignment
  • 节.sizeOfRawData 是节文件对齐后的大小
  • 节.Misc是节的实际大小

内存对齐后的大小 = [Max{实际的大小,文件对齐后的大小}÷内存对齐]向上取整×内存对齐

向上取整的意思就是 如果除后的结果为整数就直接为结果,如果除后的结果带小数则取整然后加一

例子:[5÷2]向上取整= 2.5取整+1=2+1=3,[4÷2]向上取整=2

根据工具可知,RVA = 0X25A30 处于.idata区块之中

image-20211014125443850

image-20211014130005638

程序在内存中的对齐基数为SectionAlign设置的0x1000

其实际大小 = 区块.Misc 在 StudyPE中显示为V.Size = 0x5604

Max{节.Misc,节.SizeofRawData} = Max{0x5604,0x4000} = 0x5604

内存对齐后的大小 = {0x5604 / 内存对齐} 向上取整*内存对齐 = (0x5604/0x1000)向上取整 * 0x1000 = (5 + 1 ) * 0x1000 = 0x6000

RVA>=0x25000

RVA<0X25000 + 内存对齐后的大小 = 0x25000 + 0x6000 = 0x2A000

差值 = RVA - 节.VirtualAddress = 0x25A30 - 0X25000 = 0XA30

PointerToRawData 在工具中显示为Offset,为0X25000

FOA = 节.PointerToRawData + 差值 = 0x25000 + 0XA30= 0x25A30

image-20211014131006511

由此,找到了数值地址,进行修改为65535,再次运行,发现值已被修改.

image-20211014131136540

利用代码实现VA转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
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
218
219
220
221
222
223
224
225
226
227
// PE.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <malloc.h>
#include <windows.h>
#include <winnt.h>
#include <math.h>
//在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义
#define IMAGE_FILE_MACHINE_AMD64 0x8664

//VA转FOA 32位
//第一个参数为要转换的在内存中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER *dos,_IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//得到RVA的值:RVA = VA - ImageBase
UINT rva = va - nt->OptionalHeader.ImageBase;
//输出rva
printf("rva:%X\n", rva);
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew+sizeof(_IMAGE_NT_HEADERS);
//输出PeEnd
printf("PeEnd:%X\n", PeEnd);
//判断rva是否位于PE文件头中
if (rva < PeEnd) {
//如果rva位于PE文件头中,则foa==rva,直接返回rva即可
printf("foa:%X\n", rva);
return rva;
}
else {
//如果rva在PE文件头外
//判断rva属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
//计算内存对齐后节的大小
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;

if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
//找到所属的节
//输出内存对齐后的节的大小
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
//未找到
printf("没有找到匹配的节\n");
return -1;
}
else {
//计算差值= RVA - 节.VirtualAddress
int offset = rva - sectionArr[i]->VirtualAddress;
//FOA = 节.PointerToRawData + 差值
int foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}

}

}

//VA转FOA 64位
//第一个参数为要转换的在内存中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//得到RVA的值:RVA = VA - ImageBase
UINT rva = va - nt->OptionalHeader.ImageBase;
//输出rva
printf("rva:%X\n", rva);
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
//输出PeEnd
printf("PeEnd:%X\n", PeEnd);
//判断rva是否位于PE文件头中
if (rva < PeEnd) {
//如果rva位于PE文件头中,则foa==rva,直接返回rva即可
printf("foa:%X\n", rva);
return rva;
}
else {
//如果rva在PE文件头外
//判断rva属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++) {
//计算内存对齐后节的大小
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize ,(UINT)sectionArr[i]->SizeOfRawData ) / (double)nt->OptionalHeader.SectionAlignment)* nt->OptionalHeader.SectionAlignment;

if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory)) {
//找到所属的节
//输出内存对齐后的节的大小
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections) {
//未找到
printf("没有找到匹配的节\n");
return -1;
}
else {
//计算差值= RVA - 节.VirtualAddress
int offset = rva - sectionArr[i]->VirtualAddress;
//FOA = 节.PointerToRawData + 差值
int foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}

}

}
int main(int argc, char* argv[])
{
//创建DOS对应的结构体指针
_IMAGE_DOS_HEADER* dos;
//读取文件,返回文件句柄
HANDLE hFile = CreateFileA("C:\\Users\\86156\\Desktop\\N1.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
//根据文件句柄创建映射
HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0);
//映射内容
LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
//类型转换,用结构体的方式来读取
dos = (_IMAGE_DOS_HEADER*)pFile;
//输出dos->e_magic,以十六进制输出
printf("dos->e_magic:%X\n", dos->e_magic);

//创建指向PE文件头标志的指针
DWORD* peId;
//让PE文件头标志指针指向其对应的地址=DOS首地址+偏移
peId = (DWORD*)((UINT)dos + dos->e_lfanew);
//输出PE文件头标志,其值应为4550,否则不是PE文件
printf("peId:%X\n", *peId);

//创建指向可选PE头的第一个成员magic的指针
WORD* magic;
//让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小
magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
//输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序
printf("magic:%X\n", *magic);
//根据magic判断为32位程序还是64位程序
switch (*magic) {
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
printf("32位程序\n");
//确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS* nt;
//让PE文件头指针指向其对应的地址
nt = (_IMAGE_NT_HEADERS*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);

//创建一个指针数组,该指针数组用来存储所有的节表指针
//这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**) malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

//创建指向块表的指针
_IMAGE_SECTION_HEADER* sectionHeader;
//让块表的指针指向其对应的地址
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
//计数,用来计算块表地址
int cnt = 0;
//比较 计数 和 块表的个数,即遍历所有块表
while(cnt< nt->FileHeader.NumberOfSections){
//创建指向块表的指针
_IMAGE_SECTION_HEADER* section;
//让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER)*cnt);
//将得到的块表指针存入数组
sectionArr[cnt++] = section;
//输出块表名称
printf("%s\n", section->Name);
}

VaToFoa32(0x4198B0,dos, nt, sectionArr);

break;
}

case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
printf("64位程序\n");
//确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS64* nt;
nt = (_IMAGE_NT_HEADERS64*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);

//创建一个指针数组,该指针数组用来存储所有的节表指针
//这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

//创建指向块表的指针
_IMAGE_SECTION_HEADER* sectionHeader;
//让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64));
//计数,用来计算块表地址
int cnt = 0;
//比较 计数 和 块表的个数,即遍历所有块表
while (cnt < nt->FileHeader.NumberOfSections) {
//创建指向块表的指针
_IMAGE_SECTION_HEADER* section;
//让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
//将得到的块表指针存入数组
sectionArr[cnt++] = section;
//输出块表名称
printf("%s\n", section->Name);
}
break;
}

default:
{
printf("error!\n");
break;
}

}
getchar();
return 0;
}

可以看到各个区块以及FOA RVA的值

image-20211014143505239

FOA转VA转换流程

首先,拿到FOA偏移量:0x25A30

image-20211014150205947

1.判断FOA是否在PE文件头之中

  • 如果在PE文件头内 FOA =RVA
  • 如果不在: 判断FOA位于哪个节,差值 = FOA - 节.PointerToRawData(R Offset) , RVA = 差值 + 节.VirtualAddress(RVA)

2.VA = ImageBase + RVA

image-20211014145843204

根据流程进行转换

1.判断FOA是否在文件头内

根据e_ifanew找到PE头,发现相距甚远

2.判断处于哪一区块

FOA>=节.PointerToRawData

FOA<节.PointerToRawData + 当前节文件对齐后的大小=节.PointerToRawData+节.SizeOfRawData

翻译一下就是在哪份区块之中

另外,利用FOA求VA时,由于是查找FOA在哪个区块中,因此要利用PointerToRawData(R Size)与 PointerToRawSize(R size)进行判断,而不是VirstualAddress(V Addr) 与 VirstualSize(V Size) 利用VA求FOA时则相反

image-20211014150609609

image-20211014151529385

比较可知,根据FOA = 0x25A30可知,FOA在.idata区块之中

差值 = FOR - 节.PointerToRawData = 0x25A30 - 0x25000 = 0xA30

RVA = 节.VirstualAddress + 差值 = 0x25000 + 0xA30 = 0x25A30

VA = ImageBase + RVA = 0x400000 + 0x25A30 = 0x425A30

运行程序检验,发现完全吻合!

image-20211014151728378

代码实现FOA 转 VA

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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
// PE.cpp : Defines the entry point for the console application.
//
#include <stdio.h>
#include <malloc.h>
#include <windows.h>
#include <winnt.h>
#include <math.h>
//在VC6这个比较旧的环境里,没有定义64位的这个宏,需要自己定义,在VS2019中无需自己定义
#define IMAGE_FILE_MACHINE_AMD64 0x8664

//VA转FOA 32位
//第一个参数为要转换的在内存中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT VaToFoa32(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//得到RVA的值:RVA = VA - ImageBase
UINT rva = va - nt->OptionalHeader.ImageBase;
//输出rva
printf("rva:%X\n", rva);
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS);
//输出PeEnd
printf("PeEnd:%X\n", PeEnd);
//判断rva是否位于PE文件头中
if (rva < PeEnd)
{
//如果rva位于PE文件头中,则foa==rva,直接返回rva即可
printf("foa:%X\n", rva);
return rva;
} else
{
//如果rva在PE文件头外
//判断rva属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
//计算内存对齐后节的大小
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize, (UINT)sectionArr[i]->SizeOfRawData) / (double)nt->OptionalHeader.SectionAlignment) * nt->OptionalHeader.SectionAlignment;

if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory))
{
//找到所属的节
//输出内存对齐后的节的大小
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections)
{
//未找到
printf("没有找到匹配的节\n");
return -1;
} else
{
//计算差值= RVA - 节.VirtualAddress
UINT offset = rva - sectionArr[i]->VirtualAddress;
//FOA = 节.PointerToRawData + 差值
UINT foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}

}

}

//VA转FOA 64位
//第一个参数为要转换的在内存中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT VaToFoa64(UINT va, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//得到RVA的值:RVA = VA - ImageBase
UINT rva = va - nt->OptionalHeader.ImageBase;
//输出rva
printf("rva:%X\n", rva);
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
//输出PeEnd
printf("PeEnd:%X\n", PeEnd);
//判断rva是否位于PE文件头中
if (rva < PeEnd)
{
//如果rva位于PE文件头中,则foa==rva,直接返回rva即可
printf("foa:%X\n", rva);
return rva;
} else
{
//如果rva在PE文件头外
//判断rva属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
//计算内存对齐后节的大小
UINT SizeInMemory = ceil((double)max((UINT)sectionArr[i]->Misc.VirtualSize, (UINT)sectionArr[i]->SizeOfRawData) / (double)nt->OptionalHeader.SectionAlignment) * nt->OptionalHeader.SectionAlignment;
if (rva >= sectionArr[i]->VirtualAddress && rva < (sectionArr[i]->VirtualAddress + SizeInMemory))
{
//找到所属的节
//输出内存对齐后的节的大小
printf("SizeInMemory:%X\n", SizeInMemory);
break;
}
}
if (i >= nt->FileHeader.NumberOfSections)
{
//未找到
printf("没有找到匹配的节\n");
return -1;
} else
{
//计算差值= RVA - 节.VirtualAddress
UINT offset = rva - sectionArr[i]->VirtualAddress;
//FOA = 节.PointerToRawData + 差值
UINT foa = sectionArr[i]->PointerToRawData + offset;
printf("foa:%X\n", foa);
return foa;
}

}

}

//FOA转VA 32位
//第一个参数为要转换的在文件中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT FoaToVa32(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS);
//判断FOA是否位于PE文件头中
if (foa < PeEnd)
{
//如果foa位于PE文件头中,则foa==rva,直接返回foa+ImageBase即可
printf("va:%X\n", foa + nt->OptionalHeader.ImageBase);
return foa + nt->OptionalHeader.ImageBase;
} else
{
//如果foa在PE文件头外
//判断foa属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++)
{

if (foa >= sectionArr[i]->PointerToRawData && foa < (sectionArr[i]->PointerToRawData + sectionArr[i]->SizeOfRawData))
{
//找到所属的节
break;
}
}
if (i >= nt->FileHeader.NumberOfSections)
{
//未找到
printf("没有找到匹配的节\n");
return -1;
} else
{
//计算差值= FOA - 节.PointerToRawData
UINT offset = foa - sectionArr[i]->PointerToRawData;
//RVA = 差值 + 节.VirtualAddress(RVA)
UINT rva = offset + sectionArr[i]->VirtualAddress;
printf("va:%X\n", rva + nt->OptionalHeader.ImageBase);
return rva + nt->OptionalHeader.ImageBase;
}
}
return 0;
}

//FOA转VA 64位
//第一个参数为要转换的在文件中的地址:VA
//第二个参数为指向dos头的指针
//第三个参数为指向nt头的指针
//第四个参数为存储指向节指针的数组
UINT FoaToVa64(UINT foa, _IMAGE_DOS_HEADER* dos, _IMAGE_NT_HEADERS64* nt, _IMAGE_SECTION_HEADER** sectionArr) {
//找到PE文件头后的地址 = PE文件头首地址+PE文件头大小
UINT PeEnd = (UINT)dos->e_lfanew + sizeof(_IMAGE_NT_HEADERS64);
//判断FOA是否位于PE文件头中
if (foa < PeEnd)
{
//如果foa位于PE文件头中,则foa==rva,直接返回foa+ImageBase即可
printf("va:%X\n", foa + nt->OptionalHeader.ImageBase);
return foa + nt->OptionalHeader.ImageBase;
} else
{
//如果foa在PE文件头外
//判断foa属于哪个节
int i;
for (i = 0; i < nt->FileHeader.NumberOfSections; i++)
{

if (foa >= sectionArr[i]->PointerToRawData && foa < (sectionArr[i]->PointerToRawData + sectionArr[i]->SizeOfRawData))
{
//找到所属的节
break;
}
}
if (i >= nt->FileHeader.NumberOfSections)
{
//未找到
printf("没有找到匹配的节\n");
return -1;
} else
{
//计算差值= FOA - 节.PointerToRawData
UINT offset = foa - sectionArr[i]->PointerToRawData;
//RVA = 差值 + 节.VirtualAddress(RVA)
UINT rva = offset + sectionArr[i]->VirtualAddress;
printf("va:%X\n", rva + nt->OptionalHeader.ImageBase);
return rva + nt->OptionalHeader.ImageBase;
}
}
return 0;
}

int main(int argc, char* argv[]) {
//创建DOS对应的结构体指针
_IMAGE_DOS_HEADER* dos;
//读取文件,返回文件句柄
HANDLE hFile = CreateFileA("C:\\Users\\86156\\Desktop\\N1.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
//根据文件句柄创建映射
HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0);
//映射内容
LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0);
//类型转换,用结构体的方式来读取
dos = (_IMAGE_DOS_HEADER*)pFile;
//输出dos->e_magic,以十六进制输出
printf("dos->e_magic:%X\n", dos->e_magic);

//创建指向PE文件头标志的指针
DWORD* peId;
//让PE文件头标志指针指向其对应的地址=DOS首地址+偏移
peId = (DWORD*)((UINT)dos + dos->e_lfanew);
//输出PE文件头标志,其值应为4550,否则不是PE文件
printf("peId:%X\n", *peId);

//创建指向可选PE头的第一个成员magic的指针
WORD* magic;
//让magic指针指向其对应的地址=PE文件头标志地址+PE文件头标志大小+标准PE头大小
magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER));
//输出magic,其值为0x10b代表32位程序,其值为0x20b代表64位程序
printf("magic:%X\n", *magic);
//根据magic判断为32位程序还是64位程序
switch (*magic)
{
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
{
printf("32位程序\n");
//确定为32位程序后,就可以使用_IMAGE_NT_HEADERS来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS* nt;
//让PE文件头指针指向其对应的地址
nt = (_IMAGE_NT_HEADERS*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);

//创建一个指针数组,该指针数组用来存储所有的节表指针
//这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

//创建指向块表的指针
_IMAGE_SECTION_HEADER* sectionHeader;
//让块表的指针指向其对应的地址
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS));
//计数,用来计算块表地址
int cnt = 0;
//比较 计数 和 块表的个数,即遍历所有块表
while (cnt < nt->FileHeader.NumberOfSections)
{
//创建指向块表的指针
_IMAGE_SECTION_HEADER* section;
//让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
//将得到的块表指针存入数组
sectionArr[cnt++] = section;
//输出块表名称
printf("%s\n", section->Name);
}

VaToFoa32(0x4198B0, dos, nt, sectionArr);
FoaToVa32(0x176B0, dos, nt, sectionArr);

break;
}

case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
{
printf("64位程序\n");
//确定为64位程序后,就可以使用_IMAGE_NT_HEADERS64来接收数据了
//创建指向PE文件头的指针
_IMAGE_NT_HEADERS64* nt;
nt = (_IMAGE_NT_HEADERS64*)peId;
printf("Machine:%X\n", nt->FileHeader.Machine);
printf("Magic:%X\n", nt->OptionalHeader.Magic);

//创建一个指针数组,该指针数组用来存储所有的节表指针
//这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组
_IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections);

//创建指向块表的指针
_IMAGE_SECTION_HEADER* sectionHeader;
//让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64
sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64));
//计数,用来计算块表地址
int cnt = 0;
//比较 计数 和 块表的个数,即遍历所有块表
while (cnt < nt->FileHeader.NumberOfSections)
{
//创建指向块表的指针
_IMAGE_SECTION_HEADER* section;
//让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小
section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt);
//将得到的块表指针存入数组
sectionArr[cnt++] = section;
//输出块表名称
printf("%s\n", section->Name);
}
VaToFoa32(0x4198B0, dos, (_IMAGE_NT_HEADERS *)nt, sectionArr);
FoaToVa32(0x176B0, dos, (_IMAGE_NT_HEADERS * )nt, sectionArr);
break;
}

default:
{
printf("error!\n");
break;
}

}
getchar();
return 0;
}

根据代码,我们可以.data区块的RVA值

image-20211014162356982

本文复现学习自52pojie.cn论坛的lyl610abc师傅PE文件笔记。

原文连接:

1
https://www.52pojie.cn/thread-1412395-1-1.html