吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 7171|回复: 38

[原创逆向图文] 简单分析VMProtect V3.3.1

  [复制链接]
李沉舟 发表于 2019-2-14 19:26 | 显示全部楼层 |阅读模式

本来想去某处投稿骗点稿费的,结果那地方帖子一直没审查通过,真烦,还是直接发到论坛里来吧……
前言
       VMProtect3很早就出来了,据说代码使用C++重构了,而且虚拟机架构也有很大的变化。网上关于VMP3.X的帖子不是很多,我这个弱鸡也来上篇文章分析一下。
       文章里面用到了一个解混淆的脚本,附录中我会给出这个破脚本的下载链接及大概原理。
三十二变

2019.2.13
准备工作
先对一段VREY EASY的汇编代码进行加密。如下。

1.jpg
配置VMProtect V3.3.1对@Main过程进行加密。注意将除代码虚拟化以外的保护全部去除勾选。


附注:那个szText变量请无视,我是写完文章才发现这个坑的,已经懒得改了。
2.jpg

3.jpg

编译后,发现文件大小为552KB!意思是说,不包含外壳的代码,只虚拟机部分的代码就膨胀了550KB!不敢想象这是一个怎样的存在,开始我认为是虚拟化的混淆程度又有加强,分析完后才发现原来是虚惊一场……
初探
使用IDA的Trace功能对虚拟机的运行全过程进行记录。发现共执行了1486条语句,这个膨胀率相比与VMP2.X架构可以说是非常小了。加密7条语句,在VMP2.X中光虚拟机指令就可以膨胀到700条左右。

仍然在Trace文件中从头部开始搜索RET指令。如下。
4.jpg
有一个比较令人惊讶的发现,VMP3.X没有采用栈混淆。纵览进入虚拟机的环境备份代码,发现只有简单的针对寄存器的插入死代码。
注意,不完全是以push esi/retn指令对的形式进行转移,还有以jmp esi实现流程转移的情况,事实上前者完全可以看成后者的一个变形。
运行脚本,按要求输入进程ID,起始地址(可以输入Dispatch或Handle的起始地址)。

则会输出解混淆后的代码。如下。
5.jpg
6.jpg
注意,栈中的数据被以DWORD为单位从0开始编号,read/write列表中包含每条指令读取/写入的栈变量序号。
[Asm] 纯文本查看 复制代码
45E756 push0x6dae122f read = [] write = [0]
//此处压入一个加密后的常数,经解码后可以取出指令流地址
4748B1 push esiread = [] write = [2]
4748B5 push ebxread = [] write = [3]
4748B8 push ediread = [] write = [4]
4748BF push edxread = [] write = [5]
4748C7pushfd  read = [] write = [6]
4748C8 push ecxread = [] write = [7]
4748C9 push ebpread = [] write = [8]
4748CA push eaxread = [] write = [9]
456DA6 mov eax,0 read = [] write = []
456DB2 push eaxread = [] write = [10]
//上面这个代码块是在备份虚拟机执行环境,以及重定位信息
456DBA mov ebp,dword ptr [esp + 0x28] read = [0] write = []
456DC2 neg ebpread = [] write = []
456DC4 dec ebpread = [] write = []
456DC9 rol ebp,3 read = [] write = []
456DCC xor ebp,0x3684751d read = [] write = []
456DDC lea ebp,[ebp + 0x5bae11e7] read = [] write = []
456DEA neg ebpread = [] write = []
456DEC add ebp,eax read = [] write = []
//上面这个代码块取出了压入的加密常数,进行一系列解密操作后得到指令流起始地址,并赋值给了ebp,故ebp作为新的指令流寄存器,add ebp,eax是修正重定位操作
456DF1 mov edi,esp read = [] write = []
//edi是虚拟机堆栈
456E0B mov ebx,ebp read = [] write = []
//初始化执行密钥,ebx仍然是密钥寄存器
456E26 lea esi,[0x456e26] read = [] write = []
//esi = 0x456e26
456E33 mov ecx,dword ptr [ebp] read = [] write = []
456E37 add ebp,4 read = [] write = []
456E40 xor ecx,ebx read = [] write = []
456E42 dec ecxread = [] write = []
456E44 xor ecx,0x25873dcc read = [] write = []
4691EB inc ecxread = [] write = []
4691EF neg ecxread = [] write = []
4691F1 xor ebx,ecx read = [] write = []
4691F3 add esi,ecx read = [] write = []
41F273 push esiread = [] write = [59]
41F274 ret  read = [59] write = []
//上面这个代码块从指令流中取出了一个DWORD,赋予ecx,并递增了指令流指针。将取出的指令流用密钥进行解密,而后与esi相加,得到下一条Handle的地址


