VB6 PCode Disassembly
Author: David Zimmer
Date: 08.05.19 - 10:02pm
So if you saw the last post you know I am working on a VB6 disassembler and pcode debugger. Its a project I have wanted forever and never had the time for.
Now part of writing the disassembler is having to resolve opcode arguments. They are all undocumented, so each one has to be reverse engineered. Some are easy, some not!
The most important ones are those relating to code flow to give you a meaningful (and accurate!) disassembly.
Right now I am working on the LateIdCall instruction which depends on not just the opcode byte stream itself, but also the object variable passed into it which is dependent on multiple previous instructions.
For an example usage consider the following code calling ucFunc1 on the usercontrol uc embedded in current form.
vb code: me.uc.ucfunc1 403083 21 FLdPrThis 403084 0F FC02 VCallAd Form1.uc 403087 19 78FF FStAdFunc var_88 40308A 08 78FF FLdPr var_88 40308D FEA0 000003600000 LateIdCall Form1.uc.ucfunc1Looks pretty simple right...So for this lookup to work there are 4 resolutions that needed to happen.
Moral of the story..you essentially have to write a basic decompiler just to get meaningful code flow disassembly here. Even with a good API to work with its still a lot to go through for one lookup.
Everything in stages..only 1163 more opcodes to go! I will say having a working pcode debugger does make life much easier at this point. Bootstrapping your way to better understanding. Now I can at least watch the stack live from the vantage point of the actual pcode.
Also in terms of testing, you have to discover all the variations in how the opcode is used, what triggers its generation, the variations its used on, and output patterns. For this I made a Search function I can trigger through the script automation interface. I load a big project, scan it with one line of code, and the results popup automatically in an offset viewer form where I can click to navigate. Anything less would be barbaric given how much testing this demands.
An example of a simple one would be the For/Next and ForEach loops
40273B FE62 76FF2300 ForUI1 ;FF76 = var counter location (UI1 = as byte counter) 402741 0A 00000000 ImpAdCallFPR4 DoEvents ;0x23 = func start + 0x23 = loop end goto 40274F 402746 04 7AFF FLdRfVar var_86 402749 FE78 76FF1500 NextUI1 ;FF76 = var counter location 40274F 13 ExitProcHresult ;0x15 = func start + 0x15 = loop next inst 402741 40278E FE76 60FF0C001E00 ForEachAryVar - as variant - 0c = VT_VARIANT 40278E FE76 60FF06001E00 ForEachAryVar - as currency() - 06 = VT_CY 4027AE FE76 60FF04001E00 ForEachAryVar - as single() - 04 = VT_R4 Dim bb As Long: For bb = 0 To 6 Step 2 4030C2 F5 00000000 LitI4 0x0 4030C7 04 74FF FLdRfVar var_8C 4030CA F5 06000000 LitI4 0x6 4030CF F5 02000000 LitI4 0x2 4030D4 FE6C 6CFF9300 ForStepI4 Dim bb As Currency: For bb = 0 To 6 Step 2 4030CB F4 02 LitI2_Byte 2 4030CD EF CCyI2 4030CE FE6F 60FF8D00 ForStepCyIf you know how to ask the right questions, the compiler and pe builder will tell you the right answers.
This project is like candy for a reverse engineer. No obsfuscation and full of delicious little puzzles everywhere you look. There are so many elements to it, its literally a symphony all working together.