也谈PE重定位表
最近研究脱壳,遇到了dll,所以不可避免的需要修复重定位表。以前研究过也脱过不少壳,但都是exe从来没有手工修过重定位表,于是搜索之,恩有这么一篇:《PE重定位表学习手记》,有刚巧手上有看雪段钢等编著的《加密与解密》,一并阅读。
《PE重定位表学习手记》中说到:
每个块的首部是如下定义:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
} IMAGE_BASE_RELOCATION;
……
这个可由每个块结构中的Size来确定。Size的值是以DWORD表示的当前整个块的大小,先减去IMAGE_BASE_RELOCATION的大小, 因为重定位数据是16位WORD的,再除以2,就得到个重定位数据的个数。由Size可以直接到达下一个重定位块,如图所 示:0x5E850000+0x000000EC=0x5E8500EC即为第二个重定位块的地址,直至某个块首结构的VirtualAddress为 0,表明重定位表结束。
……
do
{//处理一个接一个的重定位块,最后一个重定位块以RAV=0结束
……
}while (pRelocBlock->VirtualAddress);
《加密与解密》中说到:
如果还有更多段,将重复上面数据结构,直到VirtualAddress为NULL,表示结束。如图13.48所示是重定位表结构的一个示意图。
OK,照此,就是说如果我们自己手工创建编辑了重定位表,那么需要填充8个字节的0(也就是空的IMAGE_BASE_RELOCATION)作为重定位表的结束标记。并且pe文件头目录表中的重定位表尺寸也修改为整个重定位表数据的大小(当然包括这8个空字节)。
运行……嚯,0xC0000005……(如果8个空字节后恰好还有杂乱的随机数据,你还会看到各种有意思的Win32 ErrorCode)。
为啥呢?是修改的时候手抖了吗?眼花?神经衰弱?非也,是这些文章错了!我们先来看看微软官方权威的《Microsoft PE and COFF Specification》
以下文字来自Microsoft PE and COFF Specification(Updated: September 21, 2010 File name: pecoff_v8.docx)
5.6 The .reloc Section (Image Only)
The base relocation table contains entries for all base relocations in the image. The Base Relocation Table field in the optional header data directories gives the number of bytes in the base relocation table. For more information, see section 3.4.3, “Optional Header Data Directories (Image Only).” The base relocation table is divided into blocks. Each block represents the base relocations for a 4K page. Each block must start on a 32-bit boundary.
可以看到,微软的文档中并没有说重定位表需要padding zero来作为end标志。
下面我们看看到底windows中是怎么处理这个重定位表的,下面是反汇编windows xp sp2的 ntdll.dll,实际的LoadLibraryA/W最后会调用到 LdrLoadDll(x, x, x, x)
然后沿下列调用链(用IDA和微软的符号服务器即可方便的分析):
LdrLoadDll(x, x, x, x)->LdrpLoadDll(x,x,x,x,x,x)-> LdrpMapDll(x,x,x,x,x,x)->LdrRelocateImage(x, x, x, x, x)->LdrRelocateImageWithBias(x,x,x,x,x,x,x)
LdrRelocateImageWithBias就是实际执行重定位算法的函数,我们来看看这个函数是怎么做的:
首先RtlImageDirectoryEntryToData从目录表中获取重定位表的偏移和尺寸(TotalCountBytes)
然后指针移动到重定位表开始,然后循环开始每一项IMAGE_BASE_RELOCATION调用一次LdrProcessRelocationBlockLongLong,LdrProcessRelocationBlockLongLong返回值是下一项的指针,除非访问到了无效内存,否则这个指针我们可以认为永远是有效的,7C93D8A7这里的跳转不会执行到。TotalCountBytes为零的时候,说明重定位表处理完成,成功返回。
LdrProcessRelocationBlockLongLong是一个非常单纯的函数,里面完全根据读取到的数据来对PE进行重定位,也就是说,它永远认为输入数据是正确的!没有任何错误处理!
好了,现在我们回到一开始的问题:windows遇到了重定位表的最后一项,我们手工写的8个零,于是LdrProcessRelocationBlockLongLong一丝不苟的开始在VirtualAddress = 0的地方,开始处理(SizeOfBlock-8)/2项,别忘了SizeOfBlock=0,(SizeOfBlock-8)/2是什么请读者自己算吧……
总结:重定位表实际有多少项就是多少项!