查看原文
其他

VMProtect保护壳爆破步骤详解(入门级)

ZyOrca 看雪学苑 2024-07-16




原理


1.1 VMProtect Software 公司


VMProtect 软件公司成立于2000年,总部位于俄罗斯叶卡捷琳堡。该公司出品的软件保护软件 VMProtect(目前版本已更新到 3.x,以下简称VMP)可以说是软件破解领域的圣杯,多年来无数逆向分析人员前赴后继,一直试图揭开 VMP 的神秘面纱。

VMP 的特色功能包括虚拟化、混淆、反调试等。本文的测试内容主要针对的是VMP的虚拟化机制,即VMP 可以将受保护的代码放到内置的虚拟机中运行,以防止反编译和破解。

1.2 VMProtect 虚拟化原理


VMP 代码虚拟机是一款栈机,也就是说,VMP 的代码虚拟机与被保护程序的代码处于栈空间中。基于栈的虚拟机指令集主要是通过操作栈顶元素来完成。例如,Java 虚拟机(JVM)就是一个典型的基于栈的虚拟机。与之相对应的还有基于寄存器的虚拟机。指令操作数存储在虚拟寄存器中,指令集直接对寄存器进行操作。Lua 的虚拟机(称为 Lua 虚拟机,LVM)就是基于寄存器的。

而基于栈的 VMP 虚拟机因为要维护出栈入栈,所以执行同样的操作,需要的指令较多,间接使得执行效率较差。VMP 会让程序变得臃肿、运行速度变慢,但被保护的程序代码膨胀后,也能给逆向分析人员造成很多困扰。

(表格出自《加密与解密(第4版)》)

VMP 虚拟化流程:

1.VMP 将被保护的代码转换为虚拟机指令集(bytecode),并为每个虚拟指令对应一个处理程序(handler),每个 handler 实际上是一个小型的解释器。这些处理程序不是简单的地址,而是具体的处理逻辑代码。在代码转换过程中, VMP 还会对指令进行混淆和加密,以增加逆向工程的难度。

2.VMP 还会对一些系统对象如文件、注册表等进行虚拟化,在虚拟机内部维护虚拟的系统资源,使得在虚拟机内部的系统交互不会影响到真实系统。VMP 通常会结合多种反调试技术,例如代码自修改、反跟踪、反模拟执行等,增加调试难度。

3.受保护程序运行时,会首先执行插入的 VStartVM 代码。(VStartVM 通常是作为程序入口点被插入到受保护代码中的 )VStartVM 负责将程序从真实环境转换到虚拟环境,并初始化虚拟机的上下文环境,将真实环境中的寄存器状态保存,并准备虚拟机所需的堆栈空间。此外,VStartVM 还会对一些关键的系统API进行hook,以截获系统调用并在虚拟环境中模拟执行。VMP 允许在受保护代码块中插入特定的指令,用于主动切换回真实环境,例如调用未被 hook 的系统 API。

4.VMDispatcher 负责从虚拟指令流中读取虚拟指令(bytecode),然后通过解码确定对应的处理程序(handler)。解码后的虚拟指令并不是直接执行真实指令,而是调用相应的 handler 来模拟执行,这些 handler 处理的是虚拟机指令,而不是直接的原始指令。

5.VMP 使用两个堆栈:虚拟堆栈和真实堆栈。虚拟堆栈供虚拟机内部使用,而真实堆栈是线程原有的堆栈。为了避免两者冲突,VMP 会监控堆栈指针 esp。当虚拟指令需要修改真实寄存器时,真实寄存器的值会被临时保存在真实堆栈中,并将虚拟寄存器的值应用到真实寄存器上。如果 esp 与 ebp 即将发生冲突,VMP 会将当前虚拟机上下文保存到真实堆栈,然后开辟新的虚拟堆栈空间,确保虚拟机正常运行。对于多线程程序, VMP 会为每个线程维护独立的虚拟机上下文(VMContext),不同线程的虚拟机之间相互隔离。

6.虚拟化指令执行完之后,虚拟机并不会立即退出虚拟环境。只有在整个受保护代码块执行完毕或者发生异常时,才会退出虚拟环境,恢复真实环境中的状态。退出虚拟环境时,虚拟机会恢复原始的寄存器状态,并确保所有的上下文环境都正确恢复。不过在退出之前,还需要将虚拟寄存器的值写回到真实寄存器,并解除之前所做的系统API hook。




测试程序


2.1 测试环境


