第三十七章-论IAT重定向之修复 本章我将给大家介绍几种常见的修复IAT重定向的方法。 我们只是介绍修复IAT重定向的基本思路,有些方法可能对有个壳有效,对有的壳无效,这个就需要大家多加练习,举一反三,触类旁通了。 我们用OD加载UnPackMe_tElock0.98,按照上一章节介绍的方法定位到其OEP,这里就不再赘述了。IAT中有部分元素被重定向到壳创建的区段中去了。 这些重定向过的IAT项我们需要修复。 我们首先单步到第一个API函数GetVersion的调用处。 在数据窗口中定位到该IAT项 460ADC,这里460ADC保存的内容是9F06F7,我们打开IMP REC,填充上IAT起始地址的RVA,大小以及OEP的RVA后,看一看无效的项。 我们可以看到9F06F7这一项是无效的。 RVA 60ADC对应的IAT项正好是460ADC。 我们回到OD中,上一章我们已经介绍过了如何定位GetVersion这个API函数的入口地址,其实OD有一个自动单步跟踪的功能,我们用这个自动单步跟踪的功能可以方便定位到重定向过的IAT项对应的API函数入口地址,重定向过的IAT项单步步入5,6行就可以定位到其对应的API函数。 我们在4271D6这条CALL指令的返回地址处设置一个断点,这里我们缺少一步,由于OD的自动跟踪功能是不会主动停止的,我们应该设置自动跟踪停止的条件。 我们选择主菜单中Debug-Set condition,设置跟踪停止的条件。 我们可以看到弹出了一个对话框,对话框的标题为Conditionto pause run trace(设置自动跟踪终止的条件)。 如果设置了EIP is in range(EIP的范围)这个选项的话,当EIP位于这个范围的时候,OD就会停止自动跟踪。 例如: 例如,这里我们将这个范围指定为401000~ 500000,那么OD自动跟踪,当EIP在401000 ~ 500000这个范围的时候就会停止自动跟踪,这里我们只是举个例子,并不是真的要将范围指定为401000 ~ 500000,因为我们需要OD自动跟踪停在API函数的入口处,所以我们打开区段列表窗口看看将这个范围指定为什么比较合适。 我们看到区段列表窗口中用浅蓝色标注出来的部分,这些是系统DLL的区段,当EIP位于主程序的区段或者壳创建的区段中,我们让OD自动继续跟踪,OD跟进到DLL中的时候,我们需要OD停止跟踪,所以这里我们将该范围设置为5800000~7FFFFFFF,这样就能确保OD跟进到任意一个系统DLL中的时候就会停止跟踪,这里我并没有精确的设置为58C30000 ~ 7FFFFFFF,大家想设置的精确一点也是可以的。 也就是说OD自动单步跟踪,当EIP位于任意一个系统DLL的区段中的时候就会停止跟踪。 这里我们给4271DC处CALL指令以及其返回地址处分别设置一个断点。 我们将之前定位OEP时设置的内存访问断点清除掉,接着给460ADC处的IAT项设置4字节的内存访问断点,运行起来,当该值被读取的时候,OD就会断下来。 我们现在跟到了4271D6的CALL指令处。 现在可以让OD自动跟踪了。 这里我们在反汇编窗口中单击鼠标右键,有两个自动跟踪的选项,第一个是Trace into(自动单步步入),第二个是Trace over(自动单步步过),这里我们选择-Trace into。 这里满足了停止跟踪的条件,OD停了下来。 当EIP位于58000000~7FFFFFFF这个范围的时候,OD就会断下来,为了保险起见,我们还是需要看一下OD的下方的提示信息,看看是不是由于其他异常原因导致断下来的。OD下面的提示信息显示是由于EIP位于58000000~7FFFFFFF的范围内,满足了停止自动跟踪的条件,所以断了下来。 接下来的工作就是检查调用完该API函数以后是不是返回到我们刚刚设置了断点的返回地址处,因为有些壳会调用多个API函数来混淆视听,前面调用的几个API函数都是用来迷惑我们的,最后一个API函数才是其真正要调用的。 这里我们可以看到堆栈窗口显示的返回地址正好是我们刚刚设置了断点的那个返回地址4271DC。 我们也可以在该返回地址上面单击鼠标右键选择Follow in Disassembler定位反汇编窗口中,也可以看到正好是我们设置了断点的返回地址处。 好了,现在停在了API函数的入口处,但是OD这里并没有提示API函数的名称,我们需要确定调用的是哪个API函数,我们来分析一下DLL的代码,在反汇编窗口中单击鼠标右键选择-Analysis-Analyse code。 这里我们可以OD左下方的解释窗口中看到该API函数的名称为Kernel32.GetVersion,EIP寄存器中也显示了API函数的名称。 这里我们再来看下一个自动跟踪终止的条件选项EIP is outside the range,通过设置这个选项我们也可以定位到其要调用的API函数: (PS:这里原文笔误,作者写错了,作者写成了401000~ 129000,因为是401000 ~ 1272000才对) 这里将EIP is outside the range这个选项的为设置为401000~1272000,即当EIP超出了主程序所在区段的范围就会停止自动跟踪。 大家可以自行尝试。接下来还有一种情况。 有少数壳会将区段创建在系统DLL的区段之间,这样的话,我们需要用到Condition is TRUE(当条件成立时)这个选项了,这里我们搜索EIP指向的指令为RET,并且栈顶指针指向的是4271DC的指令。 这两个条件同时满足我们可以使用&&,表示这两个条件要同时满足,相当于AND。 如果两个条件满足任意一个的话,我们可以使用||,相当于OR。 这里我们将自动跟踪终止条件设置为[ESP] == 4271DC && byte [EIP] == 0C3 [ESP] == 4271DC即栈顶指针指向了返回地址4271DC。 byte [EIP] == 0C3即EIP指向了API函数的返回指令RET。 接着让OD自动跟踪,我们可以看到断在了API函数的返回指令RET处。 由于[ESP] == 4271DC,byte [EIP] == 0C3这两个条件都满足了,所以OD停止了自动跟踪,现在我们位于API函数的返回指令RET处,这里由于我们并不在该API函数的入口地址处,所以OD并不会提示该API函数的名称,因此有些壳会自己模拟执行API函数开头的两,三条指令,然后从API函数的第四,五行开始指令,这样OD也不会提示该API函数的名称,尽管如此,我们还是有办法可以定位该API函数的名称。 我们单击工具栏中的...按钮查看自动跟踪的日志信息。 我们看到红色箭头标注的这一行也没有显示API函数名称,我们分析一下代码,然后在该行上双击鼠标左键。 这里我们可以看到OD中EIP寄存器显示了该API函数的名称,当前EIP的值显示为红色,表示EIP寄存器被修改了,如果我们按减号键的话,EIP会变为前一条执行过的指令的地址。 有些壳会去模拟执行API函数前几条指令,然后再去指令API函数后面的代码,这样OD也不会提示该API函数的名称。 我们来看看如何应对这种情况: 我们在API函数返回指令RET的下一行指令空白处单击空格键,CALL 我们怀疑的API函数入口地址,这里我们输入: CALL 7C8114AB。 我们可以看到此时OD提示API函数的名称为Kernel32.GetVersion。 如果我们CALL不是API函数的入口地址的话,OD就不会显示函数名称了。 我们单击鼠标右键选择-undo selection(撤销刚刚所做的修改)。 好了,我们现在知道如何手工定位API函数的名称了,打开IMP REC,我们需要对重定位过的IAT项进行修复,在60ADC这个无效的IAT项上面双击鼠标左键,在弹出的窗口中选中API函数所在的模块以及其函数名称。 单击确定。 现在我们单击Show Invaild按钮,可以看到60ADC这一项已经被标记为有效了,我们要的就是这个效果。 另一种修复的方式就是直接在重定向过的IAT项中填充上正确的API函数地址。 这里我们在460ADC这一项上填入GetVersion函数的地址,我的机器上该函数的入口地址为7C8114AB。 现在我们来到IMP REC窗口中,单击Clear Imports按钮。然后再单击GetImports按钮,可以看到60ADC(RVA)这一项也被标记为了有效。 对于其他的重定向过的IAT项我们也可以按照上述方法来进行修复,接着就可以修复dump文件了。这种方法显得比较枯燥,纯属体力活。 (PS:大家想干这种体力活吗,不管你们想不想,反正我不想,嘿嘿) IMP REC提示了一个功能可以协助我们完成这个手工修复工作。通过单击Save Tree按钮可以保存我们修复过的IAT项,当我们下次想继续修复其他项的时候可以通过单击Load Tree按钮导入我们之前保存的IAT信息,这样就可以继续之前没有完成的修复工作了。 接下来的一种修复重定向的IAT项的方法就是借助于IMPREC的插件tELock1,我们将其放到IMPREC目录下的Plugin文件下。 下来我们来看看这个插件怎么用。 将插件的DLL拷贝到IMP REC中Plugin文件夹中。 现在我们重启IMP REC,输入OEP,RVA,SIZE等数据,单击Get Imports,接着单击ShowInvaild,在无效的项上面单击鼠标右键选择-Plugin Tracers-tElock1。这样该插件就会帮我们修复这些重定向的IAT项,我们可以看到日志窗口提示除了4项以外,其他重定向的IAT项都被修复了,这个插件不是很爽,帮我们完成大部分的重定向项的修复工作,剩下少数几个重定向过IAT项我们可以自己手工修复。 这里IMP REC提示4项未修复的,大家可以按照前面介绍的方法进行手工修复,这种插件的修复方式有明显的局限性,只能针对于特定的壳。 IMP REC还自带有常规的Tracers,我们可以试试看其能不能修复这些重定向过的IAT项,大多数情况,通常它也可以替代我们完成手工修复任务。 这里我们在第一个修复失败的项上面单击鼠标右键,我们可以看到3个等级,这里我们选择-Trace Leve1(Disasm),看看等级1能不能起作用。 失败了,我们继续尝试Level2,Level3,我们会发现这两项也不起作用,IMP REC死掉了,看来这几个选项只能对比较简单的壳起作用。 接下来的这种修复重定向的方法叫做关键跳法,这种方法在很多脱壳教程中都有用到。 这种方法就是定位壳填充IAT的时机,看看何时填充正确IAT项,何时填充重定向过的IAT项。 举个例子:就拿GetVersion这一项来说吧,我们重启UnPackMe-tElock0.98。 此时我们还没有到达OEP处,460ADC中的值为3D830870,在壳的解密例程执行过程中会将重定向过的值9F06F7填充到460ADC这个内存单元中。 为了能够定位何时该IAT项被写入,我们可以对460ADC设置硬件写入断点,但是由于该壳会检测硬件断点,所以这里我们设置内存写入断点,让壳在此处写入重定向过的IAT值的时候断下来。 运行起来,我们可以看到断在了这里。 这里并不是我们要定位的点,因为我们按F8键执行这一行会发现重定向过IAT值并没有被写进来,我们继续运行。 我们多运行几次,断在了这里: 这里也不是写入重定向过的IAT值,我们继续运行。 我们按F8键执行这行指令,发现也不是保存重定向过的IAT值,继续运行。 断在了这里,这里将写入一个无效的值,但是并不是我们要定位的重定向过的IAT值9F06F7。 我们执行REP MOVS指令,该无效的值被写入到该IAT项中,我们继续运行。 我们断在了这里,我们可以看到当前ECX寄存器的值正好等于重定向过的IAT值9F06F7。 ECX = 9F06F7,将被保存到EAX = 460ADC指向的IAT中,这里就是我们要定位的地方。 我们按下F8键,9F06F7就会被保存到460ADC中,这只是第一步,接下来我们需要定位关键跳转。 我们往下跟几步,达到了46651A处JE指令处,我们看看寄存器窗口中EBX寄存器的值,当前EBX正好指向了GetVersion的函数名称字符串。 继续往下我们可以看到会调用GetProcAddress获取GetVersion这个API函数的地址。 我们可以在堆栈窗口中看到参数情况,按F8键执行这个CALL。 我们可以看到此时EAX保存了GetVersion这个API函数的地址,我们继续往下跟。 这里有一个条件跳转,这个条件跳转是成立的,我们不跟过去,直接单击鼠标右键选择-Follow。 这里我们可以看到GetVersion的函数地址并不是被填充到IAT中,而是被写入到EDI指向的内存单元中,当前EDI为: 保存了该API函数的地址以后,紧接着我们到了JMP指令处。 这里通过这个JMP我们又将回到这个过程的开始处,这里是一个循环,我们单击鼠标右键选择-Follow。 这里接下来又要将第二个重定向过的IAT值填充的下一个IAT项中,然后通过GetProcAddress获取对应的API函数地址,并将获取到的API函数地址保存到某处,接着又是第三个重定向过的IAT值,循环往复,直到所有IAT项都被处理完毕为止。 由于壳只是对一部分IAT项进行重定向,所以下面我们来看看壳对IAT项重定向以及不重定向流程的差异。 IAT是如何被重定向的 这里是一个小循环,继续往下: 这里是调用GetProcAddress,接着就会到保存GetProcAddress获取到的API函数地址处。 以上就是跟踪一个IAT项被重定向的完成流程,其中有很多的条件跳转。现在我们跟到OEP处,定位到一个正确的IAT项,比较一下两者有什么差别。 这里我们选择460BAC这一项。重新启动程序。 对460BAC这4个字节设置内存写入断点,运行起来,看看壳何时会将正确的IAT项填充进来。 断在了这里,正确的IAT值将被写入。 我们可以看到此时EAX寄存器中可能包含的是一个API函数的入口地址,虽然这里OD没有提示该API函数的名称,但是我们可以Goto过去看一看,我们CTRL + G输入EAX。 我们到了这里,我们可以看到寄存器窗口中EAX显示处了该API函数的名称,嘿嘿。 接下来的任务就是需要将IAT项被重定向的流程修改为正确IAT项的处理流程。 让所有IAT项走正常流程 我们依然从循环的起始位置开始跟。 这里跟IAT项重定向的流程是一致的。 这里只是一个小跳转,我们继续跟。 这个跳转不成立。 这里跟之前IAT项被重定位的处理有明显区别,跳过的中间的大片代码。 我们可以看到这里是一个长跳转,跳转与否取决于是IAT项是否被重定向,也就是说这里是决定是IAT项是否被重定向的关键跳,我们可以将这个条件跳转替换为JMP。 但是这个跳转在程序一开始的时候并不存在,我们重启程序。 我们可以看到在程序刚开始的时候,这里都是一些垃圾指令,壳会随后的某个时间点写入实际的功能代码。 我们转到数据窗口,由于硬件断点会被检测,INT3断点这里不能用,所以我们可以使用内存写入断点,我们对460818~460f28这片IAT区域设置内存写入断点。 这里我们对所有的IAT项都设置上了内存写入断点,当第一个IAT项被写入的时候就会断下来。 断在了这里,这里的STOS指令会执行多次,直到整个IAT都被填充完毕为止,此时关键跳已经存在了。 这里我们可以看到关键跳已经生成了,我们将该关键跳转JE替换成JMP,接着清除之前设置的内存断点,接着跟踪到OEP处。 这里有两种可能,一种就是壳有自校验,运行起来会失败。另一种就是运行很正常,这里我们顺利的跟踪到了OEP处。 现在到了OEP处,我们再来看看IAT: 我们可以看到所有的IAT项都是正常的,我们成功修复了IAT,现在我们可以进行dump了。 这里我们不勾选ReBuild Import,dump出来。 接着打开IMP REC,填入OEP,RVA,SIZE等数据,单击Get Imports。 我们可以看到所有的项都是有效的,说明刚刚修改的关键跳起作用了,接着单击Fix dump修复刚刚的dump文件。 修复过的dump文件被重命名为了dumped_.exe,我们运行一下看看效果。 嘿嘿,完美运行。 不同壳关键跳的地方也不一样,但是我们细心对比正常IAT项与重定向过的IAT项的处理流程差异,总能找到一点蛛丝马迹,好了,本章就讨论到这里,下一章我们继续讨论别的壳。
本系列文章汉化版转载看雪论坛
感谢原作者:RicardoNarvaja(西班牙人)
感谢热心翻译的朋友: 1~3章译者:BGCoder 4~58章译者:安于此生
全集配套程序下载地址:
|