Last updated on October 7, 2023 pm
重定位表
程序装载过程 当我们双击一个exe的时候,操作系统会自动为这个exe分配4GB独立的虚拟空间,其中高2G作为运行操作系统内核任务,低2G才运行用户的代码。在拥有这个4GB空间后,操作系统就会把这个exe从文件状态拉伸然后复制到低2G空间中
而后则是copy系统DLL到高2G中。最后则是将eip置为EntryPoint,就可以运行整个exe了。
然而这里面有一个问题,就是每个PE文件都会优先按照其自身DLL的ImageBase来装载,那么就无法避免ImageBase冲突的状况。一旦发生ImageBase冲突就需要另找一个地方来装载。可若仅仅只是换一个地方来装载,又会带来另一个问题——地址改变。
在编译一个文件的时候,编译器往往会把一些全局变量或者函数调用的地址给写死,也就是按照预计的ImageBase来计算地址
比如图中所有的红下划线都对应一个地址,他们都是按照预计的ImageBase来加载计算的,但是如果没有按照ImageBase加载,那么这个地址就一定会有问题。
解决办法就是记录这些地址,在实际运行的时候根据ImageBase来修正这些地址,那么就需要一张表来记录这些需要修正的地址。这张表就叫做重定位表。
重定位表 找到重定位表 重定位表地址位于OptionalHeader的第六项,这个地址也是RVA,将其转化为FOA后就可以找到重定位表
重定位表结构 重定位表与其他表都有不同,它的结构体只有两个成员,一个记录基地址,一个记录块大小,每个结构体后都跟有许多个两字节数据,然后许多个这样的块紧密连在一起形成一个重定位表。可能你会问基地址是什么?块大小是什么?数据又是什么?下面就来解释每个成员的意义。
1 2 3 4 5 6 struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; DWORD SizeOfBlock; };
可以这么理解:一个重定位表中可能会有多个“块”,每个块的结构都是
4字节VirtualAddress
接着4字节SizeOfBlock
最后是一堆数据,称其为具体项
每个块的大小为SizeOfBlock(字节)。
最后一个块的RVA和SizeOfBlock都是0x00000000,表示重定位表结束
在一个exe中,需要重定位的地址可能有很多个,每个地址都是4字节,如果全部都完完全全按照4字节来存储,显然会占据很多空间。但是稍微观察一下会发现,许多需要重定位的地址都比较相近,比如上面所举的例子。
在图中需要重定位的地址就是 6DF03
,6DF0B
,6DF1A
等等(注意是需要重定位的地址,操作码是不需要改变的),它们都是6D
开头,就会很自然诞生一种想法就是以6D000
为基地址,在基地址上加一个偏移来找到具体的需要重定位的地址。那么这个基地址就是VirtualAddres
,偏移则记录在结构体后的数据中,也就是那些个01数据。
现在来思考这样一个问题,那些记录偏移的01数据都是两字节的,也就是16位,能够记录 $2^{16} = 65536$ 个数据,然而在内存中这些模块间对齐粒度都是64KB即0x1000,也就是所有的基地址都是以0x1000来对齐的。16位的偏移显然超过了这个基地址,只需要用到12位就足以表示一个基地址的所有偏移了。因此16位的偏移数据会多出4位,于是就规定这16位里面高4位为有效位,只有当高四位等于3的时候才说明这个地址需要重定位,低12位则记录偏移。
因此一个需要重定位的地址就应该这样计算 if 具体项高4位 == 3 :地址 = VirtualAddress + 具体项低12位
成员SizeOfBlock意义就比较明确了,就是整个块的大小,包括VirtualAddress 的四字节,本身SizeOfBlock的四字节,以及下面的所有具体项的两字节。所以有多少个具体项就应该这样计算:(SizeOfBlock - 8)/2
,注意这里的单位都是字节
代码遍历重定位表 代码中有注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 #include <stdio.h> #include <windows.h> DWORD Size (FILE* fp) { DWORD size = 0 ; fseek(fp,0 ,SEEK_END); size = ftell(fp); fseek(fp,0 ,SEEK_SET); return size; } DWORD RVA_TO_FOA (DWORD RVA,char * Buffer) { PIMAGE_DOS_HEADER DosHeader = NULL ; PIMAGE_NT_HEADERS NTHeader = NULL ; PIMAGE_FILE_HEADER FileHeader = NULL ; PIMAGE_OPTIONAL_HEADER OptionalHeader = NULL ; PIMAGE_SECTION_HEADER SectionHeader = NULL ; DosHeader = (PIMAGE_DOS_HEADER)Buffer; FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4 ); OptionalHeader = (PIMAGE_OPTIONAL_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader); if (RVA < OptionalHeader->SizeOfHeaders) { return RVA; } for (int i=0 ;i<FileHeader->NumberOfSections-1 ;i++){ if (RVA >= SectionHeader->VirtualAddress && RVA < (SectionHeader+1 )->VirtualAddress){ DWORD offset = RVA - SectionHeader->VirtualAddress; return SectionHeader->PointerToRawData + offset; } else { SectionHeader +=1 ; } } SectionHeader +=1 ; DWORD offset = RVA - SectionHeader->VirtualAddress; return SectionHeader->PointerToRawData + offset; }void Relocation_INFO (char * Buffer) { PIMAGE_DOS_HEADER DosHeader = NULL ; PIMAGE_NT_HEADERS NTHeader = NULL ; PIMAGE_FILE_HEADER FileHeader = NULL ; PIMAGE_OPTIONAL_HEADER32 OptionalHeader = NULL ; PIMAGE_SECTION_HEADER SectionHeader = NULL ; DosHeader = (PIMAGE_DOS_HEADER)Buffer; FileHeader = (PIMAGE_FILE_HEADER)(Buffer + DosHeader->e_lfanew + 4 ); OptionalHeader = (PIMAGE_OPTIONAL_HEADER32)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER); SectionHeader = (PIMAGE_SECTION_HEADER)(Buffer + DosHeader->e_lfanew + 4 + IMAGE_SIZEOF_FILE_HEADER + FileHeader->SizeOfOptionalHeader); DWORD Relocation_Table_RVA = OptionalHeader>DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress; if (!Relocation_Table_RVA){ printf ("This exe has not Relocation Table\n" ); return ; } DWORD Relocation_Table_FOA = RVA_TO_FOA(Relocation_Table_RVA,Buffer); PIMAGE_BASE_RELOCATION Relocation_Table = (PIMAGE_BASE_RELOCATION)(Buffer + Relocation_Table_FOA); while (Relocation_Table->VirtualAddress && Relocation_Table->SizeOfBlock){ WORD* Reloc = (WORD*)((byte*)Relocation_Table + 8 ); DWORD AddSize = (Relocation_Table->SizeOfBlock - 8 ) / 2 ; for (int i = 0 ;i < FileHeader->NumberOfSections;i++){ if (Relocation_Table->VirtualAddress >= SectionHeader->VirtualAddress && Relocation_Table->VirtualAddress < (SectionHeader->VirtualAddress + SectionHeader->Misc.VirtualSize)){ printf ("=====%s=====\n" ,SectionHeader->Name); break ; } else { SectionHeader += 1 ; } } for (int i = 0 ; i < AddSize; i++){ if ((*(Reloc + i) >> 12 ) == 3 ){ DWORD RVA = Relocation_Table->VirtualAddress + (*(Reloc + i) & 0xfff ); printf ("RVA_Address:%X FOA_Address:%X\n" ,RVA,RVA_TO_FOA(RVA,Buffer)); } } Relocation_Table = (PIMAGE_BASE_RELOCATION) (((byte*)(Relocation_Table) + Relocation_Table->SizeOfBlock)); } }int main () { char * buffer; FILE* fp = NULL ; errno_t err_1 = fopen_s(&fp, "C:\\Users\\yongrin\\Desktop\\PE_Info.exe" , "rb" ); int size = Size(fp); buffer = (char *)malloc (size); fread(buffer,size,1 ,fp); Relocation_INFO(buffer); return 0 ; }
这里贴上我的代码运行部分截图
与前面的进行对比,会发现RVA_Address加上实际ImageBase 0x10000后就是需要重定位的地址啦。