2.10 PE结构:重建重定位表结构

打印 上一主题 下一主题

主题 902|帖子 902|积分 2706

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修复完全一致,我们需要分别读入脱壳前与脱壳后的两个程序,接着通过循环正确的重定位表信息,并依次覆盖到脱壳后的程序内,以此实现对重定位表的修复功能,实现代码如下所示;
  1. #include <windows.h>
  2. #include <stdio.h>
  3. struct TypeOffset
  4. {
  5.   WORD Offset : 12;       // 低12位代表重定位地址
  6.   WORD Type : 4;          // 高4位代表重定位类型
  7. };
  8. DWORD FileSize = 0;  // 定义文件大小
  9. DWORD FileBase = 0;  // 保存文件的基地址
  10. // 定义全局变量,来存储DOS,NT,Section头
  11. PIMAGE_DOS_HEADER DosHeader = nullptr;
  12. PIMAGE_NT_HEADERS NtHeader = nullptr;
  13. PIMAGE_FILE_HEADER FileHead = nullptr;
  14. // 将RVA转换为FOA的函数
  15. DWORD RVAtoFOA(DWORD rva)
  16. {
  17.   auto SectionTables = IMAGE_FIRST_SECTION(NtHeader);    // 获取区段表
  18.   WORD Count = NtHeader->FileHeader.NumberOfSections;    // 获取区段数量
  19.   for (int i = 0; i < Count; ++i)
  20.   {
  21.     // 判断是否存在于区段中
  22.     DWORD Section_Start = SectionTables[i].VirtualAddress;
  23.     DWORD Section_Ends = SectionTables[i].VirtualAddress + SectionTables[i].SizeOfRawData;
  24.     if (rva >= Section_Start && rva < Section_Ends)
  25.     {
  26.       // 找到之后计算位置并返回值
  27.       return rva - SectionTables[i].VirtualAddress + SectionTables[i].PointerToRawData;
  28.     }
  29.   }
  30.   return -1;
  31. }
  32. // 打开PE文件
  33. bool OpenPeFile(LPCSTR FileName)
  34. {
  35.   // 打开文件
  36.   HANDLE Handle = CreateFileA(FileName, GENERIC_READ, NULL,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  37.   if (Handle == INVALID_HANDLE_VALUE)
  38.     return false;
  39.   // 获取文件大小
  40.   FileSize = GetFileSize(Handle, NULL);
  41.   // 读取文件数据
  42.   DWORD OperSize = 0;
  43.   FileBase = (DWORD)new BYTE[FileSize];
  44.   ReadFile(Handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);
  45.   // 获取DOS头并判断是不是一个有效的DOS文件
  46.   DosHeader = (PIMAGE_DOS_HEADER)FileBase;
  47.   if (DosHeader->e_magic != IMAGE_DOS_SIGNATURE)
  48.     return false;
  49.   // 获取 NT 头并判断是不是一个有效的PE文件
  50.   NtHeader = (PIMAGE_NT_HEADERS)(FileBase + DosHeader->e_lfanew);
  51.   if (NtHeader->Signature != IMAGE_NT_SIGNATURE)
  52.     return false;
  53.   // 判断是不是一个32位文件
  54.   if (NtHeader->OptionalHeader.Magic != 0x010B)
  55.     return false;
  56.   
  57.   CloseHandle(Handle);
  58.   return true;
  59. }
  60. // 修复重定位表
  61. void RepairFixReloc(char new_file[])
  62. {
  63.   DWORD base = NtHeader->OptionalHeader.ImageBase;
  64.   // 1. 获取重定位表的 rva
  65.   DWORD RelocRVA = NtHeader->OptionalHeader.DataDirectory[5].VirtualAddress;
  66.   // 2. 获取重定位表
  67.   auto Reloc = (PIMAGE_BASE_RELOCATION)(FileBase + RVAtoFOA(RelocRVA));
  68.   // 3. 遍历重定位表中的重定位块,以0结尾
  69.   while (Reloc->SizeOfBlock != 0)
  70.   {
  71.     // 3.1 输出分页基址
  72.     printf("[↓] 分页基址: 0x%08X \n\n", Reloc->VirtualAddress);
  73.     // 3.2 找到重定位项
  74.     auto Offset = (TypeOffset*)(Reloc + 1);
  75.     // 3.3 计算重定位项的个数
  76.     // Reloc->SizeOfBlock 保存的是整个重定位块的大小 结构体 + 重定位项数组
  77.     // Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) 得到单个数组大小
  78.     // 上面的结果 \ 2 = 重定位项的个数,原因是重定位项的大小为两个字节
  79.     DWORD Size = (Reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / 2;
  80.     // 3.4 遍历所有的重定位项
  81.     for (DWORD i = 0; i < Size; ++i)
  82.     {
  83.       DWORD Type = Offset[i].Type;                  // 获取重定位类型,只关心为3的类型
  84.       DWORD pianyi = Offset[i].Offset;              // 获取重定位的偏移值
  85.       DWORD rva = pianyi + Reloc->VirtualAddress;   // 获取要重定位的地址所在的RVA
  86.       DWORD foa = RVAtoFOA(rva);                    // 获取要重定位的地址所在的FOA
  87.       DWORD fa = foa + FileBase;                    // 获取要重定位的地址所在的fa
  88.       DWORD addr = *(DWORD*)fa;                     // 获取要重定位的地址
  89.       DWORD new_addr = addr - base + 0x1500000;     // 计算重定位后的数据: addr - oldbase + newbase
  90.       // 将重定位后的数据写回缓冲区(文件)
  91.       if (Offset[i].Type == 3)
  92.         *(DWORD*)fa = new_addr;
  93.       printf("\t [->] 重定位RVA: 0x%08X | 重定位FOA: 0x%08X | 重定位地址: 0x%08X | 修正地址: 0x%08X \n", rva, foa, addr, new_addr);
  94.     }
  95.     // 找到下一个重定位块
  96.     Reloc = (PIMAGE_BASE_RELOCATION)((DWORD)Reloc + Reloc->SizeOfBlock);
  97.   }
  98.   // 保存修正后的文件
  99.   NtHeader->OptionalHeader.ImageBase = 0x1500000;
  100.   // 打开一个新文件
  101.   HANDLE new_handle = CreateFileA(new_file, GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  102.   if (new_handle == INVALID_HANDLE_VALUE)
  103.     return;
  104.   DWORD OperSize = 0;
  105.   // 保存修正好的程序
  106.   BOOL ret = WriteFile(new_handle, (LPVOID)FileBase, FileSize, &OperSize, NULL);
  107.   if (ret == TRUE)
  108.   {
  109.     printf("\n\n");
  110.     CloseHandle(new_handle);
  111.     printf("[*] 修复 %s 文件 \t 写入基址: %08X \t 总长度: %d \t 写入长度: %d \n", new_file, FileBase, FileSize, OperSize);
  112.   }
  113. }
  114. void Banner()
  115. {
  116.   printf(" ____        _ _     _   ____      _            \n");
  117.   printf("| __ ) _   _(_) | __| | |  _ \\ ___| | ___   ___ \n");
  118.   printf("|  _ \\| | | | | |/ _` | | |_) / _ \\ |/ _ \\ / __|\n");
  119.   printf("| |_) | |_| | | | (_| | |  _ <  __/ | (_) | (__ \n");
  120.   printf("|____/ \\__,_|_|_|\\__,_| |_| \\_\\___|_|\\___/ \\___|\n");
  121.   printf("                                                \n");
  122.   printf("Reloc 重定位表快速修复工具 \t By: LyShark \n");
  123.   printf("Usage: BuildFix [原文件位置] [修复后文件位置] \n\n\n");
  124. }
  125. int main(int argc, char* argv[])
  126. {
  127.   Banner();
  128.   if (argc == 3)
  129.   {
  130.     bool flag = OpenPeFile(argv[1]);
  131.     if (true == flag)
  132.     {
  133.       RepairFixReloc(argv[2]);
  134.     }
  135.   }
  136.   return 0;
  137. }
复制代码
运行上述程序,读者可自行传入脱壳前的程序与脱壳后的程序,此时则会实现自动替换,如下图所示;

本文作者: 王瑞
本文链接: https://www.lyshark.com/post/3c1b31b5.html
版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

海哥

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表