PE文件基址重定位

本文介绍了PE文件基址重定位。程序编译时模块有优先加载地址,若未加载到该地址,需用重定位表调整。还介绍了基址重定位项,其位于.reloc区块,加载器通过数据目录定位,以IMAGE_BASE_RELOCATION结构起始,编译器会据此修正指令使程序正常执行。

PE文件基址重定位(Base Relocation),程序编译时每个模块有一个优先加载地址ImageBase,这个值是连接器给出的,因此连接器生成的指令中的地址是在假设模块被加载到ImageBase前提之下生成的,链接器把code段和data段的相关地址都写入到PE文件中,如果程序没有被加载到imageBase段,链接器所登记的地址就是错误的会影响code和data段数据的定位。 此时就需要用到重定位表来调整, PE文件往往单独分为一块,用'reloc'表示。

假设一个可执行文件,基址是0x400000,在这个image偏移0x1234处是一个指针,指向一个字符串,字符串始于实际地址0x404002处,所以指针应该是0x404002(RVA应该是4002) 。 加载器决定把他加载到0x600000处,连接器假设的地址和实际的地址之差成为delta(差值),上例delta为0x200000 , 那么字符串未知就应该为0x604002.为了让windows有能力这样调整,可执行文件中有许多'基址重定位数据”本例中的装载器应把0x200000 加给0x404002,并将0x604002 写回原处。

 

下面我们来介绍一下【基址重定位项】,加载器就是利用它来知道模块是否按预期的位置加载,哪些指令是需要修改的。基址重定位表位于一个.reloc的区块内,但找到他们的正确方式是通过数据目录表的IMAGE_DIRECTORY_ENTRY_BASERELOC条目

因此我们研究的重点将是【基址重定位项】,首先加载器也是通过数据目录来定位【基址重定位项】,【基址重定位项】被包装为一系列连续区段,每一个区段来描述一个4K PAGE(也就是一页)的重定位信息,长短不一(每页中需要重定位的指令数目也不一样),

它们以一个IMAGE_BASE_RELOCATION结构作为开始,格式如下:

DWORD VirtualAddress重定位内存页的起始RVA,每一个【基址重定位项】的偏移位置(即下面的TypeOffset的低12位,它是指令相对于它所在页的第一条指令的偏移),必须加上重定位页的RVA才是一个真正的RVA,指向【基址重定位项】

DWORD SizeOfBlock:结构大小,在加上跟着后面的所有【基址重定位项】(都是WORDS),为了决定【基址重定位项】的个数,=(SizeOfBlock-Sizeof(IMAGE_BASE_RELOCATION)(8个字节))/2(WORD占2个字节);例如此值为44,则个数为44-8/2=18.

总结:IMAGE_BASE_RELOCATION包含两个成员,一个是VirtualAddress,包含自身内存页起始位置RVA,另一个SizeOfBlock,表明这个结构有多大。

WORD TypeOfOffset

这并不是单独一个WORD,而是一个WORDS组,数组元素个数可以有上一个式子计算得到,每一个WORD的最底部12位代表【基址重定位项】的位置偏移,但必须在加上IMAGE_BASE_RELOCATION表头中VirtualAddress,最高4位是【基址重定位项】的型态。对于在Intel CPU中的PE文件,你将看到两种状态,0(IMAGE_REL_BASED_ABSOLUTE)此一【基址重定位数据项】无意义,只是用来充数而已,使所有【基址重定位项】总数为DWORD倍数。3(IMAGE_REL_BASED_HIGNLOW):把delta值加到欲计算的RVA值,另外还有其他状态在WINNT.h中,它们大部分是给i386以外的的CPU使用。

下面给出一些【基址重定位项】,请注意其中的RVA已经被IMAGE_BASE_RELOCATION中的VirtualAddress校正过。

VirtualAddress:00001000  Size0000012C

000001032 HIGHLOW

00000106D HIGHLOW

0000010AF  HIGNLOW

......

VirtualAddress:00002000 Size0000009C

000020A6  HIGHLOW

00002110  HIGHLOW

00002136  HIGHLOW

00002156  HIGHLOW

........

VirtualAddress:000003000 Size00000114

0000300A  HIGHLOW

0000301E  HIGHLOW

0000303B  HIGHLOW

0000306A  HIGHLOW

通过上面的例子我们可以看到相邻区段正好相差0X1000,也就是4K,证实了上面所说的“每一个区段描述image一个4K Page(也就是一页)的重定位信息”同时我们可以看到第一个区段的VirtualAddress是00001000,正好是.text起始RVA。编译器就会根据【基址重定位项】去修正哪些需要修正的指令,读取对应地址的值例如00204000的数据将其加上delta值作为新的地址值,这样程序就可以正常执行了

