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. 图解MySQL在Linux下的安装与配置

    MySQL简介 MySQL是最流行的RDBMS(Relational Database Management System:关系数据库管理系统)之一,被广泛地应用在互联网上的中小型网站中.关联数据库将 ...

  2. Kubernetes(k8s)使用ingress发布服务

    目录 一.系统环境 二.前言 三.Kubernetes ingress简介 四.Ingress vs NodePort vs LoadBalancer 五.安装部署Nginx Ingress Cont ...

  3. 快速把PDF文档里的表格粘贴到excel的方法

    1 打开需要复制的PDf文件,点一下页面上方的"选择文本"按钮(如下图中手图标左边的箭头),以便选中文本 2 ctrl c 需要复制的表格,到excel中ctrl v.这时候所有类 ...

  4. 理解ASP.NET Core - 全球化&本地化&多语言(Globalization and Localization)

    注:本文隶属于<理解ASP.NET Core>系列文章,请查看置顶博客或点击此处查看全文目录 概述 在众多知名品牌的网站中,比如微软官网.YouTube等,我们经常可以见到"切换 ...

  5. GPU技术在大规模计算和并行计算中的应用和挑战

    目录 1. 引言 2. 技术原理及概念 3. 实现步骤与流程 4. 应用示例与代码实现讲解 5. 优化与改进 GPU 技术在大规模计算和并行计算中的应用和挑战 随着计算机硬件的不断发展和计算能力的提高 ...

  6. Linux从文件中逐行读取文件名并将匹配的文件复制到指定目录

    问题应该算挺常见的但是一句话还挺难说清楚,所以百度特别难搜. 场景就是,有一堆以员工名称命名的文件(名称可能还有字母数字等前后缀),现在给定一个员工清单,需要从这些文件中筛选出员工清单上列出的员工的文 ...

  7. 提升性能的利器:深入解析SectionReader

    一. 简介 本文将介绍 Go 语言中的 SectionReader,包括 SectionReader的基本使用方法.实现原理.使用注意事项.从而能够在合适的场景下,更好得使用SectionReader ...

  8. ERP开发流程

    一.使用Xshell连线执行r.r adzi140 或 助记码r.t 都可以打开数据表设计器 表格建完后,DBA前三个需要点一下,如果表格显示需要表格重建,点最后一个,表格新建完成后,记得点击执行异动 ...

  9. python图表展示实例

    """Created on Fri Nov 8 16:09:36 2019 @author: DELL""" ""&qu ...

  10. (内附示例源码)如何通过electron构建桌面跨平台音视频应用

    近年来,视频直播.直播带货.在线教育.在线医疗等音视频领域的相关行业都非常热门,成为大众瞩目的焦点. 在不久的将来,音视频技术渗透于各行各业,无处不在.从IoT网络到个人用户的移动设备,音视频技术以不 ...