浅谈VMProtect 2.13.8 IAT修复

查看 278|回复 9
作者:ICEY   
前言:
这篇文章是《浅谈壳IAT修复》系列的最终章了,这段时间应该都不会再出这个系列了。所以我尽量写的清晰一点,详细一点。至于为什么是最终章,因为我最近真的太累了,放假这么久还没放松过,而且,我也没有再出下去的动力了。主要是没人看。唉。
往期回顾:1、https://www.52pojie.cn/thread-1344654-1-1.html2、https://www.52pojie.cn/thread-1359928-1-1.html
那我们开始吧。加密方式以及版本:


加密选项.png (172.65 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

环境:XP SP3
正片:
一、 壳到OEP的大致流程(只列出关键)


壳到OEP的大致流程.png (40.31 KB, 下载次数: 2)
下载附件
2021-2-3 17:32 上传

首先,载入OD后,先停在的VMP1区段。
    接触过VMP的人应该都知道 VirtualProtect 这个函数吧,找OEP用的。说通俗一点这其实就是一个改写 区段访问权限 的函数,VMP壳就是利用这个函数来清除我们在代码段 和 IAT 设置的内存访问断点。只要过了这个函数就可以在代码段设置访问断点了。
另外提一点,我们设置的 硬件访问 和 硬件写入 断点并不会被壳检测到,但是硬件执行断点会被检测到。 知道了 大致流程 和 哪些断点不被检测,我们就可以很轻易地找OEP了。这里给大家提供我找OEP的方法
(仅针对VMP 2.13.8):
先把程序运行起来,然后数据窗口定位到 代码段 的头部,往下拉,找到最后一个有效值(最后一个不是00的值),下一个硬件访问断点,重载程序,F9按两次(一次断在写入时,一次断在干扰时),再在代码段设置内存访问断点。就可以直接断在OEP了。
二、 api调用的分析
1.首先大家应该都知道,程序调用IAT的代码一般是:[Asm] 纯文本查看 复制代码CALL [IAT]    JMP [IAT]    mov e?? , [iat]这三种
以下简称FF15,FF25,MOV型
2.然后,我们要知道,VMP壳对原先的IAT调用改成什么样子了。这里展示三个例子:CALL [IAT]:
加壳前:


加壳前ff15.png (17.47 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

加壳后:


加壳后ff15.png (16.41 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

JMP [IAT]:
加壳前:


加壳前FF25.png (5.96 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

加壳后:


加壳后FF25.png (5.01 KB, 下载次数: 2)
下载附件
2021-2-3 17:31 上传

mov e??,[IAT]
加壳前:


加壳前MOV.png (21.58 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

加壳后:


加壳后MOV.png (17.8 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

可以发现,FF 15 ,FF 25 MOV 型的调用是 6 字节的,但重定向后的E8 是 5 字节的。
这里我们又要补充一个关于VMP对IAT重定向的知识:
VMP在加壳时会直接对代码段进行改写,将 大部分 的IAT调用均重定向到 VMP0 区段进行解密(这里和大部分加密壳不一样,大部分加密壳是对IAT表进行改写)。
又因为重定向的代码为E8,是 5 字节的,因此壳会随机填充一个字节。
填充的位置在 E8 call 的上面或者下面都有可能。
以下是VMP 重定向 和 原来的API调用 对比:


各种函数调用的类型.png (82.26 KB, 下载次数: 0)
下载附件
2021-2-3 17:32 上传

(此处没有 esp )上图我有个地方写错了,MOV eax,才是5字节,在上面那个是6字节
可以发现,关于寄存器EAX的iat调用,和VMP的重定向调用 一样,都是5字节的,也就意味着,对于mov eax,[IAT]这种类型,VMP在加壳时 是没有进行 补码 的。
那我们怎么知道一个被重定向的API,原先是什么,是以哪种方式进行调用的呢?这里我给大家提供一个我原创的方法。
首先我们先要知道,被重定向的API,进入了 VMP0 区段,再经过一系列的运算,得到真实的API的地址,放入堆栈,然后通过 RET 的方法进入真实的API。看图:


如何进入真实API.png (34.6 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

这是重定向CALL ,一直F7就可以到这一步。
这里我们用OD的跟踪步入功能,将条件设置为:



跟踪步入设置条件.png (5.06 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

两个地址自己在区段表查看,每一次加壳后的 地址都不一样,不是固定的。
这个一定要全程开着。脱壳脚本也要依赖这个。
设置好后,我们随便找一个 重定向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前的堆栈:(各个类型都一样这样设置)



全部CALL进CALL前.png (15.66 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

原FF 15,且补码在前:RET到真实API后,堆栈:



FF15 前.png (16.28 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

原FF 15,且补码在后:RET到真实API后,堆栈:



FF 15 后.png (17.02 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

原FF 25,且补码在前:RET到真实API后,堆栈:



FF 25 前.png (15.97 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

原FF 25,且补码在后:RET到真实API后,堆栈:



FF 25后.png (15.61 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

原MOV,且补码在前:RET回代码段后,堆栈:



MOV 前.png (16.43 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

原MOV,且补码在后:RET回代码段后,堆栈:



MOV 后.png (15.58 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

通过区别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:补码在后否则补码在前
例如:我们再用到上面的图。
加壳前:



再次利用1.png (30.1 KB, 下载次数: 0)
下载附件
2021-2-3 22:33 上传

加壳后:



再次利用2.png (27.08 KB, 下载次数: 1)
下载附件
2021-2-3 22:33 上传

我们是因为有未加壳的原程序,才知道他的原型是FF 15,且补码在前。
那么我们假设,我们并不知道它的原型,
那我们来跑一遍刚刚的脚本来判断他的类型。
(判断脚本我会提供,当然了我说了原理,所以你们也可以自己写)
在这个CALL右键点击此处为新EIP,然后运行我写的判断类型脚本。就可以知道他的原型和RET出来之后的地址了。



试用判断.png (17.72 KB, 下载次数: 0)
下载附件
2021-2-3 17:31 上传

以上61B71D就是出RET后的第一句(EIP会停在出RET的第一句,这看来是一个二次重定向啊),然后看信息框,原型是FF15,且补码在前(qian)。
至于为什么用拼音不用中文,因为记事本的编码问题,用中文会乱码。你们自己改判断脚本吧。
三、    修复脚本的逻辑解释
注意上面有说,脚本要依赖 OD 的跟踪步入,记得设置好条件
脚本的大致步骤:
1.从代码段头开始搜索 符合条件的CALL (也就是CALL VMP0区段 的)
2.判断这个CALL的原始类型(上面讲了判断的方法)
3.修复这个IAT调用(通过上面的判断来选择修复方法。改写代码段,使其恢复原本的调用类型)
4.搜索下一个符合条件的CALL(若不存在,跳转到结束)
5.返回 步骤2
(可以去下载脚本看是如何进行代码段的修复)为了大家方便理解我还把脚本写的特别简单,没有什么复杂语句。并且还有一些中文注释便于理解。
以下是一些细节:
1.修复的方式:
修复前:



示例修复前.png (16.91 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

修复后:



修复示例.png (18.24 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

他会将原本的API填入IAT表。顺序是搜索符合条件的顺序。
2.每获得一个API,脚本就会遍历已修复了的API,看看是否有重复的,若无重复,则填入iat下一个地址,若有重复,就不填入IAT,再修改代码段对应代码 指向 重复的地址。
不填入重复的API可以保证填入的API个数不会超过 原定IAT中API的个数,也就是不会溢出,覆盖其他有效数据。
3.对于修复不了重定位,会在C盘输出一个TXT,里面的地址就是需要你手动进行修复的地址。
4.因为OD脚本插件的一些BUG,所以有一些明明符合特征的CALL,有时却没有搜索得到,此时要右键分析代码,再跑一次脚本(又要改一个数据)。
关于这个脚本,并不是直接可以用的,要根据你的程序,特定的数据进行修改。
例如:



条件.png (23.38 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

还有一个特征码(用于寻找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 (小端序程序,以字节为个体从后往前看)
我们就可以得到 特征码:


特征码1.png (11.71 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

  ,,


特征码2.png (11.38 KB, 下载次数: 1)
下载附件
2021-2-3 17:31 上传

,  ,


特征码3.png (2.21 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

,,
合并一下同类项:
E8????2?00
E8????1?00
E8????0F00
E8????0E00
E8????0D00
E8????0C00
E8????0B00
至于为什么不合并E8????0?00,是因为符合这个特征的不仅有CALL VMP0段。更多的是CALL TEXT段。干扰太多,脚本就跑的极慢。
这就是要填入脚本的特征码了,有多少个特征码,就至少要运行多少次脚本。
修改的方法举个例子:
假设你用E8????1?00当特征码修复了若干个函数弹出脚本运行完成。那么改法如下:



脚本用法.png (33.66 KB, 下载次数: 0)
下载附件
2021-2-3 17:32 上传

这个IAT改成下一个要填充的地址,然后再把红框中的特征码改掉。
然后再运行一次脚本。结束后这两个数据又要改。直到你将所有的特征码都试完。
(不过上文我有说,OD跑脚本插件有BUG,可能有一些第一次没被搜索到,所以你可以试着多跑几次脚本。但记得改上图中IAT那个数据。)
(就算是脚本,也要修复挺久的,更别说手动修复了。)
四、    手动修复
上面跑完所有特征码后,就可以将绝大部分的重定向API都修复好,这时打开C盘,看看有没有用脚本修复失败的API,手动修复。
确实有一个:476FB8



修复示例0.png (1.89 KB, 下载次数: 0)
下载附件
2021-2-3 17:32 上传

其实这个我试了很多次,是至少必有一个函数是修复失败的。我们利用我上面提供的判断类型的脚本。可以先知道它的类型。



例外0.png (18.04 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

FF15型,补码在前,RET出来后停在77FE0000。
其实这个呢,就是一个被偷掉开头的函数,从77FE0000,到77FE0003,就是VMP偷走的函数头,下一个JMP就是跳回那个函数的对应行。我们将这个头以二进制形式复制下来,然后跟入下面那个CALL,将头补回去,你就可以知道这个函数是什么了。



得到真实API.png (13.86 KB, 下载次数: 0)
下载附件
2021-2-3 17:32 上传

我们直接手动在 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,会修复错误。)


二次重定向.png (11.63 KB, 下载次数: 2)
下载附件
2021-2-3 17:32 上传

很好找的, 看看数据和其他对比一下大小就知道了。(一般有两个二次重定向)
3.打开UIF,填入PID,代码段的头,代码段的尾,和我们添加的区段的头部地址。勾选修复输入表。然后点击修复。


UIF修复后.png (35.35 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

修复后,排好了顺序
3.用UIF修复完后,我们就可以直接用REC进行跨平台修复了。选择我们加了区段的脱壳版,填好OEP偏移,和UIF修复后的地址偏移(也就是我们新加的区段头地址减去400000)。点获取。


REC填入.png (75.11 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

然后修复转存到 你加了区段的脱壳版 上就可以完美修复了。
XP系统下脱壳修复,WIN10上运行:


win10.png (352.53 KB, 下载次数: 1)
下载附件
2021-2-3 17:32 上传

加壳工具,例子(未加壳版,加壳版,脱壳修复版),脚本(判断类型、修复)打包下载地址:

打包下载.txt
(76 Bytes, 下载次数: 143)
2021-2-3 18:05 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB

ps:如果你在其他论坛也看到此篇文章,看ID,你就知道还是我。
如果你觉得我这篇文章还行,请给我一个免费的
热心值
,求求了,这对我真的很重要!

下载次数, 补码

ICEY
OP
  


初吻给奶嘴耶 发表于 2021-2-3 18:48
emm  有个疑问
vmp3.x 没有遇到 mov reg, [function] 这种被vm  不知道是不是vmp2才有 还是我某个选项没有 ...

不好意思。没有研究过。我水平太低了。
初吻给奶嘴耶   

emm  有个疑问
vmp3.x 没有遇到 mov reg, [function] 这种被vm  不知道是不是vmp2才有 还是我某个选项没有勾选?   
R-R,   

感谢楼主分享这么好的教材
q510   

真是太难了,
初吻给奶嘴耶   


ICEY 发表于 2021-2-3 18:53
不好意思。没有研究过。我水平太低了。

感谢分享  那我去康康
宅の士   

VMP脱壳有些难,我都是基本能不脱壳完成破解或运行时资源替换尽量不脱
普罗米修斯、   

感谢分享
Airey   

感谢分享!!!!
xcvp147   

楼主牛逼
您需要登录后才可以回帖 登录 | 立即注册

返回顶部