一个EXE文件被拉伸加载至内存,分配了一定的内存空间,同时EXE需要加载DLL模块,也占用内存空间
一般情况下,EXE都是可以按照ImageBase的地址进行加载的。EXE拥有自己独立的 4GB 的虚拟内存空间,但DLL 并不是,当有EXE使用DLL时才加载到相关EXE的进程空间的
为了提高搜索的速度,模块间地址也是要对齐的,模块地址对齐为10000H也就是64K
观察反编译汇编时可以发现:编译时生成的地址 = ImageBase + RVA
全局变量,字符串等地址信息已经在编译完成后写入文件,程序运行时可以按照预定的ImageBase来加载,不需要重定位表。因此一般EXE很少有重定位表,但DLL不同,如果某个DLL模块没有按照ImageBase进行加载,那么所有类似上面中的地址就都需要修正。否则引用的地址就是无效的。需要修正的地方很多,那么如何记录哪些地方需要修正,这是重定位表需要做的事情
数据目录项的第6个结构,就是重定位表
xxxxxxxxxxtypedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size;} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;根据数据目录RVA找到的重定位表如下
xxxxxxxxxxtypedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock;} IMAGE_BASE_RELOCATION;typedef IMAGE_BASE_RELOCATION ,*PIMAGE_BASE_RELOCATION;如何判断一共有几块数据:最后一个结构的VirtualAddress与SizeOfBlock都为0
内存中的页大小是1000H也就是2的12次方,表示一个页内所有的偏移地址。具体项的宽度是16位,高四位代表类型:值为3代表的是需要修改的数据,值为0代表的是用于数据对齐的数据,可以不用修改。也就是说只关注高4位的值为3(0011)的就可以了

