欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]P-Code指令的误解[吹毛求疵]

[原创]P-Code指令的误解[吹毛求疵]

最近在搞P-Code编码解码的引擎,于是对P-Code有了一些理解。
也发现了现在很多网络上关于P-Code文章的的一些错误。
首先,在昨天以前,我也一直认为P-Code只有一种形式的变长指令,在安于此生大牛的帖子里我也是这么说的,可是事实却不是如此。
目前关于P-Code指令格式的文章,国内好像基本都是转载国外的。
而国外的好像也都是同一出处,应该都是出自wktvbdebugger目录下那个chm文档。
那个文档里有应该算是当时最全面的P-Code指令的介绍了。我得承认wktvbdbg的作者真的很牛。
但是人都会犯错,大牛也不例外,今天在研究我那个引擎解析P-Code指令参数时,发现指令长度跟vbvm实际取的参数不一致。
起先一直认为是我错了。研究了好久,才发现是wktvbdbg的作者错了。先把代码发上来看看:

661007BF    > /0FB706              movzx   eax, word ptr ds:[esi]                    ;  取第2个WORD参数
661007C2    . |83C6 02             add     esi, 0x2                                  ;  增加指令指针
661007C5    . |42                  inc     edx
661007C6    . |74 05               je      short msvbvm60.661007CD
661007C8    . |8B75 A8             mov     esi, dword ptr ss:[ebp-0x58]
661007CB    . |03F0                add     esi, eax
661007CD    > |33C0                xor     eax, eax                                  ;  eax置零
661007CF    . |8A06                mov     al, byte ptr ds:[esi]                     ;  取下个操作码
661007D1    . |46                  inc     esi                                       ;  自增指令指针
661007D2    . |FF2485 5CD40F66     jmp     dword ptr ds:[eax*4+0x660FD45C]           ;  跳转到处理函数
661007D9    > |BB 9B631066         mov     ebx, msvbvm60.6610639B
661007DE    .^|EB D2               jmp     short msvbvm60.661007B2
661007E0    > |BB CE5E1066         mov     ebx, msvbvm60.__vbaForEachCollVar
661007E5    .^|EB CB               jmp     short msvbvm60.661007B2
661007E7    > |BB E7631066         mov     ebx, msvbvm60.661063E7                    ;  ebx放入函数地址
661007EC    > |0FBF06              movsx   eax, word ptr ds:[esi]                    ;  取第1个WORD参数
661007EF    . |83C6 02             add     esi, 0x2                                  ;  增加指令指针
661007F2    . |03C5                add     eax, ebp
661007F4    . |50                  push    eax
661007F5    . |FFD3                call    ebx
661007F7    . |8BD0                mov     edx, eax
661007F9    . |F7D2                not     edx
661007FB    .^ EB C2               jmp     short msvbvm60.661007BF                   ;  无条件跳转

这段代码是vbvm处理NextEachVar指令的函数(函数入口:0x661007E7)。
NextEachVar指令前缀是:FE 8C。
关于当前指令的长度,可以在wktvbdbg文档中的Lead3 Prefix 里找到。
8Ch NextEachVar 9 

文档中说这条指令长9 Bytes。
这里的9 Bytes不是实际的长度。
p-code指令格式:
<标准指令前缀> [扩展指令前缀] [定参] [变参]
因为NextEachVar是扩展指令,所以这里描述的9 Bytes是从扩展指令前缀开始算起的。
也就是说NextEachVar实际长10 Bytes(1 Byte标准指令前缀 + 9 Bytes)。
但是看上面的代码,很明显VB虚拟机只读取了2个WORD,合计4 Bytes。
加上1 Byte的扩展指令前缀,也就5 Bytes,这样算的话是文档中写错了。
可能有人会说在661007F5还call ebx了。这里万一取了4 Bytes呢?
在这里我也不想证明了,直接上个
VB Decompiler v3.6 has been released (Nov. 8, 2007)
What's new in this version:
- Displaying External API calls in Native Code
- New plugin LangeFree for editing language DLL name in VB programs (thanks to Executioner)
- All windows centered on the owner
- Methods parsing optimized
- Strings parser rewritten (now it also works with spaces after/before
  strings, and with zero bytes in string)
- Decompilation of commands with length > 9 in mnemonics mode (P-Code)
- Calculation of VTable offset in P-Code for PropertyPages and WebClasses fixed
- Command length in P-Code (NextEachVar, ExitProcFrameCb, ExitProcFrameCbStack, 
  LateMemNamed*, LateIdNamed*, VarLateMemCallLdRfVar) fixed

- Decompilation of "On Error" command (with FFFE and FFFF parameters)

[URL="http://www.vb-decompiler.org/history.htm"]VB Decompiler History[/URL]