总结:在本样本的虚拟机结构中,edi作为虚拟机堆栈,ebp作为指令流指针,ebx仍然作为执行密钥,esi作为中转基址,esp作为上下文指针。传统的分发器结构消失了,取而代之的是一种新的链式结构的虚拟机。不同样本可能会有不同的架构,不像VMP2.X架构,3.X中寄存器是随机选用的。新架构又需要新的分析工具,不知道何年何月才会有牛人共享出来……
再探
vPopImm32
使用脚本对其解混淆后的代码如下。注意,如果后文中没有特殊说明,贴出的代码均为解混淆后的代码。
46C27Fmov edx, dword ptr [edi] read = [] write = []
46C28Cadd edi, 4 read = [] write = []
//从虚拟机堆栈中弹出操作数
46C292movzx ecx, byte ptr [ebp] read = [] write = []
46C297lea ebp, [ebp + 1] read = [] write = []
46C2A3xor cl, bl read = [] write = []
46C2A5neg cl read = [] write = []
442B1Ainc cl read = [] write = []
442B1Dror cl, 1 read = [] write = []
412AB8neg cl read = [] write = []
412AC0xor bl, cl read = [] write = []
//从指令流中取出操作数(在上下文结构中的偏移),并解密,更新密钥,递增指令流
412AC3mov dword ptr [esp + ecx], edx read = [] write = []
412AC8mov ecx, dword ptr [ebp] read = [] write = []
//写入上下文结构
412AD0add ebp, 4 read = [] write = []
412AD7xor ecx, ebx read = [] write = []
412ADFsub ecx, 0x2d2f25e5 read = [] write = []
412AE5rol ecx, 2 read = [] write = []
412AE8sub ecx, 0x1a4c24fd read = [] write = []
43CA2Aror ecx, 3 read = [] write = []
43CA2Dxor ebx, ecx read = [] write = []
43CA34add esi, ecx read = [] write = []
43CA36jmp esi read = [] write = []

该Handle从虚拟机栈中弹出一个DWORD,并写入上下文结构中指定字段。

vPushImm32

48D637mov eax, dword ptr [ebp] read = [] write = []
48D63Blea ebp, [ebp + 4] read = [] write = []
48D646xor eax, ebx read = [] write = []
48D64Cadd eax, 0xb4c16be read = [] write = []
48D651not eax read = [] write = []
48D656lea eax, [eax - 0x51cc037d] read = [] write = []
48D65Cmov cl, 0x42 read = [] write = []
48D65Eneg eax read = [] write = []
48D66Cror eax, 1 read = [] write = []
48D676lea eax, [eax - 0x61b03f11] read = [] write = []
//从指令流中取出操作数,并进行解密操作
48D67Cxor ebx, eax read = [] write = []
48D67Flea edi, [edi - 4] read = [] write = []
48D688mov dword ptr [edi], eax read = [] write = []
//将解密后的操作数压入虚拟机堆栈
48D691mov ecx, dword ptr [ebp] read = [] write = []
48D695lea ebp, [ebp + 4] read = [] write = []
40E56Dxor ecx, ebx read = [] write = []
40E570rol ecx, 3 read = [] write = []
40E576sub ecx, 0x1865595b read = [] write = []
40E583bswap ecx read = [] write = []
40E586lea ecx, [ecx - 0x7c371840] read = [] write = []
40E58Dxor ebx, ecx read = [] write = []
422848add esi, ecx read = [] write = []
4520F4lea eax, [esp + 0x60] read = [] write = []
48294Cpush esi read = [] write = [0]
48294Dret  read = [0] write = []

每次执行入栈操作后,都会检查边界,判断虚拟机栈指针与上下文指针是否接近,如果是,则会将上下文结构向下移动,如下。
lea     eax, [esp+60h]
cmp     edi, eax
但是我写的那个破脚本脚本没考虑到这点,会将这段代码舍去,所以需要特别注意一下。

该Handle向虚拟机堆栈中压入一个DWORD大小的常数。

vPushRx32

