求教一个pe重定位表学习手记解析问题

PE结构之获取重定位表
[问题点数:20分,结帖人adobase]
PE结构之获取重定位表
[问题点数:20分,结帖人adobase]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2014年11月 VC/MFC大版内专家分月排行榜第二2014年1月 VC/MFC大版内专家分月排行榜第二2013年10月 VC/MFC大版内专家分月排行榜第二2013年9月 VC/MFC大版内专家分月排行榜第二2013年6月 VC/MFC大版内专家分月排行榜第二2013年4月 VC/MFC大版内专家分月排行榜第二2012年6月 VC/MFC大版内专家分月排行榜第二
2015年10月 VC/MFC大版内专家分月排行榜第三2015年7月 VC/MFC大版内专家分月排行榜第三2012年10月 VC/MFC大版内专家分月排行榜第三
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。PE重定位表(relocation)学习
Background:
PE文件基址重定位(Base Relocation),程序编译时每个模块有一个优先加载地址ImageBase,这个值是连接器给出的,因此连接器生成的指令中的地址是在假设模块被加载到ImageBase前提之下生成的,那么一旦程序没有将模块加载到ImageBase时,那么程序中的指令地址就需要重新定位,例如:假设一个可执行文件,基址是0x400000,在这个image偏移0x1234处是一个指针,指向一个字符串,字符串始于实际地址0x404002处,所以指针应该是0x404002,加载文件时,由于种种原因,加载器决定把他加载到0x600000处,连接器假设的地址和实际的地址之差成为delta,上例delta为0x200000,整个位置提高了0x200000,那么字符串位置应该为0x604002,原来指向字符串的指针就错误了,所以要把delta加到指针值中,为了让加载器有这样的能力做调整,可执行文件内含许多个【基址重定位项】,给那些存放指针的位置使用,加载器必须把delta加载到各个基址上。本例中应该把0x200000加给原来的指针值,0x404002,并将0x604002写回原处。
先定义一下用到的几个变量:
char *hModule=NULL;//映射后的基址
PIMAGE_OPTIONAL_HEADER pOptH//扩展头
PIMAGE_DATA_DIRECTORY pRelocTable=NULL;//指向重定位表
PIMAGE_BASE_RELOCATION pRelocB//指向重定位块
WORD *pRelocD//16位的重定位数据指针
PE头的定位和分析比较简单,不再多说。
首先,先判断重定位表是否存在:
pRelocTable=&(pOptHeader-&DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
判断pRelocTable-&VirtualAddress和pRelocTable-&Size是否为0即可。
若不为0,则重定位表存在。
ModulBase+pRelocTable-&VirualAddress即可定位到重定位表。
重定位表的结构如下图所示:
重定位表由一个个的重定位块组成,如果重定位表存在的话,必定是至少有一个重定位块。
因为每个块只负责定位0x1000大小范围内的数据,因此如果要定位的数据范围比较大的话,
就会有多个重定位块存在。
每个块的首部是如下定义:
typedef struct _IMAGE_BASE_RELOCATION {
} IMAGE_BASE_RELOCATION;
把内存中需要重定位的数据按页的大小0x1000分为若干个块,而这个VirtualAddress就是每个块的起始RVA.只知道块的RVA当然还不行,我们要知道每一个需要重定位数据的具体地址。
每个需重定位的数据其地址及定位方式用两个字节来表示,记为RelocData,紧跟在IMAGE_BASE_RELOCATION结构之后,如图所示。
每个块中重定位信息的个数如何确定?
这个可由每个块结构中的Size来确定。Size的值是以DWORD表示的当前整个块的大小,先减去IMAGE_BASE_RELOCATION的大小,因为重定位数据是16位WORD的,再除以2,就得到个重定位数据的个数。由Size可以直接到达下一个重定位块,如图所示:0x5Ex000000EC=0x5E8500EC即为第二个重定位块的地址,直至某个块首结构的VirtualAddress为
0,表明重定位表结束。
每个块中重定位数据的个数确定了,如何得知具体每个需进行重定位的数据的地址呢?
每个16位重定位信息包括低12位的重定位位置和高4位的重定位类型。要得到重定位的RVA,IMAGE_BASE_RELOCATION'的'VirtualAddress'需要加上12位位置偏移量.
类型是下列之一:
IMAGE_REL_BASED_ABSOLUTE (0)
使块按照32位对齐,位置为0。
IMAGE_REL_BASED_HIGH (1) 高16位必须应用于偏移量所指高字16位。
IMAGE_REL_BASED_LOW (2) 低16位必须应用于偏移量所指低字16位。
IMAGE_REL_BASED_HIGHLOW (3) 全部32位应用于所有32位。.
IMAGE_REL_BASED_HIGHADJ (4)
需要32位,高16位位于偏移量,低16位位于下一个偏移量数组元素,组合为一个带符号数,加上32位的一个数,然后加上8000然后把高16位保存在偏移量的16位域内。
IMAGE_REL_BASED_MIPS_JMPADDR
(5)&&&&&&&
IMAGE_REL_BASED_SECTION
(6)&&&&&&&
IMAGE_REL_BASED_REL32
(7)&&&&&&&
以第一个重定位数据0x34AC为例,其高四位表明了重定位类型为3,即IMAGE_REL_BASED_HIGHLOW,Win32环境下的重定位基本都是这个类型的。
其低12位则表明了相对于VirtualAddress的RVA偏移量。VirtualAddress即需重定位的数据块的起始RVA,再加上这低12位的值就得到了具体的需要进行重定位处理的数据的RVA。感觉说得有点乱,总之就是VirtualAddress与每一个16位重定位数据一起可以得到一个具体要进行重定位处理的数据的RVA。
也就是说:
要进行重定位处理的数据的RVA=VirtualAddress+RelocData&0x0FFF
=0xx34AC)&0x0FFF
=0x000014AC
再加上模块基址,就得到了在内存中的真实地址了
由ModuleBase=0x5E830000,可得这个重定位数据的地址为:0x5Ex000014AC=0x5E8314AC
转到OD的反汇编窗口,Ctrl+G跳到这个地址,可以看到:
OD自动标上了下划线,表明这是一个重定位数据,接下来的几个数据也可以一一进行对应.
至此,重定位算是搞明白了。
那如何进行修正呢?
把需要修正的数据减去IMAGE_OPTINAL_HEADER中的ImageBase,再加上当前加载的实际基址,就可以了。至此重定位完成!当然,也可以根据别的基址进行重定位。比如加载ntoskrnl.exe时,按照ntoskrnl.exe在内存中加载的实际基址0x804E0000(我系统上的数据)进行重定位之后,然后就可以干很多事情了~~比如查找原始SSDT,或者进行
InlineHook检测,搜索未导出函数等等,总之比较有用。
下面是我程序中的一段代码:
//先获取重定位表信息
pRelocTable=&(pOptHeader-&DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]);
//得到第一个重定位块
pRelocBlock=(PIMAGE_BASE_RELOCATION)(hModule+pRelocTable-&VirtualAddress);
//开始处理所有重定位数据
{//处理一个接一个的重定位块,最后一个重定位块以RAV=0结束
//需要重定位的个数,是本块的大小减去块头的大小,结果是以DWORD表示的大小
//而重定位数据是16位的,因此除以2
numofReloc=(pRelocBlock-&SizeOfBlock-sizeof(IMAGE_BASE_RELOCATION))/2;
//printf("Reloc Data num=%d\n",numofReloc);
//重定位数据是16位的
WORD minioffset=0;
*pRelocData=(WORD*)((char*)pRelocBlock+sizeof(IMAGE_BASE_RELOCATION));
(i=0;i&numofRi++)//循环,或直接判断*pData是否为0也可以作为结束标记
DWORD *RelocAddress=0;//需要重定位的地址
//重定位的高4位是重定位类型,
(((*pRelocData)&&12)==IMAGE_REL_BASED_HIGHLOW)//判断重定位类型是否为IMAGE_REL_BASED_HIGHLOW
//计算需要进行重定位的地址
//重定位数据的低12位再加上本重定位块头的RAV即真正需要重定位的数据的RAV
minioffset=(*pRelocData)&0xFFF;//小偏移
//模块基址+重定位基址+每个数据表示的小偏移量
RelocAddress=(DWORD*)(hModule+pRelocBlock-&VirtualAddress+offset);
//对需要重定位的数据进行修正
//修正方法:减去IMAGE_OPTINAL_HEADER中的基址,再加上实际基址即可
*RelocAddress=*RelocAddress-pOptHeader-&ImageBase+hM
//指向下一个重定位数据
pRelocData++;
//指向下一个重定位块
pRelocBlock=(PIMAGE_BASE_RELOCATION)((char*)pRelocBlock+pRelocBlock-&SizeOfBlock);
(pRelocBlock-&VirtualAddress);
这些工作只是自己加载PE过程的一部分,自己加载PE有很多用处,主要是因为加载的是原始的文件,只要不是固化挂钩,都可以自己加载然后与内存中的进行对比,用处还是不少的~
1.sudami的《SDT Restore v0.2
学习手记》。不过那个源码中用了太多自定义的结构,使得读起来稍有些困难,其实这些结构都是有现成定义的。
2.看雪论坛的《PE文件格式》
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。PE文件的重定位表包含了哪些信息的地址?
[问题点数:40分]
PE文件的重定位表包含了哪些信息的地址?
[问题点数:40分]
不显示删除回复
显示所有回复
显示星级回复
显示得分回复
只显示楼主
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
2013年7月 C/C++大版内专家分月排行榜第一
2015年9月 C/C++大版内专家分月排行榜第二2013年6月 C/C++大版内专家分月排行榜第二
2013年 总版技术专家分年内排行榜第三
2012年 总版技术专家分年内排行榜第七
2013年7月 C/C++大版内专家分月排行榜第一
2015年9月 C/C++大版内专家分月排行榜第二2013年6月 C/C++大版内专家分月排行榜第二
匿名用户不能发表回复!|
每天回帖即可获得10分可用分!小技巧:
你还可以输入10000个字符
(Ctrl+Enter)
请遵守CSDN,不得违反国家法律法规。
转载文章请注明出自“CSDN(www.csdn.net)”。如是商业用途请联系原作者。4769人阅读
逆向分析(5)
前面两篇&&和&&介绍了PE文件中比较常用的两种导入方式,不知道大家有没有注意到,在调用导入函数时系统生成的代码是像下面这样的:
在这里,IE的iexplorer.exe导入了Kernel32.dll的GetCommandLineA函数,可以看到这是个间接call,这个地址的内存里保存了目的地址,根据图中显示的符号信息可知,这个地址是存在于iexplorer.exe模块中的,实际上也就是一项IAT的地址。这个是IE6的exe中的例子,当然在dll中如果导入其他dll中的函数,结果也是一样的。这样就有一个问题,代码里call的地址是一个模块内的地址,而且是一个VA,那么如果模块基地址发生了变化,这个地址岂不是就无效了?这个问题如何解决?
答案是:Windows使用重定位机制保证以上代码无论模块加载到哪个基址都能正确被调用。听起来很神奇,是怎么做到的呢?其实原理并不很复杂,这个过程分三步:
1.编译的时候由编译器识别出哪些项使用了模块内的直接VA,比如push一个全局变量、函数地址,这些指令的操作数在模块加载的时候就需要被重定位。
2.链接器生成PE文件的时候将编译器识别的重定位的项纪录在一张表里,这张表就是重定位表,保存在DataDirectory中,序号是&IMAGE_DIRECTORY_ENTRY_BASERELOC。
3.PE文件加载时,PE 加载器分析重定位表,将其中每一项按照现在的模块基址进行重定位。
以上三步,前两部涉及到了编译和链接的知识,跟本文的关系不大,我们直接看第三步,这一步符合本系列的特征。
在查看重定位表的定义前,我们先了解一下他的存储方式,有助于后面的理解。按照常规思路,每个重定位项应该是一个DWORD,里面保存需要重定位的RVA,这样只需要简单操作便能找到需要重定位的项。然而,Windows并没有这样设计,原因是这样存放太占用空间了,试想一下,加入一个文件有n个重定位项,那么就需要占用4*n个字节。所以Windows采用了分组的方式,按照重定位项所在的页面分组,每组保存一个页面其实地址的RVA,页内的每项重定位项使用一个WORD保存重定位项在页内的偏移,这样就大大缩小了重定位表的大小。
有了上面的概念,我们现在可以来看一下基址重定位表的定义了:
typedef struct _IMAGE_BASE_RELOCATION {
TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
VirtualAddress:页起始地址RVA。
SizeOfBlock:表示该分组保存了几项重定位项。
TypeOffset:这个域有两个含义,大家都知道,页内偏移用12位就可以表示,剩下的高4位用来表示重定位的类型。而事实上,Windows只用了一种类型IMAGE_REL_BASED_HIGHLOW &数值是 3。
好了,有了以上知识,相信大家可以很容易的写出自己修正重定位表的代码,不如自己做个练习验证一下吧。
本文&by evil.eagle 转载的时候请注明出处。
最后,还是总结一下,哪些项目需要被重定位呢?
1.代码中使用全局变量的指令,因为全局变量一定是模块内的地址,而且使用全局变量的语句在编译后会产生一条引用全局变量基地址的指令。
2.将模块函数指针赋值给变量或作为参数传递,因为赋值或传递参数是会产生mov和push指令,这些指令需要直接地址。
3.C++中的构造函数和析构函数赋值虚函数表指针,虚函数表中的每一项本身就是重定位项,为什么呢?大家自己考虑一下吧,不难哦~
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:66041次
排名:千里之外
原创:21篇
(3)(3)(1)(1)(1)(4)(6)(6)(1)(1)

我要回帖

更多关于 myccl 定位在pe头 的文章

 

随机推荐