PE结构-重定位表
Posted 嘻嘻兮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PE结构-重定位表相关的知识,希望对你有一定的参考价值。
首先,先来说一下为什么需要重定位表,先来观察一段程序
00401000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401002 |. 68 1D204000 push 0040201D ; |Title = "PE"
00401007 |. 68 10204000 push 00402010 ; |Text = "Hello World!"
0040100C |. 6A 00 push 0 ; |hOwner = NULL
0040100E |. E8 07000000 call <jmp.&USER32.MessageBoxA> ; \\MessageBoxA
00401013 |. 6A 00 push 0 ; /ExitCode = 0
00401015 \\. E8 06000000 call <jmp.&KERNEL32.ExitProcess> ; \\ExitProcess
0040101A $- FF25 08204000 jmp dword ptr [<&USER32.MessageBoxA>>; user32.MessageBoxA
00401020 .- FF25 00204000 jmp dword ptr [<&KERNEL32.ExitProces>; kernel32.ExitProcess
假设上述程序的建议装载地址为0x400000,然而当操作系统装载时,该地址被占用,所以实际地址装载地址为0x500000。
好了,那么你会发现,上述的代码片段中,是不是有些地址需要被修正呢,如第二行代码,其中的40201D肯定就不是原来的值了,50201D才为正确的值,OK,那么下面我们来列出上述程序所有需要被修正的地址
相对虚拟地址 需修正的地址 修正后地址
1003 40201D 50201D
1008 402010 502010
101C 402008 502008
1022 402000 502000
好了,观察这些地址,我们其实可以明白为什么这些地址需要被修正了,因为这些地址在代码中是写死的,或者说在二进制层面是固定的值,而且是虚拟地址,所以一旦基址发生变化后,这些地址都需要修正。
其实重定位表中记录的其实就是上面的相对虚拟地址,我们可以根据这个地址找到需要修正的地址并对其进行修正,那么如何修正呢?这里应该也比较明显,应该为原地址+(实际装载地址-原装载地址),后面后面括号部分其实就是个差值,把这部分差值加上就可以修正了,下面来模拟一下上面修正过程,就那1003来说吧,然后原装载地址为0x400000,实际地址装载地址为0x500000
假设已从重定位表中取出1003
1.计算修正地址VA
1003 + 500000 = 501003
2.取四字节内容,因为上面地址指向的内容是需要修正的
*(PDWORD)501003 = 40201D
3.修正上面取出的内容(加上实际装载和原装载的偏移)
40201D + (500000 - 400000) = 50201D
4.写回修正后的地址
*(PDWORD)501003 = 50201D
我们先来手工修改一下ImageBase和地址,来试一试是否可以修正成功
OD载入再次观察代码
00501000 >/$ 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00501002 |. 68 1D205000 push 0050201D ; |Title = "PE"
00501007 |. 68 10205000 push 00502010 ; |Text = "Hello World!"
0050100C |. 6A 00 push 0 ; |hOwner = NULL
0050100E |. E8 07000000 call <jmp.&USER32.MessageBoxA> ; \\MessageBoxA
00501013 |. 6A 00 push 0 ; /ExitCode = 0
00501015 \\. E8 06000000 call <jmp.&KERNEL32.ExitProcess> ; \\ExitProcess
0050101A $- FF25 08205000 jmp dword ptr [<&USER32.MessageBoxA>>; user32.MessageBoxA
00501020 .- FF25 00205000 jmp dword ptr [<&KERNEL32.ExitProces>; kernel32.ExitProcess
可以将这段代码与上面代码进行对比,对比后就会更清楚了。
好了明白了重定位的意义后,我们就可以来理解一下这个重定位表的结构了,也就是如何记录上述的那些地址
上面有提到过重定位表中记录的是VA,不过这个VA需要用多少字节来存储呢,这里的话我们想想,2字节,貌似不够,比如其真正地址为5FFFFF,那么其VA为FFFFF,明显已经超过2字节了,所以应该需要四字节,那么我们算一下总共大小
4 * sizeof DWORD = 16
好了,看着很小,只有16字节,不过是因为我们这个程序需要重定位的值比较少而已,一般都是大量的。那么如何可以优化呢,我们可以按页来划分,一页为0x1000,此时2字节就能存下了,那么页基址还是需要使用四字节来记录的
页基址: 1000
偏移: 3 8 1C 22
因为偏移肯定在页的范围内,所以只需要使用2字节记录即可
sizeof DWORD + 4 * sizeof WORD = 12
这里上下两差异是大不太,因为这里基数太小的原故,你可以把上面的4改成一个大一点的基数,那么差异就毕竟明显了,几乎可以说是折半了。
这里的这个结构就比较像重定位表的结构了,不过我们先再来想一个问题,一页的范围为[0,0xFFF],那么其实使用12位记录即可,2字节占16字节,所以是不是会多出来4位呢,这里的话操作系统使用高四位另做他用,起到状态位的效果
#define IMAGE_REL_BASED_ABSOLUTE 0 //无效重定位项,通常用于对齐
#define IMAGE_REL_BASED_HIGHLOW 3 //高低位修正,也就是四字节,windows 32位情况
#define IMAGE_REL_BASED_DIR64 10 //64位情况
这里我们只需要关注上几种即可,其余的是微软考虑其他操作系统儿准备的,比如可能有些操作系统只需要修正高位或者低位等等情况。
所以当我们在取这个VA地址的时候,切记需要验证状态位,然后取低12位当做页内偏移进行处理。
好了,重点其实差不多都罗列出来了,下面可以来看这个结构了
typedef struct _IMAGE_BASE_RELOCATION
DWORD VirtualAddress; //页基址 4字节
DWORD SizeOfBlock; //总共块大小
// WORD TypeOffset[1]; //偏移数组
IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
首先对于页基址和偏移数组都好理解,那么这个块大小是什么呢?其实因为这里的偏移数组是个不确定大小的(虽然结构体里面把这个字段注释掉了,但是在内存构造中这个偏移数组就跟在后面),因为每一页中有多少需要修正并不知道,所以才有了块大小,这里的块大小也包含VirtualAddress和SizeOfBlock两个字段的大小
(SizeOfBlock - sizeof IMAGE_BASE_RELOCATION) / sizeof WORD
(SizeOfBlock - 8) / 2
所以这个块大小减去这两个字段的大小,然后除以2就是需要修正的个数了。
这里的SizeOfBlock作用二就是定位下一块的修正块,也就是该块的地址加上该块的大小,这样子就定位到了下一块的数据。
那么何时结束呢,这里就用找数据目录表了,重定位表位于数据目录的第五项,所以上面的结构我们需要从数据目录的偏移过去
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
typedef struct _IMAGE_DATA_DIRECTORY
DWORD VirtualAddress; //重定位块的首地址
DWORD Size; //总共重定位块的大小
IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
OK,下面说说如何结束,这里数据目录项的Size是有意义的,也就是全部重定位块占用的总大小,我们需要在遍历每一块重定位块的时候减去SizeOfBlock,这样子减到零说明遍历就结束了
下面来分析一个简单的DLL模块,首先,来看一下数据目录表的第五项
做一下地址转换得出,重定位块位于0xA00,总大小为0xC,这里我们可以先预测一下,一个重定位块最少需要8字节(页内无偏移修正,不过话说回来,要是没修正的话也不会存在该块..),所以可以确定重定位块数只有一块,偏移有2项((0xC-0x8)/2)
下面来看一下数据
预测还是正确的,这里的页基址为0x1000,块大小为0xC,然后计算偏移项数为2,下面来分析下这两项,基址为0x10000000
0x3036
1.取状态 (0x3036&0xF000) >> 12
结果为3,说明是个32位的四字节修正
2.计算修正地址
计算页内偏移 0x3036 & 0x0FFF = 0x36
加上页基址 0x1000 + 0x36 = 0x1036
加上实际基址 0x10000000 + 0x1036 = 0x10001036
3.对该地址处的四字节内容进行修正
*(PDWORD)0x10001036 += (实际装载地址-建议装载地址)
0x0000
1. 同上取状态
结果为0,说明无效重定位项,直接略过即可
供2项,遍历结束
数据目录项中的Size 减去 SizeOfBlock,结果为0,遍历修正结束。
OK,下面我们使用OD打开来验证一下0x10001036项的地址是否需要修正
看结果是正确的,在OD中,需要修正的地址都会有下划线的标识(也是根据重定位识别的)。
以上是关于PE结构-重定位表的主要内容,如果未能解决你的问题,请参考以下文章