本帖由 透明色 发布于 看雪论坛, 谢绝任何形式的转载 .
首先特别感谢 zdhysd大 , 写出了那么好的vmp插件, 春节后去了几封邮件,得zdhysd大耐心解惑,表示很感谢.
本篇 以一个简单的cm为例,讲解一下vmp 从分析到破解的全过程. 但是基础的东西在这里就不再啰嗦了,
假设读者了解虚拟机的基本概念.如果不了解,请先移步去阅读一下 vmp基础的帖子.
这个cm是自己写的,使用的是mfc ,release编译,写得比较简单。没有加anti,拿来演示一下 vmp 的分析与破解方法。
register()函数的反汇编如下:对虚拟码的分析的时候,参考下原始的汇编。
--------------
00401450 sub esp, 0x3C
00401453 lea eax, dword ptr [esp+0x14]
00401457 push esi
00401458 push edi
00401459 push 0x14
0040145B push eax
0040145C mov esi, ecx
0040145E push 0x3E8
00401463 call <jmp.&MFC42.#?GetDlgItemTextA@CW>
00401468 lea ecx, dword ptr [esp+0x30]
0040146C push 0x14
0040146E push ecx
0040146F push 0x3E9
00401474 mov ecx, esi
00401476 call <jmp.&MFC42.#?GetDlgItemTextA@CW>
0040147B lea edi, dword ptr [esp+0x1C]
0040147F or ecx, 0xFFFFFFFF
00401482 xor eax, eax
00401484 lea edx, dword ptr [esp+0x1C]
00401488 repne scas byte ptr es:[edi]
0040148A not ecx
0040148C dec ecx
0040148D push ecx
0040148E push edx
0040148F call 00401150
00401494 add esp, 0x8
00401497 push eax ; /<%X>
00401498 lea eax, dword ptr [esp+0xC] ; |
0040149C push 00403080 ; |%x
004014A1 push eax ; |s
004014A2 call dword ptr [<&USER32.wsprintfA>] ; \wsprintfA
004014A8 lea edi, dword ptr [esp+0x14]
004014AC or ecx, 0xFFFFFFFF
004014AF xor eax, eax
004014B1 add esp, 0xC
004014B4 xor edx, edx
004014B6 repne scas byte ptr es:[edi]
004014B8 not ecx
004014BA dec ecx
004014BB je short 004014E1
004014BD movsx ecx, byte ptr [esp+edx+0x8]
004014C2 movsx eax, byte ptr [esp+edx+0x30]
004014C7 sub ecx, eax
004014C9 cmp ecx, 0x1
004014CC jnz short 00401509
004014CE lea edi, dword ptr [esp+0x8]
004014D2 or ecx, 0xFFFFFFFF
004014D5 xor eax, eax
004014D7 inc edx
004014D8 repne scas byte ptr es:[edi]
004014DA not ecx
004014DC dec ecx
004014DD cmp edx, ecx
004014DF jb short 004014BD
004014E1 lea eax, dword ptr [esp+0x8]
004014E5 push eax
004014E6 push 00403064 ; d7 a2 b2 e1 b3 c9 b9 a6 00
004014EB call 004011D0
004014F0 add esp, 0x8
004014F3 lea ecx, dword ptr [esp+0x8]
004014F7 push 0x0
004014F9 push 0x0
004014FB push ecx
004014FC mov ecx, esi
004014FE call <jmp.&MFC42.#?MessageBoxA@CWnd@@>
00401503 pop edi
00401504 pop esi
00401505 add esp, 0x3C
00401508 retn
00401509 lea ecx, dword ptr [esp+0x8]
0040150D push ecx
0040150E push 00403040 ; d7 a2 b2 e1 c2 eb ca a7 b0 dc 00
00401513 call 004011D0
00401518 add esp, 0x8
0040151B lea edx, dword ptr [esp+0x8]
0040151F mov ecx, esi
00401521 push 0x0
00401523 push 0x0
00401525 push edx
00401526 call <jmp.&MFC42.#?MessageBoxA@CWnd@@>
0040152B pop edi
0040152C pop esi
0040152D add esp, 0x3C
00401530 retn
--------------
vmp版本为2.09 ,保护强度共有三种(变异,虚拟,超级),将上面的代码使用 第二种编译。
od打开,(步骤一)下断在 GetDlgItem,随便输入吧,断下 两次,再返回到用户代码。
首先分析一下, vm代码的流程 。用脚本跟踪一下。得到类似于下面的流程
---------------
471422--4716E0
4716E1--4719AD
4716E1--4719AD
4716E1--4719AD
4716E1--4719AD
4716E1--4719AD
4716E1--4719AD
4592C8--459AB6; 退出vm代码⑴
425049--42519A
43D1F7--43D65B; 退出vm代码⑵
4652D3--465599
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
46559A--46583F
482A1C--482C8E
4325C0--4329E3
4817E0--481EC4; 退出vm代码⑶
439FDA--43A174
42C5A6--42C9A1; 退出vm代码⑷,调用messgeboxA 弹出注册失败。
---------------
讲一下几个关键的虚拟指令
Vjmp vmp的虚拟码都是一小块独立的段,每一段都是独立的,
它们之间的调用是通过 vjmp实现的,即在一个段的最后是一个 vjmp指令。
vret 和 vcall 是调用外部代码的 两个指令
它们的区别是 前者是退出虚拟机的 ,而后者不会退出虚拟机。(即虚拟机的栈内环境及寄存器不会破坏)
与vjmp 一样vret 也存在于段尾, 而vcall 在于于段的中间
而上面帖出的流程,是用脚本通过 跟踪vjmp 和 vret 来实现的。
好了,扫盲的知识就到这里。接着分析这个流程。
我们发现虚拟代码有两段循环,程序检验的代码实际上是一个 for{if()goto;}形式的循环判断。怎么会有两段呢,
其实这两段都不是 我们要找的关键代码,而是对虚拟机对 repne 的模拟。这些个位置其实是strlen(),应该是使用release之后,
编译器将strlen()给内联掉了。而repne是用来实现strlen的。虚拟机中是没有 repne 对应的虚拟指令的,
所以要有一组代码来模拟它。
至于⑴ ⑵ ⑶ 的作用,请大家根据原始汇编来分析。
关键的代码是这里:
---------------
482A1C--482C8E
4325C0--4329E3
---------------
再插一点内容,讲一下vmp 怎样模拟jcc ,附件里的 cm01G.exe 关键跳转改成了 vmp的样式,分析虚拟码之前,请先研究一下这个例子.
---------------
004014B3 repne scas byte ptr es:[edi]
004014B5 not ecx
004014B7 dec ecx
004014B8 je short 004014F2
004014BA movsx ecx, byte ptr [ebp+edx-0x14]
004014BF movsx eax, byte ptr [ebp+edx-0x3C]
004014C4 sub ecx, eax
004014C6 dec ecx
004014C7 push 004014E0 ;判断跳转的一个目标
004014CC push 00401518 ;判断跳转的另一个目标
004014D1 pushfd
004014D2 pop eax ;eax 等于标志寄存器的值
004014D3 and eax, 0x40 ;判断zf位
004014D6 shr eax, 0x4 ;这里只能得到 4 或者 0
004014D9 mov eax, dword ptr [esp+eax];根据结果取到偏移
004014DC push eax
004014DD retn 0x8 ;实现跳转
004014E0 lea edi, dword ptr [ebp-0x14]
004014E3 or ecx, 0xFFFFFFFF
004014E6 xor eax, eax ; d7 a2 b2 e1 b3 c9 b9 a6 00
004014E8 inc edx
---------------
附件中的 fail 和 sucess ,就是对4325C0--4329E3这段虚拟代码的记录,但是里面垃圾代码太多了,为了节约版面就不再帖出来了,已经加了注释,
请参考我们模拟的代码来理解.
接下来讲一下破解,附图演示了虚拟码 实现的跳转的过程,我们只需要修改代码中的常量,让它生成一样的假码,无论何种跳转都指向一个地址,
有些类似于爆破.我们这里把 432883 的 VpushImmx4 把这里的值改为 42D07AF1 ,重复步骤一,然后右键分析虚拟机程序,找到位置修改虚拟码, 剩下来就是写个Loader 了.
直接上代码:
---------------
#include "stdafx.h"
#include "loader.h"
int main(int argc, char* argv[])
{
const char* code =
"D8 A6 9E E6 BD 82 B1 D0 E1 1F BA 0C BB E8 E7 DC 4E 22 7F 46 8B BA 28 DF 28 C1 DE B2 21 C6 C2 8C "
"A0 8D C8 8D BC F5 65 A3 3A 55 77 ED E3 9D E6 9F 6A 54 B1 5D 96 73 75 F2 93 E2 06 ED AF 20 C1 1C "
"4E 5E 35 CA C7 D8 4F F3 52 AC 89 CF 5D 33 9E 4F 0F ED 35 FA 14 84 39 85 04 C0 CF 7C 6E 9D 6A 6C "
"23 6E 8E E2 D4 03 CB FD 6C 3F 6A 3E EB 74 A5 FD 07 F7 5C 39 86 4F CC DE 0D 7A 0E 03 89 37 54 F8 "
"0F 08 32 20 3A D1 79 2B 9A 12 C7 C8 59 86 CD E8 56 9B 2A 93 10 F1 04 ED 3E 08 CB E6 CD 7F CA 9F "
"6A 30 E4 53 41 0C 07 49 3C 33 23 8E 39 0D 24 B6 FF BA CD 17 9B 51 BE 93 4A 75 6A 5C D9 48 3E 67 "
"44 E2 1F DF 18 88 14 45 55 65 14 25 95 4A C3 8B C9 11 A0 CB 1A 59 82 67 A4 65 12 B9 D4 42 47 E0 "
"60 AF A3 4A 44 E2 29 D6 40 51 9C F7 75 7F F5 A1 E5 27 D0 03 7E 37 DD 52 C6 05 13 52 AF AB 0A AD "
"36 D8 77 F9 AB 73 AE 1B 88 FC 86 F6 94 02 31 FC B7 E2 31 94 2C F1 0B 38 D7 20 0B BD 65 56 2A 7D "
"B3 AD BA CB 08 01 B0 AE 44 7B 34 79 A3 91 43 24 01 88 2D 9C 95 DC 2F C5 42 5D 86 71 3D DD A8 30 "
"37 B1 C6 BD 97 24 6F 6D 03 02 8C A9 06 92 7E 0A D9 67 22 81 6C CF AE F5 93 B3 51 59 86 3C E9 4A ";
Loader l = Loader("cm01.vmp.exe");
l.run();
Sleep(3000);
l.patch(0x432884,code);
return 0;
}
---------------
运行生成的 cmcc ,等待 loader退出之后,随便输入几个 , 注册成功.
附件中包括 : 原始的cm , 加壳后的cm , 用来演示 vm变形的 cm01G.exe 以及记录的两个流程
ps:
一些不重要的东西 就在后面说一下吧.
1: 为什么不说一下vmp的脱壳?
请先给我一个需要脱壳的理由先 !! 哥懒得解释
2: 虚拟代码应该如何分析?
读懂几个汇编指令 ,不代表你能够读懂一段汇编 .同理,弄懂了一个个handler ,不代表你能理解一段虚拟码 .它本身就是一个拆分 ,混淆的 东西 . 就是为了对抗 静态分析 .所以vmp真心不适合 静态分析 ,个人感觉动态分析比较好一点.
3: 没有3了
好了,就说这些 .如果有需要讨论的童鞋,可以QQ我, 不过我是入门刚半年的初学者. 一些东西我也说不好. 希望可以共同进步吧.
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!