输入表中的这些间接跳转是无法正常运行的,因为在正常情况,操作系统必须知道指向各个API函数名称字符串的指针,然后通过GetProcAddress定位到各个API函数正确的入口地址并填充到IAT中,这样这些间接跳转才能起作用。可我们脱壳之后没有这些指针了自然也就无法运行了。
搞明白了问题下一步就着手重建输入表,下面我们来看看未加壳程序的IAT。
我们来定位该CrackMe PE结构中一些重要字段。首先在数据窗口中定位400000地址处。
单击鼠标右键选择-Special-PE header切换到PE头的显示模式。
往下拉。我们可以看到PE头的偏移为100。即PE头位于400100地址处。
继续往下拉,我们可以看到IT(导入表)的指针,这里大家不要将其跟IAT搞混淆了。
IT = 导入表
IAT = 输入函数地址表
我们知道当程序启动之前操作系统会将各个API函数的地址填充到IAT中,那么IT(导入表)又是怎么一回事呢?首先我们定位到导入表,该导入表偏移值为3000(即虚拟地址为403000),长度为670(十六进制),
即403670为导入表的结尾。我们一起来看一看。
我们将数据窗口的显示模式切换为正常状态,这就是导入表了,我们来介绍一下导入表的结构吧。
我们选中的这20个字节是导入表的描述符结构。官方的叫法为IMAGE_IMPORT_DESCRIPTOR。每组为20个字节,IMAGE_IMPORT_DESCRIPTOR包含了一个的字符串指针,
该指针指向了某个的动态链接库名称字符串。
这里我们将IMAGE_IMPORT_DESCRIPTOR简称为IID。这里选中的部分为导入表中的第一个IID,共五个DWORD,其中5个DWORD字段的含义如下:
OriginalFirstThunk
TimeDateStamp 时间戳
ForwarderChain 链表的前一个结构
Name1 指向DLL名称的指针
FirstThunk 指向的链表定义了针对Name1这个动态链接库引入的所有导入函数
前三个字段不是很重要,对于我们Cracker来说,我们只对第4,5字段感兴趣。
我们来看个例子:
正如大家所看到的,第4个字段为指向DLL名称字符串的指针,我们来看看403290处是哪个DLL的名称。
这里我们可以看到是USER32.DLL,第5个字段指向了USER32.DLL对应IAT项的起始地址,即403184。(不过我觉得位置不太对啊这也不是第四个DWORD啊而且403184在哪啊看不到啊)
既然我们已经知道了对应IAT的起始地址,那就跳到403184看看
这样我们就到达了IAT,导入表的结束地址为403670(根据起始地址和长度计算得到)。导入表中的每个IID项指明了DLL的名称以及其对应IAT项的起始地址。紧凑的排列在一起,供操作系统使用。
大量实验表明,IAT并不一定位于在导入表中。IAT可以位于程序中任何具有写权限的地方,只要当可执行程序运行起来时,操作系统可以定位到这些IID项,然后根据IAT中标明的API函数名称获取到函数地址即可。
下面我们来总结一下操作系统填充IAT的具体步骤:
1:定位导入表
2:解析第一个IID项,根据IID中的第4个字段定位DLL的名称
3:根据IID项的第5个字段DLL对应的IAT项的起始地址
4:根据IAT中的指针定位到相应API函数名称字符串
5:通过GetProcAddress获取API函数的地址并填充到IAT中
6:当定位到的IAT项为零的时候表示该DLL的API函数地址获取完毕了,接着继续解析第二个IID,重复上面的步骤。
下面我们来手工的体验一下这个步骤:
1)定位导入表
2)定位到导入表的起始地址
3)根据第一个IID项中的第四个字段得到DLL名称字符串的指针,这里指向的是USER32.DLL
根据第五个字段的内容定位到IAT项的起始地址,这里是403184,我们定位到该地址处。
这里我们可以看到已经被填充了正确的API函数的入口地址,跟我们dump出来的结果一样,我们再来看看相应的可执行文件偏移处的内容是什么。
这里我们可以看到第一个API函数的名称位于4032CC地址处,我们定位到该地址处。
第一个API函数是KillTimer,我们在OD中看到的KillTimer的入口地址是操作系统调用GetProcAddress获取到的。
这里我们可以看到KillTimer的入口地址为75EC79DB。该地址将被填充到IAT相应单元中去覆盖原来的值,我们看到已经被覆盖了。
这里是IAT中的第一元素。
我们再来看下一个元素,向后偏移4就是,是2f 7d ec 75这里,来看一看该API函数名称字符串的指针是多少。
定位到可执行文件的相应偏移处:
32D8即4032D8,来看看该API函数的名称是什么,这里由于该指针不为零,说明该API函数还是位于USER32.DLL中的。
这里我们可以看到第二个API函数是GetSystemMetrics,通过该函数名称可以通过GetProcAddress获取到其函数地址然后填充到IAT中。接下来按照以上步骤依次获取USER32.DLL中的其他的函数地址,直到遇到的IAT项为零为止。我们来看一看可执行文件中结束项位于哪里。
我们可以看到当IAT中元素为零的时候表明USER32.DLL就搜索完毕了,我们接着来看下一个IID。
这里我们根据第4,5字段分别可以知道第二个DLL的名称,以及对应IAT项的起始地址。
DLL的名称字符串位于40329B地址处。
我们可以看到第二个DLL为KERNEL32.DLL,该DLL对应的IAT项起始地址为40321C。
这里我们可以看到前一个DWORD是零,表示USER32.DLL的API函数的结尾。40321C表示KERNEL32.DLL的API函数地址项的开始。
根据这些指针我们就可以定位到kernel32.dll中的各个API函数名称字符串,进而获取到其函数入口地址,接着填充到对应的IAT项中覆盖原来的内容。
本章这里就结束了,我给大家描述了IAT被填充的整个过程,了解这个过程对大家来说是很有必要的,这部分内容是重建IAT必备的基础知识,大家只有理解了其基本原理,然后配上适当的工具,就可以方便进行IAT的修复工作了。