413B2Amovzx edx, byte ptr [ebp] read = [] write = []
413B2Fsetge al read = [] write = []
413B32shl ah, 0x30 read = [] write = []
413B35add ebp, 1 read = [] write = []
413B46xor dl, bl read = [] write = []
413B51ror dl, 1 read = [] write = []
413B53sub dl, 0x3e read = [] write = []
413B5Aneg dl read = [] write = []
413B5Frol dl, 1 read = [] write = []
413B61inc dl read = [] write = []
413B6Arol dl, 1 read = [] write = []
413B78xor bl, dl read = [] write = []
//取出操作数(在上下文结构中的偏移,并解密)
413B7Dmov eax, dword ptr [esp + edx] read = [] write = []
//取出指定字段
413B89sub edi, 4 read = [] write = []
413B91mov dword ptr [edi], eax read = [] write = []
//压栈
413B93mov edx, dword ptr [ebp] read = [] write = []
413B9Dadd ebp, 4 read = [] write = []
413BAAxor edx, ebx read = [] write = []
413BADror edx, 1 read = [] write = []
413BB3neg edx read = [] write = []
413BB9lea edx, [edx - 0x796d16c6] read = [] write = []
413BC2not edx read = [] write = []
413BC9xor ebx, edx read = [] write = []
413BCBadd esi, edx read = [] write = []
4520F4lea eax, [esp + 0x60] read = [] write = []
48294Cpush esi read = [] write = [0]
48294Dret  read = [0] write = []

该Handle从上下文结构中取出指定字段,并压入堆栈。

vRET

注意,那个破脚本对这条Handle完全不适用了,等我有空再看看BUG。
mov     esp, edi
pop     eax
pop     ebp
pop     ecx
popf
pop     edx
pop     edi
pop     ebx
pop     esi
retn
//还原堆栈,同时还原环境

总结:结合上述介绍的Handle,读者可自行完成对本文附带的例子的分析。该例并不复杂,与原汇编代码基本可以说是一一对应的关系。稍微注意一下,对于函数调用,VMP3.X的处理方式是先退出虚拟机,同时将返回地址设为进入虚拟机的代码地址。
举个例子。如下。
0012FFA8   00401032 <jmp.&user32.MessageBoxA>
0012FFAC   004207C7 1_vmp.004207C7
0012FFB0   00000000
0012FFB4   00403000 ASCII "VMProtect V2.12.3"
0012FFB8   00403012 ASCII "三十二变"
0012FFBC   00000000
这是在执行vRET的最后一条retn指令时的堆栈环境。
调用完成后,返回到0x004207C7,又重新进入虚拟机。
004207C7    68 B31D7ABA     push 0xBA7A1DB3
004207CC    E8 FD8FFEFF     call 1_vmp.004097CE

奇技淫巧之简单爆破

因为业务需要不同,对VM的研究程度也不同,所以对应的也会产生一些奇技淫巧,比如,无脑爆破……不是我BS这种方法,是真的无脑,但很多分析虚拟机的文章都会介绍这个,作为一篇自重自爱的虚拟机介绍文章,本文当然也不会省略这个环节。
举一个例子,如下。
cmp eax,2010
je label1
我们可以修改cmp指令实现爆破,同样可以修改je指令实现爆破。无脑就无脑到底好了,本文介绍修改跳转指令,因为它不需要了解复杂的逻辑门运算知识。
以爆破je指令为例。
以下为未加密前的源文件。
@Main   proc
    mov eax,2018h
    .if eax == 2019h
            invoke        MessageBox,0,offset szOK,offset szTitle,MB_OK
    .else
            invoke        MessageBox,0,offset szNO,offset szTitle,MB_OK
    .endif
    ret

@Main endp

因为VMP有执行密钥,用来动态解密指令流。跳转指令有两个分支,两条分支下去,不可能还能再用同一个密钥继续编码下去。所以遇到流程转移指令一定会重新设置密钥,我们直接搜索mov ebx语句。
第二次对ebx(密钥)直接赋值的地方与第一次(进入虚拟机)相差很远,我们再以此为基准向上搜索00000040(对应的是ZF标志位为1的EFLAGS)。
搜索3次后来到此处。
7.jpg
注意这条shr指令,这表明我们目前处于vShr4 Handle中。
在OD中动态调试,在该处下一个条件断点。可以按当时指令流来设置。
中断时,将EAX由0x40修改为0x0即可。
8.jpg

9.jpg
如果想要深入了解,请去参阅布尔代数,一般讲离散数学的书都有这个内容。

附录
文章中用到的破脚本可以到我的GITEE上面下载。
https://gitee.com/sanxcr/VMPFuck
大概原理就是消除死代码。比如。
int x;
x = 10; ///1
x = 5; //2
x += 20;
printf(“%d”, x);
其中1是死代码,因为1仅对x进行赋值操作而x在引用前就被重新定值了。
因为VMP3.X中没有栈混淆,所以可以直接对寄存器进行消除死代码。
VMP2.X因为有栈混淆,所以可能复杂一些,思路就是将栈中的数据以DWORD为单位开始编号,仍然将它们视为变量,进行消除死代码。
以栈变量活跃分析为例(寄存器与标志位的活跃分析是一样的),需要遵循以下原则。
1.活跃分析应从底部向顶部分析
2.出口代码处的所有栈变量都是活跃的
3.栈变量的活跃性持续向上传递,但遇到对该栈变量的读写操作时,会变更活跃性
4.当某一栈变量向上传递活跃性遇到写操作时,则活跃性变为死状态,若遇到读操作时,则活跃性变为活状态
5.当出现对同一栈变量进行读写操作时,我们默认读操作先于写操作
具体实现请看脚本……

