重定位表(Relocation Table)是Windows PE可执行文件中的一部分,主要记录了与地址相关的信息,它在程序加载和运行时被用来修改程序代码中的地址的值,因为程序在不同的内存地址中加载时,程序中使用到的地址也会受到影响,因此需要重定位表这个数据结构来完成这些地址值的修正。

当程序需要被加载到不同的内存地址时,相关的地址值需要进行修正,否则程序运行会出现异常。而重定位表就是记录了在程序加载时需要修正的地址值的相关信息,包括修正地址的位置、需要修正的字节数、需要修正的地址的类型等。重定位表中的每个记录都称为一项(entry),每个entry包含了需要修正的地址值的详细信息,通常是以可变长度数据的形式存储在一个或多个叫做重定位块(relocation block)的数据结构中。

解析重定位表需要通过PIMAGE_BASE_RELOCATION这个关键结构体来实现,PIMAGE_BASE_RELOCATION是一个指向重定位表(Relocation Table)的指针类型,它是Windows PE可执行文件中用于支持动态基地址重定位(Dynamic Base Relocation)的结构体类型。在2GB以上的虚拟地址下,Windows使用了Dynamic Base Relocation技术来提高系统的安全性,PIMAGE_BASE_RELOCATION就是在这种情况下使用的。

由于Windows系统中DLL文件并不能每次都能加载到预设的基址上,因此基址重定位主要应用于DLL文件中,通常涉及到直接寻址的指令就需要重定位,重定位信息是在编译时,由编译器生成并被保存在可执行文件中的,在程序被执行前,由操作系统根据重定位信息修正代码,这样在开发程序的时候就不用了考虑重定位问题了,我们还是使用上面的这段汇编代码。

00D21000 | 6A 00              | push 0x0                            |
00D21002 | 68 0030D200 | push main.D23000 |
00D21007 | 68 0730D200 | push main.D23007 |
00D2100C | 6A 00 | push 0x0 |
00D2100E | E8 07000000 | call <JMP.0x00D2101A> | call MessageBox
00D21013 | 6A 00 | push 0x0 |
00D21015 | E8 06000000 | call <JMP.0x00D21020> | call ExitProcess
00801017 | CC | int3 |
00D2101A | FF25 0820D200 | jmp dword ptr ds:[<&0x00D22008>] | 导入函数地址
00D21020 | FF25 0020D200 | jmp dword ptr ds:[<&0x00D22000>] | 导入函数地址

如上jmp dword ptr ds:[<&0x00D22008>]这段代码就是一句需要重定位的代码,当程序的基地址位于0x00D20000时,这段代码中的函数可以被正常调用,但有时程序会开启基址随机化,或DLL被动态装载等问题,此时基地址可能会发生变化,那么上面的汇编指令调用就会失效,这就意味着这些地址需要被修正。

此时我们假设程序基址变为了0x400000,那么jmp dword ptr ds:[<&0x00D22008>]这条指令就需要被修正,修正算法可以描述为,将直接寻址指令中的地址加上模块实际装入地址与模块建议装入地址之差,为了进行运算需要3个数据,首先是需要修正机器码地址,其次是模块建议装入地址,最后是模块的实际装入地址。

在这3个数据中,模块的建议装入地址已经在PE文件头中定义了,而模块的实际装入地址时Windows装载器在装载文件时确定的,事实上PE文件重定位表中保存的仅仅只是,一大堆需要修正的代码的地址。

重定位表IMAGE_BASE_RELOCATION解析

重定位表会被单独存放在.reloc命名的节中,重定位表的位置和大小可以从数据目录中的第6个IMAGE_DATA_DIRECTORY结构中获取到,该表的组织方式时以0x1000页为一块,每一块负责一页,从PE文件头获取到重定位表地址后,就可以顺序读取到所有表结构,每个重定位块以一个IMAGE_BASE_RELOCATION结构开头,后面跟着在本页中使用的所有重定位项,每个重定位项占用16字节,最后一个节点是一个使用0填充的_IMAGE_BASE_RELOCATION标志表的结束,其结构如下所示:

typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress; // 需重定位数据的起始RVA
DWORD SizeOfBlock; // 本结构与TypeOffset总大小
WORD TypeOffset[1]; // 原则上不属于本结构
} IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED IMAGE_BASE_RELOCATION;

TypeOffset的元素个数 = (SizeOfBlock - 8 )/ 2 TypeOffset的每个元素都是一个自定义类型结构

struct
{
WORD Offset:12; // 大小为12Bit的重定位偏移
WORD Type :4; // 大小为4Bit的重定位信息类型值
}TypeOffset; // 这个结构体是A1Pass总结的

PIMAGE_BASE_RELOCATION指针指向PE文件中的重定位表(Relocation Table)的起始地址,重定位表是一个可变长度的数据结构,其中包含了一组以4个字节为单位的记录,每个记录表示一个需要修正的地址及其操作类型。

typedef struct _IMAGE_BASE_RELOCATION
{
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;

每个重定位表项(Relocation Table Entry)包括两部分:前16位表示需要修正的地址的偏移量(Offset),后16位则表示需要对该地址进行什么样的修正操作(Relocation Type)。普通的重定位项类型有如下几种:

  • IMAGE_REL_BASED_ABSOLUTE:表示不需要进行任何修正;
  • IMAGE_REL_BASED_HIGHLOW:表示需要将地址中的低16位和高16位分别进行修正;
  • IMAGE_REL_BASED_DIR64:表示需要对64位指针进行修正;

当读者需要遍历这个表时,首先可以通过NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress获取到重定位表的相对信息,并通过(PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA))得到重定位表的FOA文件地址,在Reloc->SizeOfBlock变量内获取到重定位块,并循环输出则可实现枚举所有重定位块;

// --------------------------------------------------
// 重定位表解析结构体
// --------------------------------------------------
struct TypeOffset
{
WORD Offset : 12; // 低12位代表重定位地址
WORD Type : 4; // 高4位代表重定位类型
}; int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0); if (PE == TRUE)
{
// 1.拿到映像基地址
DWORD base = NtHeader->OptionalHeader.ImageBase; // 2.获取重定位表的RVA 相对偏移
DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress; // 3.获取重定位表FOA
auto Reloc = (PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA)); printf("映像基址: %08X 虚拟偏移: %08X 重定位表基址: %08X \n", base, RelocRVA, Reloc); // 4.遍历重定位表中的重定位块,以0结尾
while (Reloc->SizeOfBlock != 0)
{
// 计算出重定位项个数 \ 2 = 重定位项的个数,原因是重定位项的大小为2字节
DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; // 输出VirtualAddress分页基址 与SizeOfBlock重定位块长度
printf("起始RVA: %08X \t 块长度: %04d \t 重定位个数: %04d \n", Reloc->VirtualAddress, Reloc->SizeOfBlock, Size); // 找到下一个重定位块
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
}
}
else
{
printf("非标准程序 \n");
} system("pause");
return 0;
}

编译并运行上述代码片段,则读者可以看到当前程序内所具备的重定位块及该块的内存地址,输出效果图如下所示;

上图中我们得到了0x905a4d00这个内存地址,该内存地址代表的则是重定位表中一个块的基址,如果我们需要得到该基址内的其他重定位信息,则需要进一步遍历,这个遍历过程只需要更加细化将如上代码片段进行更改,增加更加细致的枚举过程即可,更改后的代码片段如下所示;

