动态链接库的重定位表分析

重定位表的作用

重定位表用于在程序加载到内存时,修正程序的内存地址。通常,当程序在加载时,它的基地址可能与编译时的预定基地址不同,特别是对于动态链接库(DLL)来说,可能会出现多个程序或库文件冲突的情况。因此,必须修正程序中的地址引用,确保它们指向正确的位置。并不是所有的exe程序都有重定位表,但是DLL却是必须需要重定位信息。

为了提高搜索的速度,模块间地址也是要对齐的。模块地址对齐为10000H,也就是64K(64*1024)。

为什么DLL文件需要重定位表
大多数EXE文件不会使用重定位表,因为它们通常按固定基址(如0x400000)加载,不会和其他程序冲突。但DLL文件由于可能被多个程序同时加载,且加载地址可能不同,因此必须有重定位表来修正各个地方的内存地址,避免内存冲突。

为什么要用重定位表

在程序加载到内存时,程序中的地址(如全局变量的地址或函数的地址)是相对于编译时的预定基地址(ImageBase)计算的。也就是说,如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表。假设编译时程序的基地址设为0x400000,那么所有的内存地址(如全局变量地址、函数地址等)都基于这个地址计算。例如:

1
2
3
4
00401D58 A1 44 CA 42 00       mov         eax,[x (0042ca44)]  
00401D5D 50 push eax
00401D5E 68 EC 91 42 00 push offset string "%d\n" (004291ec)
00401D63 E8 28 62 00 00 call printf (00407f90)

在上述反汇编中,x(存储在0042ca44)和string "%d\n"(存储在004291ec)的地址是由编译器计算的,基于程序的预定基地址ImageBase)加上相对虚拟地址(RVA)

编译时计算的地址 = ImageBase + RVA

例如,如果程序在编译时将x的地址计算为0x0042CA44(基于ImageBase + RVA),那么编译后的文件就会直接存储这个地址。

假设程序没有按照预定的ImageBase加载:

如果程序在加载时没有按照预定的ImageBase(比如0x400000)加载到内存中,而是加载到了其他位置(如0x500000),那么程序中的地址(如0042ca44)就不再有效,因为它是基于0x400000的地址计算的。

此时,程序执行时会尝试访问这些存储在编译时地址中的值(例如,尝试访问0042ca44),但实际的加载地址并不在这个位置,导致程序无法找到这些地址对应的数据。

重定位表的作用就是在程序加载到内存后,修正这些地址,使它们指向正确的内存位置。通过重定位表,程序能够在加载时动态地调整各个地址,确保它们指向实际的内存位置。

重定位表的功能

  • 编译时,程序中使用的地址(如全局变量的地址)是基于编译时预设的ImageBase地址(如0x400000)。
  • 如果程序在加载时没有按照预设的ImageBase加载,那么地址就需要修正。此时,重定位表记录了哪些地址需要被修正。
  • 重定位表不仅记录了修正的位置,还能帮助程序定位到需要修正的地址和修正类型。
  • 编译时地址 = ImageBase + RVA:编译时的地址是基于预定的ImageBase加上RVA计算的。
  • 加载时地址不一定是预定的:如果程序在加载时没有按照预定的ImageBase加载,地址就需要进行修正。
  • 重定位表修正地址:通过重定位表,程序能够根据实际的加载地址,修正所有需要修改的地址,确保它们指向正确的内存位置。

重定位表的结构

重定位表的具体内容存在于程序的可执行文件中。通过ImageDataDirectory的第6个项,可以找到重定位表的位置。

重定位表的每一块数据包含以下结构:

1
2
3
4
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 需要修复的虚拟地址
DWORD SizeOfBlock; // 该块的大小
} IMAGE_BASE_RELOCATION;

每个IMAGE_BASE_RELOCATION块包含以下两项数据:

  • VirtualAddress:该块开始修正的地址。
  • SizeOfBlock:该块大小,包括虚拟地址和大小字段。
1
2
DWORD   VirtualAddress;
DWORD SizeOfBlock;

后续的每个条目则包含需要修正的地址的偏移信息。

image-20241230223834189

重定位表的解析

  • 定位重定位表:
    重定位表的地址可以通过IMAGE_DATA_DIRECTORY结构的VirtualAddress属性来找到。通过这个地址,可以逐个读取IMAGE_BASE_RELOCATION块。
  • 块的数量:
    重定位表中的块数量不固定,直到遇到最后一个块,其VirtualAddressSizeOfBlock为0,表示结束。
  • 条目的结构:
    每个条目有2字节,表示需要修改的地址偏移。具体项的高4位表示修正类型,值为3(0011)表示需要修改的地址,值为0表示数据对齐位置,不需要修改。因此,只需要关注高4位为3的条目。

image-20241230223924672

重定位表的具体工作原理

  • 节省内存:
    假设有大量的地址需要修正,如果单独存储每个地址的值会占用大量内存(例如,修正10000个地址需要40000字节)。为了节省内存,重定位表使用基地址来存储偏移量,减少存储空间的占用。
  • 页对齐:
    内存中的页大小通常为0x1000(4KB),每个页包含多个需要修正的地址。这些地址被按页分组,基地址通过VirtualAddress存储,后续的地址偏移存储在块内。
  • 计算修正的RVA:
    每个修正项的低12位加上VirtualAddress,得到真正需要修正的RVA(相对虚拟地址)。
  • 块的大小:
    每个块的SizeOfBlock字段表示该块的总大小。具体的修正项数目可以通过计算 (SizeOfBlock - 8) / 2 来确定。这里8字节是结构头部的大小,而每个条目占2字节。

