PE文件结构(八)

Last updated on October 7, 2023 pm

重定位表

程序装载过程

当我们双击一个exe的时候,操作系统会自动为这个exe分配4GB独立的虚拟空间,其中高2G作为运行操作系统内核任务,低2G才运行用户的代码。在拥有这个4GB空间后,操作系统就会把这个exe从文件状态拉伸然后复制到低2G空间中

而后则是copy系统DLL到高2G中。最后则是将eip置为EntryPoint,就可以运行整个exe了。

然而这里面有一个问题,就是每个PE文件都会优先按照其自身DLL的ImageBase来装载,那么就无法避免ImageBase冲突的状况。一旦发生ImageBase冲突就需要另找一个地方来装载。可若仅仅只是换一个地方来装载,又会带来另一个问题——地址改变。

在编译一个文件的时候,编译器往往会把一些全局变量或者函数调用的地址给写死,也就是按照预计的ImageBase来计算地址

比如图中所有的红下划线都对应一个地址,他们都是按照预计的ImageBase来加载计算的,但是如果没有按照ImageBase加载,那么这个地址就一定会有问题。

解决办法就是记录这些地址,在实际运行的时候根据ImageBase来修正这些地址,那么就需要一张表来记录这些需要修正的地址。这张表就叫做重定位表。

重定位表

找到重定位表

重定位表地址位于OptionalHeader的第六项,这个地址也是RVA,将其转化为FOA后就可以找到重定位表

重定位表结构

重定位表与其他表都有不同,它的结构体只有两个成员,一个记录基地址,一个记录块大小,每个结构体后都跟有许多个两字节数据,然后许多个这样的块紧密连在一起形成一个重定位表。可能你会问基地址是什么?块大小是什么?数据又是什么?下面就来解释每个成员的意义。

1
2
3
4
5
6
struct _IMAGE_BASE_RELOCATION{
DWORD VirtualAddress;
DWORD SizeOfBlock;
//一堆数据...(如果是最后一个这种结构,就只有RVA和SizeOfBlock,且都为0)
};

可以这么理解:一个重定位表中可能会有多个“块”,每个块的结构都是

  • 4字节VirtualAddress
  • 接着4字节SizeOfBlock
  • 最后是一堆数据,称其为具体项
  • 每个块的大小为SizeOfBlock(字节)。

最后一个块的RVA和SizeOfBlock都是0x00000000,表示重定位表结束

在一个exe中,需要重定位的地址可能有很多个,每个地址都是4字节,如果全部都完完全全按照4字节来存储,显然会占据很多空间。但是稍微观察一下会发现,许多需要重定位的地址都比较相近,比如上面所举的例子。

在图中需要重定位的地址就是 6DF03,6DF0B,6DF1A等等(注意是需要重定位的地址,操作码是不需要改变的),它们都是6D开头,就会很自然诞生一种想法就是以6D000为基地址,在基地址上加一个偏移来找到具体的需要重定位的地址。那么这个基地址就是VirtualAddres,偏移则记录在结构体后的数据中,也就是那些个01数据。

现在来思考这样一个问题,那些记录偏移的01数据都是两字节的,也就是16位,能够记录 $2^{16} = 65536$ 个数据,然而在内存中这些模块间对齐粒度都是64KB即0x1000,也就是所有的基地址都是以0x1000来对齐的。16位的偏移显然超过了这个基地址,只需要用到12位就足以表示一个基地址的所有偏移了。因此16位的偏移数据会多出4位,于是就规定这16位里面高4位为有效位,只有当高四位等于3的时候才说明这个地址需要重定位,低12位则记录偏移。

因此一个需要重定位的地址就应该这样计算 if 具体项高4位 == 3 :地址 = VirtualAddress + 具体项低12位

成员SizeOfBlock意义就比较明确了,就是整个块的大小,包括VirtualAddress 的四字节,本身SizeOfBlock的四字节,以及下面的所有具体项的两字节。所以有多少个具体项就应该这样计算:(SizeOfBlock - 8)/2,注意这里的单位都是字节

代码遍历重定位表

