近日,做补丁程序的时候,需要在自己增加的代码中引用原数据段中的一字符串,于是直接从原代码中copy了一段引用该字符串的代码。头几天还能正常工作,不知什么原因程序突然无法运行了,打开调试器跟踪才发现问题就出在对字符串的引用上,模块载入后的基址与前几天相比发生了改变,补丁中对字符串的引用指向了一个无效空间,但是原代码中对字符串的引用却能修正到正确地址空间上。于是,我看到了“奇怪”的现象:相同的二进制代码却能导致不同运行结果。
自己写点代码实验一下。写一个空的DLL,在DllMain()中添加一条OutputDebugString("BB7887"),然后写一个可执行程序用LoadLibrary()载入该DLL文件,为了使“同一个”DLL能加载到多个地址空间,最简单的方法就是将该DLL文件复制为多个DLL文件然后在可执行程序中多次加载,呵呵,偷个懒!用OllyDbg跟踪,得到的结论是,无论该DLL被加载到任何基址上,原代码中对字符串的引用能成功,但相同的以补丁方式修改的代码,只有在DLL加载到默认的0x10000000基址上能正确引用字符串外,其余的都指向了无效空间。……Google之后才认识到,PE文件在被载入时可以根据基地址对代码中的地址进修修正,也就是“重定位”。所以虽然同样是pushxxxx却能有不同的结果,原代码中的push xxxx可以被修正为push bbbb,其中bbbb = xxxx +aaaa,aaaa为DLL模块的基址,而补丁中的pushxxxx就没这么幸运了,没人来管这个后娘养的孩子,xxxx被直接入栈。
重定位的基本原理:重定位以来与PE头文件的.reloc段,该段以索引的方式指出代码段中所有需要修正的数的地址,该地址以RVA相对虚地址的方式来表示,即该数值表示到基地址的偏移量。.reloc段由一系列子项组成,每项的数据结构如下:
DWORD VirtualAddress ; 起始 RVA值,每一个偏移位置必须加上此值才能够构成一个真正的 RVA
DWORD SizeOfBlock ; 该项的总长
WORD offset[n]; 偏移地址,只用到该字的低12bits,高4bits为标志为,一般为3
如:
Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F Ascii
00000000 00 10 00 00 60 00 00 00 35 30 43 30 5F 30 8030 ...`...50C0_0€0
00000010 8C 30 92 30 9C 30 AE 30 B8 30 BE 30 D2 30 D830 ????????
00000020 F0 30 F5 30 FC 30 03 31 0A 31 17 31 32 31 3831 ???1.112181
00000030 3E 31 44 31 4A 31 50 31 5E 31 66 31 6C 31 7731 >1D1J1P1^1f1l1w1
00000040 84 31 8C 31 9A 31 9F 31 A4 31 A9 31 B4 31 C131 ????????
00000050 CB 31 E0 31 EC 31 F2 31 14 32 26 32 82 32 9E32 ????2&2??
对应于:
->Relocation Directory
1. Relocation Block:
VirtualAddress: 0x00001000 (".text")
SizeOfBlock: 0x00000060 (0x002C block entries)
RVA Type
---------- -----------------
0x00001035 HIGHLOW
0x00001043 HIGHLOW
0x0000105F HIGHLOW
0x00001080 HIGHLOW
0x0000108C HIGHLOW
0x00001092 HIGHLOW
0x0000109C HIGHLOW
0x000010AE HIGHLOW
0x000010B8 HIGHLOW
0x000010BE HIGHLOW
在上面的例子中,所能表示的最大偏移仅仅为0x1ffff,远远是不够用的,所以.reloc段通常是由多个偏移索引的子项组成的,每个子项只能负责0x1000范围内的偏移量,每个子项以0x1000来分段对齐,比如,要指出RVA为0x43569865处的数据需重定位,那么VirtualAddress= 0x43569000,其中某索引的值offset = 0x865 or 0x3000 = 0x3865 。
用LordPE查看一下,定有更深刻的认识。另外:被重定位的数据在OllyDbg中都以下划线的方式加以表示。
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请
点击举报。