滴水逆向-重定位表
相关知识点
文字版知识点
摘自WINNT.H // // Optional header format. // typedef struct _IMAGE_OPTIONAL_HEADER { // // Standard fields. // WORD Magic; BYTE MajorLinkerVersion; BYTE MinorLinkerVersion; DWORD SizeOfCode; DWORD SizeOfInitializedData; DWORD SizeOfUninitializedData; DWORD AddressOfEntryPoint; DWORD BaseOfCode; DWORD BaseOfData; // // NT additional fields. // DWORD ImageBase; DWORD SectionAlignment; DWORD FileAlignment; WORD MajorOperatingSystemVersion; WORD MinorOperatingSystemVersion; WORD MajorImageVersion; WORD MinorImageVersion; WORD MajorSubsystemVersion; WORD MinorSubsystemVersion; DWORD Win32VersionValue; DWORD SizeOfImage; DWORD SizeOfHeaders; DWORD CheckSum; WORD Subsystem; WORD DllCharacteristics; DWORD SizeOfStackReserve; DWORD SizeOfStackCommit; DWORD SizeOfHeapReserve; DWORD SizeOfHeapCommit; DWORD LoaderFlags; DWORD NumberOfRvaAndSizes; IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; } IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; // Directory Entries #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory #define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory #define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory #define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory #define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory #define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table #define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory // IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage) #define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data #define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP #define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory #define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory #define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers #define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table #define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors #define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor 1.程序加载的过程 一个exe文件会被拉伸加载至内存,加载完成之后她在内存获得了她想要的那份内存空间,同时exe 需要加载dll模块,此时也会占用内存空间; 特别说明: (1)一般情况下,EXE都是可以按照ImageBase的地址进行加载的.因为Exe拥有自己独立的4GB 的虚拟内存空间 但DLL 不是 DLL是有EXE使用它,才加载到相关EXE的进程空间的. (2)为了提高搜索的速度,模块间地址也是要对齐的 模块地址对齐为10000H 也就是64K 10000H --> 65536D --> 65536byte --> 65536/1024=64K 2.为什么要用重定位表 打开一个程序,观察一下全局变量的反汇编 00401D58 A1 44 CA 42 00 mov eax,[x (0042ca44)] 00401D5D 50 push eax 00401D5E 68 EC 91 42 00 push offset string "%d\n" (004291ec) 00401D63 E8 28 62 00 00 call printf (00407f90) 编译时生成的地址 = ImageBase + RVA 这个地址在程序编译完成后,已经写入文件了 那假设,程序在加载的时候,没有按照预定的400000 载入到指定的位置 但程序执行的时候,仍然会按照0042ca44 和 004291ec 的地址去使用这个值! (1)也就是说,如果程序能够按照预定的ImageBase来加载的话,那么就不需要重定位表 这也是为什么exe很少有重定位表,而DLL大多都有重定位表的原因 (2)一旦某个模块没有按照ImageBase进行加载,那么所有类似上面中的地址就都需要修正,否则,引用的地址就是无效的. (3)一个EXE中,需要修正的地方会很多,那我们如何来记录都有哪些地方需要修正呢? 答案就是重定位表 重定位表定位 数据目录项的第6个结构,就是重定位表. typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; DWORD Size; } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY; 上面的VirtualAddress同样需要从RVA转到FOA typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION ,* PIMAGE_BASE_RELOCATION; 下面重定位表是摘自WINNT.T typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; // WORD TypeOffset[1]; } IMAGE_BASE_RELOCATION; typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION; #define IMAGE_SIZEOF_BASE_RELOCATION 8 解析说明: (1)通过IMAGE_DATA_DIRECTORY结构的VirtualAddress 属性 找到第一个IMAGE_BASE_RELOCATION (2)判断一共有几块数据: 最后一个结构的VirtualAddress与SizeOfBlock都为0 (3)具体项 宽度:2字节 也就是这个数据 内存中的页大小是1000H 也就是说2的12次方 就可以表示 一个页内所有的偏移地址 具体项的宽度是16字节 高四位 代表类型:值为3 代表的是需要修改的数据 值为0代表的是 用于数据对齐的数据,可以不用修改.也就是说 我们只关注 高4位的值为3的就可以了. 1000H --> 4096D --> 2的12次方 12 + 4 = 16 bit (4)VirtualAddress 宽度:4字节 当前这一个块的数据,每一个低12位的值+VirtualAddress 才是 真正需要修复的数据的RVA 真正的RVA = VirtualAddress + 具体项的低12位 (5)SizeOfBlock 宽度:4字节 当前块的总大小 具体项的数量 = (SizeOfBlock - 8)/2 这是指具体要修改的项
课后练习相关代码演示
#include "stdafx.h" #include <windows.h> #include "stdlib.h" #include "stdio.h" #define FilePath_In "C:\\cntflx\\dtljkcntf.dll" #define debug 0 //RVA格式转换FOA --- RvaToFileOffset DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva) { PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL; DWORD numberOfSection = 0; DWORD dwFOAValue = 0; //判断指针是否有效 if (!pFileBuffer) { printf("pFileBuffer 指针无效\r\n"); return 0; } //判断是否是有效的MZ标志 if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) { printf("pFileBuffer不是有效的MZ标志\r\n"); return 0; } //判断是否是一个有效的PE标志 pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) { printf("pFileBuffer不是一个有效的PE标志\r\n"); return 0; } //printf("当前的Rva地址: %#X \r\n",dwRva); pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew); pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader+0x04); pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader); //定义个临时节表指针进行下面的计算操作 numberOfSection = pPEHeader->NumberOfSections; PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader; //判断dwRva所处的节 if (dwRva <= pOptionHeader->SizeOfHeaders) { return (DWORD)dwRva; } //上面是判断如果rva地址所处的节在第一个节之前那么直接返回rva的地址; //否则下面就是开始遍历查找节; else { for (DWORD n = 0; n < numberOfSection; n++) {//下面是判断在哪个节的范围,然后根据rva所在的地址减去所在节的VirtualAddress得到的偏移值加上文件中对应节的偏移值PointerToRawData if ((dwRva >= pTempSectionHeader[n].VirtualAddress) && (dwRva < pTempSectionHeader[n].VirtualAddress + pTempSectionHeader[n].Misc.VirtualSize)) { dwFOAValue = dwRva - pTempSectionHeader[n].VirtualAddress + pTempSectionHeader[n].PointerToRawData; } } } return dwFOAValue; } DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer ) { FILE *pFile = NULL; DWORD fileSize = 0; //文件大小 LPVOID pTempFileBuffer = NULL; //缓冲区首地址 pFile = fopen(lpszFile,"rb"); //打开文件 if(!pFile) { printf("打开文件失败"); return NULL; } //读取文件大小 fseek(pFile,0,SEEK_END); //将指针从开始的位置移动到末尾 fileSize = ftell(pFile); //获取数据大小 //分配缓冲区(申请内存) pTempFileBuffer = malloc(fileSize); if(!pTempFileBuffer) { printf("分配空间失败"); fclose(pFile); return NULL; } //将文件数据读取到缓冲区 fseek(pFile,0,SEEK_SET); //将指针指向开始 size_t n = fread(pTempFileBuffer,fileSize,1,pFile); //将数据读取到缓冲区中 if(!n) { printf("读取数据失败"); free(pTempFileBuffer); //释放内存 fclose(pFile); //关闭文件 return NULL; } //关闭文件 *pFileBuffer = pTempFileBuffer; pTempFileBuffer = NULL; fclose(pFile); //关闭文件 return fileSize; } DWORD GetDataDirectoyOfBaseRelocation(IN PVOID pFileBuffer) { // 初始化PE头部结构体 PIMAGE_DOS_HEADER pDosHeader = NULL; PIMAGE_NT_HEADERS pNTHeader = NULL; PIMAGE_FILE_HEADER pPEHeader = NULL; PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL; PIMAGE_SECTION_HEADER pSectionHeader = NULL; PIMAGE_DATA_DIRECTORY pDataDirectory = NULL; PIMAGE_BASE_RELOCATION pBaseRelocation = NULL; DWORD RVA_BaseRelocationTable = 0; DWORD SizeOfBlock_BaseRelocationTable = 0; // 判断指针是否有效 if (!pFileBuffer) { printf("pFileBuffer不是有效的指针\r\n"); return 0; } //判断是否是有效的MZ标志 if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE) { printf("pFileBuffer不是有效的MZ文件\r\n"); return 0; } //判断是否是一个有效的PE标志 pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer; if (*((PWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE) { printf("pFileBuffer不是一个有效的PE标志\r\n"); return 0; } // 强制结构体类型转换 pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer + pDosHeader->e_lfanew); pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 0x04); // 这里必须强制类型转换 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + IMAGE_SIZEOF_FILE_HEADER); pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader); //pSectionHeader = (PIMAGE_SECTION_HEADER)(pNTHeader+0x01); //上述表示节表指针pSectionHeader的另一种写法,就是通过NT头的这个一个整体的结构体宽度进行移动; //因为NT头整体结构体宽度是4+20+224=248 --> 16进制F8,通过加0x01就直接移动F8的字节,刚好落在节表位置; pDataDirectory = (PIMAGE_DATA_DIRECTORY)pOptionHeader->DataDirectory; //定位重定位表VirtualAddress,即RVA地址 RVA_BaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; //定位重定位表SizeOfBlock, 即块的大小 SizeOfBlock_BaseRelocationTable = pDataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size; if (!debug) { printf("重定位表VirtualAddress地址: %#010X\r\n", RVA_BaseRelocationTable); printf("重定位表SizeOfBlock大小: %#010X\r\n", SizeOfBlock_BaseRelocationTable); } if (!RVA_BaseRelocationTable) { printf("这个程序没有导出表.\r\n"); return 0; } //重定位表FOA地址,这里的FOA地址是为从数据目录数组[5]获取的RVA并转换为FOA,目的是为了下面 //偏移到FileBuffer中准确的BaseRelocation重定位表起始位置 DWORD FOA_BaseRelocationTable = RvaToFileOffset(pFileBuffer,RVA_BaseRelocationTable); //定位重定位表文件偏移的位置,即:FIleBuffer的文件偏移 pBaseRelocation = (PIMAGE_BASE_RELOCATION)((DWORD)pFileBuffer + FOA_BaseRelocationTable); if (!debug) { printf("重定位表FOA地址: %#010X\r\n", FOA_BaseRelocationTable); printf("重定位表文件偏移地址: %#010X\r\n", pBaseRelocation); } //定义节里面名称的数组变量,并置为0 BYTE secName[9] = {0};//这里是定义9个宽度的字节数组并置为0,为后面的节操作做准备 // for (DWORD i = 0;pBaseRelocation->SizeOfBlock && pBaseRelocation->VirtualAddress; i++) { //参数pVirtualOfFOA_BaseReloc的FOA地址是从重定位表处获取的VirtualAddress将其转换为FOA地址; DWORD pVirtualOfFOA_BaseReloc = RvaToFileOffset(pFileBuffer,pBaseRelocation->VirtualAddress); DWORD pSizeOfBlock_BaseReloc = (pBaseRelocation->SizeOfBlock - 8)/2; /*上面是根据VirtualAddress,SizeOfBlock总共占8个字节,SizeOfBlock是当前块的中大小 进行计算,为了算出真正具体项的数量,然后判断哪些是需要修改的项; 具体项宽度:2字节 也就是这个数据,内存中的页大小是1000H 也就是说2的12次方 就可以表示,一个页内所有的偏移地址 具体项的宽度是16字节高四位,代表类型:值为3代表的是需要修改的数据值为0代表的是,用于数据对齐的数据 可以不用修改.也就是说 我们只关注高4位的值为3的就可以了. 1000H --> 4096D --> 2的12次方 12 + 4 = 16 bit */ //开始通过计算确定该结构所属哪个节里面 for (DWORD j = 0; j < pPEHeader->NumberOfSections; j++) { DWORD pLowAddressOfFoa = RvaToFileOffset(pFileBuffer,pSectionHeader[j].VirtualAddress); DWORD pHighAddressOfFoa = RvaToFileOffset(pFileBuffer,pSectionHeader[j].Misc.VirtualSize); if (pVirtualOfFOA_BaseReloc >= pLowAddressOfFoa && pVirtualOfFOA_BaseReloc <= pHighAddressOfFoa) { memcpy(secName,pSectionHeader[j].Name,8); break; } } //下面是打印本页的主要信息 printf("节:%X -> 重定位表VA:%#010X -> 节的名称:%s -> 具体项大小:%#010X\r\n",\ i,pBaseRelocation->VirtualAddress,secName,pSizeOfBlock_BaseReloc); //打印一个页中所有重定位信息和地址 //下面是通过偏移8个字节指向块中第一个具体项,因为宽度是2个字节,所以使用WORD并带指针类型 WORD* recAddr = (WORD*)((BYTE*)pBaseRelocation+0x08); for(j=0; j<pSizeOfBlock_BaseReloc; j++)//每个结构的内容进行遍历 { /*下面算法解释: 第一行代码:通过获得的第一个具体项2个字节的地址与0x0FFF进行与操作,结果就去除了高4位; 然后就得到了准确的后面12位的偏移,刚好是咱想要的,直接按照算法来操作,使用该节所属的 VirtualAddress进行相加偏移,当然这里的VirtualAddress是需要转换为FOA的地址之后进行计算 此时便得到了准确需要偏移的值offset参数,通过这个参数进行偏移可得到准确的需要修改的偏移 地址,而参数type,就是我们所说的高4位代表一个类型,我们需要使用右移的方式将其置为0x0011 也就是3,所以代码中操作对其进行右移12位即可得到0x0011-->3; 这里回顾下右移:因为int如果是有符号的整形数,最左端的1位是符号位,0正1负,而现在我们的最 左端是0011,符号位是0表示正,那么右移的时候前面补0即可,所以移动12位置之后type参数的结果 就是0x0011->3 下面是计算其中一个节的具体数据例子: 节:0 -> 重定位表VA:0X00001000 -> 节的名称:.text -> 具体项大小:0X00000088 recAddr[j]:0X0000315F ---> 0x315F ---> 0011 0001 0101 1111 offset:0X0000115F ---> 0011 0001 0101 1111 & 0000 1111 1111 1111 --->0x015F 0x015F + 0x1000 = 0x115F ---> offset:0X0000115F type:0X00000003 ---> 0011 0001 0101 1111 >> 12 ---> 0000 0000 0000 0011 --> 0x0011 --> 3 0X0000215F,0X00000003 */ DWORD pRepair_RvaOffset = (recAddr[j] & 0x0FFF) + pVirtualOfFOA_BaseReloc; //printf("recAddr[j]:%#010X \r\n",recAddr[j]); //printf("offset:%#010X \r\n",offset); WORD type = recAddr[j] >> 12;//三位 //printf("type:%#010X \r\n",type); if(type!=0) { printf("%#010X,%#010X\r\n",pRepair_RvaOffset,type); // system("pause"); } } memset(secName, 0, 9); pBaseRelocation = (PIMAGE_BASE_RELOCATION )((BYTE *)pBaseRelocation + pBaseRelocation->SizeOfBlock); } return 0; } void PrintBaseRelocation() { LPVOID pFileBuffer = NULL; DWORD FileBufferSize = 0; DWORD BaseName_FunctionAddr = 0; DWORD BaseOrdinals_FUnctionAddr = 0; //File-->FileBuffer FileBufferSize = ReadPEFile(FilePath_In,&pFileBuffer); if (FileBufferSize == 0 || !pFileBuffer) { printf("文件-->缓冲区失败\r\n"); return ; } printf("FileBufferSize: %#X \r\n",FileBufferSize); GetDataDirectoyOfBaseRelocation(pFileBuffer); free(pFileBuffer); } int main(int argc, char* argv[]) { PrintBaseRelocation(); printf("Hello World That Fuck Successfully!\n"); return 0; }
- 由于需要修改的重定位表信息过多,默认使用VC6 打印出来,显示不全,所以我这配置好环境变量通过命令行编译,并修改命令显示参数;
- 相关命令行配置bat如下:其他配置参加为博客文章
@echo off cd C:\cntflx\commandlx del *.obj del *.exe cl /c /W3 /WX C:\cntflx\commandlx\pelx.cpp link pelx.obj pelx.exe pause
- 展示结果
省略一部分.........
- 工具解析验证
- 手工打开winhex比对验证
- 首先是根据得出的FOA地址0x35000定位到重定位表位置,然后比对;
- 根据上面结果,再比对工具里面的内容,可确认解析成功,没问题;
迷茫的人生,需要不断努力,才能看清远方模糊的志向!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2020-09-24 Vulnhub-靶机-PRIME: 1
2020-09-24 sqlilab-Less-31-40-writeup