2.10 PE结构:重建重定位表结构
Relocation(重定位)是一种将程序中的一些地址修正为运行时可用的实际地址的机制。在程序编译过程中,由于程序中使用了各种全局变量和函数,这些变量和函数的地址还没有确定,因此它们的地址只能暂时使用一个相对地址。当程序被加载到内存中运行时,这些相对地址需要被修正为实际的绝对地址,这个过程就是重定位。
在Windows操作系统中,程序被加载到内存中运行时,需要将程序中的各种内存地址进行重定位,以使程序能够正确地运行。Windows系统使用PE(Portable Executable)文件格式来存储可执行程序,其中包括重定位信息。当程序被加载到内存中时,系统会解析这些重定位信息,并将程序中的各种内存地址进行重定位。
重定位表一般出现在DLL
中,因为DLL
都是动态加载,所以地址不固定,DLL的入口点在整个执行过程中至少要执行2次,一次是在开始时执行初始化工作,一次则是在结束时做最后的收尾工作,重定位表则是解决DLL的地址问题,为了能找到重定位表首先我们需要使用PeView
工具查询DataDirectory
数据目录表,在其中找到Base relocation
字段,里面的0x00001800
则是重定位表基地址;
我们通过使用WinHex
工具定位到0x00001800
即可看到重定位表信息,如下图中的1000
代表的是重定位RVA
地址,绿色的0104
代表的则是重定位块的长度,后面则是每两个字节代表一个重定位块,0A是重定位地址,30则是重定位的类型,以此顺序向下排列。
重定位表也是分页排列的,每一页大小都是1000
字节,通过使用FixRelocPage
命令即可查询到当前程序中的重定位块信息,并以第一个为例,查询一下起始地址RVA为1000
的页上,有哪些重定位结构,如下图所示;
其中的重定位RVA
地址0000100A
是用标黄色的1000
加上标蓝色的0xA
得到的。而修正RVA地址00003000
加上模块基地址63FF0000+3000
得到的则是第一个被修正的内存地址,读者可使用x64dbg
跳转到该程序内自行确认。
重定位表的修复原理与IAT修复完全一致,我们需要分别读入脱壳前与脱壳后的两个程序,接着通过循环正确的重定位表信息,并依次覆盖到脱壳后的程序内,以此实现对重定位表的修复功能,实现代码如下所示;
#include <windows.h>
#include <stdio.h>struct TypeOffset
{WORD Offset : 12; // 低12位代表重定位地址WORD Type : 4; // 高4位代表重定位类型
};DWORD FileSize = 0; // 定义文件大小
DWORD FileBase = 0; // 保存文件的基地址// 定义全局变量,来存储DOS,NT,Section头
PIMAGE_DOS_HEADER DosHeader = nullptr;
PIMAGE_NT_HEADERS NtHeader = nullptr;
PIMAGE_FILE_HEADER FileHead = nullptr;// 将RVA转换为FOA的函数
DWORD RVAtoFOA(DWORD rva)
{auto SectionTables = IMAGE_FIRST_SECTION(NtHeader); // 获取区段表WORD Count = NtHeader->FileHeader.NumberOfSections; // 获取区段数量for (int i = 0; i < Count; ++i){// 判断是否存在于区段中DWORD Section_Start = SectionTables[i].VirtualAddress;DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;if (rva >= Section_Start && rva < Section_Ends){// 找到之后计算位置并返回值return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;}}return -1;
}// 打开PE文件
bool OpenPeFile(LPCSTR FileName)
{// 打开文件HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (Handle == INVALID_HANDLE_VALUE)return false;// 获取文件大小FileSize = GetFileSize(Handle, NULL);// 读取文件数据DWORD OperSize = 0;FileBase = (DWORD)new BYTE[FileSize];ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);// 获取DOS头并判断是不是一个有效的DOS文件DosHeader = (PIMAGE_DOS_HEADER)FileBase;if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)return false;// 获取 NT 头并判断是不是一个有效的PE文件NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);if (NtHeader->Signature != IMAGE_NT_SIGNATURE)return false;// 判断是不是一个32位文件if (NtHeader->OptionalHeader.Magic != 0x010B)return false;CloseHandle(Handle);return true;
}// 修复重定位表
void RepairFixReloc(char new_file[])
{DWORD base = NtHeader->OptionalHeader.ImageBase;// 1. 获取重定位表的 rvaDWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;// 2. 获取重定位表auto Reloc = (PIMAGE_BASE_RELOCATION)(FileBase + RVAtoFOA(RelocRVA));// 3. 遍历重定位表中的重定位块,以0结尾while (Reloc->SizeOfBlock != 0){// 3.1 输出分页基址printf("[↓] 分页基址: 0x%08X \n\n", Reloc->VirtualAddress);// 3.2 找到重定位项auto Offset = (TypeOffset*)(Reloc + 1);// 3.3 计算重定位项的个数// Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体 + 重定位项数组// Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) 得到单个数组大小// 上面的结果 \ 2 = 重定位项的个数,原因是重定位项的大小为两个字节DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;// 3.4 遍历所有的重定位项for (DWORD i = 0; i < Size; ++i){DWORD Type = Offset[i].Type; // 获取重定位类型,只关心为3的类型DWORD pianyi = Offset[i].Offset; // 获取重定位的偏移值DWORD rva = pianyi + Reloc->VirtualAddress; // 获取要重定位的地址所在的RVADWORD foa = RVAtoFOA(rva); // 获取要重定位的地址所在的FOADWORD fa = foa + FileBase; // 获取要重定位的地址所在的faDWORD addr = *(DWORD*)fa; // 获取要重定位的地址DWORD new_addr = addr - base + 0x1500000; // 计算重定位后的数据: addr - oldbase + newbase// 将重定位后的数据写回缓冲区(文件)if (Offset[i].Type == 3)*(DWORD*)fa = new_addr;printf("\t [->] 重定位RVA: 0x%08X | 重定位FOA: 0x%08X | 重定位地址: 0x%08X | 修正地址: 0x%08X \n", rva, foa, addr, new_addr);}// 找到下一个重定位块Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);}// 保存修正后的文件NtHeader->OptionalHeader.ImageBase = 0x1500000;// 打开一个新文件HANDLE new_handle = CreateFileA(new_file, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);if (new_handle == INVALID_HANDLE_VALUE)return;DWORD OperSize = 0;// 保存修正好的程序BOOL ret = WriteFile(new_handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);if (ret == TRUE){printf("\n\n");CloseHandle(new_handle);printf("[*] 修复 %s 文件 \t 写入基址: %08X \t 总长度: %d \t 写入长度: %d \n", new_file, FileBase, FileSize, OperSize);}
}void Banner()
{printf(" ____ _ _ _ ____ _ \n");printf("| __ ) _ _(_) | __| | | _ \\ ___| | ___ ___ \n");printf("| _ \\| | | | | |/ _` | | |_) / _ \\ |/ _ \\ / __|\n");printf("| |_) | |_| | | | (_| | | _ < __/ | (_) | (__ \n");printf("|____/ \\__,_|_|_|\\__,_| |_| \\_\\___|_|\\___/ \\___|\n");printf(" \n");printf("Reloc 重定位表快速修复工具 \t By: LyShark \n");printf("Usage: BuildFix [原文件位置] [修复后文件位置] \n\n\n");
}int main(int argc, char* argv[])
{Banner();if (argc == 3){bool flag = OpenPeFile(argv[1]);if (true == flag){RepairFixReloc(argv[2]);}}return 0;
}
运行上述程序,读者可自行传入脱壳前的程序与脱壳后的程序,此时则会实现自动替换,如下图所示;
本文作者: 王瑞
本文链接: https://www.lyshark.com/post/3c1b31b5.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
相关文章:

2.10 PE结构:重建重定位表结构
Relocation(重定位)是一种将程序中的一些地址修正为运行时可用的实际地址的机制。在程序编译过程中,由于程序中使用了各种全局变量和函数,这些变量和函数的地址还没有确定,因此它们的地址只能暂时使用一个相对地址。当…...

关于content-type的理解
一.content-type的结论 告诉后端传过去的数据是什么类型的数据 二.没有请求体 (1)没有请求体的情况下content-type没有意义。 (2):图示 里面是没有请求体的 (3)有请求体的情况 二.常见的三种方式 (1)application/x-www-form-urlencoded(默认) 参数的表现形式: 传递之前可以…...
<图像处理> 空间滤波基础二
空间滤波基础二:锐化 锐化的作用的突出灰度中的过渡。图像锐化通过空间微分来实现,微分将增强边缘和其他不连续(噪声),不强化灰度变化缓慢的区域。图像锐化也叫做高通滤波,通过高频,抑制低频。…...
Java中的队列Queue
Queue(队列)是一种在计算机科学中常见的数据结构,它基于先进先出(FIFO)的原则,即最先进入队列的元素最先出队。在Java中,Queue是一个接口,定义了一组操作队列的方法,而具体的实现类可以选择性地实现这些方法。 以下是Queue的一些常见用途和操作: 添加元素: 使用off…...

机器学习技术(十)——决策树算法实操,基于运营商过往数据对用户离网情况进行预测
机器学习技术(十)——决策树算法实操 文章目录 机器学习技术(十)——决策树算法实操一、引言二、数据集介绍三、导入相关依赖库四、读取并查看数据1、读取数据2、查看数据 五、数据预处理1、选择数据2、数据转码 六、建模与参数优…...

大数据之-kafka学习笔记
Kafka Kafka 是一个分布式的基于发布/订阅模式的消息队列(Message Queue),主要应用于大数据实时处理领域。 Kafka可以用作Flink应用程序的数据源。Flink可以轻松地从一个或多个Kafka主题中消费数据流。这意味着您可以使用Kafka来捕获和传输…...

虚幻动画系统概述
本文主要整理一下高层次的概述,方便后续查阅 1.动画流程 DCC产出动画文件 -> UE动画导入 -> 动画蓝图驱动(类似unity的动画状态机) ->动画后处理蓝图驱动(例如修型骨,骨骼矫正等后期处理) 2.动…...