系统环境:Windows 7 专业版(32 位)
IDE:Dev-C++ 5.7.1
加壳程序:VMProtect 3.4

2.2 测试程序源代码


#include<bits/stdc++.h>
#include<windows.h>
using namespace std;

int main()
{
printf("Please enter your pwd >> ");
string s;
cin>>s;
if(s=="123456")
printf("successful!\n");
else
printf("failed!\n");
system("pause");
return 0;
}

编译得到CrackMe.exe。

2.3 测试目标


在输入错误密码的情况下,程序仍可打印“successful!”
(测试演示出自B站up主Rairn,文末有提供视频链接)




VMP 编译测试程序


用 OD 加载CrackMe.exe,找到一个关键且易识别的位置,用 VMP 加密;在 OD 反汇编窗口点击右键,选择中文搜索引擎->智能搜索,搜索源代码中的字符串,例如successful。



点进字符串successful对应的地址0x00401702,分析一下反汇编代码,将加壳位置定为0x004016B0,这看上去应该是一个函数的起始位置。将004016B0 55 PUSH EBP复制到剪切板,在记事本或其他类似工具记下来。



打开 VMProtect 3.4,点击需保护的进程->添加进程,然后把地址004016B0复制进去,编译类型选择超级(编译+虚拟)。




点击选项,除了移除调试信息选“是”,其余都选“否”,因为这次测试只针对虚拟机保护,暂不研究其他内容。



然后点击编译,就可以得到加壳后的文件CrackMe.vmp.exe。


用 LordPE 查看,CrackMe.vmp.exe多了一个区段.vmp0。





调试和脚本编写


4.1 调试准备


用 OD 加载CrackMe.vmp.exe,做好以下几个准备。

◆在调试选项中设置,因为记录需要跟踪的信息。


◆打开 RUN 跟踪。


◆点击菜单栏的E,将显示的 DLL 标记为系统 DLL,以免之后调试跟踪( trace) 时被记录。


4.2 调试过程


Ctrl + g 进入之前记下的地址004016B0,在此处 F2 下一个断点然后运行。可以看到,对比加壳前的情况,反汇编代码已经发生了很大变化。



Ctrl + F11 跟踪步过,程序也打印出了。
Please enter your pwd >>


4.3 Trace 记录


在程序中随便输入一个密码,例如55,然后点开 OD 菜单栏中的……,点击右键选择记录到文件,把调试跟踪的结果保存为trace.txt,记得勾选“附加到已有文件”和“写入采集的数据”。




查看trace.txt。



trace.txt搜索00000040,因为十六进制的00000040的二进制是0100 0000,可以被用来表示 ZF 标志位。在 x86 架构中,ZF 标志位的位置正好对应到 EFLAGS 寄存器的第 6 位(从第 0 位开始算起)。也就是说,当执行某些指令后,如果结果为零,则 ZF 标志位会被置 1。

VMP 会使用以下公式来计算 ZF 的值:
zf = and(0x40, eflags)

其中,0x40 就是 ZF 标志位的掩码值。当 eflags 寄存器的第 6 位为 1 时,and 运算的结果也会为 1,表示 ZF 标志位为 1。反之,当第 6 位为 0 时,and 运算的结果为 0,表示 ZF 标志位为 0。这样做可以非常高效地判断 ZF 标志位的状态,而不需要进行复杂的计算。

本次爆破测试是比较输入的密码是否是正确的密码,所以代码实现逻辑大概率依靠 CMP、JZ(若为 0 则跳转,ZF 需为 1)或者JE(若相等则跳转,ZF 需为 1)。

这类指令影响的符号位正是 ZF,所以需要搜寻
00000040

找到
00000040后还需要在附近寻找类似以下特征的汇编指令。

not a
not b
and a,b
//这三行代码通常不是连续出现,中间会有无用的代码隔开
//a, b皆为通用寄存器

寻找具备这种特征的汇编指令,是因为这是与非门(俗称万用门)的运算逻辑,表示为: Nand(a,b) = ~a & ~b,即将两个数分别取反后再进行与运算。汇编里面最基本的4种逻辑运算,都能用与非门表示。所以 VMP 3.x 版本中大量使用了Nand运算来表示其他的逻辑运算,真实地隐藏了原本的各种逻辑运算,有效地加大了逆向分析的难度。

