(此处没有 esp )
可以发现,关于寄存器EAX的iat调用,和VMP的重定向调用 一样,都是5字节的,也就意味着,对于mov eax,[IAT]这种类型,VMP在加壳时 是没有进行 补码 的。
那我们怎么知道一个被重定向的API,原先是什么,是以哪种方式进行调用的呢?这里我给大家提供一个我原创的方法。
首先我们先要知道,被重定向的API,进入了 VMP0 区段,再经过一系列的运算,得到真实的API的地址,放入堆栈,然后通过 RET 的方法进入真实的API。看图:
这是重定向CALL ,一直F7就可以到这一步。
这里我们用OD的跟踪步入功能,将条件设置为:
两个地址自己在区段表查看,每一次加壳后的 地址都不一样,不是固定的。
这个一定要全程开着。脱壳脚本也要依赖这个。
设置好后,我们随便找一个 重定向CALL,设置为新EIP,然后手动 F7步入,然后点跟踪步入,之后OD就直接停在 出RET的第一句。
区别1:(这里我直接贴结论,你自己去程序试)
先把除了ESP,EBP以外的寄存器全部置0。不改ESP,EBP是防止程序崩溃。
对加壳前FF 15,FF 25 类型的调用,加壳后,通过上面操作,RET出来的第一句就是原API地址。
对加壳前MOV类型的调用,加壳后,通过上面操作,RET出来的第一句是在代码段。API放入寄存器的相应位置。
依照上面的区别,我们可以写出一个初步判断脚本,可以判断出原型是否为MOV型。
区别2:(这里我直接贴结论,你自己去程序试)
我们先将 [ESP]置1,[ESP+4]置2,[ESP+8]置3。(有助于判断)
进CALL前的堆栈:(各个类型都一样这样设置)
原FF 15,且补码在前:RET到真实API后,堆栈:
原FF 15,且补码在后:RET到真实API后,堆栈:
原FF 25,且补码在前:RET到真实API后,堆栈:
原FF 25,且补码在后:RET到真实API后,堆栈:
原MOV,且补码在前:RET回代码段后,堆栈:
原MOV,且补码在后:RET回代码段后,堆栈:
通过区别1和区别2,我们就可以很清楚地知道,这个被重定向的API,原来是什么,是怎么被调用。然后修复是应该从哪个地址开始修改代码。
(若补码在前,则在重定向CALL的地址 -1 处开始修改,若补码在后,则直接从重定向CALL的地址开始修改)
我利用这两个区别,就能写一个完整的判断脚本,判断这个CALL的原型,并且补码在前还是在后。(记得把跟踪步入的条件设置好哦)
结论:
先经过区别1将MOV型和FF15、FF25型分开
若已知不是MOV型:
RET到API,且[ESP]=1:FF25型,补码在后
RET到API,且[ESP+4]=2:FF15型,补码在前
RET到API,且[ESP+4]=1:FF15型,且补码在后
RET到API,且[ESP+4]=3:FF25型,且补码在前
若已知是MOV型:
RET回代码段,且[ESP]=1:补码在后
否则补码在前
例如:我们再用到上面的图。
看上面展示的FF15型的图,重复发不了。
我们是因为有未加壳的原程序,才知道他的原型是FF 15,且补码在前。
那么我们假设,我们并不知道它的原型,
那我们来跑一遍刚刚的脚本来判断他的类型。
(判断脚本我会提供,当然了我说了原理,所以你们也可以自己写)
在这个CALL右键点击此处为新EIP,然后运行我写的判断类型脚本。就可以知道他的原型和RET出来之后的地址了。
以上61B71D就是出RET后的第一句(EIP会停在出RET的第一句,这看来是一个二次重定向啊),然后看信息框,原型是FF15,且补码在前(qian)。
至于为什么用拼音不用中文,因为记事本的编码问题,用中文会乱码。你们自己改判断脚本吧。
三、 修复脚本的逻辑解释
注意上面有说,脚本要依赖 OD 的跟踪步入,记得设置好条件
脚本的大致步骤:
1.从代码段头开始搜索 符合条件的CALL (也就是CALL VMP0区段 的)
2.判断这个CALL的原始类型(上面讲了判断的方法)
3.修复这个IAT调用(通过上面的判断来选择修复方法。改写代码段,使其恢复原本的调用类型)
4.搜索下一个符合条件的CALL(若不存在,跳转到结束)
5.返回 步骤2
以下是一些细节:
1.
修复的方式:
修复前:
修复后:
他会将原本的API填入IAT表。顺序是搜索符合条件的顺序。
2.每获得一个API,脚本就会遍历已修复了的API,看看是否有重复的,若无重复,则填入iat下一个地址,若有重复,就不填入IAT,再修改代码段对应代码 指向 重复的地址。
不填入重复的API可以保证填入的API个数不会超过 原定IAT中API的个数,也就是不会溢出,覆盖其他有效数据。
3.对于修复不了重定位,会在C盘输出一个TXT,里面的地址就是需要你手动进行修复的地址。
4.因为OD脚本插件的一些BUG,所以有一些明明符合特征的CALL,有时却没有搜索得到,此时要右键分析代码,再跑一次脚本(又要改一个数据)。
关于这个脚本,并不是直接可以用的,要根据你的程序,特定的数据进行修改。
例如:
还有一个特征码(用于寻找CALL VMP0段 的地址),这个根据VMP0区段的地址范围来找。首先你要知道一个 e8调用时后面跟的是函数地址相对于当前地址的偏移量。
举个找特征码的例子:
Vmp0 区段的地址是 从 53C000 ~ 615000
那么,你去代码段的头,输入 CALL 615000(VMP0段尾) 得到:
00401000 E8 FB3F2100 call 未加壳_v.00615000
再到代码段的尾(不用真的到尾,接近就行了),输入 CALL 53C000(VMP0段首)得到:
004801E3 E8 18BE0B00 call 未加壳_v.0053C000
这样,你就可以得到两个极限值,也就是说所有的CALL VMP0段的CALL,E8后面的数值肯定 大于 18BE0B00 ,小于 FB3F2100 (小端序程序,以字节为个体从后往前看)
我们就可以得到 特征码:
,,
, ,
,,
合并一下同类项:
E8????2?00
E8????1?00
E8????0F00
E8????0E00
E8????0D00
E8????0C00
E8????0B00
这就是要填入脚本的特征码了,有多少个特征码,就至少要运行多少次脚本。
修改的方法举个例子:
假设你用E8????1?00当特征码修复了若干个函数弹出脚本运行完成。那么改法如下:
这个IAT改成下一个要填充的地址,然后再把红框中的特征码改掉。
然后再运行一次脚本。结束后这两个数据又要改。直到你将所有的特征码都试完。
(不过上文我有说,OD跑脚本插件有BUG,可能有一些第一次没被搜索到,所以你可以试着多跑几次脚本。但记得改上图中IAT那个数据。)
(就算是脚本,也要修复挺久的,更别说手动修复了。)
四、 手动修复
上面跑完所有特征码后,就可以将绝大部分的重定向API都修复好,这时打开C盘,看看有没有用脚本修复失败的API,手动修复。
确实有一个:476FB8
其实这个我试了很多次,是至少必有一个函数是修复失败的。我们利用我上面提供的判断类型的脚本。可以先知道它的类型。
FF15型,补码在前,RET出来后停在77FE0000。
其实这个呢,就是一个被偷掉开头的函数,从77FE0000,到77FE0003,就是VMP偷走的函数头,下一个JMP就是跳回那个函数的对应行。我们将这个头以二进制形式复制下来,然后跟入下面那个CALL,将头补回去,你就可以知道这个函数是什么了。
我们直接手动在 476FB8-1 这个地址修改就行了。记得将API记录下来,手动填到IAT中。
其实干完这一步,我们就已经修复完所有的API了。可以用OD自带的插件DUMP出来一份(保留以下现场),这是可以运行的但是跨平台不能运行。但此时你用REC也修复不了,因为我们修复出来的IAT是乱序的,并不是以一个一个DLL的顺序排列好。
所以我们要先用UIF进行修复(也就是帮我们排序)。不得不说UIF是真的强大。
但是用UIF前,我们先要先进行其他的操作。
用ODdump出来的那一份程序进行修改:
1.将dump出来那一份脱壳版拖进 StudyPE+ x86 ,给它加一个区段,用于存放我们UIF修复的API。(在原有的IAT进行修复可能会出错)
2.再将添加了区段的脱壳版 拖进OD,在我们修复的IAT中找到二次重定位的API,将它更改,直接填入真实的API。(因为UIF识别不了重定向的api,会修复错误。)
3.用UIF修复完后,我们就可以直接用REC进行跨平台修复了。选择我们加了区段的脱壳版,填好OEP偏移,和UIF修复后的地址(也就是我们新加的区段头地址)。点获取。