什么是集成测试?集成测试方法有哪些?
1、基本概念: 将软件集成起来后进行测试。集成测试又叫子系统测试、组装测试、部件测试等。集成测试主要是针对软件高层设计进行测试,一般来说是以模块和子系统为单位进行测试。 2、集成测试包含的层次: 1. 模块内的集成,主要是…...
elementUI中的el-form常用校验规则
elementUI中的el-form常用校验规则: 校验使用方式: rules: {name: [{ required: true, message: 请输入活动名称, trigger: blur },{ min: 3, max: 5, message: 长度在 3 到 5 个字符, trigger: blur }],region: [{ required: true, message: 请选择活动区域, trig…...

蓝桥杯打卡Day9
文章目录 直角三角形最长平衡串 一、直角三角形IO链接 本题思路:本题就是利用欧几里得距离求解即可。 #include <bits/stdc.h>int main() {std::ios::sync_with_stdio(false);std::cin.tie(nullptr);std::cout.tie(nullptr);int T;std::cin>>T;while(T--){int x…...

C# 辗转相除法求最大公约数
辗转相除法求最大公约数 public static void CalcGCD(int largeNumber, int smallNumber, out int GCD){GCD 1;int remain -1;while (remain ! 0){remain largeNumber % smallNumber;GCD smallNumber;largeNumber smallNumber;smallNumber remain;}}...

腾讯mini项目-【指标监控服务重构】2023-08-03
今日已办 a,b两组的trace放到一个分支里 可以看到先前的没看到的 profile trace 的耗时,是由于时间跨度较长,没有滑动到 trace 末尾 明日待办 组长会议汇报项目进度和问题...

redis缓存穿透、击穿、雪崩介绍
缓存穿透 概念 缓存穿透指某一特定时间批量请求打进来并访问了缓存和数据库都没有的key,此时会直接穿透缓存直达数据库,从而造成数据库瞬时压力倍增导致响应速度下降甚至崩溃的风险; 解决方案 一、通过布隆过滤器解决 原理:将…...

Redis 基础总结
1、NoSQL概述 1.1 数据库分类 目前数据库分:关系型数据库与非关系型数据库 常用的关系型数据库: Oracle,MySQL,SqlServer,DB2 常用的非关系数据库:Redis,MongoDB,ElasticSearch&…...
基于nginx的tomcat负载均衡和集群(超简单)
今天看到"基于apache的tomcat负载均衡和集群配置 "这篇文章成为javaEye热点。 略看了一下,感觉太复杂,要配置的东西太多,因此在这里写出一种更简洁的方法。 要集群tomcat主要是解决SESSION共享的问题,因此我利用memcac…...

ESIM实战文本匹配
引言 今天我们来实现ESIM文本匹配,这是一个典型的交互型文本匹配方式,也是近期第一个测试集准确率超过80%的模型。 我们来看下是如何实现的。 模型架构 我们主要实现左边的ESIM网络。 从下往上看,分别是 输入编码层(Input Ecoding) 对前…...

基于虚拟仿真技术的汽车燃油泵控制
在当前激烈的竞争环境下,汽车行业正在加速产业和技术更迭,整车厂对大型ECU嵌入式控制系统和软件的需求迫在眉睫。 然而,复杂而庞大的汽车系统往往由多个物理系统组成,系统所对应的模型都需要在不同的领域实现:发动机、…...
angular:HtmlElement的子节点有Shadow dom时奇怪的现象
描述: 这样写时,会自动跳过shadow dom节点的遍历 const cloneElement this.contentElement.cloneNode(true) as HTMLElement; for(let childNodeIndex 0; childNodeIndex < cloneElement.childNodes.length; childNodeIndex) {element.appendChild…...
栈与队列--删除字符串中的所有相邻重复项
给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。 在 S 上反复执行重复项删除操作,直到无法继续删除。 在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。 示例: 输入&#x…...

使用SSH地址拉取远程仓库代码报下面的错误
说明:配置了SSH秘钥后,使用SSH地址克隆代码,依旧无法拉取代码,提示下面这个信息。 Their offer:ssh-rsa,ssh-dss fatal:Could not read from remote repository. Please make sure you have the…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

JavaScript 中的 ES|QL:利用 Apache Arrow 工具
作者:来自 Elastic Jeffrey Rengifo 学习如何将 ES|QL 与 JavaScript 的 Apache Arrow 客户端工具一起使用。 想获得 Elastic 认证吗?了解下一期 Elasticsearch Engineer 培训的时间吧! Elasticsearch 拥有众多新功能,助你为自己…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...

视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化
缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...

倒装芯片凸点成型工艺
UBM(Under Bump Metallization)与Bump(焊球)形成工艺流程。我们可以将整张流程图分为三大阶段来理解: 🔧 一、UBM(Under Bump Metallization)工艺流程(黄色区域ÿ…...
大数据治理的常见方式
大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法,以下是几种常见的治理方式: 1. 数据质量管理 核心方法: 数据校验:建立数据校验规则(格式、范围、一致性等)数据清洗&…...