VirtualAddress:当前这一个块的数据,每一个低12位的值加VirtualAddress才是
真正的RVA = VirtualAddress + 具体项的低12位
当前块的总大小:具体项的数量 = (SizeOfBlock - 8)/2
解析DOS头和PE头,通过可选PE头拿到数据目录
其中IMAGE_DIRECTORY_ENTRY_BASERELOC是宏定义5,数据目录第六项是重定位表
另外数据目录中保存的Size字段,在解析分析重定位表时不会参考
xxxxxxxxxx// ...RvaBaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress;relocationSize = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;if (!RvaBaseRelocationTable){ return 0;}数据目录并没有保存真正的重定位表,只是一个地址。通过前文提到的RVA to FOA 文章,将
xxxxxxxxxxDWORD FoaBaseRelocationTable = RvaToFileOffset(pFileBuffer, RvaBaseRelocationTable);pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + FoaBaseRelocationTable);printf("重定位表FOA地址: %#010X\n", FoaBaseRelocationTable);遍历所有重定位表项,下一个和上一个的距离是SizeOfBlock大小,通过当前重定位表起始地址加当前的SizeOfBlock即可得到下一个重定位表的起始位置,强转PIMAGE_BASE_RELOCATION得到下一个重定位表
xxxxxxxxxxBYTE secName[9] = { 0 };for (DWORD i = 0; pBaseRelocation->SizeOfBlock && pBaseRelocation->VirtualAddress; i++){ // ... pBaseRelocation = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseRelocation + pBaseRelocation->SizeOfBlock);}将每个重定位表的VirtualAddress转为FOA地址,并计算出没块中的项目数量:由于VirtualAddress和SizeOfBlock分别是4字节,真正大小应该是SizeOfBlock减八。参考上图每项2字节,除以二得到结果
xxxxxxxxxxDWORD virtualFoaBaseReloc = RvaToFileOffset(pFileBuffer, pBaseRelocation->VirtualAddress);DWORD sizeOfBlockBaseReloc = (pBaseRelocation->SizeOfBlock - 8) / 2;确认当前表来自哪一个节:如果该节位于PointerToRawData和Misc之间,认为重定位表在当前节。使用memcpy将节名复制到数组中,节名是8字节,保留第九字节固定00表示字符串结束
xxxxxxxxxxBYTE secName[9] = { 0 };for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++){ DWORD pLowAddressOfFoa = pSectionHeader[j].PointerToRawData; DWORD pHighAddressOfFoa = pSectionHeader[j].PointerToRawData + pSectionHeader[j].Misc.VirtualSize;
if (virtualFoaBaseReloc >= pLowAddressOfFoa && virtualFoaBaseReloc <= pHighAddressOfFoa) { memcpy(secName, pSectionHeader[j].Name, 8); break; }}遍历每个重定位表项需要先偏移8字节2字
xxxxxxxxxxWORD* recAddr = (WORD*)((BYTE*)pBaseRelocation + 0x08);和0x0FFF进行与操作,对高4位清零,得到相对于RVA的偏移,相加得到该项真正的偏移
右移动12位得到高4位,验证是否为0,如果为0表示结束,为3表示有效
xxxxxxxxxxfor (DWORD j = 0; j < sizeOfBlockBaseReloc; j++){ DWORD itemRvaOffset = (recAddr[j] & 0x0FFF) + virtualFoaBaseReloc; WORD type = recAddr[j] >> 12; if (type != 0) { printf("%#010X,%#010X\r\n", itemRvaOffset, type); }}
完整代码
xxxxxxxxxxDWORD GetDataDirectoyOfBaseRelocation(IN PVOID pFileBuffer){ PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL; PIMAGE_DATA_DIRECTORY pDataDirectory = NULL; PIMAGE_BASE_RELOCATION pBaseRelocation = NULL; DWORD RvaBaseRelocationTable = 0; DWORD relocationSize = 0; if (!pFileBuffer) { return 0; } if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) { return 0; } pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; if (*((PWORD)((DWORD)pFileBuffer + pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) { return 0; } pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew); pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04); pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader); pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory;
RvaBaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; relocationSize = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; if (!RvaBaseRelocationTable) { return 0; }
DWORD FoaBaseRelocationTable = RvaToFileOffset(pFileBuffer, RvaBaseRelocationTable); pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + FoaBaseRelocationTable); printf("重定位表FOA地址: %#010X\n", FoaBaseRelocationTable);
BYTE secName[9] = { 0 }; for (DWORD i = 0; pBaseRelocation->SizeOfBlock && pBaseRelocation->VirtualAddress; i++) { DWORD virtualFoaBaseReloc = RvaToFileOffset(pFileBuffer, pBaseRelocation->VirtualAddress); DWORD sizeOfBlockBaseReloc = (pBaseRelocation->SizeOfBlock - 8) / 2; for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++) { DWORD pLowAddressOfFoa = pSectionHeader[j].PointerToRawData; DWORD pHighAddressOfFoa = pSectionHeader[j].PointerToRawData + pSectionHeader[j].Misc.VirtualSize;
if (virtualFoaBaseReloc >= pLowAddressOfFoa && virtualFoaBaseReloc <= pHighAddressOfFoa) { memcpy(secName, pSectionHeader[j].Name, 8); break; } } printf("重定位表VA:%#010X -> 节的名称:%s -> 具体项大小:%#010X -> 总大小:%#010X\n", pBaseRelocation->VirtualAddress, secName, sizeOfBlockBaseReloc, pBaseRelocation->SizeOfBlock);
WORD* recAddr = (WORD*)((BYTE*)pBaseRelocation + 0x08);
for (DWORD j = 0; j < sizeOfBlockBaseReloc; j++) { DWORD itemRvaOffset = (recAddr[j] & 0x0FFF) + virtualFoaBaseReloc; WORD type = recAddr[j] >> 12; if (type != 0) { printf("%#010X,%#010X\r\n", itemRvaOffset, type); } } memset(secName, 0, 9); pBaseRelocation = (PIMAGE_BASE_RELOCATION)((PBYTE)pBaseRelocation + pBaseRelocation->SizeOfBlock); } return 0;}