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. 获取重定位表的 rva
DWORD 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; // 获取要重定位的地址所在的RVA
DWORD foa = RVAtoFOA(rva); // 获取要重定位的地址所在的FOA
DWORD fa = foa + FileBase; // 获取要重定位的地址所在的fa
DWORD 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;
}
运行上述程序,读者可自行传入脱壳前的程序与脱壳后的程序,此时则会实现自动替换,如下图所示;

2.10 PE结构:重建重定位表结构的更多相关文章
- PE结构之重定位表
什么是重定位: 重定位就是你本来这个程序理论上要占据这个地址,但是由于某种原因,这个地址现在不能让你占用,你必须转移到别的地址,这就需要基址重定位.你可能会问,不是说过每个进程都有自己独立的虚拟地址空 ...
- PE文件 03 重定位表
0x01 重定位表结构 重定位表是由数据目录表中的第六个成员指出的: typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; D ...
- 零基础逆向工程23_PE结构07_重定位表_IAT表(待补充)
重定位表 待补充 IAT表 待补充
- PE知识复习之PE的重定位表
PE知识复习之PE的重定位表 一丶何为重定位 重定位的意思就是修正偏移的意思. 如一个地址位 0x401234 ,Imagebase = 0x400000 . 那么RVA就是 1234. 如果Im ...
- WindowsPE权威指南-PE文件头中的重定位表
PE加载的过程 任何一个EXE程序会被分配4GB的内存空间,用户层处理低2G的内存,驱动处理高2G的内存. 1.双击EXE程序,操作系统开辟一个4GB的空间. 2.从ImageBase决定了加载后的基 ...
- 逆向-PE重定位表
重定位表 当链接器生成一个PE文件时,会假设这个文件在执行时被装载到默认的基地址处(基地址+RVA就是VA).并把code和data的相关地址写入PE文件.如果像EXE一样首先加载就是它image ...
- Windows PE第6章 栈与重定位表
第六章 栈与重定位表 本章主要介绍栈和代码重定位.站和重定位表两者并没有必然的联系,但都和代码有关.栈描述的是代码运行过程中,操作系统为调度程序之间相互调用关系,或临时存放操作数而设置的一种数据结构. ...
- PE格式第七讲,重定位表
PE格式第七讲,重定位表 作者:IBinary出处:http://www.cnblogs.com/iBinary/版权所有,欢迎保留原文链接进行转载:) 一丶何为重定位(注意,不是重定位表格) 首先, ...
- 【旧文章搬运】PE重定位表学习手记
原文发表于百度空间,2008-11-02========================================================================== 先定义一下 ...
- Reverse Core 第二部分 - 16&17章 - 基址重定位表&.reloc节区
第16-17章 - 基址重定位表&.reloc节区 @date: 2016/11/31 @author: dlive 0x00 前言 这几天忙着挖邮箱漏洞,吃火锅,马上要被关禁闭,看书进度比较 ...
随机推荐
- 2023-05-25:给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式 其中每个运算符 op1,op2,… 可以是加、减、乘、除之一 例如
2023-05-25:给定一个正整数 x,我们将会写出一个形如 x (op1) x (op2) x (op3) x ... 的表达式 其中每个运算符 op1,op2,- 可以是加.减.乘.除之一 例如 ...
- celery笔记三之task和task的调用
本文首发于公众号:Hunter后端 原文链接:celery笔记三之task和task的调用 这一篇笔记介绍 task 和 task 的调用. 以下是本篇笔记目录: 基础的 task 定义方式 日志处理 ...
- 3. AOP
1. 代理模式 1.1 概念 ① 介绍 二十三种设计模式中的一种,属于结构型模式.它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用.让不 ...
- uniapp-chatgpt跨端仿ChatGPT实例|uniapp+vue3+pinia多端聊天模板
基于uniapp+vite4+pinia跨多端实现chatgpt会话模板Uniapp-ChatGPT. uni-chatgpt 使用uni-app+vite4+vue3+pinia+uview-plu ...
- 2023-07-04:给定一个数组A, 把它分成两个数组B和C 对于数组A每个i位置的数来说, A[i] = B[i] + C[i] 也就是一个数字分成两份,然后各自进入B和C 要求B[i], C[i
2023-07-04:给定一个数组A, 把它分成两个数组B和C 对于数组A每个i位置的数来说, A[i] = B[i] + C[i] 也就是一个数字分成两份,然后各自进入B和C 要求B[i], C[i ...
- prometheus Histogram 统计原理
大家好,我是蓝胖子,书接上文,我在prometheus描点原理那一篇文章里,留了一个思考题: 我们通常会用到histogram_quantile去计算服务接口时间的耗时情况. histogram_qu ...
- P2709 小B的询问题解
本题需要用到莫队算法 关于莫队算法 莫队算法是一种离线算法,适用于序列中统计区间特定的目标的问题. 时间复杂度通常是 \(O(n \sqrt n)\) 或更高. P2709 小B的询问 点击查看原题 ...
- SVE学习记录- SVE特性以及寄存器
本文地址:https://www.cnblogs.com/wanger-sjtu/p/SVE_learn_0.html SVE对比NEON有几个新增的地方. 变长的向量 支持Gather-load & ...
- 《最新出炉》系列入门篇-Python+Playwright自动化测试-8-上下文(Context)
1.简介 其实前边的文章中也提到过Context,只不过是 一笔带过,但是宏哥觉得在playwright中挺重要的,所以宏哥今天单独将其拎出来讲解和分享一下,希望对您有所帮助或者参考. 2.前言 Pl ...
- Kerberos、黄金票据与白银票据
kerberos Kerberos是一个网络认证协议,用于验证用户和服务之间的身份,解决分布式计算环境中的身份验证问题.它使用加密技术来提供安全的身份验证,并防止网络中的身份欺骗攻击.Kerberos ...