重定位表
什么是重定位表?
用于程序在加载依赖模块时,发生地址冲突的情况下,用来进行模块基址修正的表。
重定位表的结构
数据目录项的第六个就是重定位表结构
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //RVA
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
通过RVA转FOA,定位第一个IMAGE_BASE_RELOCATION
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION;
上图看看重定位表数据
1、内存的块数如何确定:if判断 VirtualAddress 和SizeOfBlock都为0,则内存块结束
2、一个块中的具体项,如0011 1010 1010 1010 ,高4位0011表示类型,0011表示需要修改,0000表示不需要修改只是为了做内存对齐用的。低12位表示一个页内偏移地址。
3、如何计算真正需要的修复的RVA地址:数据项中低12位+该页的VirtualAddress
为什么需要重定位表?
程序的加载过程
- 先读入PE文件的DOS头、PE头和Section节表
- 判断PE头中的ImageBase是否可用,如果该ImageBase已被其他模块占用,则重新分配一块空间。
- 根据Section节表,把文件的各个Section映射到分配的空间,并根据各个Section定义的数据来修改所映射的页的属性。
- 如果文件被加载的地址不是ImageBase定义的地址,则重新修正ImageBase。
- 根据PE文件的输入表加载所需的dll到进程空间。
- 替换IAT白哦内的数据为函数的实际调用地址。
- 根据PE头内的数据生成初始化的堆和栈。
- 创建初始化线程,开始运行进程。
在步骤2中,如果PE头中的ImageBase发生冲突,则需要分配新的ImageBase,这时ImageBase发生了变化,而各种头部中的相对虚拟地址不会发生改变,这就导致无法正确定位虚拟地址。这时,就诞生了重定位表 。
Windows加载器是如何实现重定位的?
在程序加载的第4步中如果文件被加载的地址不是ImageBase定义的地址,则重新修正ImageBase
,修正的步骤如下:
当某模块未被载入到首选基地址时,加载器会计算模块实际载入的地址(dwVA)跟首选基地址(ImageBase)的差值,将这个差值加到机器指令所引用的原来的地址,得到的就是模块中各指令所引用的数据在本进程地址空间的正确地址,随后加载器会修正模块中对每个内存地址的引用。
动手实现重定位表的输出
// ReloadTableInfo.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
unsigned char* FileBuffer(const char* FileName);
void PrintReloadTb();
DWORD RVA2Offset(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA);
int main(int argc, char* argv[])
{
PrintReloadTb();
return 0;
}
//打印重定位表
void PrintReloadTb()
{
PIMAGE_DOS_HEADER pDosHeader;
PIMAGE_NT_HEADERS pNtHeaders;
PIMAGE_BASE_RELOCATION pBaseRec;
PIMAGE_SECTION_HEADER pSectionHeader;
unsigned char* fileBuffer = FileBuffer("C:\TestLib\TestDynLib2.dll");
pDosHeader = (PIMAGE_DOS_HEADER)fileBuffer;
pNtHeaders = (PIMAGE_NT_HEADERS)(fileBuffer+pDosHeader->e_lfanew);
printf("重定位表的相对虚拟地址:%x
",pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
printf("重定位表的大小:%x
",pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size);
DWORD pBaseRecOffset = RVA2Offset(pNtHeaders, pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); //注意这个VirtualAddress是VA
pBaseRec = (PIMAGE_BASE_RELOCATION)(pBaseRecOffset+fileBuffer);
pSectionHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders+1);
BYTE secName[9] = {0};
int i =0;
for(i; pBaseRec->SizeOfBlock && pBaseRec->VirtualAddress; i++)
{
DWORD FOA = RVA2Offset(pNtHeaders, pBaseRec->VirtualAddress);
DWORD size = (pBaseRec->SizeOfBlock - 8 )/2; // VirtualAddress SizeOfBlock共占8字节,每个项2字节
//确定该结构所属的节
DWORD j=0;
for(j=0;j<pNtHeaders->FileHeader.NumberOfSections;j++)
{
DWORD lower =RVA2Offset(pNtHeaders, pSectionHeader[j].VirtualAddress);
DWORD upper =RVA2Offset(pNtHeaders, pSectionHeader[j].VirtualAddress+pSectionHeader[j].Misc.VirtualSize);
if(FOA>=lower && FOA<=upper )
{
memcpy(secName,pSectionHeader[j].Name,8);
break;
}
}
printf("%d.RELOCATION
VirtualAddress %x(%s)
SizeOfBlock %x
", i, pBaseRec->VirtualAddress,secName, size); //打印本页的主要信息
printf("RVA,TYPE
");
//打印一个页中,所有重定位地址与信息
WORD * recAddr = (WORD *)((BYTE *)pBaseRec+8); //指向第一个目录项
for(j=0; j<size; j++)
{
DWORD offset = (recAddr[j] & 0x0FFF) + FOA ;
WORD type = recAddr[j] >> 12;
if(type!=0)
{
printf("%08X, %x
",offset+pBaseRec->VirtualAddress,type);
}
}
memset(secName, 0, 9);
pBaseRec = (PIMAGE_BASE_RELOCATION )((BYTE *)pBaseRec + pBaseRec->SizeOfBlock);//移到下一页
}
}
//将PE文件读到FileBuffer
unsigned char* FileBuffer(const char* FileName)
{
unsigned char* Heap = NULL;
FILE* Stream;
//打开文件
Stream = fopen(FileName,"rb");
//计算文件大小
fseek(Stream,0,SEEK_END);
long FileSize = ftell(Stream);
fseek(Stream,0,SEEK_SET);
//分配堆空间
Heap = (unsigned char*)malloc(sizeof(char)*FileSize);
//将文件拷到堆
fread(Heap,sizeof(char),FileSize,Stream);
fclose(Stream);
return Heap;
}
DWORD RVA2Offset(PIMAGE_NT_HEADERS pNTHeader, DWORD dwRVA)
{
PIMAGE_SECTION_HEADER pSection = (PIMAGE_SECTION_HEADER)((DWORD)pNTHeader + sizeof(IMAGE_NT_HEADERS));
for(int i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++)
{
if(dwRVA >= pSection[i].VirtualAddress && dwRVA < (pSection[i].VirtualAddress + pSection[i].SizeOfRawData))
{
return pSection[i].PointerToRawData + (dwRVA - pSection[i].VirtualAddress);
}
}
return 0;
}
部分结果图如下:
LordPE查看结果如下: