来文来源于我的新浪博客 http://blog.sina.com.cn/zd9
突然对VB编写的程序好奇,闲来无事,研究几天,有些收获,留下这些文字,对那些第一次接触p-code的人,也许会有帮助,国内采用P-code代码,比较有名的软件之一有金蝶财务软件。我现在的水平足以解开一个复杂的VB语言编写的软件系统。
p-code全称是pseudo-code,就是伪代码的意思,在VB编译中,有两种编译方式,一种是Native-Code(本地代码),另一种就是p-code,它由MSVBVM6.0.DLL翻译,交CPU运行。
当第一次跟踪VB生成EXE文件时,你会发现根本没法跟踪,尽管对INTEL指令系统相当清楚,也毫无帮助,后来才明白我跟踪的是解释器,程序总在一个地方打转转,原来自己是在打祝家庄,象迷宫一样。VB可以生成p-code代码,它是一种虚拟机代码,由微软定义的,微软接触它的人,要签保密协议,所以外人只能猜测,p-code出现很多年了,这方面的资料很少,中文资料更少,我想可能是市场需求并不是很大,研究它的人,有些是非常优秀的,当时结果激动人心,最著名的成果是西班牙破解小组的WKT VB Debugger,目前研究p-code的人非常少,属偏门,原因可能是动力不足了,现在的动态调试工具还是10年前的老样子。静态工具最好的可能是VB Decompiler,至今仍然在不断更新,可在翻译某些程序时会死机,也许是我不会用吧。在国内没什么好文章,很多文章,感觉作者也不太清楚p-code,很不具体,解密的软件也是很简单的软件,文章写得象说评书,嘴一张,北京就到了,不到首都的人听起来蛮好听,对我们这些很向往北京的人根本就没用。我认为解密最高境界是,对程序不做修改,但大部分情况下这是不可能的,比如,程序带一只加密狗,除非你仿造一只,如果做不到,非改程序不可,所以解密插入一段代码是基本功,但在p-code里这样做比INTEL难。
看了下面的说明,你就会明白p-code语言插入语句为什么困难,如果你懂INTEL汇编,这事并不太难,加句NOP(90)很easy,但p-code不一样,他的语句大部分都与堆栈有关,比如:p-code里面转向语句一共有三个,
Branch (1E) ---- 无条件跳转
BranchT(1D) ---- 栈顶数据为真则跳转
BranchF(1F) ---- 栈顶数据为假则跳转
第一次接触p-code的人会想当然的认为,解密时可以把条件跳转,换成无条件跳转,比如BranchT(1D) 换成Branch(1E)但这是错误的,我想这也是解密者犯的第一个错误,因为BranchT branchF都有一个退栈动作,而Branch与堆栈无关,所以只能用BranchT与branchF互换,下面给出一段我想像的程序,看看他们与通常的机器语言有什么不同,假设一个过程,被调入的基址为00004000,我不知道怎么用p-code中标准术语描述,就称之为一个过程吧。在解密时,你不要想插入所谓空指令,因为栈会乱,改动程序要当心。
4000 …
…
4100 6C 7A FF ILdRf 100 堆栈指针SP-4,将内存FF7A 中的内容100压入堆栈,这里假设FF7A中数
为100,FF7A 为虚拟机的内存地址,所对应具体物理地是一个基址
与FF7A符号扩展为双字之和,在这就是基址加0xFFFFFF7A,
网上所有介绍p-code中文 资料,说的都不清楚,所以再详细说说,
假设p-code指令中地址为0x00 0x80,解释器基地为0x0012e000,
那么你在调试时,看到的具体物理地为,0x0012e000+0xffff8000。
4103 6C 7C FF ILdRf 200 堆栈指针SP-4,将内存FF7C 中的内容200压入堆栈,这里假设内存FF7C
中是200
4106 DB GtI4 退栈SP+4,比较栈顶二数,也就100>200吗?在这不成立,
因为100不大于200,所以条件为假,将0(也就是假)推进栈顶,
如果真是 0xfffffff,比较指令也会退栈,这是INTEL没有的。
4107 1C 00 02 BranchF 4200
判断栈顶值,然后退栈SP+4,这里因为栈顶为假,所以程序将
转到4200运行 BranchF 是三字节指令 操作码为1C后面跟偏移量,
P-CODE 偏移量与一般机器码不同,转向地址是,调入块的起始地址
值加偏移,也就是一个过程中跳到固定位置时,偏移量是一样的,
而 INTEL中是以下一条地址为基址,比如EB FE 就表 JMP $ 死循环,
而P-CODE中,死循环的偏移取决
于此过程调入的程序起始位置,这个例子我假设起始为4000,
所以偏移200 也就是 00 02。转向的位置是4200。
4200 F5 00 03 00 00 Lit4 300 SP-4 将立即数300压入堆栈
4205 F5 00 04 00 00 Lit4 400 SP-4 将立即数400压入堆栈
B2 MulI4 栈顶二数相乘,这里是300*400 退栈
SP+4, 结果放入栈顶
71 60 FF FStR4 将栈顶数存入内存,退栈SP+4,FStR4是一个三字节指令,操作
码为71 后面二字节为虚拟机内存地
址, 比如可以是 71 60 FF ,表示内存地址为FF60,在INTEL中具体
地址 是一个虚拟机基址加上0xFFFFFF60,
…
F4 FE Lit2 FE 这是把一个字节FE压入堆栈,p-code是以32位为单位,实际上这是
把FFFFFFFE压入栈,字节以符号扩展。
70 7A FF FStI2 将栈顶存入内存,然后退栈,理解这二句对解密者很重要,这二条指令
可以解释为赋值语句, 这句的意思是把一个字(WORD )存入存储器,
存储器地址为FF7A 假设存储变量为X,也就是X=0xFFFE,如果是双字
可以用FStR4(71).
以上为作者猜测,作者力求准确,但无法保证完全正确。
转载时请注明出处。