一丶何为重定位
重定位的意思就是修正偏移的意思. 如一个地址位 0x401234 ,Imagebase = 0x400000 . 那么RVA就是 1234. 如果Imagebase 变了成了0x300000, 那么修正之后就是 ImageBase + RVA = 0X300000+1234 = 0x301234.
首先我们知道.一个EXE文件.会调用很多DLL(PE) 有多个PE文件组成.
exe文件启动的基址 (ImageBase) 是0x40000. 假设我们调用三个DLL A B C.
A DLL 在EXE展开的基址位置是0x10000000
那么恰巧 B DLL 展开的位置也是 0x1000000 两个DLL位置展开地方是一样的.那么就出现问题了.
如下图:
这时候操作系统就会给我们进行修正. 将B DLL 换个内存位置. 进行展开. 这也是为什么很多游戏外挂.等等.都选择DLL注入. 因为系统帮你重定位了各种信息. 代码写在DLL中即可.
如下图: B DLL 从0x20.... 展开了.规避了使用相同地址
虽然这样解决了入口基址不一样.内存展开不一样. 但是我们知道.PE文件中有很多RVA .RVA 是相对于ImageBase的偏移进行存放的. 如果PE文件中都是 RVA 那就好办了.
但是不一定呀.
如一下代码所示:
#include <stdio.h> #include <stdlib.h> #include <Windows.h> int g_Value; int main() { g_Value = 10; }
查看其反汇编
我们发现,在给全局变量赋值的的时候.地址是一个固定的.他不是 RVA
再次运行截图:
我们发现地址变了. 而且硬编码 是一个固定的. 0x012c813c ,他直接编译到二进制文件中了.
他把 ImageBase + RVA的值. 直接编译到二进制当中. 就是说.这个全局变量的地址. 是一个 RVA + IMAGEBASE 的地址. 但是他是直接编译到二进制中的.
问题所在:
假设A编译的全局变量的地址是 RVA + iMAGEbase 假设是 0x1012345,那么A展开的位置是 0x10..... 那么全局变量地址是正确的. 但是如果B编译的时候.地址也是1012345. 但是模块基址加载不一样.那么就会出问题了.如下图:
根据上面我们发现了问题所在.所以现在我们需要一张表.记录那个地方需要进行重定位. 我们把这个地方的值改一下即可.
也就是要记录我们修改需要重定位的位置.以上图的代码进行反汇编查看.
也就是记录需要重定位的地方即可.
重定位表就是记录所有需要修正的地址.只要有了重定位表.我们就不用担心我们的ImageBase 没有占住位置.
非常重要的一张表.
二丶重定位表的定位以及结构
重定位表.的定位在扩展头中的数据目录中. 数据目录的第6项就是重定位表的 RVA偏移.以及重定位表的大小.
定位到重定位表,那么有额外的结构体来描述重定位表.
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; //存储的值以字节为单位.字节多大.表示了一个重定位快有多大. // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
看着重定位表就两个成员. 其实非常复杂. 我们设 VirtualAddress 为 x 设 SizeofBlock为Y
如下图所示,一个格子为1个字节.
第一行四个字节为x.也就是 Virtualaddress.. y则是第二个成员
SizeOfBlock 成员你的意思. 以字节为单位.代表重定位的快由多大. 我们知道.一个PE文件需要很多地方进行重定位的.比如这个记录的
大小为16. 也就是两个重定位块,那么我们的重定位表的大小就是如下图所示:
下面则是新的重定位表.结构就是重定位表的结构,如果SzieofBlock大小为20个字节.那么重定位表大小就是20个自己.
第三个依次类推,重定位表的结束是不停的往下找,知道最后一个重定位表的结构成员你都为0
这样设计的原因:
算术题解惑.
比如我们有地址 101234 101235 101236 这种修正的地址有10000个.
那么每个地址有4个字节的. 那么 4 * 10000 = 4万个字节. 也就是我们要准备一张4万个字节的表来保存重定位的.
但是我们发现一个规律.我们要修正的表的偏移都很近, 1234 1235 ....
那么我们可不可以这样那. 我们把 100000取出来. 两个字节存储1234 另外两个地址存储1235,不用准备四个字节了.小的偏移我们两个字节存储.这样的话我们的表的字节就会缩小一半.
VirtuallAddress 就是存储了 100000这个值,也就是需要 ""基址"" 公用的地址.
SizeofBlock 就是下面的偏移由多大, 我们要修正的偏移是 VirtualAddress + sizeofBlock下面的值.
如下图:
我们的重定位表,需要修正的基址是 0x11000,大小是54. 那么需要修正的偏移是 "36b0" "36bc" "36e0"....
我们基址 + 偏移就是要修正的位置.
偏移的概念:
重定位表,是按照一个物理页(4kb)进行存储的. 也就是一个4kb内存,有一个重定位块, 一个重定位表只管自己当前的物理页的重定位.
一个重定位表的记录偏移的大小是2个字节,也就是16位. 而记录偏移的大小. 是由 SizeofBlock决定的.
但是我们记录偏移的位置,12位就够了. 高4位.挪作他用. 并不是记录的才会修正偏移.只有高4位为3的时候.才会进行重定位(基址 + 偏移)
真正修复的位置 virtualaddress + (高四位为3 ? 低12位偏移 : 无所谓的值.)
也就是高四位为3 Vir + 低12位偏移就等于真正要修复的RVA 例如 36b0 高位为3 低12位就是6b0 要修复的RVA = vir + 6b0 ,如果加上当前DLL的ImagebASE 才是真正要修复的虚拟地址 (VA) 我们计算出的是RVA
如果高位不为3,那么这个值是无所谓了.因为内存对齐的原因.
例如上图重定位表. 0x11000代表了当前要进行修复的块位置. 要修复偏移的地址第一个是36b0 . 高位为3是要进行修复.
所以低位为6B0. 所以修复的位置是 0x116b0的位置. 0x116b0 + 当前PE文件的ImageBase就是要进行重定位的位置
当前PE的Imagebase为0x400000 重定位地方为 0x4116b0位置.
我们第一个修正的位置是4116b0位置,从内存中.反汇编查看. 4116b0的位置的值是0x0041813c. 也就是说.这个位置的值.是我们需要重定位的. 也就是我们上面写的程序.为全局变量赋值的时候.全局变量的地址.需要进行更改.
而需要重定位的值. 则是 全局变量的RVA值 + Imagebase 填写到这里面了. 全局变量是在内存中的data节存储着.所以观看前几篇博客.能知道如何定位全局变量在文件的位置.
三丶总结重定位
重定位表有两个成员. VirtuallAddress sizeofBlock
1.virtualladdress 记录了当前物理页需要进行重定位的起始地址.
2.sizeofBlock 记录了重定位表多大.去掉8个字节(重定位表大小) 下面都是记录了重定位表需要重定位的偏移.
3.偏移是2个字节存储. 12位存储偏移. 高4位存储是否进行重定位. 高4位为3则需要进行重定位. virtuall + 低12位 就是要修正的 RVA偏移.