示例

假设在重定位表中,有一个条目,VirtualAddress为0x1000,SizeOfBlock为0x20,表示这一块大小为32字节。具体的修正项是2字节一项,因此这块有12个需要修正的地址。

1
2
DWORD   VirtualAddress = 0x1000;
DWORD SizeOfBlock = 0x20;

根据块大小和条目的数量,程序通过修正这些地址,确保它们指向正确的内存位置。

总结

重定位表在程序加载过程中起到了至关重要的作用。通过重定位表,程序能够动态地修正内存地址,避免由于不同程序间加载位置冲突带来的问题。DLL文件由于需要支持多进程加载,因此几乎总是需要使用重定位表来确保地址的正确性。EXE文件较少需要重定位表,除非存在特殊的加载需求或地址冲突情况。

Work:打印重定位表

示例代码

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

LPVOID ReadPE(
IN LPCSTR lpszName
) {
FILE* file = nullptr;
fopen_s(&file, lpszName, "rb");
if (!file)
{
printf("打开文件失败!\n");
return nullptr;
}

fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, 0, SEEK_SET);

LPVOID fileBuff = malloc(size);
if (!fileBuff)
{
printf("申请内存空间失败!\n");
fclose(file);
return nullptr;
}
fread_s(fileBuff, size, 1, size, file);

WORD mz = *((PWORD)fileBuff);
if (mz != 0x5a4d)
{
printf("该文件不是pe可执行程序!\n");
fclose(file);
free(fileBuff);
return nullptr;
}

return fileBuff;
}


void AnlyzePE(
IN LPVOID pe,
OUT PIMAGE_DOS_HEADER& dos,
OUT PIMAGE_FILE_HEADER& file,
OUT PIMAGE_OPTIONAL_HEADER32& optional,
OUT PIMAGE_SECTION_HEADER*& section
) {
dos = (PIMAGE_DOS_HEADER)pe;
file = (PIMAGE_FILE_HEADER)((PCHAR)pe + dos->e_lfanew + 4);
optional = (PIMAGE_OPTIONAL_HEADER32)((PCHAR)pe + dos->e_lfanew + 4 + 20);
section = (PIMAGE_SECTION_HEADER*)malloc(file->NumberOfSections * sizeof(IMAGE_SECTION_HEADER));
if (section != nullptr)
{
for (int i = 0; i < file->NumberOfSections; i++)
{
*(section + i) = (PIMAGE_SECTION_HEADER)((PCHAR)pe + dos->e_lfanew + 4 + 20 + file->SizeOfOptionalHeader + (i * sizeof(IMAGE_SECTION_HEADER)));
}
}
}

int RvaToFoa(
IN LPVOID pe,
IN UINT_PTR rva
) {
PIMAGE_DOS_HEADER dos = nullptr;
PIMAGE_FILE_HEADER file = nullptr;
PIMAGE_OPTIONAL_HEADER32 optional = nullptr;
PIMAGE_SECTION_HEADER* section = nullptr;
AnlyzePE(pe, dos, file, optional, section);
DWORD foa = -1;

for (int i = 0; i < file->NumberOfSections; i++)
{
UINT_PTR begin = optional->ImageBase + (*section + i)->VirtualAddress;
UINT_PTR end = optional->ImageBase + (*section + i)->VirtualAddress + (*section + i)->SizeOfRawData;
if (begin <= rva && rva <= end)
{
foa = rva - begin + (*section + i)->PointerToRawData;
break;
}
}
free(section);
return foa;
}

void PrintRelocTab(
IN LPVOID pe
) {
PIMAGE_DOS_HEADER dosHeader = nullptr;
PIMAGE_FILE_HEADER fileHeader = nullptr;
PIMAGE_OPTIONAL_HEADER32 optionalHeader = nullptr;
PIMAGE_SECTION_HEADER* sectionTable = nullptr;
AnlyzePE(pe, dosHeader, fileHeader, optionalHeader, sectionTable);


DWORD offset = RvaToFoa(pe, optionalHeader->ImageBase + optionalHeader->DataDirectory[5].VirtualAddress);
PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((PCHAR)pe + offset);
while (relocationTable->VirtualAddress != NULL || relocationTable->SizeOfBlock != NULL)
{
for (DWORD i = 0; i < (relocationTable->SizeOfBlock - 9) / 2; i++)
{
WORD hige = *((PWORD)((PCHAR)relocationTable + 8) + i) >> 12;
WORD low = *((PWORD)((PCHAR)relocationTable + 8) + i) & 0x0FFF;
printf("%d: %d = %x = %x\n", i, hige, relocationTable->VirtualAddress + low, RvaToFoa(pe, optionalHeader->ImageBase + relocationTable->VirtualAddress + low));
}
printf("=============================\n");
relocationTable = (PIMAGE_BASE_RELOCATION)((PCHAR)relocationTable + relocationTable->SizeOfBlock);
}

free(sectionTable);
}

int main()
{
LPVOID pe = ReadPE(R"(D:\source\repos\dllmain\Debug\MyDll.dll)");
if (pe != nullptr)
{
PrintRelocTab(pe);
free(pe);
}
return 0;
}

运行结果

image-20241230225414906