第三十三章-神马是IAT,如何修复在介绍如何修复IAT之前,我们首先来介绍一下IAT的相关基本概念,本章的实验对象依然是Cruehead的CrackMe。首先我们来定位该程序的IAT位于何处,然后再来看看对其加了UPX的壳后,IAT又位于何处。什么是IAT:我们知道每个API函数在对应的进程空间中都有其相应的入口地址,例如:我们用OD加载Cruehead的CrackMe,在命令栏中输入?MessageBoxA大家可以看到在我的机器上,MessageBoxA这个API函数的地址为77D504EA,如果大家在自己的机器上面定位到这个地址的话,可能有部分人的机器上该地址对应的还是MessageBoxA的入口地址,而另外一部分人的机器上该地址对应的就不是MesageBoxA的入口地址了,这取决于大家机器的操作系统版本,以及打补丁的情况。众所周知,操作系统动态库版本的更新,其包含的API函数入口地址通常也会改变。比如User32.dll我们就拿Cruehead的CrackMe中的MessageBoxA这个API函数来说吧,其入口地址为77D504EA,在我的机器上运行的很好,那些跟我操作系统版本以及User32.dll版本相同的童鞋的机器上该程序运行可能也很正常,但是如果在操作系统版本或者User32.dll的版本跟我的不同童鞋的机器上运行,可能就会出错。为了解决以上兼容问题,操作系统就必须提供一些措施来确保该CrackMe可以在其他版本的Windows操作系统,以及DLL版本下也能正常运行。这时IAT(ImportAddressTable:输入函数地址表)就应运而生了。大家不要觉得其名字很霸气,就会问是不是很难?其实不然。接下来我们一起来探讨一下如何在脱壳过程中定位IAT。我们现在通过在反汇编窗口中单击鼠标右键选择-Searchfor-Allintermodularcalls来看看主模块中调用了哪些模块以及API函数。这里我们可以看到有几处调用了MessageBoxA,我们在第一个MessageBoxA调用处双击鼠标左键。反汇编窗口就会马上定位到该调用处,OD提示窗口中显示其实际调用的是40143A处的JMP.&USER32.MessageBoxA,这里用尖括号括起来了,说明这里是直接调用,而非间接调用。这里其实就是CALL40143A,显示为CALLJMP.&USER32.MessageBoxA大家可能会觉得不太直观。这里我们打开Debuggingoptions菜单项:Disasm标签页中的Showsysmbolicaddresses选项被勾选上了,如果我们去掉该对勾,将不会显示函数地址。我们可以看到右边的注释窗口中同样显示了API函数的参数以及函数名称,比刚刚显示符号地址看起来更直观,一眼就可以看出是一个直接调用。CALL40143A在Searchfor-Allintermodularcalls窗口中显示如下:我们可以看到这里有三处通过CALL40143A调用MessageBoxA,我们定位到40143A处看看是什么。这里我们可以看到是一个间接跳转。即JMP[4031AC]这里我们再次勾选上显示符号地址的选项,可以更加直观的看出其调用的API函数。这里有意思的地方就来了,我们看到JMP[4031AC](4031AC这个内存单元中保存的数值才是MessageBoxA真正的入口地址)。我们还可以看到很多类似的间接JMP。这就是为了解决各操作系统之间的兼容问题而设计的,当程序需要调用某个API函数的时候,都是通过一个间接跳转来调用的,读取某个地址中保存的API函数地址,然后调用之。我们现在在数据窗口中定位到4031AC地址处,看看该内存单元中存放的是什么。这里我们可以看到,4031AC中保存的是77D504EA,这一片区域包含了该程序调用的所有API函数的入口地址,这块区域我们称之为IAT(导入函数地址表),这里就是解决不同版本操作系统间调用API兼容问题的关键所在,该程序在不同版本操作系统上都是调用间接跳转到IAT表中,在IAT中读取到真正的API函数入口地址,然后调用之,所以说只需要将不同系统中的API函数地址填充到IAT中,这样就可以确保不同版本系统调用的都是正确的API函数。有些人可能会问,4031AC这个地址在不同机器上也可能会不同的吧?呵呵,这个问题提的非常好,我们一起来看看操作系统将正确的API函数入口地址填充到IAT中的具体原理,大家就会明白了。这里我们选中4031AC中保存的内容,单击鼠标右键选择-Viewexecutablefile,就能看到4031AC这个虚拟地址对应于可执行文件中的文件偏移是多少了。我们可以看到在可执行文件对应文件偏移处中的内容为60330000,当程序运行起来的时候,0FAC这个文件偏移对应的虚拟地址处就会被填充为EA04D577,也就是说该CrackMe进程空间中的4031AC地址处会被填入正确的API函数地址。有这么神奇?Windows操作系统当可执行文件被加载到进程所在内存空间中时,会将正确的API函数地址填充到IAT中,这里就是4031AC中被填入了MessageBoxA的入口地址,其他IAT项也会被填入对应的API函数地址。其实操作系统并没有大家想象得的那么神奇,我们看到0FAC文件偏移处的值3360,该数值其实是RVA(相对虚拟地址),其指向对应的API函数名称。这里3360加上映像基址即403360,我们定位到403360处,看看是什么。这里我们可以看到指向的是MessageBoxA这个字符串,也就是说操作系统可以根据这个指针,定位到相应的API函数名称,然后通过调用GetProcAddress获取对应API函数的地址,然后将该地址填充到IAT中,覆盖原来的3360。这样就能保证在程序执行前,IAT中被填充了正确的API函数地址。如果我们换一台机器,定位到4031AC处,可能会看到里面存放着不同的地址。JMP[4031AC]这样就能够调用MessageBoxA了,大家可能会觉得这个过程很复杂,其实填充IAT的过程都是操作系统帮我们完成的,在程序开始执行前,IAT已经被填入了正确的API函数地址。也就是说,为了确保操作系统将正确的API函数地址填充到IAT中,应该满足一下几点要求:1:可执行文件各IAT项所在的文件偏移处必须是一个指针,指向一个字符串。2:该字符串为API函数的名称。如果这两项满足,就可以确保程序在启动时,操作系统会将正确的API函数地址填充到IAT中(后面会详细介绍操作系统是如何填充IAT的)。假如,我们当前位于被加壳程序的OEP处,我们接下来可以将程序dump出来,但是在dump之前我们必须修复IAT,为什么要修复IAT呢?难道壳将IAT破坏了吗?对,的确是这样,壳压根不需要原程序的IAT,因为被加壳程序首先会执行解密例程,读取IAT中所需要的API的名称指针,然后定位到API函数地址,将其填入到IAT中,这个时候,IAT中已经被填充了正确的API函数地址,对应的API函数名称的字符串已经不需要了,可以清除掉。大部分的壳会将API函数名称对应的字符串以密文的形式保存到某个地址处,让Cracker们不能那么容易找到它们。下面我们来看看CrackMeUPX这个程序,在dump之前我们需要修复IAT。我们定位到4031AC处-原程序MessageBoxA入口地址的存放处。是空的,那么403360指向的字符串呢?也是空的,我们跟到OEP处,再来看看这几个地址处有没有内容,我们知道原程序在运行之前,IAT必须被填充上正确的API函数地址。JMP[4031AC]如果此时IAT还是空的话,那么程序运行起来就会出错,我们现在定位到OEP。我们在这个JMPOEP指令处设置一个断点,运行起来,接着来看看IAT:我们可以看到壳的解密例程已经将正确的API函数地址填充到原程序的IAT中,如果这个时候我们将程序dump出来的话,运行会出错,因为dump出来的程序启动所必须的数据是不完整的。我们现在来看看各个API函数名称,定位到403360处,会发现是空的。现在我们dump出来看看,dump出来的原程序代码肯定是正确的,但是程序仍然无法正常运行,因为缺少数据,操作系统无法填充IAT。Dump的话我们需要用到一个工具,名字叫做LordPE(PS:大家应该用的很多吧)。我们运行LordPE,定位到需要dump的CRACKMEUPX所在的进程,当前该进程处于OEP处。选中CRACKMEUPX所在的进程。我们单击鼠标右键选择-activedumpengine-IntelliDump-Select!。接着选择dumpfull。我们将dump出来的程序命名为dumped.exe。如果我们直接运行dumped.exe的话会发现无法启动,尝试用OD加载dumped.exe,OD会报错,我们来看看日志窗口中的错误信息。这里我们机器上提示错误发现在7C929913地址处,我们定位到该地址(大家可以根据自己机器上显示的错误地址自行定位)。这里我们可以给这一行设置一个硬件执行断点或者INT3断点,即当断在这一行时看看错误发生之前是什么状况。我们运行起来,会发现没有断在这一行,这是因为勾选了忽略异常选项的缘故,这里我们去掉忽略异常选项的对勾,重新运行起来。断了下来,我们可以看到该错误是在到达入口点之前产生的,所以dumped.exe无法正常运行,我们现在来看看IAT的情况。我们可以看到当前虽然在我的机器上各个API函数的地址被填充到IAT中,但是想要正常运行在其他机器上的话,必须要指向各个API函数名称字符串的指针,这样才是确保操作系统能够通过GetProcAddress获取到正确的API函数地址并填充到IAT中。这里该dumped.exe缺少这些指向API函数名称字符串的指针,所以运行的时候会发生错误。这里大家不要尝试先dump出来,然后再恢复各个API函数的名称字符串以及其指针,如果这样手工修复的话,是一件极其困难的工作,你需要将4031AC地址处的内容修改为MessageBoxA这个字符串的指针,IAT中的其他项也要进行相应的处理。比较明智的做法是,dump出来之前就将IAT修复了。我们知道dump出来的代码肯定是正确的,我们定位到401000处看一看。我们看到API函数的调用处,40135C地址处应该是调用的MessageBoxA。我们定位到40143A处,这里依然是通过一个间接跳转。这些间接跳转是无法正常运行的,因为在正常情况,操作系统必须知道指向各个API函数名称字符串的指针,然后通过GetProcAddress定位到各个API函数正确的入口地址并填充到IAT中,这样这些间接跳转才能起作用。下面我们来看看未加壳程序的IAT。我们用OD加载Cruehead的CrackMe。我们来定位该CrackMePE结构中一些重要字段。首先在数据窗口中定位400000地址处。单击鼠标右键选择-Special-PEheader切换到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。这里选中的部分为导