PE格式第七讲,重定位表

         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/
版权所有,欢迎保留原文链接进行转载:)

posted @ 2017-10-19 01:30  iBinary  阅读(3368)  评论(0编辑  收藏  举报