### PE文件重定位表概述 PE(Portable Executable)文件重定位表是一种重要的数据结构,主要用于支持动态基址加载(Dynamic Base Relocation)。这一特性允许操作系统在加载PE文件时调整其基地址,从而避免与其他已加载模块发生冲突。重定位表由一系列`IMAGE_BASE_RELOCATION`结构组成,每个结构描述了一组需要进行地址修正的数据项。 #### 1. 数据目录条目 重定位表的信息存储在PE文件头部的“数据目录”中。具体而言,可选头中的第6个数据目录条目`IMAGE_DIRECTORY_ENTRY_BASERELOC`指向了重定位表的位置和大小[^5]。该条目的定义如下: ```c typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; // 表格起始RVA DWORD Size; // 表格总长度 } IMAGE_DATA_DIRECTORY; ``` 其中: - `VirtualAddress`表示重定位表相对于映像基址的虚拟地址(RVA),即表格在内存中的相对位置。 - `Size`表示整个重定位表占用的空间大小。 --- ### 2. 重定位表的核心结构 重定位表是由多个`IMAGE_BASE_RELOCATION`结构组成的数组,每个结构代表一个重定位块。以下是`IMAGE_BASE_RELOCATION`的具体定义: ```c typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; // 当前块对应的区域RVA DWORD SizeOfBlock; // 当前块的总大小(包括TypeOffset字段) WORD TypeOffset[]; // 类型与偏移量组合列表 } IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION; ``` #### 字段解释 - **VirtualAddress**: 指定了当前重定位块所覆盖的内存区域起点的RVA。这意味着所有需要被重定位的项目都在此区域内。 - **SizeOfBlock**: 定义了当前重定位块的整体大小,单位为字节。需要注意的是,这个值不仅包含了`VirtualAddress`和`SizeOfBlock`本身,还涵盖了后续所有的`TypeOffset`字段。 - **TypeOffset**: 数组形式存在的一系列16位值,每两个字节记录了一个重定位项目的类型及其偏移量。高4位表示重定位类型,低12位则指定目标地址在此块内的偏移量。 常见的重定位类型有以下几种: - **IMAGE_REL_BASED_HIGHLOW (3)**: 修改32位指针的目标地址。 - **IMAGE_REL_BASED_DIR64 (10)**: 修改64位指针的目标地址。 --- ### 3. 解析流程 解析重定位表的过程可以分为以下几个方面展开讨论: #### 获取重定位表入口 首先读取PE文件头部的`DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]`字段获取重定位表的起始RVA和总大小[^5]。 #### 遍历重定位块 按照`IMAGE_BASE_RELOCATION.SizeOfBlock`逐步遍历每一个重定位块。对于每个块,提取出它的`VirtualAddress`以及随后紧跟的若干个`TypeOffset`值[^1]。 #### 计算实际物理地址 针对每个`TypeOffset`,分离出高位的重定位类型和低位的实际偏移量。然后依据不同的重定位类型执行相应的计算逻辑。例如,如果是`IMAGE_REL_BASED_HIGHLOW`类型的重定位,则需将原始地址加上新的加载基址减去旧的图像基址差值得到更新后的有效地址[^4]。 --- ### 示例代码展示 下面提供一段简单的Python伪代码演示如何初步解析PE文件中的重定位表信息: ```python import pefile def parse_relocation_table(pe_file_path): pe = pefile.PE(pe_file_path) reloc_dir = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_BASERELOC']] if not reloc_dir.VirtualAddress or not reloc_dir.Size: print("No relocation table found.") return base_reloc_rva = reloc_dir.VirtualAddress size_of_block = reloc_dir.Size entries = [] offset = 0 while offset < size_of_block: block = pe.get_data(base_reloc_rva + offset, 8) virtual_address = int.from_bytes(block[:4], byteorder='little') block_size = int.from_bytes(block[4:], byteorder='little') num_entries = (block_size - 8) // 2 type_offsets = [] for i in range(num_entries): entry_offset = offset + 8 + i*2 entry = pe.get_dword_at_rva(base_reloc_rva + entry_offset) rel_type = (entry >> 12) & 0xF rel_offset = entry & 0xFFF type_offsets.append((rel_type, rel_offset)) entries.append({ 'virtual_address': virtual_address, 'type_offsets': type_offsets }) offset += block_size return entries ``` --- ### 总结 通过对PE文件重定位表的学习可以看出,它主要服务于程序能够在不同基址环境下正常运行的目的。通过合理利用`IMAGE_BASE_RELOCATION`结构及相关算法能够有效地完成对特定内存位置数值的自动校正工作[^2]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值