初识p-code (pcode)

来源:互联网 发布:美工专业培训 编辑:程序博客网 时间:2024/05/17 16:00

 

                                              来文来源于我的新浪博客 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).                                    
           

 

 

以上为作者猜测,作者力求准确,但无法保证完全正确。

 

转载时请注明出处。

 

                                                   

 

原创粉丝点击