*岁末大盘点*
VB P-code -- 虚拟机的艺术
作者:cyclotron
初学crack的时候,总有不少大虾好心地提醒菜鸟们:别碰VB P-code的东东。不可否认,这确实是善意的提醒。VB P-code特殊的运作方式以及由此造成的天然的调试困难,是不少早期cracker难以忘却的恶梦。然而,随着各类专业的VB P-code Debugger和Decompiler层出不穷,VB P-code已经不再像以前那样遥不可及了。退一步来说,即使是使用通用调试器SoftICE或OllyDBG来跟踪VB P-code程序,只要掌握其原理,它也并非是不可战胜的。
VB P-code到底是怎样一种机制?它与普通的可执行程序有何不同?下面由我为大家来揭开VB P-code神秘的面纱……
众所周知,采用VB编写的应用程序有两种编译方式,一种是Native Code方式,另一种就是P-code方式。事实上,VB 4.0以前只采用P-code编译,VB Native Code是在VB 5.0以后发展起来的,其目的是为了在一定程度上改善VB应用程序的运行速度。VB P-code的运行速度较慢,这是由它本身的运行机制所决定的。
P-code,即Pseudo Code(伪代码),这一概念最早出现在Pascal编译器中,它是为了提供跨平台可移植性而产生的,实现这一编译机制的Pascal编译器被称为"Pascal P Compiler"。Sun公司在其推出的Java语言上也成功地实现了这种机制。Java程序的伪编译代码由一系列代表一定意义的字节码(byte code)组成,它们同属于一套特定的指令集,这种字节码不能由不同的CPU直接执行,而是要通过特殊的解释器翻译为CPU可以识别的指令才能执行,这种解释器就是我们常说的"虚拟机"。只要在不同的平台上提供虚拟机,把字节码翻译为对应的CPU指令集,也就实现了所谓的跨平台特性。Microsoft推出的VB P-code,实际上也是一组自定义的指令集,必须通过基于堆栈的虚拟机翻译为80X86上的指令集才能执行,担任虚拟机任务的就是msvbvm50.dll和msvbvm60.dll这两个动态链接库文件。由于在文件执行过程中多出了这一个解释的步骤,自然要影响到其执行的速度。正如我们所看到的,VB P-code并没有实现所谓的跨平台运行特性,这对于Pseudo这个词的起源是不恰当的;另一方面,采用P-code形式编译的VB应用程序的体积要小于采用Native Code形式编译的同样程序(这是由于P-code指令集的每一条指令对应于一组80X86指令所完成的任务),所以VB P-code实际上意味着VB Packed-code(压缩代码),用以强调VB P-code程序较小的代码体积。
作为程序员,他们现在已经拥有了充分的选择依据--速度,还是体积;然而,作为cracker,仅仅知道这些是远远不够的,正如本文的标题所道出的,我们需要了解的,是虚拟机的运作方式--这才是在调试中真正起作用的东西。
为了说明虚拟机的运作方式,我们写个小程序来调试一下,下面是这个小程序的源码:
代码:
Private Sub Command1_Click() X = InputBox("Please input an integer", "Input") If X <> "" Then Y = X + 2 X = Y * 3 Out.Text = X End If End Sub
代码:
6A360CF2 >PUSH EBP ;这里就是rtcInputBox的第一句指令了 6A360CF3 MOV EBP,ESP 6A360CF5 SUB ESP,54 6A360CF8 MOV EAX,DWORD PTR SS:[EBP+1C] 6A360CFB PUSH EBX 6A360CFC PUSH ESI 6A360CFD PUSH EDI 6A360CFE CMP WORD PTR DS:[EAX],0A 6A360D02 MOV EDI,80020004 6A360D07 JNZ MSVBVM60.6A360E6C 6A360D0D CMP DWORD PTR DS:[EAX+8],EDI 6A360D10 JNZ MSVBVM60.6A360E6C 6A360D16 OR DWORD PTR SS:[EBP-8],FFFFFFFF
代码:
6A37D2CD MOVSX EAX,WORD PTR DS:[ESI] 6A37D2D0 PUSH DWORD PTR DS:[EAX+EBP] 6A37D2D3 XOR EAX,EAX 6A37D2D5 MOV AL,BYTE PTR DS:[ESI+2] 6A37D2D8 ADD ESI,3 6A37D2DB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意这句 6A37D2E2 MOVSX EAX,WORD PTR DS:[ESI] ;我们来到这里 6A37D2E5 ADD EAX,EBP 6A37D2E7 PUSH EAX 6A37D2E8 XOR EAX,EAX 6A37D2EA MOV AL,BYTE PTR DS:[ESI+2] 6A37D2ED ADD ESI,3 6A37D2F0 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意这句 6A37D2F7 MOVSX EAX,WORD PTR DS:[ESI] 6A37D2FA POP EBX 6A37D2FB MOV WORD PTR DS:[EAX+EBP],BX 6A37D2FF XOR EAX,EAX 6A37D301 MOV AL,BYTE PTR DS:[ESI+2] 6A37D304 ADD ESI,3 6A37D307 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;注意这句
代码:
XOR EAX,EAX MOV AL,BYTE PTR DS:[ESI+2] ADD ESI,3 JMP DWORD PTR DS:[EAX*4+6A37DA58]
代码:
6A37D2E2 MOVSX EAX,WORD PTR DS:[ESI] ;esi指向待解释指令的操作数 6A37D2E5 ADD EAX,EBP ;取得某种形式的字符串指针 6A37D2E7 PUSH EAX ;压栈 6A37D2E8 XOR EAX,EAX ;eax清零 6A37D2EA MOV AL,BYTE PTR DS:[ESI+2] ;取下一条指令的操作码 6A37D2ED ADD ESI,3 ;移至下一条指令的操作数 6A37D2F0 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根据跳转地址表到下一条指令的解释单元
代码:
6A37D3B3 ADD EDI,EBP 6A37D3B5 PUSH EDI 6A37D3B6 XOR EAX,EAX 6A37D3B8 MOV AL,BYTE PTR DS:[ESI] 6A37D3BA INC ESI 6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;al=FBh
代码:
6A37D9BA XOR EAX,EAX 6A37D9BC MOV AL,BYTE PTR DS:[ESI] 6A37D9BE INC ESI 6A37D9BF JMP DWORD PTR DS:[EAX*4+6A37DE58] ;al=94h
代码:
6A384628 LEA EBX,DWORD PTR DS:[__vbaVarSub] 6A38462E JMP SHORT MSVBVM60.6A384610 6A384630 LEA EBX,DWORD PTR DS:[__vbaVarMul] 6A384636 JMP SHORT MSVBVM60.6A384610 6A384638 LEA EBX,DWORD PTR DS:[__vbaVarDiv] 6A38463E JMP SHORT MSVBVM60.6A384610 6A384640 LEA EBX,DWORD PTR DS:[__vbaVarIdiv] 6A384646 JMP SHORT MSVBVM60.6A384610 6A384648 LEA EBX,DWORD PTR DS:[__vbaVarMod] 6A38464E JMP SHORT MSVBVM60.6A384610 6A384650 LEA EBX,DWORD PTR DS:[__vbaVarAdd] ;我们跳转到这里 6A384656 JMP SHORT MSVBVM60.6A384610 6A384658 LEA EBX,DWORD PTR DS:[__vbaVarAnd] 6A38465E JMP SHORT MSVBVM60.6A384610 6A384660 LEA EBX,DWORD PTR DS:[__vbaVarOr] 6A384666 JMP SHORT MSVBVM60.6A384610 6A384668 LEA EBX,DWORD PTR DS:[__vbaVarXor] 6A38466E JMP SHORT MSVBVM60.6A384610 6A384670 LEA EBX,DWORD PTR DS:[__vbaVarEqv] 6A384676 JMP SHORT MSVBVM60.6A384610 6A384678 LEA EBX,DWORD PTR DS:[__vbaVarImp] 6A38467E JMP SHORT MSVBVM60.6A384610
代码:
6A384610 MOVSX EDI,WORD PTR DS:[ESI] ;取加法指令的操作数 6A384613 ADD EDI,EBP 6A384615 PUSH EDI ;操作数入栈 6A384616 CALL EBX ;这里调用了函数__vbaVarAdd执行加法操作 6A384618 PUSH EDI ;运算结果保存在堆栈结构中 6A384619 XOR EAX,EAX 6A38461B MOV AL,BYTE PTR DS:[ESI+2] ;继续取下一条伪指令的操作码 6A38461E ADD ESI,3 ;指向下一条伪指令的操作数 6A384621 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;跳向下一条伪指令的解释单元