*岁末大盘点*
VB P-code -- 伪代码的奥秘
作者:cyclotron
欢迎回到VB P-code伪代码世界。
前两期我为大家介绍了VB P-code虚拟机的运行机制和P-code专用调试器WKTVBDebugger,相信大家已经对VB P-code程序有所了解了。我们的任务当然不仅仅局限于理论研究,解析VB P-code的最终目的还是加密解密和逆向工程。由于Microsoft没有公开VB P-code伪代码的技术文档,我们无法获得现成的伪代码指令说明,而单凭VB P-code反编译器给出的助记符信息是远远不够的,这就要求我们自行发掘伪代码执行的奥秘。
可能有些朋友还不太明白,既然WKTVBDebugger作为一个伪代码级的调试器已经屏蔽了VB P-code虚拟机的解释过程,为什么还费神要去了解这些伪指令执行的细节呢?这里我请大家思考一个问题:假设你现在用WKTVBDebugger跟踪AddVar这句伪指令,你如何知道它的操作数和操作结果分别是多少?也许有人说,既然VB P-code虚拟机是基于堆栈的,那么操作数和操作结果一定存放在堆栈里了。不错,这些内容确实存在于堆栈中,但是你知道它的存放形式吗?是单个的操作数,是指针,还是其他复杂的数据结构?我敢说,如果你是第一次调试这句VB P-code指令,它一定会令你不知所措,即使你能最终解决这个问题,也一定会花上不少时间,结果也会令你觉得不可思议。此外,对于不同的P-code伪指令,其存放形式是各不相同的。如果你在调试一个软件时,不能看到每句指令的操作数和操作结果,那与静态反编译何异之有?WKTVBDebugger的调试功能还不是形同虚设?当然,这只是其中的一个方面。如果你连伪指令的抽象动作都不明白(毕竟不是所有的伪指令都能从其助记符看出其含义的),那又该如何呢?正如我前面所言,要理解这些伪指令的执行细节,是要费一番功夫的。
既然如此,我们应当如何解决这些问题呢?答案将由黄金组合OllyDBG+WKTVBDebugger+VBParser来揭晓。为了方便,这次使用的例子程序还是上两篇使用的VB P-code.exe,我们将通过跟踪虚拟机的解释过程来研究P-code伪代码的执行细节。
首先使用ljtt的VBParser(非常专业的VB P-code反编译器)解析VB P-code.exe,得到伪代码如下:
代码:
FileName: D:\Contribution\VB P-code\VB P-code--伪代码的奥秘\VB P-code.exe-----=====-----=====-----=====--Pcode--=====-----=====-----=====-----[CommandButton]Private Sub Command1_Click()'-=-=-=-=-=-=-= ProcAddr Range: [004019C4 - 00401A54] , ProcSize: 90 =-=-=-=-=-=-=-004019C4: 27 9C FE LitVar_Missing PushVarError 80020004 (missing) 004019C7: 27 BC FE LitVar_Missing PushVarError 80020004 (missing) 004019CA: 27 DC FE LitVar_Missing PushVarError 80020004 (missing) 004019CD: 27 FC FE LitVar_Missing PushVarError 80020004 (missing) 004019D0: 27 1C FF LitVar_Missing PushVarError 80020004 (missing) *********** Referent String: "Input" *********** |004019D3: 3A 4C FF 00 00 LitVarStr PushVarString Ptr_00401434004019D8: 4E 3C FF FStVarCopyObj [local_C4]=vbaVarDup(Pop)004019DB: 04 3C FF FLdRfVar Push local_C4*********** Referent String: "Please input an integer" *********** |004019DE: 3A 6C FF 01 00 LitVarStr PushVarString Ptr_00401400004019E3: 4E 5C FF FStVarCopyObj [local_A4]=vbaVarDup(Pop)004019E6: 04 5C FF FLdRfVar Push local_A4004019E9: 0B 02 00 1C 00 ImpAdCallI2 Call Ptr_00401020; check stack 001C; Push EAX004019EE: 46 7C FE CVarStr 004019F1: FC F6 8C FE FStVar (……省略)
代码:
6A37D153 MOV AL,BYTE PTR DS:[ESI] ;中断在这句,开始读操作码了,注意esi的值为004019C46A37D155 INC ESI ;使esi指向操作数6A37D156 JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根据跳转地址表和操作码寻址解释单元
代码:
6A37D39F MOVSX EDI,WORD PTR DS:[ESI] ;把字操作数带符号扩展到edi6A37D3A2 ADD ESI,2 ;esi指向下一句伪指令的操作码6A37D3A5 MOV WORD PTR DS:[EDI+EBP],0A ;ebp显然是程序堆栈区某处的基址,但不是堆栈顶的指针,它把0A保存到edi指向的偏移地址处6A37D3AB MOV DWORD PTR DS:[EDI+EBP+8],80020004 ;向下8个字节处存入80020004,根据VBParser的说明,这个数字表示空参数(缺省参数),事实上我在源代码中确实没有提供这个参数6A37D3B3 ADD EDI,EBP ;这次edi得到0A所在的虚拟地址6A37D3B5 PUSH EDI ;在堆栈中压入这个虚拟地址6A37D3B6 XOR EAX,EAX ;清空eax,准备读取下一句伪指令6A37D3B8 MOV AL,BYTE PTR DS:[ESI] ;读取下一句伪指令的操作码6A37D3BA INC ESI ;esi指向下一句伪指令的次级操作码或操作数6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58] ;根据地址跳转表和操作码寻址解释单元
代码:
0012F458 0012F494 ;栈顶0012F45C 000000000012F460 00000000…………………………………………0012F488 000000000012F48C 000000000012F490 000000000012F494 0000000A ;这就是刚才压入栈顶的数据了0012F498 000000000012F49C 800200040012F4A0 00000000
代码:
6A37D3B8 MOV AL,BYTE PTR DS:[ESI] ;esi=004019D36A37D3BA INC ESI6A37D3BB JMP DWORD PTR DS:[EAX*4+6A37DA58]
代码:
6A37D3C2 MOVSX EDI,WORD PTR DS:[ESI] ;第一个字操作数FF4C(堆栈区偏移量)带符号扩展到edi6A37D3C5 MOVZX EAX,WORD PTR DS:[ESI+2] ;第二个字操作数0000(数据区偏移量)无符号扩展到eax6A37D3C9 MOV EDX,DWORD PTR SS:[EBP-54] ;根据下一句指令来看,这是P-code程序数据区的基址6A37D3CC MOV EAX,DWORD PTR DS:[EDX+EAX*4] ;根据eax产生偏移量,取得数据区的数据,这里我们看到eax最后取得 一个虚拟地址,指向Unicode字符串"Input"6A37D3CF ADD EDI,EBP ;edi(堆栈区偏移量)指向堆栈区即将保存数据的地方6A37D3D1 MOV WORD PTR DS:[EDI],8 ;存入表示类型的数据6A37D3D6 MOV DWORD PTR DS:[EDI+8],EAX ;向下偏移8个字节处存入指向Unicode字符串"Input"的虚拟地址6A37D3D9 PUSH EDI ;最后堆栈区数据指针入栈6A37D3DA XOR EAX,EAX6A37D3DC MOV AL,BYTE PTR DS:[ESI+4]6A37D3DF ADD ESI,56A37D3E2 JMP DWORD PTR DS:[EAX*4+6A37DA58]
代码:
0012F444 0012F544 ;栈顶0012F448 0012F514 ;下面是前面其他指令形成地堆栈0012F44C 0012F4F40012F450 0012F4D40012F454 0012F4B40012F458 0012F4940012F45C 00000000…………………………………………0012F540 000000000012F544 000000080012F548 000000000012F54C 00401434 UNICODE "Input"0012F550 00000000
代码:
004019C4: 27 LitVar_Missing 0012F474h004019C7: 27 LitVar_Missing 0012F494h004019CA: 27 LitVar_Missing 0012F4B4h004019CD: 27 LitVar_Missing 0012F4D4h004019D0: 27 LitVar_Missing 0012F4F4h004019D3: 3A LitVarStr 'Input' ;这句就是我们在OllyDBG中跟踪的LitVarStr伪指令004019D8: 4E FStVarCopyObj 0012F514h004019DB: 04 FLdRfVar 0012F514h004019DE: 3A LitVarStr 'Please input an integer' 004019E3: 4E FStVarCopyObj 0012F534h004019E6: 04 FLdRfVar 0012F534h004019E9: 0B ImpAdCallI2 rtcInputBox on address 73472265h
代码:
0012F424: 0012F524 0012F4F4 ;注意这里的栈顶0012F5240012F41C: 0012FE3C 0000004E…………………………………………0012F3B4: 00000000 000000000012F3AC: 77E6780F 0000008C
代码:
0012F524:08 00 00 00 00 00 00 000012F52C:34 14 40 00 00 00 00 00 ;好了,还记得那个8个字节的偏移吗?00401434就是我们需要的那个数据区字符串的指针了!0012F534:00 00 00 00 00 00 00 00
代码:
00401434:49 00 6E 00 70 00 75 00 I.n.p.u.0040143C:74 00 00 00 00 00 00 00 t.......00401444:00 00 00 00 E1 4E AD 33 ....酦?
联系客服