Loading... 源程序如下: ``` #include<windows.h> #include<tchar.h> int main(void) { MessageBox(NULL,_T("Hello World"),_T("对话框"),MB_OK); return 0; } ``` 编译为hw.exe,以此程序来学习PE结构中的IAT表。 ##### 什么是IAT 当我们要调用某个DLL中的函数时,需要借助IAT(Import Address Table,导入地址表)作为中间桥梁。PE文件在装载时由PE装载器将导入函数的真实地址写入到IAT,函数调用时则需要从IAT表中获取函数的真实地址。 用IDA反汇编hw.exe: ![image.png](http://47.117.131.13/usr/uploads/2021/11/615971335.png) ![image.png](http://47.117.131.13/usr/uploads/2021/11/531281122.png) FF 15是call的opcode,C8 42 42 00是小端表示的地址,即0x004242C8,该call指令并没有直接跳转到0x004242C8这个地址,而是从.idata段读取了strcmp函数的真实地址后再进行跳转。.idata段内的数据被称为IAT(Import Address Table,导入地址表),这个过程被称为从IAT获取函数的真实地址。 然而,.idata段内的数据并没有初始化,也就是说,MessageBoxA函数的地址是在运行时才能得到,准确来说是在PE文件被加载到内存时才能得到。 MessageBoxA是user32.dl的导出函数: ![image.png](http://47.117.131.13/usr/uploads/2021/11/2278590273.png) 下面我们就来研究IAT是怎样被初始化的。 ##### IAT初始化过程以及INT IMAGE_IMPORT_DESCRIPTOR结构体中记录着PE文件要导入哪些库文件。 ``` typedef struct _IMAGE_IMPORT_DESCRIPTOR{ union{ DWORD Characteristics; //? DWORD OriginalFirstThunk; //桥1 指向IMAGE_THUNK_DATA(输入名称表)结构的数组 }; DWORD TimeDateStamp; //时间戳 DWORD ForwarderChain; //链表的前一个结构 DWORD Name; //指向链接库名字的指针 rva DWORD FirstThunk; //桥2 指向输入地址表(IAT)的RVA,IAT是一个IMAGE_THUNK_DATA结构的数组 }IMAGE_IMPORT_DESCRIPTOR; typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR; ``` 其结构如图所示: ![image.png](http://47.117.131.13/usr/uploads/2021/11/3039867027.png) OriginalFirstThunk存储的INT(Import Name Table)的RVA,INT中各元素的值为IMAGE_IMPORT_BY_NAME结构体指针。 ``` typedef struct _IMAGE_IMPORT_BY_NAME{ WORD Hint; CHAR Name[1]; }IMAGE_IMPORT_BY_NAME,*PIMAGE_IMPORT_BY_NAME; ``` 在010 editor中找到导入表(IMAGE_IMPORT_DESCRIPTOR) ![image.png](http://47.117.131.13/usr/uploads/2021/11/1311103416.png) ![image.png](http://47.117.131.13/usr/uploads/2021/11/2698017999.png) IMAGE_IMPORT_DESCRIPTOR.**OriginalFirstThunk**: ![image.png](http://47.117.131.13/usr/uploads/2021/11/1377942182.png) IMAGE_IMPORT_DESCRIPTOR.**FirstThunk**: ![image.png](http://47.117.131.13/usr/uploads/2021/11/404028791.png) OriginalFirstThunk(桥1)和FirstThunk(桥2)指向不同的地址,但内容是一样的。指向一个数组,数组中每一个元素都是IMAGE_THUNK_DATA结构(双字,RVA): ![image.png](http://47.117.131.13/usr/uploads/2021/11/648773632.png) 最终指向一个数组,每一个元素都是IMAGE_IMPORT_BY_NAME结构(编号+函数名称): ![image.png](http://47.117.131.13/usr/uploads/2021/11/3720142553.png) 当PE文件装载到内存时,IMAGE_IMPORT_DESCRIPTOR.FirstThunk(桥2,RVA=2428Ch)指向的内容发生了变化,被填充为MessaBoxA函数真实的地址: BaseAddress=0x400000,VA=RVA+BaseAddress=0x400000+2428Ch=0x0042428C. ![image.png](http://47.117.131.13/usr/uploads/2021/11/3610022679.png) ##### 导入函数地址表 现在我们知道: **通过定位到import(导入表),通过IMAGE_IMPORT_DESCRIPTOR.FirstThunk(桥2)可以定位到IAT**。 此外,可以**直接定位到IAT表,也即ImportAddressTable**: ![image.png](http://47.117.131.13/usr/uploads/2021/11/2163475674.png) ![image.png](http://47.117.131.13/usr/uploads/2021/11/2733557897.png) 在IDA中可以看到导入的DLL模块有两个uer32.dll、kernel32.dll: ![image.png](http://47.117.131.13/usr/uploads/2021/11/2704565325.png) 通过前边的分析,我们知道,在文件中IAT的内容和INT是一样的,指向一个数组,每一个数组都是IMAGE_THUNK_DATA结构,双字:![image.png](http://47.117.131.13/usr/uploads/2021/11/4266638808.png) 我们再看一下,在内存中ImportAddressTable表(RVA=2417Ch)中的内容,就是IAT表。 BaseAddress=0x400000,VA=RVA+BassAddress=0x42417C。 ![image.png](http://47.117.131.13/usr/uploads/2021/11/4070349646.png) ##### 总结 1. 在文件中,IAT和INT的内容是一样的; 2. 当PE文件加载到内存后,“桥2”发生断裂,IAT被赋值为真实的导入函数地址; 3. 定位IAT表的方式有两种: a.定位Import(导入表,数据目录表的第2项),通过"桥2"(FirstThunk)定位到IAT; b.定位到ImportAddressTable(导入地址表,数据目录表的第13项),直接定位到IAT. 最后修改:2021 年 11 月 02 日 05 : 38 PM © 允许规范转载