PE格式第七讲,重定位表
作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)
一丶何为重定位(注意,不是重定位表格)
首先,我们先看一段代码,比如调用Printf函数,使用OD查看.
那么大家有没有想过这么一个问题,函数的字符串偏移是00407030位置,函数Call的地址是00401020的位置
但是如果模块首地址申请不到了,变为了00100000的位置,那么此时的偏移是不是都是错的了?
首先说下,一般重定位表格都是DLL中的,因为满足不了模块首地址的需求,所以会遇到函数的重定位问题.
那么如果磨坏地址变为了00100000的位置,那么对应的字符串位置是不是也要变为00107030的位置,而Call的地址,是不是也要变为00101020的位置
那么这个就叫做重定位,我们要把偏移,以及各种需要修正的位置,变为正确的.
二丶重定位表格如何设计?
首先我们自己先想一下,重定位的表格要如何设计?
我猜想,你要保存模块的地址 ,修改地址,偏移, 以及大小.
新的模块 ImageBase
旧的模块 iMageBase
修改的地址
偏移
修改的大小
那么如果这样设计会不会出现问题?
会出现很多问题,比如占得字节太多了,如果是Kerner32.dll里面都是这样设计,那么得要多少内存.
那么进一步的优化
可不可以一个分页,保存修改的偏移,以及长度
分页: page (DWORD) 占4个字节
大小: size (DWORD) 偏移:offset(DWORD)
表格设计为上面的,
感觉这样可以了.但是感觉还可以进一步的优化,大小,以及偏移都占4个字节,是不是浪费了
而且如果记录一个分页中的重定位的数据,那么偏移是不会超过12位的(二进制12位,转为10进制是1024), 那么如果一个DWORD存储文件偏移,那么高4位是没有用的.
而且我们发现,大小也是很占位置的.大小一个字节就可以表示了,比如0 做对齐使用,1修改高16位的偏移,2修改低16位的偏移,3修改4个字节大小
那么是不是可以合并了
page (DWORD)
sizeofoffset
0x3005 代表的意思就是看高位,3代表我要修改4个字节,005代表修改的当前页的偏移位置.
三丶真正的重定位表格
看下重定位表格的真正的结构体吧.
代码:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; 页存储的RVA
DWORD SizeOfBlock; word类型数组的个数,也就是下面注释的
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
那么看看是不是和我们猜想的一样,我们随便找个DLL,在数据目录中定位重定位表格
1.寻找数据目录RVA偏移
我们首先要找到数据目录中重定位表格的RVA偏移然后判断属于哪个节,通过公式转化,得到在文件中的实际偏移位置.
得出RVA = 6000h
2.判断属于哪个节
我们发现,新增加了一个节,这个节就是重定位的节然后虚拟地址是6000位置,而且在文件偏移的位置也是6000h
那么我们就得出 FA = RVA了,那么就不用算了,可以确定,文件偏移位置就是6000就是重定位表的位置
3.定位文件偏移处,查看排列
然后可以看出 前八个字节分别保存页的RVA偏移,以及大小,.我们使用计算器计算一下,看看有多大
计算的出 160h,这个大小,保存的是数组大小+上我们八个字节的总大小,也就是说160 - 8 = 数组的大小了.
可以看出确实是怎么大,然后就到记录下一个分页了.
四丶数组解析查看
那么按照我们的想法看上面重定位表中的数组的第一个,按照小尾方式读取则是
0x3005 那么高位是3那么就是要修改大小是4个字节,005则是代表偏移.
至于高位怎么查看,VC++6.0中的宏已经定义了.
代码:
#define IMAGE_REL_I386_ABSOLUTE 0x0000 // Reference is absolute, no relocation is necessary
#define IMAGE_REL_I386_DIR16 0x0001 // Direct 16-bit reference to the symbols virtual address
#define IMAGE_REL_I386_REL16 0x0002 // PC-relative 16-bit reference to the symbols virtual address
#define IMAGE_REL_I386_DIR32 0x0006 // Direct 32-bit reference to the symbols virtual address
#define IMAGE_REL_I386_DIR32NB 0x0007 // Direct 32-bit reference to the symbols virtual address, base not included
#define IMAGE_REL_I386_SEG12 0x0009 // Direct 16-bit reference to the segment-selector bits of a 32-bit virtual address
#define IMAGE_REL_I386_SECTION 0x000A
#define IMAGE_REL_I386_SECREL 0x000B
#define IMAGE_REL_I386_REL32 0x0014 // PC-relative 32-bit reference to the symbols virtual address
这里只需要知道0 1 2 代表的意思即可,因为0x3005的高位是 用位运算 | 上去的,所以3代表的是1 和 2的组合
0 对齐使用
1.修改高16位
2.修改低16位
1和2 使用位运算|起来就是修改4个字节.
1.定位修改位置
那么怎么定位要修改的位置那?
公式:
现在的ImageBase(模块地址) + 当前分页大小的虚拟地址 +5的位置等于要修改的位置:
比如假设我们的现在的模块地址是00400000位置,而DLL以前的位置是10000000 而它以前的字符串的偏移是 10003045
首先定位修改地址:
00400000 + 1000 + 005 = 401005 那么在401005的位置就是你要修改的位置
比如我们在写一个
0x3096 = 400000 + 1000 + 96 = 401096 那么定位的位置就是401096是你要修改的偏移,大小是4个字节,高位为3 为什么是4个字节,一会看下内部存储
2.修改的偏移计算
比如我们调用一个printf
push 10003096 "HelloWorld"
call 10004086
那么我们要修正他的偏移
我们现在得知,以前的DLL偏移是 10000000 以前的字符串偏移是 10003096 ,不过因为DLL的模块地址没有满足,那么现在的模块地址变为了00400000的位置
那么我们要修正偏移
公式:
现在的ImageBase (00400000) - 以前的ImageBase(10000000) + 以前的偏移(10003096)
这样写汇编代码好写,如果便于理解的话,可以写成下面那样,只不过你需要知道的是汇编代码就是上面这种写法就行
以前的偏移(10003096) - 以前的ImageBase(10000000) + 现在的ImageBase(00400000)
= 3096 + 400000
= 403096 (计算出来的偏移)
那么push的位置就成了 403096了,已经重定位了.
五丶实战演练查看
因为DLL中的重定位表中的项太多,所以这里使用一个EXE(没有导出函数的EXE),然后注入这个DLL,那么这个EXE就有重定位表格了.
首先我们先看DLL, 3005的位置要重定位
按照公式我们得出,要修改的位置是
现在模块地址 + 当前表中记录分页 + 数组中后三位的偏移(上面说过,如果按照分页存储,那么3位就可以表达一个分页需要记录的了)
那么现在 我们的EXE的模块地址是00500000 + 1000(重定位表中第一项成员) + 005 (这是一个数组,第一个成员是0x3005 取出后三位则是005)
得出修改的位置是 00501005的位置,我们OD中CTRL+ G看看这个位置是不是要修正.
代码乱了,那么我们可能断掉指令了,那么此时CTRL + A重新分析一下.
可以看出,我们修正的位置是501005的位置,不过汇编代码在501004才能显示出来,501005后面正好是要修正的地址,那么只需要计算偏移填进去就可以了,大小是按照高4个字节, 现在0x3005 高位是3那么代表了要修正4个字节的偏移.
算出偏移位置:
偏移位置我们要进行反推了
因为OD已经帮我们重定位好了.
503000 = 现在的ImageBase - 以前的ImageBase + 以前的偏移 = 现在的偏移(503000)
那么现在计算以前的偏移
以前的偏移 = 现在的偏移 - 现在的ImageBase + 以前的ImageBase
= 503000 - 50000 + 60000000
= 3000 + 60000000
= 60003000 (以前的偏移)
那么算出了以前的偏移,我们就计算这4个字节要填写的偏移,也就是503000怎么得出来的
公式上面说了:
Cur (缩写,代表当前的意思) Old(代表旧的意思) offset(代表偏移的意思)
CurImageBase - OldImageBase + OldOffset = 要填入的偏移
代入公式:
00500000 - 60000000 + 60030000 = 00503000 (要填写的文件偏移)
看下我们当前的模块地址:
Inject是我们当前的EXE的名称,模块地址在00500000的位置
DLL的模块地址是60000000 这个地址是我们通过修改DLL中的选项头中的ImageBase得到的.
六丶总结
上面讲的很细致
今天主要就是结构体会看,偏移会算即可.
总结一下公式
1.定位重定位的地址 (也就是在哪里修改)
首先从数组取出一项,(2个字节大小)
比如0x3005
公式:
定位修改地址 = 现在模块 + 当前结构记录分页的RVA + 取出数组的2个字节的低3位
例子: 00401000 + 1000 + 005 = 世纪你要修改的地址,修改大小和取出word自己的第一位有关
2.计算出偏移地址,填写到定位地址的位置,使其偏移正确
现在的模块地址 - DLL模块地址 + 以前的偏移 = 实际修改的偏移
便于理解的公式:
以前的偏移 - DLL模块地址 + 现在模块地址
3.计算出以前偏移
要计算出以前的偏移,你首先要得出现在的偏移,好在OD已经写好了,其实文件中也有存储的.(自己找吧)
以前的偏移 = 现在的偏移 - 现在模块地址 + DLL模块地址
作者:IBinary
出处:http://www.cnblogs.com/iBinary/
版权所有,欢迎保留原文链接进行转载:)