Not(a) = ~a = ~a & ~a = Nand(a,a)
Or(a,b) = a | b = ~(~a & ~b) = Nand(Nand(a,b),Nand(a,b))
And(a,b) = a & b = ~~a & ~~b = Nand(Nand(a,a),Nand(b,b))
Xor(a,b) = (~a & b) | (a & ~b) = (0 | (a & ~b)) | (0 | (b & ~a)) = (a & (~a | ~b)) | (b & (~a | ~b)) = (~a | ~b) & (a | b) = ~(a & b) | ~(~a & ~b) = Nand(And(a,b),Nand(a,b)) =Nand(Nand(Nand(a,a),Nand(b,b)),Nand(a,b))

cmp指令本质上是减法,只不过结果不会写回操作数,Nand 门也能实现减法:

-a = ~a+1 => ~a = -a -1
~(~a+b) = ~(-a-1+b) = -(-a-1+b)-1 = a-b => a-b = Not(Not(a)+b)
a-b最终可以由Not(Not(a)+b)来表示,而Not(a)又可以用Nand(a,a)来表示

简而言之,看到了not a, not b, and a,b这类特征的反汇编指令,就可以合理怀疑此处是 VMP 处理虚拟机指令的 handler 。VMP 会在 handler 入口处大量使用 nand 门运算来隐藏原本的各种逻辑运算。这些"Nand"指令往往会涉及对EFLAGS寄存器的读写操作。通过精细控制EFLAGS标志位的状态,VMP 可以实现对程序控制流的保护。

找到了疑似的指令,记下做 and 运算指令的地址
0049B79E、以及两个寄存器的值ECX=00000040EAX=00000246。

and ecx, eax//运算结果为0x40


(FL 是 Flags标志位的缩写,FL=0 意味着在执行指令and ecx, eax后没有发生任何标志位的改变。因为and ecx, eax执行结果为0x40,也就是说原本的 ZF 标志位就是 1。在后面的操作中,我们只需要将修改 ecx ,就能将 ZF 的标志位改为 0, 就能改变程序原本的跳转逻辑。例如,在输入错误时,原本打印“failed”,就能跳转到 “successful!“)

4.4 脚本编写

bp 0049B79E //设置断点,当程序执行到地址 0049B79E 时暂停执行。

start: //标签,用于标识后续指令的起始位置。
run //继续执行程序,直到触发了设置的断点。
cmp ecx, 00000040
jnz start
cmp eax, 00000246
jnz start

end:
bc 0049B79E //清除断点,使得程序不再在地址 0049B79E 处暂停执行。
ret //返回指令,结束当前脚本的执行。

保存为script.txt




重新调试和破解


5.1 运行脚本


OD 重新加载CrackMe.vmp.exe,在反汇编窗口点击右键,选择运行脚本->script.txt。



此时程序会运行,并打印字符串Please enter your pwd >>,随便输入 66 按回车键,会弹出窗口显示“脚本运行完成”。


5.2 修改寄存器的值


点击“确定”后,发现 EIP 停留在了0049B79E,因为之前脚本就在这个地址下了断点。注意此时的寄存器窗口,各个寄存器的值。

EAX 00000246
ECX 00000040
EDX 50655CD1
EBX 004D7A92 CrackMe_.004D7A92
ESP 0022FDE4
EBP 0049B777 CrackMe_.0049B777
ESI 0022FEC6
EDI 004F9B18 CrackMe_.004F9B18
EIP 0049B79E CrackMe_.0049B79E


F7 走一下,寄存器的值没有发生任何变化。



我们将 ECX 的值从00000040改为00000000。





破解成功


然后再点击运行,破解成功,注意此时的 ZF 标志位为 0;

eax = 0x246;
ecx = 0x0;
运算结果是0;


对比看一下,在输入错误密码且没有修改 ecx 的情况下,ZF 标志位为1。


参考链接:

https://bbs.kanxue.com/thread-224732.htm

https://www.cnblogs.com/theseventhson/p/14274653.html

https://www.bilibili.com/video/BV1vK4y1Q7K7/?spm_id_from=333.337.search-card.all.click&vd_source=e5b65cf3bea873b0cfe83c6f3d30a710

《加密与解密(第4版)》





看雪ID:ZyOrca

https://bbs.kanxue.com/user-home-944427.htm

*本文为看雪论坛优秀文章,由 ZyOrca 原创,转载请注明来自看雪社区



# 往期推荐

1、Windows主机入侵检测与防御内核技术深入解析

2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge

3、银狐样本分析

4、使用pysqlcipher3操作Windows微信数据库

5、XYCTF两道Unity IL2CPP题的出题思路与题解



球分享

球点赞

球在看



点击阅读原文查看更多

继续滑动看下一个
向上滑动看下一个

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存