// --------------------------------------------------
// 传入一个十六进制字符串,将其自动转化为十进制格式:例如传入40158b转为4199819
// --------------------------------------------------
int HexStringToDec(char hexStr[])
{
int i, m, n, temp = 0; // 循环读入每一个十六进制数
m = strlen(hexStr);
for (i = 0; i < m; i++)
{
// 十六进制还要判断他是不是在A-F或0-9之间的数
if (hexStr[i] >= 'A' && hexStr[i] <= 'F')
n = hexStr[i] - 'A' + 10;
else if (hexStr[i] >= 'a' && hexStr[i] <= 'f')
n = hexStr[i] - 'a' + 10;
else n = hexStr[i] - '0';
// 将数据加起来
temp = temp * 16 + n;
}
return temp;
} int main(int argc, char * argv[])
{
BOOL PE = IsPeFile(OpenPeFile("c://pe/x86.exe"), 0);
char * GetRva = "0x905a4d00"; if (PE == TRUE)
{
DWORD base = NtHeader->OptionalHeader.ImageBase; // 1. 获取重定位表的 rva
DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress; // 2. 获取重定位表
auto Reloc = (PIMAGE_BASE_RELOCATION)(GlobalFileBase + RVAtoFOA(RelocRVA)); printf("起始RVA \t 类型 \t 重定位RVA \t 重定位地址 \t 修正RVA \n"); // 起始RVA:% 08X-- > 类型:% d-- > 重定位RVA:% 08X-- > 重定位地址:% 08X 修正RVA : % 08X // 3. 遍历重定位表中的重定位块,以0结尾
while (Reloc->SizeOfBlock != 0)
{
// 3.2 找到重定位项
auto Offset = (TypeOffset*)(Reloc + 1); // 3.3 计算重定位项的个数
DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2; // 3.4 遍历所有的重定位项
for (DWORD i = 0; i < Size; ++i)
{
// 获取重定位类型,只关心为3的类型
DWORD Type = Offset[i].Type; // 获取重定位的偏移值
DWORD pianyi = Offset[i].Offset; // 获取要重定位的地址所在的RVA: offset+virtualaddress
DWORD rva = pianyi + Reloc->VirtualAddress; // 获取要重定位的地址所在的FOA
DWORD foa = RVAtoFOA(rva); // 获取要重定位的地址所在的fa
DWORD fa = foa + GlobalFileBase; // 获取要重定位的地址
DWORD addr = *(DWORD*)fa; // 计算重定位后的数据: addr - oldbase + newbase
DWORD new_addr = addr - base; // 如果传入了数值,则说明要遍历特定的重定位表项
if (Reloc->VirtualAddress == HexStringToDec(GetRva))
{
printf("%08X \t %d \t %08X \t %08X \t%08X \n", Reloc->VirtualAddress, Type, rva, addr, new_addr);
}
// 否则如果不传参数,则默认遍历全部RVA
else if (strcmp(GetRva, "all") == 0)
{
printf("%08X \t %d \t %08X \t %08X \t%08X \n", Reloc->VirtualAddress, Type, rva, addr, new_addr);
}
}
// 找到下一个重定位块
Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
}
}
else
{
printf("非标准程序 \n");
} system("pause");
return 0;
}

当读者运行这段程序,则会输出0x905a4d00这段内存地址中所具有的所有重定位信息,输出效果图如下图所示;

本文作者: 王瑞

本文链接: https://www.lyshark.com/post/72fc3188.html

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

2.7 PE结构:重定位表详细解析的更多相关文章

  1. PE知识复习之PE的重定位表

    PE知识复习之PE的重定位表 一丶何为重定位 重定位的意思就是修正偏移的意思.  如一个地址位 0x401234 ,Imagebase = 0x400000 . 那么RVA就是 1234.  如果Im ...

  2. WindowsPE权威指南-PE文件头中的重定位表

    PE加载的过程 任何一个EXE程序会被分配4GB的内存空间,用户层处理低2G的内存,驱动处理高2G的内存. 1.双击EXE程序,操作系统开辟一个4GB的空间. 2.从ImageBase决定了加载后的基 ...

  3. PE结构之重定位表

    什么是重定位: 重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你占用,你必须转移到别的地址,这就需要基址重定位.你可能会问,不是说过每个进程都有自己独立的虚拟地址空 ...

  4. PE格式第七讲,重定位表

    PE格式第七讲,重定位表 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首先, ...

  5. PE文件 03 重定位表

    0x01  重定位表结构   重定位表是由数据目录表中的第六个成员指出的: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; D ...

  6. 【旧文章搬运】PE重定位表学习手记

    原文发表于百度空间,2008-11-02========================================================================== 先定义一下 ...

  7. 逆向-PE重定位表

    重定位表 ​ 当链接器生成一个PE文件时,会假设这个文件在执行时被装载到默认的基地址处(基地址+RVA就是VA).并把code和data的相关地址写入PE文件.如果像EXE一样首先加载就是它image ...

  8. Windows PE第6章 栈与重定位表

    第六章 栈与重定位表 本章主要介绍栈和代码重定位.站和重定位表两者并没有必然的联系,但都和代码有关.栈描述的是代码运行过程中,操作系统为调度程序之间相互调用关系,或临时存放操作数而设置的一种数据结构. ...

  9. 零基础逆向工程23_PE结构07_重定位表_IAT表(待补充)

    重定位表 待补充 IAT表 待补充

  10. Reverse Core 第二部分 - 16&17章 - 基址重定位表&.reloc节区

    第16-17章 - 基址重定位表&.reloc节区 @date: 2016/11/31 @author: dlive 0x00 前言 这几天忙着挖邮箱漏洞,吃火锅,马上要被关禁闭,看书进度比较 ...

