PE文件分析之表的导入与导出及重定位

时间:2024-03-19 07:29:58

PE文件分析之表的导入与导出及重定位

导入地址表(Import Address Table, IAT)

导入函数: 导入函数是指,在PE程序运行时会调用的,且代码又不在程序中的函数,一般位于DLL文件中。在调用者程序即PE程序中,只保留导入函数的DLL名称、函数名称等信息。

DLL的装载方式: 采用 隐式链接方式,程序员在建立一个DLL文件时,链接程序会自动生成一个与之对应的LIB导入文件。该文件包含了每一个DLL导出函数的符号名和可选的标识号,但是不含有实际的代码。LIB文件作为DLL的替代文件被编译到应用程序项目中。当应用程序加载DLL文件时,Windows根据这些信息发现并加载DLL,通过符号名或标识号实现对DLL函数的动态链接 显式链接由Windows装载器来调用LoadLibrary( )和GetProcAddress( ).

导入地址表: 导入地址表IAT是用来记录程序正在使用哪些DLL中的哪些函数。Windows装载器将该DLL中被调用的函数的地址逐个写入到函数指针数组的每一项,即写入导入地址表中去。将call指令与导入函数在内存中的实际地址关联起来。

导入表在PE文件的位置: IAT的位置信息存储在PE头中,找到NT头中 OPTIONAL_HEADER32最后一个元素DataDirectory结构体数组位置;

DataDirectory[1]一共8个字节,第一个4字节表示IAT的RVA,第二个4字节表示IAT的大小。

PE文件分析之表的导入与导出及重定位
Image_Import_Descriptor结构体
PE文件分析之表的导入与导出及重定位
导入表由一系列Image_Import_Descriptor结构组成,结构数量等于程序要调用的DLL文件数量。 每个Image_Import_Descriptor结构对应一个DLL.

Image_Import_Descriptor记录了PE文件要导入哪些库文件及相应的地址。
在所有这些Image_Import_Descriptor结构的最后,由一个全0的Image_Import_Descriptor结构作为结束。

导入函数的调用

PE文件分析之表的导入与导出及重定位 编译器在程序所有代码的后面自动加上 Jmp dword ptr[xxxxxxxx]指令,这个间接寻址的跳转指令中xxxxxxxx地址存放的是真正的导入函数的地址。

当PE文件被加载时,windows装载器会根据xxxxxxxx处的RVA得到函数名,再根据该函数名在内存中找到函数地址,并用这个函数地址替换掉xxxxxxxx 处的内容。

导入表被装入内存

装入前:
PE文件分析之表的导入与导出及重定位

  • Image_Thunk_Data定义了一个导入函数的信息:索引&函数名(字符串)
  • 当Image_Thunk_Data双字的最高位为1时,函数以序号方式导入;双字的低位是函数的序号,如80000010h。
  • 当Image_Thunk_Data双字的最高位为0时,函数以字符串类型的函数名方式导入;这时双字的值为一个RVA,指向用于定义函数名称的Image_Import_By_Name结构

装入后:
PE文件分析之表的导入与导出及重定位重点:当PE文件装入内存后,原先由FirstThunk字段指向的数组中每个双字都被替换成真正导入函数的入口地址,而不再是指向Image_Import_By_Name结构。该字段不再是Original, 这时FirstThunk指向的数组称为导入地址数组,数组的组合称为导入地址表IAT。
IAT中第一个Image_Import_Descriptor结构的FirstThunk字段指向IAT的起始地址。
(pe文件装入内存后FirstThunk的指向变化)

从Import_Descriptor得到IAT

装载器把导入函数的实际地址输入IAT的步骤:

  1. 读取name字段(RVA) , RVA to RAW,获取库名字符串kernel32.dll;调用Loadlibrary(kernel32.dll);
  2. 读取OriginalFirstThunk (RVA),获取INT数组起始地址(RAW) ;遍历INT中数组值 ,由于 INT是IMPORT_BY_NAME结构体指针数组,第一个元素hint指向导入函数在库中的序号;第二个元素指向函数名字符串。
  3. 利用hint或函数名,调用GetProcAddress(),获得函数相应的内存地址。
  4. 读取FirstThunk字段(RVA) , 确定IAT起始地址;将步骤4获得的函数内存地址写入对应项。
    PE文件分析之表的导入与导出及重定位

导出表

  • 在包含导出函数的DLL文件中,导出信息被保存在导出表中,通过导出表,DLL文件向系统提供导出函数的名称、序号和入口地址等信息,便于装载器完成动态链接。
  • 导出表的位置信息位于Image_Data_Directory[0]
  • 导出表的起始位置是一个Image_Export_Directory结构;导出表只有一个该数据结构!

PE文件分析之表的导入与导出及重定位

重定位表

条件: 涉及直接寻址的指令都需要重定位
重定位算法:将直接寻址指令中的双字地址+(模块实际装入地址-模块建议装入地址)
PE文件分析之表的导入与导出及重定位

重定位表的位置和结构
PE文件分析之表的导入与导出及重定位

  • 重定位表在内存中的位置和大小:Image_Data_Directory[5]
  • 每个需要修正的指令机器码地址占4字节,如果有n个重定位项,则重定位表大小是4n字节。
  • 比较靠近的重定位表项,即在同一个内存页中的重定位项,32位指针的高16位总相同;把相近表项的高16位统一表示,在一个页面中寻址需要12位(OnePage=4096B),将12位凑齐16位放入一个字类型数据中。
  • 用一个双字表示页的起始指针,另一个双字表示本页中重定位项数,用一个字表示某个重定位表项的页偏移地址,那么一个页中重定位块的大小为4+4+2n字节。
  • 按页分割、每个重定位块描述一个内存页中的所有重定位项。 重定位块以一个Image_Base_Relocation结构开始(64Byte),后面跟着本页面中使用的所有重定位项,每个重定位项占2字节(低12位表示在页中的地址,高4位描述重定位项的种类)。

一个重定位的实例:
PE文件分析之表的导入与导出及重定位