动态链接库的重定位表分析
重定位表的作用
重定位表用于在程序加载到内存时,修正程序的内存地址。通常,当程序在加载时,它的基地址可能与编译时的预定基地址不同,特别是对于动态链接库(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_DATA_DIRECTORY
结构的VirtualAddress
属性来找到。通过这个地址,可以逐个读取IMAGE_BASE_RELOCATION
块。
- 块的数量:
重定位表中的块数量不固定,直到遇到最后一个块,其VirtualAddress
和SizeOfBlock
为0,表示结束。
- 条目的结构:
每个条目有2字节,表示需要修改的地址偏移。具体项的高4位表示修正类型,值为3(0011)表示需要修改的地址,值为0表示数据对齐位置,不需要修改。因此,只需要关注高4位为3的条目。
重定位表的具体工作原理
- 节省内存:
假设有大量的地址需要修正,如果单独存储每个地址的值会占用大量内存(例如,修正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; }
|
运行结果