导入地址表是PE文件结构中的一个表结构,在学习PE文件结构到时候虽然没有提到导入地址表,但是提到了数据目录,数据目录在IMAGE_OPTIONAL_HEADER中的DataDirectory中,我们回忆一下他的定义:
(1)NumberOfRvaAndSizes:该字段标识数据目录的个数,该个数的定义是16,如下:
(2)DataDirectory:数据目录表,由NumberOfRvaAndSizes个IMAGE_DATA_DIRECFORY结构体组成,该数组中包含了输入表,输出表,资源等数据的RVA。IMAGE_DATA_DIRECTORY的定义如下:
该结构体的第一个变量为该目录的相对虚拟地址的起始值,第二个是该目录的长度。
导入表简介
在可执行文件中使用其他DLL可执行文件的代码或者数据时称为导入,或者成为输入,当PE文件被加载时候,Windows加载器会定位所有的导入的函数或者数据,这个定位是需要借助于导入表来完成的,导入表存放了是的DLL的模块名称,及导入的函数。
在加壳与脱壳的研究中,导入表示非常关键的部分,加壳要尽可能的隐藏导入表,脱壳一定要找到导入表,如果无法还原或者修复脱壳后的导入表的话,那么可执行文件仍然是无法运行的。
在免杀中也有何导入表相关的内容,比如移动导入表函数,修改导入表描述信息,隐藏导入表.........不过这些操作都是杀毒软件将特征码定位到了导入表上才需要这样做,不过可以看出导入表也同样受到杀毒软件的关注。
既然导入表这么重要,我们先来学习一下关于导入表的知识吧。
导入表的数据结构定义
在数据目录中定位第二个目录,即IMAGE_DIRECTORY_IMPORT,该结构体中保存了导入函数的重要信息,每个DLL都对应一个IMAGE_import_DESCRIPTOR结构,也就是说导入的DLL文件和IMAGE_IMPORT_DESCRIPTOR是一对一的关系。IMAGE_IMPORT_DESCRIPTOR在文件中是一个数组,但是文件中并没有明确的指出导入表的个数,在导入表中是一个以全0的IMAGE_IMPORT_DESCRIPTOR为结束的,导入表对应的结构体定义如下:
(1)originalfirsthunk:该字段指向导入名称表的RVA,该表是一个IMAGE_THUNK_DATA的结构体数组。
(2)TIMEDATASTAMP:该字段可以被忽略。
(3)forwarderchain:该字段一般为0
(4)Name:该字段为DLL名称的指针,该指针也为一个RVA
(5)firstThunk:该租店包含了导入地址表的RVA,IAT是一个IMAGE_YHUNK_DATA的结构体数组。
IMAGE_YHUNK_DATA结构体的定义如下:
该结构体的成员是一个联合体,虽然联合体中有若干个变量,但是由于该结构体中包含的是一个联合体,那么这个结构体也就相当于只有一个成员变量,只是有时代表的意义不同,看其本质,该结构体实际上是一个DWORD类型,当IMAGE_THUNK_DATA值的最高位为1的时候,表示函数以序号方式导入,而这个时候第31为被看作是一个导入序号,当最高位为0的时候,表示函数以函数名字符串的方式导入,这个时候DWORD的值表示一个RVA,并且指向一个IMAGE_IMPORT_BY_NAME结构。
IMAGE_IMPORT_BY_NAME结构定义如下:
(1)hint:该字段表示该函数在其DLL中的导出表中的序号
(2)name:该字段表示导入函数的函数名,导入函数是一个以ASCII编码的字符串,并以NULL来结尾,在IMAGE_IMPORT_BY_NAME中使用NAME[1]来定义该字段,表示这是只有1个长度大小的字符串,通过越界访问,来达到访问变长字符串的功能。IMAGE_THUNK_DATA为IMAGE_IMPORT_DESCRIPTOR类似,同样是以一个全0的IMAGE_THUNK_DATA为结束的。
手动分析导入表
在学习PE文件结构的时候就借助十六进制编辑器完成了我们的学习,现在仍然通过十六进制编辑器来学习导入表的结构体IMAGE_IMPORT_DESCRIPTOR,在这里随便找个PE文件来进行分析,大家可以找一个PE文件进行分析。
用C32ASM打开找来的PE文件,首先定位到数据目录的第二项,如图5-17所示。
在图5-17中看到了数据目录中的第二项内容,其值分别是0X00024000和0x0000003c。0x00024000的值表示IMAGE_IMPORT_DESCRIPTOR的RVA,注意这里给出的是RVA,现在使用十六进制编辑器打开,那么就要通过RVA抓还为FileOFFSET,也就是从相对虚拟地址转换为文件偏移地址,使用LordPE来进行转换,如图5-18所示。
从图5-18中看出,0x00024000这个RVA对应的FILEOFFSET为0X00023000,那么在C32中转换到0X00023000的位置处,按下CTRL+G组合键,在弹出的对话框中填入2300如图5-19所示。
单击确定按钮,来到了文件偏移为00023000的位置处,如图5-20所示。
来到文件偏移的00023000处就是IMACrE_JMPORT_DESCFUPTOR的开始位置了。从图5-20中可以看出,该文件有2个IMAGE IMPORT_ DESCRIPTOR结构体。我们重点分析第一个IMAGE._ IMPORT_ DESCRWTOR,同样关于其他几个相关的数据结构也只分析第一个。
在IMAGE_IM_PORT_DESCRIPTOR中只看最后两个字段,分别是Name和FirstThunk。
看一下第一个IMAGE_IMPORT_DESCRIPTOR的这两个字段的值分别是00024338和O0024190。这两个值都是RVA值。
先来看一下00024338这个值,该值表示DLL名字符串的RVA,将其转换为文件偏移后值为00023338。转到00023338这个文件偏移处查看,如图5-21所示。
从图5-21中可以看出,这个位置保存的内容是一个字符串,该字符串的内容为KERNEL32.DLL。说明这个NAME值的确保存的是DLL名字符串的RVA。
在来看一下00024190这个值,该值表示IMAGE_YHUNK_DATA的RVA,将该值转换为文件偏移后的值为00023190,转到0023190这个文件偏移处查看,如图5-22所示。
从图5-23中可以看出,这里保存的是CLOSEHANDLE()这个函数名称的函数字符串,在IMAGE_LMPORT_DESCRIPTOR中,有两个INAGE_THUNK_DATA结构体,第一个为导入名字表,第二个为导入地址表,两个结构体在文件当中没有差别的,但是当PE文件被装载内存后,第二个IMAGE_THUNK_DATA的值会被修正,该值为一个RVA,该RVA加上映像基址后,虚拟地址就保存了真正的导入函数的入口地址。
枚举导入地址表
从上面的分析过程中已经学习了IMAGE_IMPORT_DESCRIPTOR这个结构体,那么下面就来用代码实现枚举导入地址表的内容,我们知道一个DLL文件会对应一个IMAGE_IMPORT_DESCRIPTOR结构,而一个DLL文件中有多个函数,那么休要使用两个循环来进行枚举,外层循环来枚举所有的DLL,而内层循环来枚举所导入的该DLL的所有的函数名和函数地址,代码如下:
只要对于手动分析导入表能够理解的话,那么上面这段代码就不难理解了,希望大家可以理解上面的代码,对某个程序进行一个测试,看其输出结果,如图5-24所示。
用OD进行验证一下,对该测试程序的导^表信息的获取是否正确。用OD载入测试程序,然后在数据窗口中按下Ctrl+G组合键,输入地址“424190”,然后在数据窗日上单击鼠表右键,在弹出的菜单中选择长型→地址命令,看数据窗口的内容,如图5-25所示。
那么说明程序是正确的,关于导入表的知识就介绍到这里了,接下来,要介绍关于如何对IAT进行HOOK的内容了,请大家务必掌握该小节的内容。
INA HOOK介绍
在前面的内容中提到这样一个问题,在IMAGE_IMPORT_DESCRIPTOR中,有两个IMAGE_THUNK_DATA结构体,第一个为导入名字表,第二个为导入地址表,两个结构体在文件当中是没有差别的,但是当PE文件被装载内存后,第二个IMAGE_THUNK_DATA的值会被修正,该值为一个RVA,该RVA加上映像基址后,虚拟地址就保存了真正的导入函数的入口地址。
比如要在IAT中HOOK系统模块kerne132.dll中的READFILE()函数,那么首先是获得READFILE()函数的地址,第二步是找到READFILE()所保存的IAT地址,最后一步是把IAT中的READFILE()函数地址修改为HOOK函数的地址,这样是不是就能够明白了呢?下面通过一个实例来借晒IATHOOK的具体过程和步骤IATHOOK之CREATEFILEW()
上次对EXPIORER.EXE进程的CREATEPROCESSW()函数进行了INLINEHOOK,这次对记事本进程的CREATEPROCESSW()函数尽心IATHOOK,对CREATEPROCESSW()函数进行HOOK后主要是管控记事本要打开的文件是否允许被打开,我们一步一步的来完成代码。
先建立一个DLL文件,然后定义好DLL文件的主函数,并且定义一个HOOKNOTEPADPROCESSIAT()函数,在DLL被进程加载的时候,让DLL文件去调用HOOKNOTEPADPROCESSIAT()函数代码如下:
在遍历某程序的导入表时事通过文件映射来完成的,但是当一个可执行文件已经被Windows装载器装载入内存后,便可以省去CREATEFILE()、createfilemapping()等诸多繁琐的步骤,取而代之的是通过简单的Getmodulehandle()函数就可以得到EXE文件的模块映像地址,并且能够很容易获取DLL文件导入表的虚拟地址,代码如下:
在获得导入表的位置以后,要在导入表中找寻要HOOK函数的模块名称,也就是说。要对GREATEFILEW()函数进行HOOK,首先要找到该进程是否有KERNE132.DLL这个模块存在。一般情况下,KERNE132.DLL模块一定会存在于进程的地址空间内,因为他是WIN32子系统的基本模块,当然。我们并不是简单的要找到该模块是否存在,关键是要找到这个模块所对应的IMAGE_IMPORT_DESCRIPTOR结构体,这样才能通过KERNE132.DLL所对应的IMAGE_IMPORT_DESCRIPTOR结构体去查找保存CREATEFILEW()函数的地址,并且进行修改,看一下代码:
对CreateFileWO函数进行HOOK,目的是为了对其打开的文件进行管控。由于这是演示程序,那么笔者在G盘下建立一个test,txt文件,然后对其进行管控,也就是说,如果用记事本打开这个程序的话,可以选择性的是否允许打开,或者不运行打开,代码如下:
我们HOOK的函数是CreateFileW().通过函数中的w可以看出,这个函数是一个UNICODE版本的字符串,也就是宽字符串。在CreateFileW()函数的参数中, lpFileName孵类型是一个指向宽字符的指针变量。那么,就需要在操作该字符串时使用宽字符集的字符串茵数。而不应该再使用操作 ANSI字符串的函数。在代码中wcscpy()、wcscmp()、wcslwr()部是针对宽字符集的字符串。WCHAR是定义宽字符集类型的关键字。L”g:\\test.txt"中的“L”表示这个字符串常量是一个宽字符型的,
打开一个记事本程序,然后将编译连接好的DLL文件注入到记事本进程中。,当注入并HOOK成功后会用对话框提示“Hook Successfully!”。然后用记事本打开G盘下的test.Oa文件,会弹出对话框询问“是否打开文件”,单击“否”按钮,也就是拒绝打开该文件,如图5-26和图5-27所示。
在图5,27中单击“确定”按钮,然后可以看到记事本并没有打开G盘下的test.txt文件,这说明对G盘下的teat.txt文件的管控还算是成功的(这个程序并不完善,希望大家可以自行将其完善)。
以上实例演示了如何对IAT进行HOOK。不过上面针对IAT进行HOOK的做法只能针对隐型调用。也就是说,可执行文件是直接调用了DLL的导出函数,用上面的代码可以对IAT进行HOOK。如果是显式调用的话,以上的例子就无法达到HOOK的作用了。当可执行文件直接通过调用LoadLibrary()函数和GetProcAddress()函数来使用某个函数的话,上面的HOOK代码是无能为力的。如何解决这样的问题,答案是要对LoadLibrary()和GetProcAddress()函数也进行HOOK,这样就可以避免对DLL的显式加载,和对函数的显式调用了。