1.重定位的需求:
在生成程序的时候,很多涉及到地址的代码,都使用一个绝对的虚拟内存地址(这个虚拟内存地址是假设程序加载到0x400000的地方时才能够使用的),但是当程序的加载基址产生变化的时候,新的加载基址和默认的加载基址就不一样了,那些涉及到地址的代码就不能运行了,此时就需要将那些涉及到地址的代码,把他们的操作数修改(去掉默认加载基址,再加上新的加载基址)才能够使程序运行起来.
2.产生重定位需求的代码
- 当代码段使用了其他区段的数据,所生成的代码就会产生重定位的需求,其他段数据的首地址,就是产生重定位需求的根源
- 凡是出现全局变量的地方,都会导致重定位的产生
- 调用外部模块的函数都会产生重定位
3.怎么修复重定位
0x0谁来修复重定位?
- 加载器会负责修复所有会产生重定位的代码
0x1重定位怎么被修复?
0x1-1如何知道哪些地址上的代码会产生重定位?
- 利用重定位表去知道产生产生重定位代码的地址
重定位表所记录的信息:在哪个地址上产生了重定位。
0x1-2如何修改?
- 将指令中的操作数按照指针字节数读取出来,然后将之减去默认加载基址(扩展头.ImageBase),在加上新的加载基址,最后把新地址存入原来的地址中.
0x1-3要修改的是什么?
要修改的内存地址操作数:0x403000
0x401000 A3 00304000 ------>对应汇编指令: mov dword ptr ds:[0x403000] ,eax
被修改的四个字节(0x4010001)
重定位块中的VirtualAddress+offset得到的是0x401001
*(DWORD*)0x401001
得到的是----->0x403000
- 修复重定位(假定默认基址为0x40 0000,新的加载基址0x30 0000):
*(DWORD*)(VirtualAddress+offset+当前加载基址)-=0x400000
*(DWORD*)(VirtualAddress+offset+当前加载基址)+=0x300000
再举一个例子(重点)
假设实际加载基址为0x80 0000默认加载基址为0x40 0000
在0x401000这个地方有这么一条指令:call 0x403000
即0x40 1000 call 0x403000
重定位表中的VA为0x1000(4KB),此时的offset为1
那么此时0x403000这个指令所对应的地址为1000+1+400000
对其进行解引用,就会得到0x403000这个值,然后用0x403000减去0x400000+0x800000即可,得到的值为0x803000,再将0x803000写入原来的地址,则原来那条指令就变成了0x401000 call 0x803000
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//这里的VA实际上是一个相对虚拟地址(RVA)
DWORD SizeOfBlock;
// WORD TypeOffset[1]; //这里的1表示不定长,长度不知道
} IMAGE_BASE_RELOCATION;
重定位表解析:
// 006_解析重定位表.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
struct TypeOffset {
WORD Offset : 12; // (1) 大小为12Bit的重定位偏移
WORD Type : 4; // (2) 大小为4Bit的重定位信息类型值,m类型为3表示的需要重定位的类型,
//非3(一般指0)表示的是不需要重定位的类型
//以位段的方式来写的!!!!!
};
DWORD RvaToOffset( IMAGE_NT_HEADERS* pNtHdr , DWORD dwRva ) {
// 1. 找Rva所在的区段
// 2. 用Rva减去所在区段的首Rva ,再用减出来的差,加上所在
// 区段的首区段偏移
IMAGE_SECTION_HEADER* pSechdr = NULL;
pSechdr = IMAGE_FIRST_SECTION( pNtHdr );
for( int i = 0 ; i < pNtHdr->FileHeader.NumberOfSections; ++i ) {
if( dwRva >= pSechdr[ i ].VirtualAddress
&& dwRva <= pSechdr[ i ].VirtualAddress + pSechdr[ i ].SizeOfRawData ) {
dwRva -= pSechdr[ i ].VirtualAddress;
dwRva += pSechdr[ i ].PointerToRawData;
return dwRva;
}
}
return -1;
}
int main() {
HMODULE hModule = GetModuleHandle( L"kernel32.dll" );//loadlibrary就是把dll加载到4GB虚拟内存空间中
//GetModuleHandle是在4GB内存空间中有了dll文件之后获得当前
//加载基址
ULONG_PTR dwAddres = (ULONG_PTR)GetProcAddress( hModule ,
"CreateFileW" );//通过GetProcAddress函数以及hModule的值
//获得CreateFileW这个函数的地址!!!
//typedef struct _IMAGE_EXPORT_DIRECTORY {
// DWORD Characteristics;
// DWORD TimeDateStamp;
// WORD MajorVersion;
// WORD MinorVersion;
// DWORD Name; /*导出DLL的名字(RVA)*/
// DWORD Base; /*序号基数,用此基数+地址表的下标,就是函数的导出序号*/
// DWORD NumberOfFunctions; /*所有导出符号的个数*/
// DWORD NumberOfNames; /*以名称方式导出的函数的个数*/
// DWORD AddressOfFunctions; /*函数地址表*/
// DWORD AddressOfNames; /*名称表*/
// DWORD AddressOfNameOrdinals; /*名称的序号表*/
//} IMAGE_EXPORT_DIRECTORY , *PIMAGE_EXPORT_DIRECTORY;
// 解析表
// 1. 读取文件到内存
printf( "请输入要解析的DLL(PE文件)的路径:" );
char szPath[ MAX_PATH ];
gets_s( szPath , MAX_PATH );//获取需要解析的dll文件的路径
HANDLE hFile = INVALID_HANDLE_VALUE;
hFile = CreateFileA( szPath , GENERIC_READ , FILE_SHARE_READ ,
NULL , OPEN_EXISTING , FILE_ATTRIBUTE_NORMAL , NULL );
if( hFile == INVALID_HANDLE_VALUE ) {
printf( "文件不存在\n" );
system( "pause" );
return 0;
}
DWORD dwHeight = 0;
DWORD dwFileSize = GetFileSize( hFile , &dwHeight );
BYTE* pBuff = new BYTE[ dwFileSize ];
ReadFile( hFile , pBuff , dwFileSize , &dwFileSize , NULL );//将内存pe里面中的内容拷贝到pBuff缓冲区里面
// 2. 解析出Dos头,Nt头,扩展头
IMAGE_DOS_HEADER* pDosHdr = NULL;
IMAGE_NT_HEADERS* pNtHdr = NULL;
IMAGE_OPTIONAL_HEADER* pOptHdr = NULL;
IMAGE_DATA_DIRECTORY* pDataDir = NULL;
IMAGE_EXPORT_DIRECTORY* pExortTable = NULL;//导出表!!!
pDosHdr = (IMAGE_DOS_HEADER*)pBuff;//为什么要进行类型强制转换,(指针有两重含义,一是指它本身里面的地址,
//二是指它所指向地址所对应的内容),前面的指针是啥类型,右边就要转换成什么类型
pNtHdr = (IMAGE_NT_HEADERS*)( (ULONG_PTR)pDosHdr + pDosHdr->e_lfanew );//为什个么要加(ULONG_PTR),指针不也4字节么
//只是对pDosHdr进行转换!
pOptHdr = & (pNtHdr->OptionalHeader);
// 3. 得到扩展头中的数据目录表
pDataDir = pOptHdr->DataDirectory;
// 4. 获取重定位表数组的首地址
IMAGE_BASE_RELOCATION* pRel = (IMAGE_BASE_RELOCATION*)
( RvaToOffset( pNtHdr , pDataDir[ 5 ].VirtualAddress ) + (ULONG_PTR)pDosHdr );
// 5. 开始遍历重定位表数组
while( pRel->SizeOfBlock != 0 ) {//SizeOfBlock ==0的话就不需要重定位!!!
// 先得到TypeOffset数组的元素个数和地址
TypeOffset* pTypeOffset ; //
pTypeOffset = (TypeOffset*)( pRel + 1 );//由于pRel是指向IMAGE_BASE_RELOCATION类型的指针
//所以这里 需要强转,加1指的是加的一个结构体大小(8字节)
//dwCount指的是需要重定位的个数
DWORD dwCount = ( pRel->SizeOfBlock - sizeof( IMAGE_BASE_RELOCATION ) ) / sizeof( WORD );
for( DWORD i = 0; i < dwCount ; ++i ) {
if( pTypeOffset[ i ].Type == 3 ) {//类型为3表示的需要重定位的类型,非3(一般指0)表示的是不需要重定位的类型
printf( "RVA:%04X" ,
pRel->VirtualAddress + pTypeOffset[ i ].Offset );
// 要修复的是哪个位置?
// 要修复的位置是: VirtualAdress + offset => RVA
//
// 需要分清重定位项的所在的地址 , 重定位项自身的地址.
//
// 1. 通过pRel->VirtualAddress + pTypeOffset[ i ].Offset;得到
// 的是重定位项的所在的地址
// 2. 通过重定位项所在的地址找到的一块内存, 在此内存上的4字节的数据
// 就是重定位项自身的地址,也就是需要被修复的值
//
DWORD dwFixAddr = pRel->VirtualAddress + pTypeOffset[ i ].Offset;
DWORD dwFixAddrOffset = RvaToOffset( pNtHdr , dwFixAddr );
printf( "需要修改的地址:RVA:0x%08X , OFS: 0x%08X\n" ,
dwFixAddr ,
dwFixAddrOffset );
//ULONG_PTR* pFixAddr = (ULONG_PTR*)( dwFixAddr + (ULONG_PTR)pDosHdr );
}
}
// 得到下一个块重定位块的首地址
pRel = (IMAGE_BASE_RELOCATION*)
( (ULONG_PTR)pRel + pRel->SizeOfBlock );
}
system( "pause" );
return 0;
}
下面这段话很重要!
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;//这里的VA实际上是一个相对虚拟地址(RVA)
DWORD SizeOfBlock;
// WORD TypeOffset[1]; //这里的1表示不定长,长度不知道
} IMAGE_BASE_RELOCATION;
//重定位表里面 像上面这样的结构体有若干个(类似于结构体数组,但却不是结构体数组,因为结构体数组它是定长的,这里不定长),
//DWORD VirtualAddress;
//DWORD SizeOfBlock;这两个是定长的,而TypeOffset[1]紧跟在结构体之后,它是不定长的
//每一个结构体中的VirtualAddress指的是每一个内存分页的起始的RVA
//SizeOfBlock 指的是这个分页到下个分页之间需要重定位的所有数据的相关大小
//实际上有两大块组成1.指的是DWORD VirtualAddress; DWORD SizeOfBlock;所在结构体大小
//2.指的是需要重定位的偏移offset, 用offset+VirtualAddress得到的是一个RVA,
//这个RVA是存放 需要修改的那个地址 的地址所对应RVA
//就是说 需要修改的那个地址 作为一个变量 存放在内存里面,而这个内存自己本身 也有自己的地址
//存放 需要修改的那个地址 的内存的地址 是需要通过 RVA + 一个加载基址得到的
//而这个RVA就是通过上面的 offset+VirtualAddress得到的,所对应的这个加载基址是由GetModuleHandle获得的
//得到 这个 需要修改的那个地址(通常需要解引用,解引用才能得到所要修改的地址)之后,用它减去原来的加载基址(默认的加载基址),
//再加上此时的加载基址(由GetModuleHandle获得的),就得到了加载后的真正的地址
-
重定位表实际上是一个结构体数组(不是标准的结构体数组),以全0结束,每个数组元素表示了4KB的大小