重定位就是把程序的逻辑地址空间变换成内存中的实际物理地址空间的过程,也就是说在装入时对目标程序中指令和数据的修改过程。
为什么需要重定位?
程序员在编写程序时,在使用某些常量、全局变量时,编译器把这些数据的地址编译成一个绝对地址(比如:call 0x400100),是以ImageBase(0x400000)为基准。当程序加载到ImageBase时,程序正常运行,可是当程序加载地址为0x500000时,访问的绝对地址是一个错误地址,实际应该为call 0x500100,这时就用到了重定位技术。重定位就是修改编译时某些绝对地址的过程。
怎样实现重定位?
1、汇编实现
dwVar dw ? ;全局变量需要重定位
push ebx
call Flg
Flg:
pop ebx
sub ebx, offset Flg
mov dwVar, ebx;dwVar 保存了自身的运行地址 - 编译地址
pop ebx
2、高级语言的实现方式 (C++/C)
int __stdcall _GetRunAddrSubAbsAddr(unsigned int RetAddr);
__declspec(naked) int GetRunAddrSubAbsAddr()
{
__asm call _GetRunAddrSubAbsAddr
}
int __stdcall _GetRunAddrSubAbsAddr(unsigned int RetAddr)
{
unsigned int *pRet;
int delta;
pRet = &RetAddr;
pRet--;
delta = *pRet - 5 - (unsigned int)GetRunAddrSubAbsAddr;
*pRet = RetAddr;//程序返回地址
return delta;
}
int main(int argc, _TCHAR* argv[])
{
int delat = GetRunAddrSubAbsAddr();//返回的为运行地址和绝对地址的差值
return 0;
}
__declspec(naked)是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码。
如何查看重定位信息?
在数据目录表的重定位表中可以找到重定位数据段的地址。
But:
我有个疑问。。。。这两段代码好像有点瑕疵。不可以把程序二进制码写入内存中任意位置执行。why?
网上的很多资料包括书本中使用过类似上面的代码,一般来说这样的结果是正确的。但是我还有些小疑问?
我分析是:WindowsNT 系统下, call @F 时,应该类似于call 0x7c00, 而这时调用的地址是一个绝对地址(0x7c00),本身就是需要重定位的,怎么可以用来求自身运行地址和编译地址的差值!?更别说把代码移植到内存任意位置执行,但是每次调试时运行的结果又都是正确的,这又是为啥嘞?
我分析可能有两个原因:
1、程序运行时载入的地址就是ImageBase(PE结构里面),这是编译地址恰好等于运行地址,dwVar值为0.
为了验证一下猜想:
.386
.model flat,stdcall
option casemap:none
;***********************************************
;include 文件定义
;***********************************************
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;************************************************
;堆栈段
;************************************************
.stack
;************************************************
;数据段 初始化数据定义
;************************************************
.data
szCaption db 'A MessageBox!',0
szText db 'Hello,World!',0
;************************************************
;数据段 未始化数据定义
;************************************************
.data?
;***********************************************
;常量定义
;************************************************
.const
;***********************************************
;代码段
;************************************************
.code
start:
invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK or MB_ICONWARNING
invoke ExitProcess,NULL
;****************************************************
END start
当用masm32 编译链接成EXE时,我发现重定位表竟然是零(结果如下图)!难道汇编编译成的EXE文件一定会加载到ImageBase处!这么说的话,在汇编下编译EXE文件时,那么重定位技术就没必要用了。但是在DLL远程注入技术中,必须要用这种重定位方式 计算出执行代码的内存位置,然后拷贝代码内容。
2、当程序运行时载入的地址不是ImageBase时, call @F的地址应该是运行地址。这就是PE结构里重定位表的功劳了,在程序载入内存时把需要数据重定位的结果。
那么问题来了,怎样把重定位表清零后,可以实现程序重定位呢?
其实,也不是没办法。。我把程序测试后再添加代码。
经过测试和windbg的反汇编调试发现,第一种汇编实现的方式是可以实现任意内存位置重定位的 (囧。。,上面的分析都是渣,看来我还是 兔羊 兔森泡),为啥呢?
00c919d0 55 push ebp
00c919d1 8bec mov ebp,esp
00c919d3 81ecd0000000 sub esp,0D0h
00c919d9 53 push ebx
00c919da 56 push esi
00c919db 57 push edi
00c919dc 8dbd30ffffff lea edi,[ebp-0D0h]
00c919e2 b934000000 mov ecx,34h
00c919e7 b8cccccccc mov eax,0CCCCCCCCh
00c919ec f3ab rep stos dword ptr es:[edi]
00c919ee a12480c900 mov eax,dword ptr [Test!__security_cookie (00c98024)]
00c919f3 33c5 xor eax,ebp
00c919f5 8945fc mov dword ptr [ebp-4],eax
00c919f8 53 push ebx
00c919f9 e800000000 call Test!wmain+0x2e (00c919fe)//这句是关键 -- call Flg ----
00c919fe 5b pop ebx