http://hi.baidu.com/ximo2006/blog/item/87a21549c21da5fd82025c9b.html
本次分析的对象是加了VMP外壳的程序。当然如果你觉得脱VMP2.05的壳比直接爆破关键然后patch掉效验来的简单的话,你可以直接脱壳后进行破解。
首先还是写个简单的程序,做为试练品,来进行练习。
关键部分的源码如下:
int RegCode(char* szInput)
{
int len=strlen(szInput);
int sum=0;
if (len==0)
{
return 0;
}
for (int i=0;i<len;i++)
{
sum+=szInput[i];
}
return sum;
}
void CCmDlg::OnButton1()
{
// TODO: Add your control notification handler code here
char szInput[256]={0};
GetDlgItemText(IDC_EDIT1,szInput,255);
if (RegCode(szInput)==2012)
{
MessageBox("yes");
}
else
{
MessageBox("no");
}
}
然后,对编译后的程序,加上VMP的外壳,也就是默认选项,未对关键算法代码进行VM。
下面来进行破解。
一.寻找关键点:由于加了VMP外壳,我们也不打算对其进行脱壳,于是,直接在OD里F9运行,然后定位到代码段00401000,可以发现代码都已经解密了,接着直接查找字符串“yes”,就可以来到代码的关键部分:
004014B0 81EC 00010000 sub esp,100
004014B6 56 push esi
004014B7 8BF1 mov esi,ecx
004014B9 57 push edi
004014BA B9 3F000000 mov ecx,3F
004014BF 33C0 xor eax,eax
004014C1 8D7C24 09 lea edi,dword ptr ss:[esp+9]
004014C5 C64424 08 00 mov byte ptr ss:[esp+8],0
004014CA 68 FF000000 push 0FF
004014CF F3:AB rep stos dword ptr es:[edi]
004014D1 66:AB stos word ptr es:[edi]
004014D3 AA stos byte ptr es:[edi]
004014D4 8D4424 0C lea eax,dword ptr ss:[esp+C]
004014D8 8BCE mov ecx,esi
004014DA 50 push eax
004014DB 68 E8030000 push 3E8
004014E0 E8 69020000 call crackme2.0040174E
004014E5 8D4C24 08 lea ecx,dword ptr ss:[esp+8]
004014E9 51 push ecx
004014EA E8 91FFFFFF call crackme2.00401480
004014EF 83C4 04 add esp,4
004014F2 3D DC070000 cmp eax,7DC
004014F7 6A 00 push 0
004014F9 6A 00 push 0
004014FB 75 15 jnz short crackme2.00401512
004014FD 68 24304000 push crackme2.00403024 ; yes
00401502 8BCE mov ecx,esi
00401504 E8 3F020000 call crackme2.00401748
00401509 5F pop edi
0040150A 5E pop esi
0040150B 81C4 00010000 add esp,100
00401511 C3 retn
00401512 68 20304000 push crackme2.00403020 ; no
00401517 8BCE mov ecx,esi
00401519 E8 2A020000 call crackme2.00401748
0040151E 5F pop edi
0040151F 5E pop esi
00401520 81C4 00010000 add esp,100
00401526 C3 retn
代码很清晰,爆破很容易。
004014FB 75 15 jnz short crackme2.00401512
把这句代码的jnz改成jz,也就是75改74,或者直接nop掉,即可完成爆破。
但是,由于加了外壳,不能直接修改后进行保存。不过我们找到了关键位置4014FB。
二.寻找解密过程,进行patch,实现爆破
重新加载程序,然后直接在4014FB下硬件写入断点,选择byte。F9运行,程序中断下来。
断下后看寄存器,注意EDI跟ESI的值:
ESI 0040E456 crackme2.0040E456 //待解密的数据
EDI 004014FC crackme2.004014FC //解密后存放的位置
然后单步跟,看解密算法:
0041B46E AC lods byte ptr ds:[esi] //取待解密的数据
0041B46F F5 cmc
0041B470 C0C0 05 rol al,5
0041B473 C0FB 05 sar bl,5
0041B476 0F98C3 sets bl
0041B479 18F3 sbb bl,dh
0041B47B 04 47 add al,47
0041B47D 08EB or bl,ch
0041B47F F6D8 neg al
0041B481 28FB sub bl,bh
0041B483 2C 1C sub al,1C
0040FF44 F6D0 not al
0040FF46 F6D3 not bl
0041999E AA stos byte ptr es:[edi] //写入解密后的数据
0041999F 9C pushfd
总结得到,解密算法为:
rol al,5
add al,47
neg al
sub al,1C
not al
下面我们来看下我们爆破的数据:
爆破点:4014FB 75---74
然后来计算下,74加密后的数据为多少。
写个解密函数的逆运算来进行解密吧,
mov al,74
not al
add al,1c
neg al
sub al,47
ror al,5
解密后可得,74加密后的数据为90.
于是,我们把4014fb这个地址的数据修改成90,保存一下,即可完成破解。
三.去除VMP效验
修改后,运行爆破后的程序,程序会提示错误,错误的提示为非法修改,于是下面的任务就是来去除效验。去效验,我使用的是最笨的方法,也就是开2个OD进行比较,分析代码的流程实在是个很蛋疼的体力活。
法1:
效验1:程序完整性的效验。
2个0D分别bp MapViewOfFile,然后运行。
断下后,根据上篇文章,直接找handle_nor32,
0041EA8D 8B55 04 mov edx,dword ptr ss:[ebp+4]
这个这里下好断点,然后就是运行后进行比较。
可以发现,当esi==0x411b12时,被修改后的eflag值=0x202,而原程序的eflag=0x246
效验2:去掉第一个效验后,接着在上面我们修改的位置4014fb下硬件访问断点,byte就行,断下后,单步走几步看
0041E4D5 895C24 0C mov dword ptr ss:[esp+C],ebx
0041E4D9 881C24 mov byte ptr ss:[esp],bl
0041E4DC 8D6424 34 lea esp,dword ptr ss:[esp+34]
0041E4E0 0F85 ED130000 jnz cm_vmp_c.0041F8D3 //解密循环
0041E4E6 68 7161C09F push 9FC06171 //解密后的出口
0041E4EB E8 D8080000 call cm_vmp_c.0041EDC8
0041E4F0 FF7424 04 push dword ptr ss:[esp+4]
0041E4F4 8F45 00 pop dword ptr ss:[ebp]
这是个解密循环,直接在解密出口0041E4E6下好断点,F9运行。
解密的出口又是个VM,再次查找handle_nor32,进行比较
可以得到:当esi==0x41df21时,修改后的efalg=0x206,未修改后的为0x246,于是再次进行修改,这是patch点2
效验3:patch上面的2处后,程序还是出错。继续来进行patch。
继续在0041E4E6下好断点,F9运行,发现运行89后,程序出错,于是写个脚本定位到出错的前1次,
var time
mov time,0
loop:
run
inc time
cmp time,88
jne loop
同样,在原始的程序里,也运行脚本,中断下来。
接着,继续hook住handle_nor32,比较。
可得,当esi==0x41009时,修改后的eflag=0x202,原始的eflag=0x246
OK,修改完这3处后,程序可以正常运行了。简单的patch代码如下:
0041EA8D 8B55 04 mov edx,dword ptr ss:[ebp+4]
0041EA90 F9 stc
0041EA91 60 pushad
0041EA92 80F9 04 cmp cl,4
ptach后:
0041EA8D /E9 461F0000 jmp cm_vmp_c.004209D8
0041EA92 |80F9 04 cmp cl,4
004209D8 60 pushad
004209D9 81FE 121B4100 cmp esi,cm_vmp_c.00411B12
004209DF 75 07 jnz short cm_vmp_c.004209E8
004209E1 C745 04 46020000 mov dword ptr ss:[ebp+4],246
004209E8 81FE 21DF4100 cmp esi,cm_vmp_c.0041DF21
004209EE 75 07 jnz short cm_vmp_c.004209F7
004209F0 C745 04 46020000 mov dword ptr ss:[ebp+4],246
004209F7 81FE 09004100 cmp esi,cm_vmp_c.00410009
004209FD 75 07 jnz short cm_vmp_c.00420A06
004209FF C745 04 46020000 mov dword ptr ss:[ebp+4],246
00420A06 61 popad
00420A07 8B55 04 mov edx,dword ptr ss:[ebp+4]
00420A0A F9 stc
00420A0B 60 pushad
00420A0C ^ E9 81E0FFFF jmp cm_vmp_c.0041EA92
法2:
由法1可知效验数据的地方,于是直接在解密出口0041E4E6下后断点。
运行后,看寄存器,其中eax就为存放效验值的地方,这就是突破口。
比如:F9 4次后,修改后的为:
EAX 24652334
ECX 00000034
EDX 003DD3C4
而原来的程序为:
EAX 24652314
ECX 00000014
EDX 003DD3C4
发现效验值变了,那么到底用哪个寄存器做patch的参照寄存器呢?
比如用eax为参照,则patch后,效验值也就是eax就会改变,失败。
如果用ecx做参照,由于只是1个byte值,猜想会有重复,写个脚本跑一下可知,得确存在的重复,失败。而且根据不同的patch,ecx的值也会改变,失败。
于是,只能用edx作为参照寄存器。但是edx的值是否完全一致呢?发现,不同的机器,edx的值并不相同,比如本机,edx==0x003DD3C4,而虚拟机中则为:0x003CD3C4
但是也有发现,发现edx的低16位,也就是dx是一致的,于是选定edx的低16位作为参照。
于是,patch代码可以这样写:
cmp dx,XX //1个参照值
jne old
mov eax,yy //写入正确的效验值
jmp old
old:
//原始代码
jmp patch后的代码
具体的过程不再赘述了,方法就比较2边的效验值,然后进行写入。比如可以patch代码如下:
0041E4E6 68 7161C09F push 9FC06171
0041E4EB E8 D8080000 call cm_vmp_c.0041EDC8
0041E4F0 FF7424 04 push dword ptr ss:[esp+4]
0041E4F4 8F45 00 pop dword ptr ss:[ebp]
patch后的:
0041E4E6 /E9 D5220000 jmp cm_vmp_c.004207C0
0041E4EB |E8 D8080000 call cm_vmp_c.0041EDC8
0041E4F0 |FF7424 04 push dword ptr ss:[esp+4]
0041E4F4 |8F45 00 pop dword ptr ss:[ebp]
0041E4F7 |51 push ecx
004207C0 66:81FA C4D3 cmp dx,0D3C4
004207C5 75 07 jnz short ximo6.004207CE
004207C7 B8 14236524 mov eax,24652314
004207CC EB 36 jmp short ximo6.00420804
004207CE 66:81FA 0050 cmp dx,5000
004207D3 75 07 jnz short ximo6.004207DC
004207D5 B8 7A7DE99A mov eax,9AE97D7A
004207DA EB 28 jmp short ximo6.00420804
004207DC 66:81FA 6DE9 cmp dx,0E96D
004207E1 75 07 jnz short ximo6.004207EA
004207E3 B8 C23966A7 mov eax,A76639C2
004207E8 EB 1A jmp short ximo6.00420804
004207EA 66:81FA F8FA cmp dx,0FAF8
004207EF 75 07 jnz short ximo6.004207F8
004207F1 B8 D651DB65 mov eax,65DB51D6
004207F6 EB 0C jmp short ximo6.00420804
004207F8 66:81FA 021A cmp dx,1A02
004207FD 75 05 jnz short ximo6.00420804
004207FF B8 C4A0613F mov eax,3F61A0C4
00420804 68 7161C09F push 9FC06171
00420809 ^ E9 DDDCFFFF jmp ximo6.0041E4EB
保存修改后,同样正常运行。
总结下:2种方法都采用了最笨也是最直接的对比法,当然,可能还存在更为简洁的方法,能力有限,如有错误,请多多指点。
附上原始程序,跟用2种方法爆破后的程序。