一
原理
1.1 VMProtect Software 公司
VMP 的特色功能包括虚拟化、混淆、反调试等。本文的测试内容主要针对的是VMP的虚拟化机制,即VMP 可以将受保护的代码放到内置的虚拟机中运行,以防止反编译和破解。
1.2 VMProtect 虚拟化原理
而基于栈的 VMP 虚拟机因为要维护出栈入栈,所以执行同样的操作,需要的指令较多,间接使得执行效率较差。VMP 会让程序变得臃肿、运行速度变慢,但被保护的程序代码膨胀后,也能给逆向分析人员造成很多困扰。
二
测试程序
2.1 测试环境
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 测试目标
(测试演示出自B站up主Rairn,文末有提供视频链接)
三
VMP 编译测试程序
中文搜索引擎->智能搜索
,搜索源代码中的字符串,例如successful。
successful
对应的地址0x00401702
,分析一下反汇编代码,将加壳位置定为0x004016B0
,这看上去应该是一个函数的起始位置。将004016B0 55 PUSH EBP
复制到剪切板,在记事本或其他类似工具记下来。需保护的进程->添加进程
,然后把地址004016B0
复制进去,编译类型选择超级(编译+虚拟)。
选项
,除了移除调试信息
选“是”,其余都选“否”,因为这次测试只针对虚拟机保护,暂不研究其他内容。编译
,就可以得到加壳后的文件CrackMe.vmp.exe。
用 LordPE 查看,
CrackMe.vmp.exe
多了一个区段.vmp0。
四
调试和脚本编写
4.1 调试准备
CrackMe.vmp.exe
,做好以下几个准备。E
,将显示的 DLL 标记为系统 DLL,以免之后调试跟踪( trace) 时被记录。4.2 调试过程
004016B0
,在此处 F2 下一个断点然后运行。可以看到,对比加壳前的情况,反汇编代码已经发生了很大变化。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)
本次爆破测试是比较输入的密码是否是正确的密码,所以代码实现逻辑大概率依靠 CMP、JZ(若为 0 则跳转,ZF 需为 1)或者JE(若相等则跳转,ZF 需为 1)。
这类指令影响的符号位正是 ZF,所以需要搜寻
00000040
。找到
00000040
后还需要在附近寻找类似以下特征的汇编指令。not a
not b
and a,b
//这三行代码通常不是连续出现,中间会有无用的代码隔开
//a, b皆为通用寄存器
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))
-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=00000040
、EAX=00000246。
and ecx, eax//运算结果为0x40
and ecx, eax
后没有发生任何标志位的改变。因为and ecx, eax
执行结果为0x40,也就是说原本的 ZF 标志位就是 1。在后面的操作中,我们只需要将修改 ecx ,就能将 ZF 的标志位改为 0, 就能改变程序原本的跳转逻辑。例如,在输入错误时,原本打印“failed”,就能跳转到 “successful!“)bp 0049B79E //设置断点,当程序执行到地址 0049B79E 时暂停执行。
start: //标签,用于标识后续指令的起始位置。
run //继续执行程序,直到触发了设置的断点。
cmp ecx, 00000040
jnz start
cmp eax, 00000246
jnz start
end:
bc 0049B79E //清除断点,使得程序不再在地址 0049B79E 处暂停执行。
ret //返回指令,结束当前脚本的执行。
script.txt
。五
重新调试和破解
5.1 运行脚本
CrackMe.vmp.exe
,在反汇编窗口点击右键,选择运行脚本->script.txt。
Please enter your pwd >>
,随便输入 66 按回车键,会弹出窗口显示“脚本运行完成”。5.2 修改寄存器的值
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
00000040
改为00000000。
六
破解成功
eax = 0x246;
ecx = 0x0;
运算结果是0;
参考链接:
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
# 往期推荐
2、BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
3、银狐样本分析
球分享
球点赞
球在看
点击阅读原文查看更多
原文始发于微信公众号(看雪学苑):VMProtect保护壳爆破步骤详解(入门级)