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. RocketMQ 顺序消费机制

    顺序消息是指对于一个指定的 Topic ,消息严格按照先进先出(FIFO)的原则进行消息发布和消费,即先发布的消息先消费,后发布的消息后消费. 顺序消息分为分区顺序消息和全局顺序消息. 1.分区顺序消 ...

  2. java中接口,抽象类,具体类之间的关系

    抽象类实现接口,具体类继承于抽象类

  3. 【AGC】云监控日志服务查询不到Logger日志相关问题2

    ​[关键字] AGC.云监控.日志服务 [问题描述] 之前有开发者反馈在使用AGC云监控,填写了Logger日志,但是在云监控的日志服务查不到的问题.具体如下所述: 云函数按要求写了Logger日志, ...

  4. 通过安装GVM 安装GO 操作步骤

    转载请注明出处: 1.GVM GVM是Go Version Manager的缩写,是一个用于管理Go语言版本的工具.通过GVM,我们可以轻松地安装.切换和卸载不同版本的Go语言.GVM会在用户的hom ...

  5. A First course in FEM —— matlab代码实现求解传热问题(稳态)

    这篇文章会将FEM全流程走一遍,包括网格.矩阵组装.求解.后处理.内容是大三时的大作业,今天拿出来回顾下. 1. 问题简介 涡轮机叶片需要冷却以提高涡轮的性能和涡轮叶片的寿命.我们现在考虑一个如上图所 ...

  6. 【Netty】Netty部分源码分析(启动流程,EventLoop,accept流程,read流程)

    源码分析 Netty源码中调用链特别长,且涉及到线程切换等步骤,令人头大:) 1 启动剖析 我们就来看看 netty 中对下面的代码是怎样进行处理的 //1 netty 中使用 NioEventLoo ...

  7. PostgreSQL 性能优化: 等待事件

    等待事件 等待事件是 PostgreSQL 的重要优化工具.当您能查明会话为什么在等待资源以及会话在做什么时,您就能更好地减少瓶颈.您可以使用本节中的信息来查找可能的原因和纠正措施. 目录 等待事件概 ...

  8. 详解在Linux中修改Tomcat使用的jdk版本

    问题分析 由于部署个人项目使用了openjdk11,但是我之前安装的是jdk1.8,jdk版本升级的后果就是,tomcat运行的时候报一点小bug(因为之前安装tomcat默认使用了系统的jdk版本) ...

  9. Linux 基础(一)

    Linux 基础(一) 理念 一切皆文件 硬件 文件名 显示器 fb0 鼠标 mouse1 键盘 event0 触摸屏 event1 摄像头 video0 打开摄像头:open video0 ​​打开 ...

  10. python3使用ESL和sipp自动多轮压测FreeSWITCH

    环境:CentOS 7.6_x64   FreeSWITCH版本 :1.10.9   sipp版本:3.6.1   python版本:3.9.12 日常工作中,有时会遇到批量自动压测FreeSWITC ...