我以VMP3.85的版本示例来演示另外一种修复的方法,这里因为我们只需要修复IAT,所以在资源加密上我选择了否。
1.png (59.98 KB, 下载次数: 0)
下载附件
2024-1-11 13:06 上传
VMP3.8以下的版本,在API调用的时候一般是以lea reg,dword ptr ds:[reg+ 0xx]这种形式调用,但在3.85的版本上可以看到在API的调用上,
lea reg,dword ptr ds:[reg+reg+0xxxx],显然,在3.8的版本上API的调用也带来了一些变化。
但这并不影响,在图下API的调用上,会有reg+reg调用,这里我回退看了下两个寄存器的值
mov edi,0x8438AD1A
mov eax,dword ptr ds:[eax+edi+0x7BC752E6]
lea edi,dword ptr ds:[edi*2+0x97BCCB3C]
Eax是以内存的形式存储,edi的值则是以一种固定的形式计算。
2.png (160.67 KB, 下载次数: 0)
下载附件
2024-1-11 13:07 上传
思路打开:那么现在可以推断出,VMP首先会调用自建的GetProcAddress来获取表里的API地址然后经过计算得出Reg的存储在内存的值,
在调用时就会取出这个内存中的值经过在vm代码里面的计算得出API地址,那么现在我们抛开一切不必要的因素找到重点:找到它计算后的内存值,和对应的API地址,然后我们构建一段汇编代码模拟vm的计算方式来计算出内存值填充进去。
上面图中的eax值是从vmp段里获取的,对地址下硬件写入断点,运行几次在出现到内存值时如下图,Eax为计算后的内存值,ecx为写入值的内存地址,esp+10为对应的API地址。
3.png (411.95 KB, 下载次数: 0)
下载附件
2024-1-11 13:09 上传
脚本构建思路,现在得到了计算后的值和对应的API地址,那么我们要获取到那个被减值,也就是用来计算内存值的那个固定值,
用脚本构建的话,获取到被减值,内存写入地址,API名,和对应的模块名,如下图,为了方便快捷,我选择了友好的易语言,当然脚本应该会更快,获取方法如下图。
4.png (73.22 KB, 下载次数: 0)
下载附件
2024-1-11 13:10 上传
在就是修复了,有了这些信息,完全可以构建汇编来模拟vmp的计算方式。
为了快捷方便,我选择了友好的易语言来构建dll。
首先我们得到了计算值,计算值大部分会产生借位问题,这里只需要保留低八位即可,因为在lea的时候越位也会舍去高位,这也是vmp的一个精妙设计之一。
有了被计算值,有了模块名和对应的函数名,那么在写dll时就可以这样:通过函数名和模块名获取到对应的函数地址,然后通过函数地址减去固定的被减值,
得到新的内存值,然后写入对应的内存地址,最后绑定dll导出函数,在运行OEP之前写入内存值,这样无论在任意机器上我们就仍然可以保证vmp在vm里调用API地址时一直都是正确的API了。
dll代码如下图
5.png (21.41 KB, 下载次数: 0)
下载附件
2024-1-11 13:11 上传
还有一点,vmp在对Getversion和GetversionEx特殊照顾,他是直接保留在内存里,在脱壳时可以在vm段里扫到这两个地址然后重建出来。
我经过了几个版本的脱壳测试,这种修复方式还算是比较稳定高效的。
题外话:在vmp脱壳时iat修复其实算的上是比较小的一个问题。
资源的修复,vmp的vm校验,这些比较难,当然,其实深入一点也不是很难。
在资源修复上补全PEB.heap,HeapCreate两段内存,找到vmphook的函数,然后照着挂钩上即可。
至于vm函数里的校验,补版本的低四位和cpuid即可,当然,我选择更好的方式就是找到cpuid的算法,然后用算法计算出hash写到校验内存里,这样所有的vm函数都能在脱壳时过掉,而不需要补所有的cpuid。
最后就是vmp的授权,vmp的授权在没加sdk时可以直接至0过掉,这个地方是比较简单的,最难的一点是vmp在vm函数时有个锁定到序列号,这个校验点除了Patch机器码和keygen之外,我发现了另外一直方法,只需要补全它的ProductCode即可。