2.7 PE结构:重定位表详细解析

news2024/11/18 1:32:06

重定位表(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 许可协议。转载请注明出处!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/984701.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单月打造8个10w+,情感类视频号如何爆火?

上月&#xff0c;腾讯公布了2023年Q2财报&#xff0c;其中&#xff0c;较为亮眼的是微信视频号的广告收入。据财报显示&#xff0c;二季度视频号用户使用时长与去年同期相比几乎翻倍&#xff0c;广告收入超过30亿元。作为微信生态的核心组件&#xff0c;视频号的内容生态呈现出…

C/C++浮点数向零舍入 2019年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C浮点数向零舍入 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C浮点数向零舍入 2019年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入一个单精度浮点数&#…

09-JVM垃圾收集底层算法实现

上一篇&#xff1a;08-JVM垃圾收集器详解 1.三色标记 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生。 这里我们引入“三色标记”来给大家解释下&#xff0c;把Gcroots可达性…

uni-app动态tabBar,根据不同用户展示不同的tabBar

1.uni框架的api实现 因为我们用的是uni-app框架开发&#xff0c;所以在创建项目的时候直接创建uni-ui的项目即可&#xff0c;这个项目模板中自带了uni的一些好用的组件和api。 起初我想着这个效果不难实现&#xff0c;因为官方也有api可以直接使用&#xff0c;所以我最开始尝试…

DeeTune:基于 eBPF 的百度网络框架设计与应用

作者 | 百度APP云原生技术研发组 导读 随着云计算的技术的不断迭代演进&#xff0c;百度内部服务逐渐搬迁到云环境中&#xff0c;部署成本和效率取得明显收益&#xff0c;但一些可观测能力的短板和缺失逐渐显露&#xff0c;传统的方式往往通过植入代码进行修改来实现&#xff0…

esp-hosted 方案介绍

esp-hosted SDK esp-hosted 方案介绍 esp-hosted 方案主要为 Linux 或者 MCU 提供无线连接功能&#xff08;WiFi 或者 BT/BLE&#xff09; esp-hosted 解决方案包含了 esp-hosted FG 和 esp-hosted NG 两套方案 与传统 WiFi 网卡的区别在于 ESP 设备端需要烧录固件&#xff0…

RGMII 与 GMII 转换电路设计

文章目录 前言一、RGMII 接口的信号说明二、RGMII 发送的 FPGA 实现方案1. OPPOSITE_EDGE 模式2. SAME_EDGE 模式三、使用 FPGA 实现 RGMII 接口前言 RGMII 是 IEEE802.3z 标准中定义的千兆媒体独立接口(Gigabit Medium Independent Interface)GMII 的一个替代品。相较于 GM…

详解Redis之Lettuce实战

摘要 是 Redis 的一款高级 Java 客户端&#xff0c;已成为 SpringBoot 2.0 版本默认的 redis 客户端。Lettuce 后起之秀&#xff0c;不仅功能丰富&#xff0c;提供了很多新的功能特性&#xff0c;比如异步操作、响应式编程等&#xff0c;还解决了 Jedis 中线程不安全的问题。 …

Python中的依赖管理是什么? - 易智编译EaseEditing

Python中的依赖管理是指管理和跟踪项目所依赖的外部软件包或库的过程。 在Python项目中&#xff0c;通常会使用许多第三方库或模块来实现特定的功能&#xff0c;而依赖管理就是确保这些库可以正确地安装、更新和卸载&#xff0c;以确保项目的可靠性和稳定性。 以下是Python中…

正则表达式使用总结

一、字符匹配 普通字符&#xff1a;普通字符按照字面意义进行匹配&#xff0c;例如匹配字母 "a" 将匹配到文本中的 "a" 字符。 元字符&#xff1a;元字符具有特殊的含义&#xff0c;例如 \d 匹配任意数字字符&#xff0c;\w 匹配任意字母数字字符&#xf…

Market Insight:中国低代码/零代码市场典型供应商名录(2023)发布

Market Insight&#xff1a;典型供应商名录根据厂商调研、行业用户点评、研究团队评估来综合确定。典型供应商名录的入围者拥有值得客户信赖的能力&#xff0c;这种能力可以表现在产品与技术、服务能力、市场力等其中的一项或多项。典型供应商名录的入围者可以是市场领导者&…

Exception_json反序列化失败_JSONException

TokenGroup tokenGroup JSONObject.parseObject(tokenGroup1, TokenGroup.class);com.alibaba.fastjson.JSONException: create instance error, null, public com.daikin.snapshot.controller.auth.token.TokenGroup 解决方法: 在反序列化失败的实体类添加无参构造方法

学习笔记|回顾(1-12节课)|应用模块化的编程|添加函数头|静态变量static|STC32G单片机视频开发教程(冲哥)|阶段小结:应用模块化的编程(上)

文章目录 1.回顾(1-12节课)2.应用模块化的编程(.c .h)Tips:添加函数头创建程序文件三步引脚定义都在.h文件函数定义三步bdata位寻址变量的使用 3.工程文件编写静态变量static的使用完整程序为&#xff1a;demo.c&#xff1a;seg_led.c:seg_led.h: 1.回顾(1-12节课) 一、认识单…

数字化新零售营销模式如何落地?数字化新零售营销功能推荐

​通过科技手段&#xff0c;针对对线下零售店面的客户进行消费行为、频次等的分析&#xff0c;并进一步整合线上线下资源&#xff0c;实现实体零售的效率充分化&#xff0c;便是目前很火的新零售营销模式&#xff0c;能够将实体门店与数字化技术进行有机结合&#xff0c;通过为…

在Photoshop上标小图标的操作记录

1、做小图标 收集背景图 的背景的rgb值 把这个rgb值记下来&#xff0c;上面的背景要用。 2、统一图标大小 宽度、高度&#xff0c;都设置成1.52 3、把图标往地图上拖 拖到背景图上&#xff0c;可以用上下左右键调整位置 4、在图片上写字 右键这个&#xff0c;就可以写字了。…

SpringBoot / Vue 对SSE的基本使用(简单上手)

一、SSE是什么&#xff1f; SSE技术是基于单工通信模式&#xff0c;只是单纯的客户端向服务端发送请求&#xff0c;服务端不会主动发送给客户端。服务端采取的策略是抓住这个请求不放&#xff0c;等数据更新的时候才返回给客户端&#xff0c;当客户端接收到消息后&#xff0c;…

异步编程 - 06 基于JDK中的Future实现异步编程(中)_CompletableFuture源码解析

文章目录 CompletableFuture 类图结构CompletionStage接口属性resultstackasyncPool 方法CompletableFuture<Void>runAsync(Runnable runnable)CompletableFuture<U> supplyAsync(Supplier<U>supplier)CompletableFuture<U> supplyAsync(Supplier<U…

【java】【SSM框架系列】【一】Spring

目录 一、简介 1.1 为什么学 1.2 学什么 1.3 怎么学 1.4 初识Spring 1.5 Spring发展史 1.6 Spring Framework系统架构图 1.7 Spring Framework学习线路 二、核心概念&#xff08;IoC/DI&#xff0c;IoC容器&#xff0c;Bean&#xff09; 2.1 概念 2.2 IoC入门案例 …

TVC广告片制作成本多少

电视是广告传播的主要媒介之一&#xff0c;具有广泛的受众群体和较高的覆盖率。通过在电视上播放广告片&#xff0c;企业可以将产品或者服务的信息传达给大量潜在客户&#xff0c;提高知名度和曝光度。接下来由深圳TVC广告片制作公司老友记小编从以下几个方面浅析制作一条TVC广…

SAP PI 配置SSL链接接口报错问题处理Peer certificate rejected by ChainVerifier

出现这种情况一般无非是没有正确导入证书或者证书过期的情况 第一种&#xff0c;如果没有导入证书的话&#xff0c;需要在NWA中的证书与验证-》CAs中导入管理员提供的证书&#xff0c;这里需要注意的是&#xff0c;需要导入完整的证书链。 第二种如果是证书过期的&#xff0c…