这些指令长度的错误,在VB Decompiler 3.6版中被修复,时间是2007年11月8号。
我测试的结果表示,Semi VB Decompiler、wktvbdbg、exdec、p32dasm等常用P-Code反汇编器全部都败给这些指令。
能正确识别出这些指令实际长度的反汇编器只有VB Decompiler(商业软件就是不一样)。
这些指令前几个就不讲解了,上面也介绍了一个,而且这些都是固定长度的,修改一下就好了。
重要的是LateMemNamed*、LateIdNamed*这两种指令,这两种是新的变长指令。
0xFE前缀:
LateMemNamed* :
  /*A6h*/ LateMemNamedCall
  /*A7h*/ LateMemNamedCallLdVar
  /*A8h*/ LateMemNamedCallStVar
  /*A9h*/ LateMemNamedStAd
LateIdNamed* :
  /*AAh*/ LateIdNamedCall
  /*ABh*/ LateIdNamedCallLdVar
  /*ACh*/ LateIdNamedCallStVar
  /*ADh*/ LateIdNamedStAd

先看LateMemNamedCall在wktvbdbg文档中的定义:
A6h LateMemNamedCall 7 

LateMemNamedCall被定义成7 Bytes长,再说下变长指令:
在文档中变长指令长度被定义成-1,如下所示:
32h FFreeStr -1 

而wktvbdbg的文档中也只有这一种变长指令。
这种变长指令格式如下:
<指令前缀 1或2 Byte(s)> [尾部长度 1个WORD] [变参 WORD数组,长度由尾部长度定义]
这里的尾部长度描述的起始地址是:1或2 + 2的地址,简单点说就是尾部长度后面的成员的起始地址。
正常情况下尾部长度一定能被2整除,如果有余数会被vbvm忽略。
我的分析结果是LateMemNamedCall指令也是变长的,但是这里LateMemNamedCall指令与-1表示的变长指令结构是不一样的,先上代码:

66101167    >  0FB716              movzx   edx, word ptr ds:[esi]                    ;  后缀长度
6610116A    . |0FB746 02           movzx   eax, word ptr ds:[esi+0x2]
6610116E    . |83EA 04             sub     edx, 0x4                                  ;  后缀长度-4
66101171    . |0FB75E 04           movzx   ebx, word ptr ds:[esi+0x4]
66101175    . |83C6 06             add     esi, 0x6                                  ;  增加指令指针
66101178    . |33FF                xor     edi, edi
6610117A    . |B9 01000000         mov     ecx, 0x1
6610117F    > |57                  push    edi
66101180    . |D1EA                shr     edx, 1                                    ;  后缀长度右移1位 (后缀长度 / 2)
66101182    . |52                  push    edx
66101183    . |56                  push    esi
66101184    . |D1E2                shl     edx, 1                                    ;  后缀长度左移1位 (后缀长度 * 2)
66101186    . |03F2                add     esi, edx                                  ;  指令指针+后缀长度
66101188    . |53                  push    ebx
66101189    . |8BD4                mov     edx, esp
6610118B    . |83C2 10             add     edx, 0x10
6610118E    . |52                  push    edx
6610118F    . |51                  push    ecx
66101190    . |FF75 AC             push    dword ptr ss:[ebp-0x54]
66101193    . |50                  push    eax
66101194    . |FF75 B4             push    dword ptr ss:[ebp-0x4C]
66101197    . |E8 EC6D0000         call    msvbvm60.66107F88
6610119C    .^|E9 19FDFFFF         jmp     msvbvm60.66100EBA

以上代码就是vbvm处理LateMemNamedCall指令的函数(函数入口:0x66101167)。
从代码可以分析出LateMemNamedCall指令的格式:
2字节指令前缀  2字节后缀长度  2字节参数1  2字节参数2  变参 具体如下:
std      lead           tlen            wd1           wd2           变参
FE        A6           0006h          0000h        0000h        ......
其中后缀长度(tlen)描述的大小是从Word1(wd1)开始算起的地址到变参结束的大小。
这里假定tlen=6,这里的6 Bytes包括了wd1、wd2,所以在6610116E要把tlen-4。
因为在66101175,已经算过定参的长度了。
tlen-4=2,所以这里判定变参为1个Word(2 Bytes),LateMemNamedCall总长为10 Bytes。
你也许想问如果tlen为0会怎么样呢?那样tlen-4后,edx=0xFFFFFFFC(-4)。
这样会导致指令指针指向已经处理过的数据。
剩下的变长指令就不一一列举了,个别的如LateMemNamedCallLdVar,定参不一样而已。
以上是本人的分析结果,如有错误,欢迎指出!

上传的附件 1.png
0.png

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途。
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功