好友
阅读权限25
听众
最后登录1970-1-1
|
标 题: 【原创】虚拟机保护逆向分析实战
作 者: 天易love
时 间: 2009-12-05,14:34:03
链 接: http://bbs.pediy.com/showthread.php?t=102528
前言:
很久以前写了篇文章是穷举vm保护的CM算法的,当时对vm保护一无所知,只好穷举解决,很长时间“耿耿于怀”。现在对vm保护有了点初步的了解,就拿这个简单的CM开刀吧,权当抛砖引玉,高手莫笑。(此Cm在论坛中搜jackozoo_2009.rar即可),非常感谢jackozoo贡献了这个例子。
一、解决的思路和方法
由于字节码没有加密等特殊处理,并且只vm了一段关键的算法代码,vm_context也没有变化。人肉应该不成问题。首先要知道有多少个字节码指令。
找到虚拟引擎的入口而后单步进去。
004010FB |. 68 68F14000 PUSH ZooCMa.0040F168 //该处开始存放字节码
00401100 |. E8 7B1A0000 CALL ZooCMa.00402B80 //虚拟引擎的入口
00401105 |. 83F8 01 CMP EAX,1 //判断返回值
进入之后立即来到下面的字节码指令循环处理的地方。
00402BD6 > > /8B9D ECFEFFFF MOV EBX,DWORD PTR SS:[EBP-114]
00402BDC . |0FB613 MOVZX EDX,BYTE PTR DS:[EBX]
00402BDF . |FF85 ECFEFFFF INC DWORD PTR SS:[EBP-114]
00402BE5 . |BB 00C04000 MOV EBX,ZooCMa.0040C000 //ebx就是handler表的首地址
00402BEA . |FF2493 JMP DWORD PTR DS:[EBX+EDX*4] ; dispatch
到handler表去看看,简单算一下一共59个,即十六进制0-3a
那么这0-3a到底是虚拟了哪3b条x86汇编指令呢?这里只有一个一个分析。
当然[EBP-124]等局部变量到底是表示vm_context中的哪个虚拟寄存器或虚拟临时变量需要参考虚拟引擎初始化或结束部分以及在调试过程中加以确定。例如:
00402B89 . 8985 CCFEFFFF MOV DWORD PTR SS:[EBP-134],EAX//[EBP-134]存放__eax
00402B8F . 898D D0FEFFFF MOV DWORD PTR SS:[EBP-130],ECX//[EBP-130]存放__ecx
00402B95 . 8995 D4FEFFFF MOV DWORD PTR SS:[EBP-12C],EDX//[EBP-12c]存放__edx
00402B9B . 899D D8FEFFFF MOV DWORD PTR SS:[EBP-128],EBX//[EBP-128]存放__ebx
00402BA1 . 8D85 00FFFFFF LEA EAX,DWORD PTR SS:[EBP-100]
00402BA7 . 05 00010000 ADD EAX,100
00402BAC . 8985 DCFEFFFF MOV DWORD PTR SS:[EBP-124],EAX //[EBP-124]存放__esp
00402BB2 . 8B45 00 MOV EAX,DWORD PTR SS:[EBP]
00402BB5 . 8985 E0FEFFFF MOV DWORD PTR SS:[EBP-120],EAX
00402BBB . 89B5 E4FEFFFF MOV DWORD PTR SS:[EBP-11C],ESI
00402BC1 . 89BD E8FEFFFF MOV DWORD PTR SS:[EBP-118],EDI
00402BC7 . 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8] //eax=入口参数 字节码首地址
00402BCA . 8985 C8FEFFFF MOV DWORD PTR SS:[EBP-138],EAX //[EBP-138] 字节码首地址
00402BD0 . 8985 ECFEFFFF MOV DWORD PTR SS:[EBP-114],EAX //__eip
简单分析两条指令:VCmp 、VJl
分析一下VCmp
004031DA . 8B9D DCFEFFFF MOV EBX,DWORD PTR SS:[EBP-124]
004031E0 . 8B13 MOV EDX,DWORD PTR DS:[EBX] //虚拟栈顶弹出一个DWORD
004031E2 . 83C3 04 ADD EBX,4
004031E5 . 8B0B MOV ECX,DWORD PTR DS:[EBX] //虚拟栈顶再弹出一个DWORD
004031E7 . 8385 DCFEFFFF>ADD DWORD PTR SS:[EBP-124],8 //调整__esp
004031EE . 3BD1 CMP EDX,ECX //进行比较
004031F0 . 9C PUSHFD
004031F1 . 58 POP EAX //save EFL to eax
004031F2 . 8BC8 MOV ECX,EAX
004031F4 . 83E0 01 AND EAX,1
004031F7 . 8985 F8FEFFFF MOV DWORD PTR SS:[EBP-108],EAX //save CF to __CF
004031FD . 8BC1 MOV EAX,ECX
004031FF . 83E0 40 AND EAX,40
00403202 . 8985 F0FEFFFF MOV DWORD PTR SS:[EBP-110],EAX //save ZF to __ZF
00403208 . 8BC1 MOV EAX,ECX
0040320A . 25 80000000 AND EAX,80
0040320F . 8985 F4FEFFFF MOV DWORD PTR SS:[EBP-10C],EAX //save SF to __SF
00403215 . 8BC1 MOV EAX,ECX
00403217 . 25 00080000 AND EAX,800
0040321C . 8985 FCFEFFFF MOV DWORD PTR SS:[EBP-104],EAX //save OF to __OF
00403222 .^ E9 AFF9FFFF JMP <ZooCMa.handler> // VCmp
分析一下VJl
004032FF . 8B9D ECFEFFFF MOV EBX,DWORD PTR SS:[EBP-114] //__eip
00403305 . 0FB713 MOVZX EDX,WORD PTR DS:[EBX] //edx=跳转偏移
00403308 . 8B85 F4FEFFFF MOV EAX,DWORD PTR SS:[EBP-10C] // __SF to eax
0040330E . 8B9D FCFEFFFF MOV EBX,DWORD PTR SS:[EBP-104] // __OF to ebx
00403314 . 3BC3 CMP EAX,EBX // __SF==__OF
00403316 . 74 10 JE SHORT ZooCMa.00403328 //not jmp
00403318 . 8B8D C8FEFFFF MOV ECX,DWORD PTR SS:[EBP-138] //ecx=字节码首地址
0040331E . 03CA ADD ECX,EDX //ecx=字节码中的目的地址
00403320 . 898D ECFEFFFF MOV DWORD PTR SS:[EBP-114],ECX //__eip=跳转目的地址
00403326 . EB 07 JMP SHORT ZooCMa.0040332F
00403328 > 8385 ECFEFFFF>ADD DWORD PTR SS:[EBP-114],2
0040332F >^ E9 A2F8FFFF JMP <ZooCMa.handler> // VJl
如此这般你就可以得到opcode 0-3a实现了哪些x86汇编指令的功能。如下表所示:
VNop 0 VPushImm8 1 VPushImm16 2 VPushImm32 3
VPushReg8 4 VPushReg16 5 VPushReg32 6 VPopReg8 7
VPopReg16 8 VPopReg32 9 VPushMem8 a VPushMem16 b
VPushMem32 c VpopMem8 d VPopMem16 e VPopMem32 f
VAdd8 10 VSub8 11 VMul8 12 VDiv8 13
VAnd8 14 VOr8 15 VXor8 16 VAdd16 17
VSub16 18 VMul16 19 VDiv16 1a VAnd16 1b
VOr16 1c VXor16 1d VAdd32 1e VSub32 1f
VMul32 20 VDiv32 21 VAnd32 22 VOr32 23
VXor32 24 VTest8 25 VTest32 26 VCmp 27
VJmp 28 VJz 29 VJnz 2a VJs 2b
VJns 2c VJl 2d VJle 2e VJge 2f
VJg 30 VRetN 31 VExit 32 VPushMem8Reg 33
VPushMem32Reg 34 VPopMem8Reg 35 VPopMem32Reg 36 VsaveReg 37
VloadReg 38 VPopMovsx 39 VsaveEsp 3a
二、逆向VM保护的算法
为了了解算法的大致流程,我们可以重点关注这段代码,因为算法循环总以jl语句判断是否要退出循环。
vjl.JPG
004032FF . 8B9D ECFEFFFF MOV EBX,DWORD PTR SS:[EBP-114] //关注ebx当前地址 下断
00403305 . 0FB713 MOVZX EDX,WORD PTR DS:[EBX]
00403308 . 8B85 F4FEFFFF MOV EAX,DWORD PTR SS:[EBP-10C]
0040330E . 8B9D FCFEFFFF MOV EBX,DWORD PTR SS:[EBP-104]
00403314 . 3BC3 CMP EAX,EBX
00403316 . 74 10 JE SHORT ZooCMa.00403328
00403318 . 8B8D C8FEFFFF MOV ECX,DWORD PTR SS:[EBP-138]
0040331E . 03CA ADD ECX,EDX
00403320 . 898D ECFEFFFF MOV DWORD PTR SS:[EBP-114],ECX //关注ecx 目的地址下断
00403326 . EB 07 JMP SHORT ZooCMa.0040332F
00403328 > 8385 ECFEFFFF >ADD DWORD PTR SS:[EBP-114],2
0040332F >^ E9 A2F8FFFF JMP <ZooCMa.handler> ; VJl
通过测试可以发现关键算法循环开始于40fa26处。大致可以看出算法是:判断当前密码字符+当前用户名字符是否等于000000db,在调试时发现进入算法循环后用户名字符的第二、三位总是为固定值,是由于进入循环前的如下两段代码所致。
在40f991处开始的一段字节码,修改用户名第二个字符为’q’
37 03 mov temp,_ebx 初始值0
03 01 00 00 00 push 01
06 08 push 00
20 mul
03 01 00 00 00 push 01
1E 00+01=01
06 05 push _ebp 用户名首地址入虚拟栈
1E 用户名首地址+1
09 03 pop _ebx 这样_ebx=用户名首地址+1
01 71 push 71 即’q’
35 03 mov [_ebx],71 修改用户名第二个字符为q
38 03 mov _ebx,temp 即mov _ebx,0
3A
在40f9d7处开始的一段字节码,修改用户名第三个字符为’p’
37 03 03 01 00 00 00 06 08 20
03 02 00 00 00 push 02
1E 00+02=02
06 02
1E
09 03 pop _ebx 这样_ebx=用户名首地址+2
01 70 push 70 即’p’
35 03 mov [_ebx],70 修改用户名第三个字符为’p’
38 03
3A
vadd.JPG
在40fa26处开始的一段字节码 关键算法call
3A ; jl 指令跳转到此 算法call开始处
37 03 mov temp,当前字符索引 保存计数变量i
03 01 00 00 00 push 1
06 02 push _edx ;用户名首地址
20 mul ;= = push _edx
03 64 00 00 00 push 00000064 ;+100
1E add ;= = push _edx+100
06 00 pus _eax ; + 字符索引 初始值为0
1E add
09 03 mov _ebx,当前密码字符地址
33 03 push 当前密码字符
39 06 mov _esi, 当前密码字符
38 03 mov _ebx, 当前字符索引
3A
37 03 mov temp,当前字符索引
03 01 00 00 00 push 00000001
06 02 push _edx ; 用户名首地址
20 mul; = = push _edx
03 00 00 00 00 push 0
1E add ; _edx + 0
06 00 push _eax ; 即为push I 字符索引
1E add ; _edx + 0+i
09 03 mov _ebx,当前用户名字符地址
33 03 push 当前用户名字符
39 07 mov _edi, 当前用户名字符
38 03 mov _ebx, 当前字符索引
3A
06 07 push 当前用户名字符
06 06 push 当前密码字符
1E add;当前密码字符+当前用户名字符
09 06 mov _esi,sum
3a
03 DB 00 00 00 push 000000db
06 06 push sum
27 cmp //当前密码字符+当前用户名字符=000000db
3A
2A 43 09 jnz ;错误注册码结束
3A
03 01 00 00 00 push 00000001
06 00 push I ;计数变量
1E ; i++
09 00 mov _eax, 计数变量值 ;更新当前比较的字符数
3A
06 01 push _ecx ;用户名长度
06 00 push计数变量值
27 cmp
3A
2D BE 08 Jl ;继续循环 40f168 +8be = 40fa26
3A
09 07 pop _edi
3A
09 06 pop _esi
3A
09 05 pop _ebp
3A
03 01 00 00 00 push 00000001
09 00 pop _eax ;mov _eax,1
3A
09 03 pop _ebx
3A
03 70 00 00 00 push 00000070
06 04 push _esp
1E _esp+00000070
09 04 pop _esp 调整虚拟堆栈指针
3A
31 add _esp,4
Mov eax,_eax ;return 1
Ret
算法大致如下:
name[1] ='q';
name[2] ='p';
for (int i=0;i<namelen;i++)
{
Regcode=name+100;
If ((name + regcode)<>0xdb) return 0;//出错
}
return 1;//成功
总结:VM保护非常有效,它可以浪费逆向者大量的时间和精力。简单的VM况且如此,遇到商业的,你的破解成本就太高了,得不偿失。
by 天易love 09-12-5 |
|