vb6 API and call backs

Author: David Zimmer
Date: 08.29.19 - 1:03pm

Next lets look at how vb6 handles call back routines. Consider the following code:
Private Declare Function CallWindowProc Lib "user32" _
    Alias "CallWindowProcA" ( _
    ByVal lpPrevWndFunc As Long, _
    ByVal hwnd As Long, _
    ByVal msg As Long, _
    ByVal wParam As Long, _
    ByVal lParam As Long _
) As Long

Private Sub Main()
    CallWindowProc AddressOf WindowProc, 1, 2, 3, 4
End Sub

Function WindowProc(ByVal hwnd As Long, ByVal msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
    MsgBox "In WindowProc : " & Join(Array(hwnd, msg, wParam, lParam), ",")
End Function
Lets start with a pcode compile:
401D88 Form1.Form_Load:
401D88    F5 04000000           LitI4 0x4
401D8D    F5 03000000           LitI4 0x3
401D92    F5 02000000           LitI4 0x2
401D97    F5 01000000           LitI4 0x1
401D9C    05 0000               ImpAdLdRf Module1.Proc_401DD8
401D9F    0A 01001400           ImpAdCallFPR4 user32.CallWindowProcA
401DA4    3C                    GetLastError
401DA5    FCC8                  End
401DA7    13                    ExitProcHresult 
Here is a screen shot of vbdec stopped just before the api call:

Module1.Proc_401DD8 is the address of the start of the pcode function, however we notice that ImpAdLdRf actually pushed a different value onto the stack. In my test case it actually pushed 4012D0. We then look at that address in our memdump pane and view the disassembly. Here we find:
4012D0 mov edx 401e84 ;last offset of Module1.Proc_401DD8 function 
4012D5 mov ecx 40104e
4012DA jmp ecx
401e84 is the last offset of Module1.Proc_401DD8 function
40104e = jmp [401024] = msvbvm60.ProcCallEngine

So here is the code flow. CallWindowProcA will transfer execution to 4012D0. This is a custom stub embedded in the vb6 executable .text section.

This stub moves the target pcode functions raw addr into edx and then through two jmp's sends execution to the vb runtime export ProcCallEngine.

After note: So I looked into the disassembler guts and ImpAdLdRf handler. The 0000 arg is actually a const pool index for...4012D0..the const pool viewer detects it an external api stub and resolves it to the pcode function automatically. The disassembler engine also recognized it as such and auto resolved it to the pcode function.

In this case did the disassembler actual do to much work and hide info on us? Tough call..I guess I have to say no, I rather have it resolved to the actual function to be called, but its good to know the nitty gritty of whats going on too!.

To be thorough I might as well also throw in how execution gets to the actual api.
401D9F    0A 01001400           ImpAdCallFPR4 user32.CallWindowProcA
ImpAdCallFPR4 takes two two byte arguments. 0014 is the stack adjustment that takes place. If you look at our prototype its 5 longs, 20 bytes, or 0x14 in hex. The first arg of 0001 is a const pool offset, here resolving to 00401C7C. This is used used in a direct call eax. Again this points to a small native stub:
.text:00401C7C                 mov     eax, dword_40233C
.text:00401C81                 or      eax, eax
.text:00401C83                 jz      short loc_401C87
.text:00401C85                 jmp     eax
.text:00401C87 ; ------------------------------------------- 
.text:00401C87 loc_401C87:                            
.text:00401C87                 push    401C64h
.text:00401C8C                 mov     eax, offset DllFunctionCall
.text:00401C91                 call    eax ; DllFunctionCall
.text:00401C93                 jmp     eax
In this clip of code we see a pointer address being check to see if its empty, if not we jmp to it. If it has not yet been loaded then it pushs the offset of some string pointers:
.text:00401C44                 dd 7
.text:00401C48 aUser32         db 'user32',0           
.text:00401C4F                 db 0
.text:00401C50                 dd 10h
.text:00401C54 aCallwindowproc db 'CallWindowProcA',0  
.text:00401C64                 dd offset aUser32       ; "user32"
.text:00401C68                 dd offset aCallwindowproc ; "CallWindowProcA"
.text:00401C6C                 dd 40000h
.text:00401C70                 dd offset unk_402334
.text:00401C74                 db    0

It then calls the msvbvm export DllFunctionCall export to load the dll, resolve the target function address then does a jmp to the api. Something in DllFunctionCall must know where to cache the lookup. The 402334 after the string pointers is probably the key since its very close to the known 40233C address. I might dig into that detail somewhere along the way, but we can observe it in action so would not be to hard to find if we really wanted explicit knowledge of where.

Since the stubs used jmp's, execution naturally returns from the win api call back to the lower portion of our ImpAdCallFPR4 handler. I will include the entire commented handler below:
ENGINE:6610664E _lblEX_ImpAdCallFPR4:                  
ENGINE:6610664E                 movzx   ecx, word ptr [esi] ; pool index
ENGINE:66106651                 movzx   edi, word ptr [esi+2] ; stack check arg
ENGINE:66106655                 add     esi, 4
ENGINE:66106658                 add     edi, esp        ; expected stack value on function return
ENGINE:6610665A                 mov     edx, [ebp-54h]  ; const pool
ENGINE:6610665D                 mov     eax, [edx+ecx*4] ; get value from pool
ENGINE:66106660                 or      eax, eax
ENGINE:66106662                 nop
ENGINE:66106663                 jz      errorVal        ; null check on pool value
ENGINE:66106669                 cmp     _g_EventMonitorsEnabled, 0
ENGINE:66106670                 jnz     ??0002200
ENGINE:66106676                 call    eax             ; call our embedded stub to look up api or used cached addr
ENGINE:66106678                 cmp     edi, esp        ; is the stack what we expected or corrupt?
ENGINE:6610667A                 jnz     StackErr_0
ENGINE:66106680                 xor     eax, eax
ENGINE:66106682                 mov     al, [esi]       ; get next pcode index
ENGINE:66106684                 inc     esi             ; inc pcode to next byte
ENGINE:66106685                 jmp     ds:_tblByteDisp[eax*4] ; now jump to next pcode handler from table
So now you have seen the entire chain for a call back.
  1. ImpAdCallFPR4 handler jmps to a custom native api lookup stub
  2. lookup stub will use a cached api addr or do dll lookup on first pass
  3. jmp actual api
  4. actual api will call back into another custom native stub
  5. second custom stub loads pcode method address and calls msvbvm60.ProcCallEngine
  6. pCode routines do its stuff, ExitProcI4 then returns execution back to the last native call in User32.CallWindowProcA api
  7. CallWindowProcA returns back to the lower portion of ImpAdCallFPR8 (66106678)
  8. a stack check is done to make sure no corruption occurred based on the expected value extracted from the second embedded bytecode arg
  9. pcode esi pointer is used to load the next opcode
  10. pcode execution resumes on its merry way!
Now that wasnt so complicated now was it?! I think I am going to write a childrens book on it. Also I am sorry if anyone's head explodes from reading this information. I will feel bad and hope the chair can be easily cleaned.

Comments: (0)

Leave Comment:
Email: (not shown)
Message: (Required)
Math Question: 20 + 26 = ? followed by the letter: Q 

About Me
More Blogs
Main Site
Posts: (All)
2024 ( 1 )
2023 ( 4 )
2022 ( 5 )
2021 ( 2 )
2020 ( 5 )
2019 (6)
     Yara WorkBench
     vb6 API and call backs
     UConnect Disable Cell Modem
2017 (5)
     IDA python over IPC
     dns wildcard blocking
     64bit IDA Plugins
     anterior lines
     misc news/updates
2016 (4)
     KANAL Mod
     Decoders again
     CDO.Message Breakpoints
     SysAnalyzer Updates
2015 ( 5 )
2014 ( 5 )
2013 ( 9 )
2012 ( 13 )
2011 ( 19 )
2010 ( 11 )
2009 ( 1 )