一个EXE文件被拉伸加载至内存,分配了一定的内存空间,同时EXE需要加载DLL模块,也占用内存空间
一般情况下,EXE都是可以按照ImageBase
的地址进行加载的。EXE拥有自己独立的 4GB 的虚拟内存空间,但DLL 并不是,当有EXE使用DLL时才加载到相关EXE的进程空间的
为了提高搜索的速度,模块间地址也是要对齐的,模块地址对齐为10000H
也就是64K
观察反编译汇编时可以发现:编译时生成的地址 = ImageBase + RVA
全局变量,字符串等地址信息已经在编译完成后写入文件,程序运行时可以按照预定的ImageBase
来加载,不需要重定位表。因此一般EXE很少有重定位表,但DLL不同,如果某个DLL模块没有按照ImageBase
进行加载,那么所有类似上面中的地址就都需要修正。否则引用的地址就是无效的。需要修正的地方很多,那么如何记录哪些地方需要修正,这是重定位表需要做的事情
数据目录项的第6个结构,就是重定位表
xxxxxxxxxx
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
根据数据目录RVA找到的重定位表如下
xxxxxxxxxx
typedef 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
文章,将
xxxxxxxxxx
DWORD FoaBaseRelocationTable = RvaToFileOffset(pFileBuffer, RvaBaseRelocationTable);
pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + FoaBaseRelocationTable);
printf("重定位表FOA地址: %#010X\n", FoaBaseRelocationTable);
遍历所有重定位表项,下一个和上一个的距离是SizeOfBlock
大小,通过当前重定位表起始地址加当前的SizeOfBlock
即可得到下一个重定位表的起始位置,强转PIMAGE_BASE_RELOCATION
得到下一个重定位表
xxxxxxxxxx
BYTE 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字节,除以二得到结果
xxxxxxxxxx
DWORD virtualFoaBaseReloc = RvaToFileOffset(pFileBuffer, pBaseRelocation->VirtualAddress);
DWORD sizeOfBlockBaseReloc = (pBaseRelocation->SizeOfBlock - 8) / 2;
确认当前表来自哪一个节:如果该节位于PointerToRawData
和Misc
之间,认为重定位表在当前节。使用memcpy
将节名复制到数组中,节名是8字节,保留第九字节固定00表示字符串结束
xxxxxxxxxx
BYTE 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字
xxxxxxxxxx
WORD* recAddr = (WORD*)((BYTE*)pBaseRelocation + 0x08);
和0x0FFF
进行与操作,对高4位清零,得到相对于RVA
的偏移,相加得到该项真正的偏移
右移动12位得到高4位,验证是否为0,如果为0表示结束,为3表示有效
xxxxxxxxxx
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);
}
}
完整代码
xxxxxxxxxx
DWORD 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;
}