2011年10月09日 星期日 16:23



Phoenixos镜像文件 firpe镜像文件_加载器


图1

PE加载器在完成文件实体数据到内存虚拟数据的映射之后,便开始从位于IMAGE_OPTION_HEADER末端的IMAGE_DATA_DIRECTORY数组的第2项(如图2),取出输入表的RVA和大小,准备开始输入函数的初始化。输入表的RVA实际上是指向一个以IMAGE_IMPORT_DESCRIPTOR为元素的数组,该数组以一个全零的结构作为结尾。

Phoenixos镜像文件 firpe镜像文件_数组_02


图2

Phoenixos镜像文件 firpe镜像文件_加载器_03


图3

 

从该结构(如图3)的Name字段,可以推测每一个该结构都只能描述一个动态库,事实上也是如此。

其中OriginalFirstThunk和FristThunk都是IMAGE_THUNK_DATA32的RVA,它们如此相像不是巧合,因为在本质上它们所描述的信息是一致的。如图4我们可以清楚的看出OriginalFristThunk和FristThunk是不同的值。

Phoenixos镜像文件 firpe镜像文件_Phoenixos镜像文件_04

图4

但实际上它们如描述的RVA却是相同的如图5(OriginalFirstThunk)和6(FirstThunk)。

 

Phoenixos镜像文件 firpe镜像文件_加载器_05


图5

 

Phoenixos镜像文件 firpe镜像文件_Phoenixos镜像文件_06


图6

PE加载器一般会从OriginalFirstThunk读取输入函数的信息(如果OriginalFirstThunk为0则从FirstThunk处读取),并将它(输入函数)的地址填入FristThunk所描述的RVA处,你一定想问一个动态库的输入函数不一定只有一个,那其他的函数应该怎么办?其实答案很简单,OriginalFirstThunk和FristThunk所存储的RVA都是一个数组的起始位置,PE加载器只需要每处理一对输入信息就将指针下移4个字节即可,谈到输入表,你一定会想到IAT(Import address table),其实你只要足够细心就会发现第一个输入表描述符中的FristThunk和IAT的表头是同一个RVA,这足以证明PE加载器实际上是在完成IAT表的填写工作。那么原来存储在IAT所描述的RVA处的数据又是什么呢?在本测试程序上IAT的地址是2000H也就是文件偏移E00H。即PE文件的映射基址+2000H开始的数据也就是图6上的数据将被系统填充为输入函数的地址,该过程也就是传说中的填写IAT。

我们再来看看图5图6中描述的数据是什么,EA210000,FC21000也就内存RVA:000021EA和000021FC,我们将第一个000021EA转换中文件偏移,也就是21EA-2000+E00=FEAh。

可以看到此处的数据都是一个IMPORT_BY_NAME结构。如图7和8。

Phoenixos镜像文件 firpe镜像文件_数组_07


图7

 

Phoenixos镜像文件 firpe镜像文件_数组_08

图8

这样输入函数名和输出序号就都有了,PE加载器接下来的工具就跟我们平时动态加载一个DLL获取其输出函数差不多了:PE通过调用LoadLibrary+DLL名加载所需要的动态库,并根据OriginalFirstThunk描述的RVA找到IMAGE_IMPORT_BY_NAME结构指针数组,然后循环遍历该组,为该DLL导入的所有函数填写地址,之后PE加载器会继续遍历下一个DLL直到遇到一个全零的IMAGE_IMPORT_DESCRIPTOR结构结束输入函数初始化,完成IAT的构建。