PE loader装载时做的部分工作、导入表、导出表

发布于 2021-08-26  40 次阅读


背景

假设我们在源码中CALL MessageBox,编译之后的静态机器码是什么?装载之后动调时的机器码又是什么?

正文

首先, MessageBox 作为导入函数,其实现是封装在dll内的,而编译器在静态编译时并不能动态的获取动态链接库(dll)的地址,所以在PE文件的静态机器码层面,编译器只能给出函数名这一结果,给出方式就是导入表

顺便补一个RVA的笔记

对于RVA需要注意的的一点:他是基于装载后内存空间的,而非基于静态文件的,比如

从这张图可以看到,.text的RVA为1000h,换句话来说就是AA 401000h ,但是实际上相对于头的静态文件偏移是RAW偏移

也就是400h

从这点也能看出PE loader的一个特点:装载这一动作并非单纯按部就班的加载磁盘文件,而是会基于PE头当中的信息进行一些额外操作,比如安排各种节区的动态位置

这就要提到重定位表了,不过话题先回到导入表

如果我们在动调中去查看call MessageBox的地址,会发现他指向地址以0040h开头的区域,该区域属于EXE程序自身模块,如果跟入,会发现一个far jmp,如果跟跳,便会来到MessageBox的真正入口点

我们记下这个call的地址,在测试程序中,这一地址为00402008h

我们先需要用RVA换算虚拟地址所属区域,可以发现RVA值为2008h,属于.rdata,再查PE头当中记录的数据,得到对应静态文件的偏移值也就是608h——虽然段起始地址变了,但是偏移没变,都是8h

发现静态文件当中[.radata+8h]处的数据为20 5Ch

这里需要再查RVA 20 5Ch,仍然属于2000h范围,查5Ch偏移

正是函数名:MessageBox字符串

但是这里有一个很大的问题:前前后后总共只出现了字符串,或者说,函数名

这样是不可能调用的,调用函数的唯一方法是获取函数入口点地址,然后移交控制权

这里就需要提到PE loader了

其实字符串翻译成函数入口的操作正是PE loader在装载时做的, 装载器会根据上述流程获取RVA,进而得到函数名,再根据函数名在内存中找到对应DLL,然后装载DLL,根据DLL导出表获得函数地址,并自动替换CALL处的内容

所以导入表是什么?

导入表就是让上述过程不必写一个特殊换算算法就可以轻松查表实现的结构

导出表只需要提一嘴

一般来说EXE文件没有导出表,但是实际上PE文件都可以有导出表

导出表利用字符串来确定目标函数:正如导入表那样

话题回到重定位

搬运一段文字就行

重定位的算法可以描述为:将直接寻址指令中的双字地址加上模块实际装入地址与模块建议装入地址之差。为了进行这个运算,需要有3 个数据,

首先是需要修正的机器码地址;

其次是模块的建议装入地址;

最后是模块的实际装入地址。

在这3 个数据中,模块的建议装入地址已经在PE 文件头中定义了,而模块的实际装入地址是Windows 装载器确定的,到装载文件的时候自然会知道,所以问题的答案很简单,应该被保存在重定位表中的仅仅是需要修正的代码的地址。

事实上正是如此,PE 文件的重定位表中保存的就是一大堆需要修正的代码的地址。