PE重定位
向进程的虚拟内存加载PE文件(EXE/DLL/SYS)时,文件会被加载到PE头的ImageBase所指的地址处。
若加载的是DLL(SYS)文件,且在ImageBase位置处已经加载了其他DLL(SYS)文件,
那么PE装载器就会将其加载到其他未被占用的空间。这就涉及PE文件重定位的问题,
PE重定位是指PE文件无法加载到1mageBase所指位置,而是被加载到其他地址时发生的一系列的处理行为。
win32sdk中
EXE默认的ImageBase 为 00400000
DLL默认的ImageBase为 10000000
使用DDK(DriverDevelopmentKit)
SYS默认的ImageBase为10000
DLL/SYS
重定位就是说,一个执行文件的10000000地址初已经加载了A.DLL,但是B.DLL尝试加载这个地址,PE装载器就会将B.DLL加载到另一个未被占用的地址(3C000000)
EXE
创建好进程后
EXE
文件会首先加载到内存,所以在EXE
不用考虑重定位的问题
在Windows Vista
之后的版本引入了ASLR
安全机制,每次运行EXE
文件都会被加载到随机地址
ASLR机制也适用于
DLL/SYS
。对于各OS的主要系统DLL,微软会根据不同版本赋予不同的IMAGEBASE
同一系统的kernel32.dll,user32.dll等会被加载到自身固有的imagebase
因此,系统的DLL不会发生重定位问题
Vista
后的系统DLL
虽然也拥有自身固有的ImageBase
,但是ASLR
机制使每次启动加载的地址都不相同
PE重定位原理
在使用ollydbg
运行可执行程序
每当在ollydbg
重启程序,地址值就随加载地址的不同而改变。
使硬编码在程序中的内存地址随当前加载地址变化而改变的处理过程就是PE重定位
硬编码(Hardcoded)是指在程序代码中直接写入的具体数值或固定地址,而不是通过变量、常量定义或者运行时计算得到的值。在编程中,硬编码通常指的是那些没有设计为动态调整或配置的值,它们被静态地嵌入到源代码或编译后的二进制文件中。
当提到内存地址硬编码时,意味着程序员直接在代码中写入了特定的内存地址,用来访问数据或执行函数。例如,如果一个程序需要读取某个特定内存位置的数据,而这个位置是预先知道的,程序员可能会直接把这个地址写成一个常数值(如
0x401000
)。然而,在Windows等现代操作系统中,为了安全和资源管理,进程的基地址(即程序加载到内存的起始地址)在每次程序启动时可能不同,这是由于地址空间布局随机化(ASLR)技术的应用。因此,如果程序中硬编码了内存地址,那么当程序加载到不同的基地址时,原先硬编码的地址就会失效,导致程序崩溃或行为异常。
PE重定位的基本操作原理
- 首先在应用程序中查找硬编码的地址位置
- 读取值后,减去
ImageBase
(VA->RVA) - 加上实际加载地址(RVA->VA)
基址重定位表
当PE文件加载到内存中时,操作系统会读取基址重定位表,并根据实际的加载基址对这些地址进行相应的调整,这一过程称为重定位。
通过这种方式,即使程序加载到不同的内存位置,其中的指令和数据引用也能被正确更新,从而保证程序的正常执行。
基址重定位表地址位于PE
头的DataDirectory
数组的第6个元素
IMAGE_NT_HEADERS\IMAGE_OPTION_HEADER\IMAGE_DATA_DIRECTORY[5]
IMAGE_BASE_RELOCATION结构体
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress;
DWORD SizeOfBlock;
// WORD TypeOffset[1];
} IMAGE_BASE_RELOCATION;
typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
//
// Based relocation types.
//
#define IMAGE_REL_BASED_ABSOLUTE 0
#define IMAGE_REL_BASED_HIGH 1
#define IMAGE_REL_BASED_LOW 2
#define IMAGE_REL_BASED_HIGHLOW 3
#define IMAGE_REL_BASED_HIGHADJ 4
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_5 5
#define IMAGE_REL_BASED_RESERVED 6
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_7 7
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_8 8
#define IMAGE_REL_BASED_MACHINE_SPECIFIC_9 9
#define IMAGE_REL_BASED_DIR64 10
- 第一个成员是
VirtualAddress
,是一个基准地址(BaseAddress),实际是RVA
值 - 第二个成员是
SizeOfBlock
,指重定位块的大小 - 最后一项
TypeOffset
数组不是结构体成员,而是以注释形式存在,表示该结构体之下会出现WORD
类型的数组,并且该数组元素的值就是硬编码在程序中的地址偏移
分析基址重定位表的方法
如何根据这张表计算硬编码的位置
由IMAGE_BASE_RELOCATION
结构体可知
TypeOffset数组的基准地址RVA1000,块的总大小为150
块的末端显示为0
TypeOffset的值为2个字节大小,是由4位Type与12位Offset合成
例如TypeOffset
值为3420
typeoffset值为3420解析如下
类型(4位) | 偏移(12位) |
---|---|
3 | 420 |
Type的值常见为3(IMAGE_REL_BASED_HIGHLOW)
,64位的PE+文件中常见值为A(IMAGE_REL_BASED_DIR64)
在恶意代码中正常修改文件代码后,有时要修改指向相应区域的重定位表
为了略去PE装载器的重定位过程,常常把Type值修改为0(IMAGE_REL_BASED_ABSOLUTE)
TypeOffset
的低12位是真正的位移,该位移值是基于VA
的偏移,也就说
硬编码的地址偏移=VirtualAddress+Offset
这个是硬编码的RVA,加上基址就可以算出来硬编码的地址了,然后对硬编码地址进行更改,这样就完成了重定位的操作
TypeOffset项中低12位拥有的最大地址值为1000
为了表示更大的地址,要添加1个与其对应的块,由于这些块以数组形式罗列,
故称为重定位表
总之,如果想认为分析PE重定位,步骤如下
- 首先查找程序中硬编码地址的位置
- 读取值后减去IMAGBASE
- 加上实际加载地址
若TypeOffset
值为0,则表明一个Image_Base_Relocation
结构体结束
对重定位表中出现的所有IMAGE_BASE_RELOCATION
结构体都要重复上述处理,
重定位表以NULL
结构体(IMAGE_BASE_RELOCATION结构体。成员全是NULL)结束
删除.reloc节区
删除EXE文件中的基址重定位表对运行是没有影响
删除步骤
-
删除.reloc节区头
用0 nop掉.reloc节区头的数据
-
删除.reloc节区
物理删除掉.reloc节区
-
修改IMAGE_FILE_HEADER
修改
IMAGE_FILE_HEADER/NumberofSections
一项 -
修改IMAGE_OPTIONAL_HEADER
修改
IAMGE_OPTIONAL_HEADER-sizeOfImage
要根据
.reloc
节区的VA 以及SectionAliginment 进行计算.reloc
节区】在内存中的大小
例如
.reloc
的VirtualSize为E40根据Section Alignment拓展(对齐)后变为1000
需要在当前的SizeOfImage中减去1000
甚至可以根据这个原理倒过来操作,在原有的PE节区中再加上一个节区