使用需基于capstonepythonwin
安装capstone
1.      pip install capstone
安装pythonwin
1.      百度搜索pythonwin,找到对应你的python版本即可
简单分析VMP3.X.rar (778.05 KB, 下载次数: 45)

点评

Shark恒”点评说:
本文于2022年1月7日发表在“吾爱汇编”公众号。  发表于 2022-1-7 14:45

评分

参与人数 40威望 +1 HB +139 THX +26 收起 理由
longge188 + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
虚心学习 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
DDK4282 + 1 + 1
sjtkxy + 1 + 1
悦应 + 5 + 1
李卓吾 + 1
行行行行行行 + 1
WolfKing + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
消逝的过去 + 2
冷亦飞 + 1
飞刀梦想 + 1
筱默 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
三月十六 + 1
luo271338515 + 1
小菜虫 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
summersn0w + 1
weiran324 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
别来无恙 + 1
fjgh + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
拿着雪糕 + 1 + 1
missaa + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
kalove + 1
ams + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
开天辟地 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
zxjzzh + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!
zwc123xyz + 1 [吾爱汇编论坛52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
muker + 1
迷途狂狼 + 1
liugu0hai + 1 + 1 谢谢分享
endbeach + 1
flyinsky + 1 + 1 [快捷评语]--积极评分,从我做起。感谢分享!
豆0o0豆 + 1 + 1
丶自我拉扯 + 1 + 1 [快捷评语]--你将受到所有人的崇拜!
白云点缀的蓝 + 6 + 1 [快捷评语]--积极评分,从我做起。感谢分享!
2lht_love + 2 + 1 [快捷评语] - 吃水不忘打井人,给个评分懂感恩!
wangxp + 1 + 1 [快捷评语] - 评分=感恩!简单却充满爱!感谢您的作品!
860695151 + 1 + 1 [快捷评语] - 评分=感恩!简单却充满爱!感谢您的作品!
tianbowen0 + 1 + 1 [快捷评语] - 评分=感恩!简单却充满爱!感谢您的作品!
冰冰 + 1 + 1 [快捷评语] - 评分=感恩!简单却充满爱!感谢您的作品!
Shark恒 + 1 + 100 + 1 [快捷评语] - 吃水不忘打井人,给个评分懂感恩!

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Shark恒 发表于 2019-2-14 21:19 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
狐白小刺客 发表于 2019-2-14 22:34 | 显示全部楼层

你个老头子坏的很啊
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
售野生奥特曼 发表于 2019-2-15 21:34 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
贪念红尘 发表于 2019-2-15 22:23 | 显示全部楼层

数学基础依旧是个瓶颈 至今都没能理解vmp是如何实现cmp 和跳转的 也没有什么书籍有讲解vmp的最新版本 就连看雪的《加密与解密》第四版也只介绍了老版本的分析
看狐白大神的vm爆破教程也没看懂 还是只能在论坛慢慢积累
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
张长海 发表于 2019-2-15 23:11 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| 李沉舟 发表于 2019-2-16 08:07 | 显示全部楼层

贪念红尘 发表于 2019-2-15 22:23
数学基础依旧是个瓶颈 至今都没能理解vmp是如何实现cmp 和跳转的 也没有什么书籍有讲解vmp的最新版本 就连 ...

a -  b = NOT(a - b) + 1 = NOT(a - b - 1) = NOT(NOT(a) + 1 - 1 + b) = NOT(NOT(a) + b)
eflags的得到我不清楚数学证明,不过穷举证明确实无误。
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
1481645902 发表于 2019-2-17 18:17 | 显示全部楼层

Shark恒 发表于 2019-2-14 21:19
沉舟同学越来越厉害了

厉害,vmp都研究这么深入了,我好惭愧啊。
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
crad121 发表于 2019-3-9 08:08 | 显示全部楼层

厉害了我的哥,学习学习先,赞!!!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
2lht_love 发表于 2019-3-9 17:45 | 显示全部楼层

得把工辞了努力学习才行了。
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层
9层
10层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

快速回复 返回顶部 返回列表