随机推荐

  1. 2013年蓝桥杯C/C++大学B组省赛真题(翻硬币)

    题目描述: 明正在玩一个"翻硬币"的游戏. 桌上放着排成一排的若干硬币.我们用 * 表示正面,用 o 表示反面(是小写字母,不是零). 比如,可能情形是:**oo***oooo 如 ...

  2. Spring boot+vue打包、上传宝塔面板并配置https

    终于把网站搞完了,也终于能够通过域名访问了,这次就简单回顾一下这么多时间的经历,总结一下. 项目地址穆音博客,本文发布原地址在Spring boot+vue打包.上传宝塔面板并配置https 我的开发 ...

  3. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-1-环境准备与搭建

    1.简介 有很多人私信留言宏哥问能不能介绍一下Playwright这款自动化神器的相关知识,现在网上的资料太少了.其实在各大博客和公众号也看到过其相关的介绍和讲解.要不就是不全面.不系统,要不就是系统 ...

  4. CF1477E&大户爱的送分题题解

    CF1477E&大户爱的送分题题解 (CF1477E为我出的校内模拟赛的一道题--<大户爱的送分题>的待修版本) 大户爱的送分题 文件名OhtoAiFirst.cpp/.in/.o ...

  5. 10.5. 版本控制(如Git)

    版本控制系统(Version Control System,VCS)是软件开发过程中用于管理源代码的工具.它可以帮助你跟踪代码的变更历史,方便回滚到之前的版本,以及协同多人共同开发.Git是当前最流行 ...

  6. C#使用HtmlAgilityPack解析Html 爬取图片和视频

    HtmlAgilityPack简介 HtmlAgilityPack是.net下的一个HTML解析类库.支持用XPath来解析HTML. 问题来了,有人就会问为什么要使用能XPath呢? 小编答:因为对 ...

  7. Centos7安装配置Hive

    Centos7安装配置 一 . 安装 安装就不多做详述,选择好自己的镜像设置好路径即可 二 .配置 2.1 网络配置 桌面右键进入 cmd 命令编辑窗口,在 Linux 中设置网络的相关配置都需要管理 ...

  8. Pinot2的无人机创新和发展

    目录 1. 引言 2. 技术原理及概念 2.1 基本概念解释 2.2 技术原理介绍 2.3 相关技术比较 3. 实现步骤与流程 3.1 准备工作:环境配置与依赖安装 3.2 核心模块实现 3.3 集成 ...

  9. 关于Java已死,看看国外开发者怎么说的

    博主在浏览 medium 社区时,发现了一篇点赞量 1.5k 的文章,名称叫<Java is Dead - 5 Misconceptions of developers that still t ...

  10. Redis缓存同步1-策略介绍

    缓存数据同步策略示意图 在大多数情况下,我们通过浏览器查询到的数据都是缓存数据,如果缓存数据与数据库的数据存在较大差异的话,可能会产生比较严重的后果的.所以,我们应该也必须保证数据库数据.缓存数据的一 ...