代码中有注释

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
#include<stdio.h>
#include<windows.h>

DWORD Size(FILE* fp){
DWORD size = 0;
fseek(fp,0,SEEK_END);
size = ftell(fp);
fseek(fp,0,SEEK_SET);
return size;
}


DWORD RVA_TO_FOA(DWORD RVA,char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

if (RVA < OptionalHeader->SizeOfHeaders)
{
return RVA;
}

for(int i=0;i<FileHeader->NumberOfSections-1;i++){
if(RVA >= SectionHeader->VirtualAddress && RVA < (SectionHeader+1)->VirtualAddress){
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}
else{
SectionHeader +=1;
}
}

//最后一个节区
SectionHeader +=1;
DWORD offset = RVA - SectionHeader->VirtualAddress;
return SectionHeader->PointerToRawData + offset;
}



void Relocation_INFO(char* Buffer){
PIMAGE_DOS_HEADER DosHeader = NULL;
PIMAGE_NT_HEADERS NTHeader = NULL;
PIMAGE_FILE_HEADER FileHeader = NULL;
PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL;
PIMAGE_SECTION_HEADER SectionHeader = NULL;

DosHeader = (PIMAGE_DOS_HEADER)Buffer;
FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4);
OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER);
SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader);

//在目录项中找到重定位表地址
DWORD Relocation_Table_RVA = OptionalHeader>DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;
if (!Relocation_Table_RVA){
printf("This exe has not Relocation Table\n");
return;
}

//将重定位表地址转化FOA
DWORD Relocation_Table_FOA = RVA_TO_FOA(Relocation_Table_RVA,Buffer);

//找到第一个重定位表块
PIMAGE_BASE_RELOCATION Relocation_Table = (PIMAGE_BASE_RELOCATION)(Buffer + Relocation_Table_FOA);

//遍历所有重定位表
while(Relocation_Table->VirtualAddress && Relocation_Table->SizeOfBlock){
//先转为byte*,加8字节,然后转为Word*,这里就是代表每一个具体项
WORD* Reloc = (WORD*)((byte*)Relocation_Table + 8);
//算出有多少个据具体项
DWORD AddSize = (Relocation_Table->SizeOfBlock - 8) / 2;

//这里是计算需要重定位地址属于哪一个块,可加可不加
//********************************Section****************************************
for(int i = 0;i < FileHeader->NumberOfSections;i++){
if (Relocation_Table->VirtualAddress >= SectionHeader->VirtualAddress && Relocation_Table->VirtualAddress < (SectionHeader->VirtualAddress + SectionHeader->Misc.VirtualSize)){
printf("=====%s=====\n",SectionHeader->Name);
break;
}
else{
SectionHeader += 1;
}
}
//********************************Section****************************************

//这里就是遍历所有具体项
for(int i = 0; i < AddSize; i++){
//高4位是否为3
if((*(Reloc + i) >> 12 ) == 3){
//计算需要重定位地址的RVA
DWORD RVA = Relocation_Table->VirtualAddress + (*(Reloc + i) & 0xfff);
printf("RVA_Address:%X FOA_Address:%X\n",RVA,RVA_TO_FOA(RVA,Buffer));
}
}

//根据SizeOfBlock找到下一个重定位表块,同样也是需要先转为byte*再计算
Relocation_Table = (PIMAGE_BASE_RELOCATION) (((byte*)(Relocation_Table) + Relocation_Table->SizeOfBlock));
}

}

int main(){
char* buffer;

FILE* fp = NULL;
errno_t err_1 = fopen_s(&fp, "C:\\Users\\yongrin\\Desktop\\PE_Info.exe", "rb");

//将文件读取到程序中
int size = Size(fp);
buffer = (char*)malloc(size);
fread(buffer,size,1,fp);

Relocation_INFO(buffer);


return 0;
}

这里贴上我的代码运行部分截图

与前面的进行对比,会发现RVA_Address加上实际ImageBase 0x10000后就是需要重定位的地址啦。


PE文件结构(八)
http://example.com/2023/07/09/PE_7/
Author
yring
Posted on
July 9, 2023
Licensed under