VB6 PCode Disassembly

Author: David Zimmer
Date: 08.05.19 - 10:02pm

Note: Vbdec is now available and has its own product page

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.ucfunc1
Looks pretty simple right...So for this lookup to work there are 4 resolutions that needed to happen.
  1. VCallAd had to know which embedded control to resolve the 0x2fc offset to and which form its embedded on (here from FLdPrThis)
  2. FStAdFunc and FLdPr had to know which variable the result was saved in
  3. LateIdCall had to know ALL of the above before it even started parsing the byte stream arguments to the opcode itself.
Below is the current post processor for LateIdCall, which is still incomplete..so far it is only handling userControl types, I will still have to do typelibrary and guid lookups for all others! (More notes below)

Function Post_LateIdCall(disasm, cb As CCodeBody, ByVal va As Long) As String 
    '43EB7E    0F 4804             VCallAd Form2.txtJS
    '43EB81    19 78FF             FStAdFunc var_88
    '43EB84    08 78FF             FLdPr var_88
    '43EB87    FEA0 6E0003600000   LateIdCall
    Dim var As String, va2 As Long, setDisasm As String
    Dim vcallDisasm As String, parts As String
    Dim tmp() As String, cco As CCodeObject
    Dim methodIndex As Integer, cb2 As CCodeBody
    Dim cc As CControl, cco2 As CCodeObject
    Post_LateIdCall = disasm 'default retval
    va2 = va 'va2 will be modified by calls
    If Extract(modPCode2.lastDisasmLine, "var_", "", var, True) = 0 Then Exit Function
    setDisasm = cb.ScanForPrev("FStAdFunc " & var, va2)
    If Len(setDisasm) = 0 Then Exit Function
    vcallDisasm = cb.ScanForPrev("VCallAd", va2)
    If Len(vcallDisasm) = 0 Then Exit Function
    If Extract(vcallDisasm, " ", "", parts) = 0 Then Exit Function
    If InStr(parts, ".") < 1 Then Exit Function 'it wasnt resolved
    tmp() = Split(parts, ".") 'Form.controlName
    Set cco = vbp.CodeObjFromName(tmp(0))
    If cco Is Nothing Then Exit Function
    For Each cc In cco.EmbeddedControls
        If cc.name = tmp(1) Then Exit For
    If cc Is Nothing Then Exit Function 'zeroed on normal end of loop unless exit hit
    methodIndex = modPCode2.File16(va + 2)
    If cc.ControlType = "UserControl" Then
        Set cco2 = vbp.CodeObjFromName(cc.BaseExtName)
        If cco2 Is Nothing Then Exit Function
        methodIndex = methodIndex + 1
        If methodIndex < 1 Or methodIndex > cco2.Methods2.count Then Exit Function
        Set cb2 = cco2.Methods2.BaseCollection(methodIndex)
        Post_LateIdCall = "LateIdCall " & parts & "." & cb2.displayName()
    End If
End Function

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       ForStepCy
If 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.
  • Intricate nested file structures, file offsets galore,
  • disassembly engine that takes embedded opcode arguments and requires the context of previous instructions,
  • the ability to build a live debugger for it,
  • able to generate as many test binaries as you want watching variations,
  • native debugging of the runtime w/symbols to watch how it operates
  • complex dev tasks and designing efficient GUI behaviors
Just...delicious....nom nom nom

Comments: (0)

Leave Comment:
Email: (not shown)
Message: (Required)
Math Question: 2 + 22 = ? followed by the letter: E 

About Me
More Blogs
Main Site
Posts: (All)
2022 ( 2 )
2021 ( 4 )
2020 ( 8 )
2019 (12)
     Yara WorkBench
     vbdec dbg updates
     vb6 PCode NOP
     vb6 API and call backs
     how pcode works Pt1
     Reversing PCode Args
     VB6 PCode Disassembly
     VB6 PCode Debugger
     UConnect Disable Cell Modem
2017 (5)
     IDA python over IPC
     dns wildcard blocking
     64bit IDA Plugins
     anterior lines
     misc news/updates
2016 ( 4 )
2015 ( 6 )
2014 ( 5 )
2013 ( 9 )
2012 ( 13 )
2011 ( 19 )
2010 ( 11 )
2009 ( 1 )