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结构:重建重定位表结构的更多相关文章

  1. PE结构之重定位表

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

  2. PE文件 03 重定位表

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

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

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

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

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

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

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

  6. 逆向-PE重定位表

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

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

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

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

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

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

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

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

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

随机推荐

  1. Python相关镜像

    Python相关镜像 (1) pip使用说明 对于Python开发用户来讲,我们会经常使用pip安装软件包.但国外的源下载速度实在太慢,浪费时间且经常出现下载后安装出错问题.所以把PIP安装源替换成国 ...

  2. DevOps| 研发效能和PMO如何合作共赢?

    项目经理(PMO)对于大组织.跨团队高效协同有着不可替代的作用.跳出组织架构的束缚,横向推动公司级别的大项目向前推进,跟进进展和拿到结果,PMO的小伙伴有着独特的优势. 我之前写过小团队如何高效协作的 ...

  3. 一分钟学一个 Linux 命令 - tar

    前言 大家好,我是 god23bin.今天给大家带来的是 Linux 命令系列,每天只需一分钟,记住一个 Linux 命令不成问题.今天,我们要介绍的是一个常用且强大的命令:tar. 什么是 tar ...

  4. 【翻译】高效numpy指北

    ref:link why numpy 运算高效 numpy 内存结构 一块内存区域 dtype 确定了内存区域数据类型 metadata 比如 shape.strides etc 注:numpy 内存 ...

  5. 前端vue实现页面加水印文字 单个页面所有页面加水印 水印颜色

    前端vue实现页面加水印文字, 可以实现系统所有页面加水印,也可以单个页面加水印, 可更改水印颜色, 下载完整代码请访问uni-app插件市场地址: https://ext.dcloud.net.cn ...

  6. 前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用

    前端Vue自定义顶部搜索框 热门搜索 历史搜索 用于搜索跳转使用, 下载完整代码请访问uni-app插件市场地址:https://ext.dcloud.net.cn/plugin?id=13128 效 ...

  7. 自然语言处理 Paddle NLP - 开放域对话系统-理论

    常见的对话系统 个人助手 •Apple Siri, Amazon Alexa, Baidu Xiaodu 客户服务•餐厅预定.商品咨询.债务催收 休闲娱乐 •Microsoft XiaoIce,Rep ...

  8. 基于Sa-Token实现微服务之前的单点登录

    修改配置文件,准备好四个域名 127.0.0.1 auth.server.com 127.0.0.1 user.server.com 127.0.0.1 third.server.com 127.0. ...

  9. Mysql基础6-常用数据库函数

    一.字符串函数 1.常见Mysql内置字符串函数 concat(s1,s2,s3,...):字符串拼接,将s1,s2,s3...等拼接成一个字符串 lower(str):将字符串str全部转为小写 u ...

  10. kbuild系统中最简单的目标 -- help(三)

    当你学完help目标后你会发现它与我们的代码基本没有什么直接的关系,仅仅是用来生成kbuild的简短使用说明,但是用它来作为学习掌握kbuild编译系统的开篇,不管是从难度还是学习感受上真的是再适合不 ...