这章的内容主要是bitarts 5.0和telock0.98两款壳。
bitarts 5.0
先将bitarts5.0拖进OD,可以看到和之前的两个壳不一样,并不是先pushad保存寄存器环境,我们F7往下走。
走到PUAHAD处,按下F7,采用ESP定律等方法都不可,应该是和操作系统有关系,所以拖到了win7里面调试,采用OD自带的查找OEP的方式,断在了OEP处,但是需要修复一下数据:analysis-> analysis code
看一下这个函数属于的dll,在数据区段不同的dll函数用00隔开
直接拉到底部标红的地方是最后一个IAT项,右键find references
后面的内容就不是IAT的内容了(可以右键find reference试一下,是空的),所以IAT结束地址是460F28,往上拉我们可以看到80000008这样的值,明显不属于任何一个dll,右键find referenc也没有,所以IAT起始地址是460818
IMP REC需要的三条数据我们都有了:
OEP = 271B0(RVA)
IAT起始地址 = 60818(RVA)
IAT 大小 = 710
有一些垃圾数据,我们直接show invalid-右键cut thunk,最后成功
telock0.98
照常用OD自带的功能找到OEP处,程序其实和前面的一样,只是壳不一样。先看看程序调用了那些API函数,右键选择-Search for-All intermodular calls。
可以看到都被重定向到了其他段,这些间接CALL并不是去调用系统DLL的中API函数,而是转向了2Axxxx这类地址的一个区段
其中有一部分是直接调用API函数的,我们选择其中一个跟随,然后查看数据窗口,拉到最下面可以看到IAT的结束地址为460F28,往上看可以看到一些不一样的数据
这些就是IAT中重定向的一些项,当程序运行起来时,壳的解密例程会覆盖掉IAT中的某些项,将这些项重定向到解密例程中,我们拿4038A6这处为例,我们在前面程序知道这里是GetVersion,但是目前不知道,所以跟进去看看是什么
该地址不属于源程序区段,如果我们重启OD断在入口点处时,会发现这个时候该区段并不存在。**因此,这个内存块是在壳解密例程运行过程中创建的,**我们来看看它是什么时候被创建的。
这里我们可以在区段列表窗口中看看刚刚创建的这个内存块,可以看到该内存块被标记为了Priv(私有的),也就是说该内存块是壳自己创建的。
往下跟几步,可以看到这里的PUSH指令将GetVersion的地址压入到堆栈中,接着RET将会返回到GetVersion的入口处,这样就可以达到间接调用API函数的目的。
也就是说,壳会将GetVersion的IAT项替换成自己创建的内存单元中的地址,起到了一个重定向的作用。因此我们在定位IAT的起始和结束位置的时候,不仅仅要判断是否为系统DLL中的地址,还是需要判断其是否为重定向过的地址。
下面我们继续来定位IAT的起始和结束位置。往上看可以看到80000000,明显不属于任何一个内存单元。,并且前面的0006xxxx右键find referrence并没有东西。
所以IAT的起始地址为460818,长度为710,OEP为4271B0。
OEP = 271B0(RVA)
IAT起始地址 = 60818(RVA)
IAT长度 = 710
可以看到有无效的IAT项,接下来进行修复
修复
第一种是手动修复(非常麻烦),第二种是可以采用Imrpec的插件tElock1,这种方法会有一些遗留未修复,并且只针对特定的壳,第三种方法是imprec自带的功能(右键trace level123),这几个选项只能对比较简单的壳起作用。
还有一种方法叫关键跳法,这种方法就是定位壳填充IAT的时机,看看何时填充正确IAT项,何时填充重定向过的IAT项。
我们重新加载程序,首先定位到第一个函数Getversion,460ADC中的值为3D830870,在壳的解密例程执行过程中会将重定向过的值2706F7填充到460ADC这个内存单元中。为了能够定位何时该IAT项被写入,我们可以对460ADC设置内存写入断点,让壳在此处写入重定向过的IAT值的时候断下来。
发现断在了这里。这里并不是是我们要定位的点,因为我们按F8键执行这一行会发现重定向过IAT值并没有被写进来,我们继续运行。过程中虽然460ADC处的值有变化,但是都不是我们想要的值2706F7
我们可以看到当前ECX寄存器的值正好等于重定向过的IAT值2706F7。ECX = 2706F7,将被保存到EAX = 460ADC指向的IAT中,这里就是我们要定位的地方。我们按下F8键,2706F7就会被保存到460ADC中,这只是第一步,接下来我们需要定位关键跳转。
达到了46651A处JE指令处,我们看看寄存器窗口中EBX寄存器的值,当前EBX正好指向了GetVersion的函数名称字符串。继续往下我们可以看到会调用GetProcAddress获取GetVersion这个API函数的地址。
F8执行后,我们可以看到此时EAX保存了GetVersion这个API函数的地址。
(下面的图是我自己的忘记保存了,直接截取了教程的)
继续往下我们可以看到会调用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项的处理流程。
我们在460BAC处设置断点(因为这不是重定向),对460BAC这4个字节设置内存写入断点,运行起来,看看壳何时会将正确的IAT项填充进来。断在了下面这里,正确的IAT值将被写入。
我们从循环的起始位置开始跟,到这里跟之前IAT项被重定位的处理有明显区别,跳过的中间的大片代码。
可以看到这里是一个长跳转,跳转与否取决于是IAT项是否被重定向,也就是说这里是决定是IAT项是否被重定向的关键跳,我们可以将这个条件跳转替换为JMP。
但是这个跳转在程序一开始的时候并不存在,我们重启程序。我们可以看到在程序刚开始的时候,这里都是一些垃圾指令,壳会随后的某个时间点写入实际的功能代码。
我们对460818~460f28这片IAT区域设置内存写入断点。这里我们对所有的IAT项都设置上了内存写入断点,当第一个IAT项被写入的时候就会断下来。
断下来后继续执行,直到整个IAT都被填充完毕为止,此时关键跳已经存在了。我们将该关键跳转JE替换成JMP,接着清除之前设置的内存断点,接着跟踪到OEP处。
到了OEP后的IAT:
我们可以看到所有的IAT项都是正常的,我们成功修复了IAT,现在我们可以进行dump了。dump之后再用Imprec即可