avatar

目录
PE文件与虚拟内存的映射

PE文件与虚拟内存之间的映射

1.静态反汇编工具看到的PE文件中的某条指令是相对于磁盘文件的,即所谓的文件偏移,我们还需要知道这条指令在虚拟内存中的位置,即虚拟内存地址(VA)

2.在动态调试时,看到的某条指令的地址是虚拟内存地址

PE文件和虚拟内存地址之间的关系:

几个概念:

1.文件偏移地址(File Offset)

​ 数据在PE文件中的地址叫做文件文件偏移地址,是文件在磁盘上存放时相对于文件开头的偏移

2.装载基址(Image Base)

​ PE文件装入内存时的基址。默认情况下,EXE的基址为0x00400000,DLL的基址是0x10000000。这些位置可以通过修改编译选项更改。

3.虚拟内存地址(Virtual Address,VA)

​ PE文件中的指令被装入内存后的地址

4.相对虚拟地址(Relative Virtual Address,RVA)

​ 相对虚拟地址是内存地址相对于基址的偏移量,RVA=VA-Image Base

文件数据存放单位与内存数据存放单位的差异:

1.PE文件中的数据按照磁盘数据标准存放,以0x200字节为基本单位进行组织,当一个节(Section)不足0x200大小时,不足的地方将被0x00填充,当一个节大于0x200字节时,将把下一块0x200分配给这个节使用。因此PE数据节的大小永远是0x200的倍数。

2.当代码装入内存后,按照内存数据标准存放,以0x10000字节为基本单位进行组织。同理不足将补全,大于则分配下一个0x1000空间。因为,内存中的节总是0x1000的倍数。

由于内存中的数据节相对于装载基址的偏移量和文件中数据解的偏移量有差异,所以进行文件偏移到虚拟内存地址之间的换算时,还要看所转换的地址位于第几个节内。

这种由存储单位差异引起的节基址差叫做节偏移,上图中:

​ 节偏移 = RVA - PointerToRawData(文件偏移量)

Code
1
2
3
4
5
6
7
.text 节偏移 = 0x1000 - 0x400 = 0xcc

.rdata 节偏移 = 0x7000 - 0x6200 = 0xe00

.data节偏移 = 0x9000 - 0x7400 = 0x1c00

.rsrc节偏移 = 0x2d000 - 0x7800 = 0x25800

文件偏移地址与虚拟内存地址间的换算关系:

​ 文件偏移地址(File Offset) = 虚拟内存地址(VA)- 装载基址(Image Base) - 节偏移

​ = RVA - 节偏移

以上表为例,假如调试中虚拟内存中0x00404141处的一条指令,那么换算出这条指令在文件中的偏移量,则:

​ 文件偏移地址 = 0x00404141 - 0x00400000 - (0x1000 - 0x400)

​ = 0x3541

实现节偏移与文件偏移地址计算

c
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
#include <stdio.h>
#include <windows.h>
#define MAX_SIZE 256

int i;
int Address = 0x00000000;
char path[MAX_SIZE]; //文件路径
int secnum; //节表数
HANDLE hfile; //文件句柄
HANDLE hmapfile; //映射文件句柄
LPVOID ImageBase; //映射基址
PIMAGE_DOS_HEADER Pdh; //指向DOS头的指针
PIMAGE_NT_HEADERS Pnh; //指向NT头的指针
PIMAGE_FILE_HEADER Pfh; //指向Image_File的指针
PIMAGE_OPTIONAL_HEADER Poh; //指向IMAGE_OPTIONAL的指针
PIMAGE_SECTION_HEADER Psh; //指向Section Header的指针
PIMAGE_SECTION_HEADER pTmpSec;

int Getpath(void) //获得文件路径
{
printf("Input your path\n");
gets(path);
//printf("%s", path);

}
int mSecOffset(void) //计算节偏移
{

hfile = CreateFile(path, GENERIC_ALL, 0, 0, 3, FILE_ATTRIBUTE_NORMAL, 0);//打开文件
printf("%d", GetLastError());
hmapfile = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, 0, NULL); //创建文件内核映射对象
ImageBase = MapViewOfFile(hmapfile, FILE_MAP_READ|FILE_MAP_WRITE, 0, 0, 0); //映射到内存,得到基址
Pdh = (PIMAGE_DOS_HEADER)ImageBase;
Pnh = (PIMAGE_NT_HEADERS)((DWORD)Pdh + Pdh->e_lfanew);
Pfh = &Pnh->FileHeader;
Poh = &Pnh->OptionalHeader;
if (Pdh->e_magic != IMAGE_DOS_SIGNATURE) //判断是否为PE文件
{
printf("No PE file");
UnmapViewOfFile(ImageBase);
CloseHandle(hmapfile);
CloseHandle(hfile);
}
secnum = Pnh->FileHeader.NumberOfSections;//得到节区表数量
//指向Section Header
PIMAGE_SECTION_HEADER Psh = (PIMAGE_SECTION_HEADER)((DWORD) & (Pnh->OptionalHeader) + Pnh->FileHeader.SizeOfOptionalHeader);
for (i = 0; i < secnum; i++)
{
pTmpSec = Psh + i;//遍历节区表
printf("%s节的Voffset是%08X Roffset是%08X 节偏移是%08X\n\n", pTmpSec->Name,pTmpSec->VirtualAddress,pTmpSec->PointerToRawData,
pTmpSec->VirtualAddress-pTmpSec->PointerToRawData); //输出每个节的节偏移
}
pTmpSec = Psh; //.text段
printf("输入指令在内存中的地址");
scanf("%x", &Address);
//printf("File Offset是0X%08X\n", Address - Poh->ImageBase - (pTmpSec->VirtualAddress - pTmpSec->PointerToRawData));
for (i = 0; i < secnum; i++)
{
pTmpSec = Psh + i;
DWORD SectionBegin, SectionEnd;
SectionBegin = pTmpSec->VirtualAddress; //节区开始地址
SectionEnd = pTmpSec->VirtualAddress + pTmpSec->SizeOfRawData; //节区结束地址
if (Address>SectionBegin && Address<SectionEnd); //判断在哪一个节区
{
//输出文件偏移地址
printf("File Offset是0X%08X\n", Address - Poh->ImageBase - (pTmpSec->VirtualAddress - pTmpSec->PointerToRawData));
break;
}
}

}

int main()
{
Getpath();
mSecOffset();
system("pause");
}
文章作者: Yenn_
文章链接: https://0xdf1001f.github.io/2020/04/24/PE%E6%96%87%E4%BB%B6%E4%B8%8E%E8%99%9A%E6%8B%9F%E5%86%85%E5%AD%98%E7%9A%84%E6%98%A0%E5%B0%84/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Wei's Blog

评论