Results 1 to 29 of 29

Thread: Injecting a VB6 dll and manipulating the UI

  1. #1

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Unhappy Injecting a VB6 dll and manipulating the UI

    Hi guys.

    I am new here. I'll try to keep it simple.

    Mission: Manipulate a legacy VB6 app UI.

    The strategy:
    1. Create a VB6 ActiveXDll
    2. inject it into the process using SetWindowHookex
    3. set a timer for handling execution outside the handler itself
    4. Do as I please with the UI

    I have reached step 4 after much effort, Yet I find no way to access the forms.
    I have read that the 'Forms' collection is not accessible via activex dll. as a matter of fact - the entire VB.Global is not accessible.

    So my friends - any VB6 guru here can show me the way?
    Given that I have an dll that is running - is it possible for me to gain access to the open forms?

    (I thought about obtaining the IAccessible pointer and then try to manipulate it into a form "pointer" yet I find no clues for that, nor do I know if the form class itself is COM accessible, or what is it's COM GUID if any.)

    Thank you!

  2. #2
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    36,629

    Re: Injecting a VB6 dll and manipulating the UI

    Do you have the source code for the VB6 app? I assume you don't, but you didn't say. Is the point the manipulation, or is the point the ActiveXDll? It seems like it would be the former via the latter, in which case the latter may be irrelevant, and it may be easier to do things with the form without the ActiveX approach, so the objective is kind of important rather than the particular mechanism used. Finally, what kind of manipulation? Some are much easier than others, and have simpler means available.
    My usual boring signature: Nothing

  3. #3
    Fanatic Member
    Join Date
    Jun 2015
    Posts
    519

    Re: Injecting a VB6 dll and manipulating the UI

    the standard approach is to use the FindWindow API, enumerate children handles, then sendwindow message to do stuff.

    here are some scrapers: https://github.com/dzzie/libs/blob/m...indow.cls#L195

    Code:
    Function CopyListBox(Optional toListBox As Object) As Collection
    Function CopyComboBox(Optional toCombo As Object) As Collection
    Function CopyConsole(Optional hwnd As Long) As String
    Function IEDOMFromhWnd() As Object 'HTMLDocument
    Function FindChild(ByVal className As String) As Cwindow
    Function CopyRemoteListView(Optional destLV As Object, Optional colSeperator As String = vbTab) As Collection
    Function CopyRemoteTreeView(Optional destTV As Object) As Collection
    Something like
    Code:
     Dim c2 As Collection, wTv As Cwindow, tmp As String
     Dim c As Collection, w As Cwindow, kanal as CWindow
    
       Set c = ChildWindows()
        For Each w In c
            If VBA.Left(w.Caption, 5) = "KANAL" Then
                'Debug.Print Now & " - found kanal window attaching to " & w.hwnd & " (" & Hex(w.hwnd) & " )"
                Set kanal = w
                If kanal.isValid Then
                    Set wTv = kanal.FindChild("SysTreeView32")
                    If wTv.isValid Then
                         Set c2 = wTv.CopyRemoteTreeView()
                         tmp = ColToStr(c2)
                         Clipboard.Clear
                         Clipboard.SetText tmp
    This example isnt exactly the same, but is as close as I have. Adds new functionality to an existing C form using vb6:
    http://sandsprite.com/blogs/index.ph...=407&year=2016

    overview:
    - load a c dll which is a plugin for another app and launch it as if we were the normal parent app
    - modify the plugins res file form definition to add a button
    - subclass the window looking for button clicks
    - on click of new button find the tree view on the form
    - copy its contents to clipboard

    enumerating windows and sending messages or sendkeys can do a lot without having to be in process.
    if you have to subclass controls then you will want to be in process. I wouldnt really do it in vb6 though.

    I have never explored trying to access the main exe COM objects from within an injection only from legit plugins
    started by the host app. It would be interesting, I am sure there is a way to do it, but you will have to reach into the
    runtime itself to find pointers to live com objects. offsets will vary based on runtime version number
    Last edited by dz32; Jun 30th, 2021 at 06:39 PM.

  4. #4

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    Wow guys...It was a shot in the dark and here I get people to converse with.
    I was so lonely, so thank you so much for your answers!

    back to business

    "Do you have the source code for the VB6 App?"
    you assume correctly. I have not.

    "Is the point the manipulation, or is the point the ActiveXDll?"
    The manipulation. I ellaborated on the mechanism just to give some context and to show I have put the effort before I reached out.

    "Finally, what kind of manipulation?"
    I am developing a 3rd party "addon" to a legacy app which is supposed to allow "enhancement".
    The code itself cannot be changed, but there are many processes that must be changed to allow incorporation of external data.

    first off, there is the issue of reading\extracting data from controls.
    Of course for simple controls, there are other ways to go (UIA, Accessibility, Win32 etc.)
    BUT - for more complex third party controls (like MSFlexGrid) reading data is much more complex.

    Things get interesting (nice word for hard) when I want to write back data into the program.
    using methods such as RPA for clicks just doesn't cut it. nor does subclassing.
    I wish to have true access to the components, and have all the VB6 capabilities at my disposal.

    when I think about it : It goes even beyond "GUI" manipulation.
    There are places where I want to completely ovveride the behaviour, and instead of an old form opening, being filled, and then the resulting object is passed downstream,
    I want to hijack the form even being opened, create my own data object based on external data, and pass THAT downstream of the flow.

    And if I may revisit part my question again - assuming I am inside the app with a DLL, how can I grab hold of any pointer to an open form, or even all the open forms.


    Regarding dz32 super details answer - this approach is a "fallback" for me and will leave me with limited functionality I believe.
    because I intend on using VB6 decompiler to locate the methods I wish (dream?) to invoke public method of forms to hijack the flow and insert my own data.

    Thank you guys.
    This kind of help is not to be taken for granted.

  5. #5
    Frenzied Member
    Join Date
    Feb 2015
    Posts
    1,834

    Re: Injecting a VB6 dll and manipulating the UI

    how can I grab hold of any pointer to an open form, or even all the open forms.
    The simpliest way is to access the VB.Global.Forms collection of a project and then get all the forms. To get VB.Global object you can inspect VBHeader.lpProjectData->lpExternalTable items and search for items with the tag (at 0 offset) equals to 6. The item value (at 4 offset) is a pointer to an object descriptor which consists of a pointer to the object CLSID (at zero offset) and pointer to the Global object itself (at 4 offset).

    So you can extract this pointer and call AddRef method then you can cast this pointer to a VB.Global object variable. From this moment, the project methods and properties (like App/Screen/Forms etc.) are available to you.

  6. #6

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    WOW...
    Will do.

    I started implementing the code as best as I can, following your instructions.

    I have a problem following this one:
    " search for items with the tag (at 0 offset) equals to 6"

    I was unable to find in the forums or in google the structure of such "items" so I do not know how to iterate over them.
    can you direct me to the structure? if not, what size is it so I can iterate it?

    Here is what I got so far:

    Code:
    Private Sub Form_Load()
        Dim vbHeader As Long
        vbHeader = GetVBHeader()
        MsgBox CStr(vbHeader)
        Dim lpProjectData As Long
        lpProjectData = vbHeader + &H30
        
        Dim lpExternalTable As Long
        lpExternalTable = lpProjectData + &H234
        
        'What next???
        
    End Sub
    
    ' // Get VBHeader structure
    Private Function GetVBHeader() As Long
        Dim ptr     As Long
        Dim hModule As Long
        hModule = GetModuleHandle(ByVal "Project1.exe")
        ' Get e_lfanew
        GetMem4 ByVal hModule + &H3C, ptr
        ' Get AddressOfEntryPoint
        GetMem4 ByVal ptr + &H28 + hModule, ptr
        ' Get VBHeader
        GetMem4 ByVal ptr + hModule + 1, GetVBHeader
        
    End Function
    I will be so happy if it works.
    Thank you thank you thank you.

  7. #7

  8. #8

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    ohhh man. I have been programing for 15 years. I never reached out to a community like this.
    I feel like I missed so much. this is a game changer for me.

    I will describe my approach for injecting the dll.

    not sure it will go to production, but this is what I use now for closing a loop and making sure all the parts work.

    1. Created a C++ (using VS2019) native dll. lets call it NativeHook.dll
    2. Created a C# project that calls SetWindowsHookEx for WM_GETMESSAGE, and using the NativeHook.dll as it's module. the target process is the VB6 App.
    3. once the NativeHook.dll is loaded, it loads a VB6 activeX dll (using LoadLibrary), and invokes a function in it. lets call it InjectedVb.dll.
    4. the invoked function in InjectedVb.dll starts a timer to postpone the initialization of the stuff needed later, so not to hold the msg queue for too long.
    5. once the timer is raised, I have my own nice VB6 code that runs in the same thread of the VB6 App ui thread, with a runtime that looks well alive.

    that code in the injected dll will soon be replaced with your code and let the magic begin!!!!!!!!
    Last edited by Broder; Jul 1st, 2021 at 03:47 PM.

  9. #9
    PowerPoster
    Join Date
    Feb 2006
    Posts
    22,865

    Re: Injecting a VB6 dll and manipulating the UI

    Wouldn't it be far easier just to rewrite the program from scratch?

  10. #10
    Fanatic Member
    Join Date
    Jun 2015
    Posts
    519

    Re: Injecting a VB6 dll and manipulating the UI

    so I played around a bit with this based on the tips from trick. I will describe the complete process.

    first I made an exe which set a textbox to hex(objptr(vb.global)) so I knew what value I should be searching for.
    then I opened that exe up in vbdec to look at the vb headers and offsets.
    then I attached ollydbg to the process and started probing those offsets

    I ended up finding the path trick mentioned to the vb.global pointer. set a memory write breakpoint on that offset and reloaded.
    its set in MSVBVM60.6601802F 8908 MOV DWORD PTR DS:[EAX],ECX which is _TipRegAppObject

    (vbruntime: EEBEB73979D0AD3C74B248EBF1B6E770 with symbols: http://sandsprite.com/vb-reversing/files/msvbvm60.zip )


    Then I loaded up a complex project set a breakpoint at my runtime offset (6601802F) to catch the value it will write
    for me it was (These offsets are relative to my exe but included for clarity)

    offset to write to: 0047D11C (eax)
    value (ecx): 02BF1294 -> 660130D0 MSVBVM60.??_7CVBApplication@@6BVBGlobal@@@

    Next I examine the structures in vbdec to get offsets to walk in olly

    Code:
    VB Header
    VA          rel offset    name                  value
    4048A8   0x30        aProjectInfo           40540C
    
    VB Header.aProjectInfo @40540C
    405640   0x234   aExternalTable         404FF8
    405644   0x238   ExternalCount          3E
    
    at 404FF8 I find data such as 
    
    00404FF8  00000007
    00404FFC  00429470  PDFStrea.00429470
    00405000  00000007
    00405004  0042942C  PDFStrea.0042942C
    00405008  00000007
    0040500C  004293E8  PDFStrea.004293E8
    ... many more entries ....
    004051A8  00000006                 <--- flag we are looking for
    004051AC  0041CC7C  PDFStrea.0041CC7C     <---
    
    Looking at 0041CC7C
    
    	0041CC7C  0041C95C  PDFStrea.0041C95C  -> offset + 0 points to clsid/IID
    	0041CC80  0047D11C  PDFStrea.0047D11C  <-- offset+ 4 value we saw written to in _TipRegAppObject 
    
    looking at 47D11C
    	0047D11C  = 02BF1294 -> 660130D0  MSVBVM60.??_7CVBApplication@@6BVBGlobal@@@
    
    Then from there you can work with the vb.global object (sorry didnt switch to relative offsets before closing process)
    
    660130D0 >660E2074  MSVBVM60.?QueryInterface@CVBApplication@@UAGJABU_GUID@@PAPAX@Z
    660130D4  6601808A  MSVBVM60.?AddRef@CVBApplication@@UAGKXZ
    660130D8  66028553  MSVBVM60.?Release@CVBApplication@@UAGKXZ
    660130DC  6605CE6B  MSVBVM60.?Load@CVBApplication@@UAGJPAUIDispatch@@@Z
    660130E0  6605CEE2  MSVBVM60.?Unload@CVBApplication@@UAGJPAUIDispatch@@@Z
    660130E4  66026F9D  MSVBVM60.?get_App@CVBApplication@@UAGJPAPAV_AppObject@@@Z
    660130E8  6603D750  MSVBVM60.?get_Screen@CVBApplication@@UAGJPAPAV_ScreenObject@@@Z
    660130EC  660489C7  MSVBVM60.?get_Clipboard@CVBApplication@@UAGJPAPAV_ClipboardObject@@@Z
    660130F0  660E1EC6  MSVBVM60.?get_Printer@CVBApplication@@UAGJPAPAV_PrinterObject@@@Z
    660130F4  660E1EF8  MSVBVM60.?putref_Printer@CVBApplication@@UAGJPAV_PrinterObject@@@Z
    660130F8  66048EAE  MSVBVM60.?get_Forms@CVBApplication@@UAGJPAPAUIDispatch@@@Z
    660130FC  660E1EE0  MSVBVM60.?get_Printers@CVBApplication@@UAGJPAPAUIDispatch@@@Z
    66013100  660E1FA0  MSVBVM60.?LoadResStringOld@CVBApplication@@UAGJFPAPAG@Z
    66013104  6604A7C3  MSVBVM60.?LoadResPicture@CVBApplication@@UAGJUtagVARIANT@@FPAPAUIPictureDisp@@@Z
    66013108  6604AB06  MSVBVM60.?LoadResData@CVBApplication@@UAGJUtagVARIANT@@0PAU2@@Z
    6601310C  660E1FD9  MSVBVM60.?LoadPictureOld@CVBApplication@@UAGJUtagVARIANT@@PAPAUIPictureDisp@@@Z
    66013110  6604776E  MSVBVM60.?SavePicture@CVBApplication@@UAGJPAUIPictureDisp@@PAG@Z
    66013114  6602C9D8  MSVBVM60.?LoadPicture@CVBApplication@@UAGJUtagVARIANT@@0000PAPAUIPictureDisp@@@Z
    66013118  6604A683  MSVBVM60.?LoadResString@CVBApplication@@UAGJJPAPAG@Z
    6601311C  660E1F71  MSVBVM60.?get_Licenses@CVBApplication@@UAGJPAPAULicenses@@@Z
    
    0041C95C  23 3D FB FC FA A0 68 10 A7 38 08 00 2B 33 71 B5  = {FCFB3D23-A0FA-1068-A738-08002B3371B5} = VBEGlobal Clsid
    0041C96C  22 3D FB FC FA A0 68 10 A7 38 08 00 2B 33 71 B5 = {FCFB3D22-A0FA-1068-A738-08002B3371B5} = VBGlobal IID
    Misc notes:
    Since you are working with a static exe that wont be recompiled, your VB Header.aProjectInfo.aExternalTable offset and count will be static

    VbHeader.aExternalComponentTable was = VB Header.aProjectInfo.aExternalTable for a small native exe, but not for a large pcode exe
    so I would stick with the latter

    In the large exe my runtime breakpoint hit twice, once for the exe, and once again for another vb6 ocx control it loaded. values written were different

    in my test exe I could not set tmp as object = vb.global got type mismatch
    Last edited by dz32; Jul 3rd, 2021 at 11:21 AM.

  11. #11

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    Quote Originally Posted by dilettante View Post
    Wouldn't it be far easier just to rewrite the program from scratch?
    only in a utopia
    Not a possibility. 3rd party App.

  12. #12

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    Quote Originally Posted by dz32 View Post
    so I played around a bit with this based on the tips from trick. I will describe the complete process...
    Thanks man.
    To be honest - I need to catch up with some material in order to reproduce the process you laid out.
    What Trick suggested I understand better, plus it seems to be more dynamic in the sense that it will still work in the presence of binary changes - and that is a must.

    Although the App is legacy, it has several versions out there in the ether AND once in a while it gets a tiny update, while moving parts of it to web wrappers.

  13. #13
    Fanatic Member
    Join Date
    Jun 2015
    Posts
    519

    Re: Injecting a VB6 dll and manipulating the UI

    the test app, debugger and runtime breakpoint stuff was just to validate the results the rest is exactly as trick outlined.
    I only included it so you can validate your results as well.

    find the VBHeader.aProjectInfo at offset 0x30 of the VBHeader

    walk the VBHeader.aProjectInfo.aExternalTable (offset 0x234 in VBHeader.aProjectInfo struct)
    of which there will be VBHeader.aProjectInfo.ExternalCount entries (offset 0x238 from start of VBHeader.aProjectInfo )

    each entry will be an 8 byte structure

    private type extEntry
    flag as long
    offset as long
    end type

    when you find flag = 6 you found your target. at that target.offset you will find another 8 byte structure

    private type extEntryTarget
    clsidIIDStructOffset as long
    lpValue as long
    end type

    you can verify the clsidIID values they are actually another struct 32 bytes consecutive of just use the hex dump of the guids
    the value of the actual vb.globals you want will be found at the lpValue offset.

  14. #14
    Fanatic Member
    Join Date
    Jun 2015
    Posts
    519

    Re: Injecting a VB6 dll and manipulating the UI

    and you can test your lookup code in your own compiled test executable comparing the final value to objptr(vb.global) before you attempt to do it from an injection

  15. #15

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    followed your advice with AWE.
    but I am a newbie here. what am I missing?
    code attached:

    Code:
    Option Explicit
    
    Dim WithEvents btn As CommandButton
    
    Private Declare Function GetModuleHandle Lib "kernel32" _
    Alias "GetModuleHandleA" (ByVal lpModuleName As String) _
    As Long
    
    Private Declare Sub GetMem4 Lib "msvbvm60.dll" (ByVal lAddress As Long, var As Long)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes&)
    
    
    Private Sub Form_Load()
        DoTheTrickStuff
    End Sub
    
    Private Sub DoTheTrickStuff()
        MsgBox "The pointer I am after :" + CStr(ObjPtr(VB.Global))
        
        Dim vbHeader As Long
        vbHeader = GetVBHeader()
        MsgBox "Header is at : " + CStr(vbHeader)
        
        Dim lpProjectData As Long
        lpProjectData = vbHeader + &H30
        
        Dim projectDataPointerValue As Long
        GetMem4 ByVal lpProjectData, projectDataPointerValue
        
        
        Dim lpExternalTable As Long
        lpExternalTable = projectDataPointerValue + &H234
        
        Dim lpExternalcount As Long
        lpExternalcount = projectDataPointerValue + &H238
        
        Dim externalCount As Long
        GetMem4 ByVal lpExternalcount, externalCount
        
        Dim externalTablePointerValue As Long
        GetMem4 ByVal lpExternalTable, externalTablePointerValue
        
        Dim tag As Long
        Dim index As Integer
        Dim pExtEntryTarget As Long
        
        For index = 0 To externalCount
            GetMem4 ByVal externalTablePointerValue + (index * 8), tag
            GetMem4 ByVal externalTablePointerValue + (index * 8) + 4, pExtEntryTarget
            
            If tag = 6 Then 'Tag of global object
                MsgBox "Found Tag=6"
                
                Dim globalPtr As Long
                GetMem4 ByVal pExtEntryTarget + 4, globalPtr
                
                MsgBox "Global Pointer = " + CStr(globalPtr)
                
                Dim globalClone As VB.Global
                Set globalClone = GlobalFromPointer(ByVal globalPtr)
                MsgBox "Got back the object. will it work?"
                
                MsgBox "WELL WELL WELL : " + globalClone.Forms(0).Caption 'this crashes!
            End If
        Next
    End Sub
    
    Private Function GlobalFromPointer(ByVal ptr&) As VB.Global
      ' this function returns a reference to our form class
      
      ' dimension an object variable that we can copy the
      ' passed pointer into
      Dim globalClone As VB.Global
        
      ' use the CopyMemory API function to copy the
      ' long pointer into the object variable.
      CopyMemory globalClone, ptr, 4&
      Set GlobalFromPointer = globalClone
      CopyMemory ptr&, 0&, 4&
      
    End Function
    
    Public Function ObjFromPtr(ByVal pObj As Long) As Object
        Dim obj As Object
        CopyMemory obj, pObj, LenB(obj) 'not size 4!
        Set ObjFromPtr = obj
        CopyMemory obj, 0&, LenB(obj) 'not size 4!
    End Function
    
    ' // Get VBHeader structure
    Private Function GetVBHeader() As Long
        Dim ptr     As Long
        Dim hModule As Long
        hModule = GetModuleHandle(ByVal "Project1.exe")
        ' Get e_lfanew
        GetMem4 ByVal hModule + &H3C, ptr
        ' Get AddressOfEntryPoint
        GetMem4 ByVal ptr + &H28 + hModule, ptr
        ' Get VBHeader
        GetMem4 ByVal ptr + hModule + 1, GetVBHeader
        
    End Function

  16. #16

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    The pointer I get back is not equal to the value I get from ObjPtr(VB.Global)

    no wonder it crashes...

  17. #17
    Fanatic Member
    Join Date
    Jun 2015
    Posts
    519

    Re: Injecting a VB6 dll and manipulating the UI

    add a listbox and 2 command buttons to your form
    fully working

    Code:
    Option Explicit
    
    Dim WithEvents btn As CommandButton
    
    Private Declare Function GetModuleHandle Lib "kernel32" Alias "GetModuleHandleA" (ByVal lpModuleName As String) As Long
    Private Declare Sub GetMem4 Lib "msvbvm60.dll" (ByVal lAddress As Long, var As Long)
    Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (lpDest As Any, lpSource As Any, ByVal cBytes&)
    
    Private Type extEntry
        flag As Long
        offset As Long
    End Type
    
    Private Type extEntryTarget
        clsidIIDStructOffset As Long
        lpValue As Long
    End Type
    
    Dim readyToReturn As Boolean
    
    Private Sub Command1_Click()
        readyToReturn = True
    End Sub
    
    Function IsIde() As Boolean
        On Error GoTo out
        Debug.Print 1 / 0
    out: IsIde = Err
    End Function
    
    Private Sub Command2_Click()
        End
    End Sub
    
    Private Sub Form_Load()
    
    '    Dim v  As VB.Global
    '    MsgBox v.App.EXEName 'variable not set..this is good, sanity check
    '    End
        
        If IsIde Then
            MsgBox "Only run compiled"
            End
        Else
            Me.Caption = "It worked!"
            Command1.Caption = "Continue"
            Command2.Caption = "End"
            List1.Move 0, 0, 8000, 8000
            Me.Width = 8400
            Me.Height = 9100
            Command1.Move Me.Width - 3000, 8025
            Command2.Move Me.Width - 1500, 8025
            DoTheTrickStuff
        End If
        
    End Sub
    
    Sub xx(msg As String, v As Long, Optional wait As Boolean = False)
        List1.AddItem msg & ": " & Hex(v) & IIf(wait, "  --- Waiting for continue click...", Empty)
        List1.Refresh
        DoEvents
        If wait Then
            readyToReturn = False
            While Not readyToReturn
                DoEvents
            Wend
        End If
    End Sub
    
    
    Private Sub DoTheTrickStuff()
       
        Dim vbHeader As Long, lpProjectData As Long, projectDataPointerValue As Long
        Dim lpExternalTable As Long, externalTablePointerValue As Long
        Dim externalCount As Long, lpExternalCount As Long
        Dim i As Integer, e As extEntry, myCaption As String
        Dim globalPtr As Long, lpGlobalPtr As Long, globalClone As VB.Global
    
        Me.Visible = True
        xx "The pointer I am after", ObjPtr(VB.Global)
         
        vbHeader = GetVBHeader()
        xx "Header", vbHeader
        
        lpProjectData = vbHeader + &H30
        GetMem4 ByVal lpProjectData, projectDataPointerValue
        xx "lpProject", projectDataPointerValue
    
        lpExternalCount = projectDataPointerValue + &H238
        xx "lpExternalCount", lpExternalCount
        
        GetMem4 ByVal lpExternalCount, externalCount
        xx "ExtCount", externalCount
        
        lpExternalTable = projectDataPointerValue + &H234
        xx "lpExtTable", lpExternalTable
        
        GetMem4 ByVal lpExternalTable, externalTablePointerValue
        xx "ExtTable", externalTablePointerValue
        
        For i = 0 To externalCount
            CopyMemory ByVal VarPtr(e), ByVal externalTablePointerValue, 8
            
            Debug.Print i & ") " & Hex(externalTablePointerValue) & " " & e.flag & " " & Hex(e.offset)
            
            If e.flag = 6 Then 'Tag of global object
                xx "Found flag", e.flag
            
                GetMem4 ByVal e.offset + 4, lpGlobalPtr
                xx "lpGlobalPointer", lpGlobalPtr
                
                GetMem4 ByVal lpGlobalPtr, globalPtr
                xx "GlobalPointer", globalPtr
                
                If globalPtr = ObjPtr(VB.Global) Then
                    List1.AddItem "Success!!"
                Else
                    List1.AddItem "FAIL!!"
                End If
                
                Set globalClone = GlobalFromPointer(ByVal globalPtr)
                xx "ObjPtr(globalClone)", ObjPtr(globalClone) ', True
    
                myCaption = globalClone.Forms(0).Caption
                List1.AddItem "globalClone.Forms(0).Caption = " & myCaption
                globalClone.Forms(0).Caption = "Are you sure?!"
                
                Exit For
            End If
            
            externalTablePointerValue = externalTablePointerValue + 8
        Next
        
        List1.AddItem "Complete"
        
    End Sub
    
    Private Function GlobalFromPointer(ByVal ptr&) As VB.Global
      ' this function returns a reference to our form class
      
      ' dimension an object variable that we can copy the
      ' passed pointer into
      Dim globalClone As VB.Global
        
      ' use the CopyMemory API function to copy the
      ' long pointer into the object variable.
      CopyMemory globalClone, ptr, 4&
      Set GlobalFromPointer = globalClone
      CopyMemory ptr&, 0&, 4&
      
    End Function
    
    Public Function ObjFromPtr(ByVal pObj As Long) As Object
        Dim obj As Object
        CopyMemory obj, pObj, LenB(obj) 'not size 4!
        Set ObjFromPtr = obj
        CopyMemory obj, 0&, LenB(obj) 'not size 4!
    End Function
    
    ' // Get VBHeader structure
    Private Function GetVBHeader() As Long
        Dim ptr     As Long
        Dim hModule As Long
        hModule = GetModuleHandle(ByVal "Project1.exe")
        ' Get e_lfanew
        GetMem4 ByVal hModule + &H3C, ptr
        ' Get AddressOfEntryPoint
        GetMem4 ByVal ptr + &H28 + hModule, ptr
        ' Get VBHeader
        GetMem4 ByVal ptr + hModule + 1, GetVBHeader
        
    End Function
    
    Private Sub Form_Resize()
        On Error Resume Next
        List1.Width = Me.Width
    End Sub
    Last edited by dz32; Jul 3rd, 2021 at 07:25 PM.

  18. #18

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    lots of Emojis? elaborated CAP'd words ?
    nahhh... we are old school here.

    Thank you both.
    Thank you dz32.
    Thank you Trick.


    I will post a polished solution later for next generations to come. this is gold what you guys laid out here.

  19. #19

    Thread Starter
    New Member
    Join Date
    Jun 2021
    Posts
    14

    Re: Injecting a VB6 dll and manipulating the UI

    well, come to think about it:

    THANK YOU!!!!!!!!!!!!

  20. #20
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    36,629

    Re: Injecting a VB6 dll and manipulating the UI

    If this is still being updated, and with multiple versions, then it doesn't sound so much like legacy. In that case, you need to be sure that you are not violating any legal agreement with the approach you are taking.
    My usual boring signature: Nothing

  21. #21
    Frenzied Member
    Join Date
    Feb 2015
    Posts
    1,834

    Re: Injecting a VB6 dll and manipulating the UI

    I've made special example where you can access to Forms collection (and controls) from your code like that:
    Code:
    ' //
    ' // Access Forms collection of a VB6 executable
    ' // by The trick 2021
    ' //
    
    Option Explicit
    
    Private Type STARTUPINFO
        cb              As Long
        lpReserved      As Long
        lpDesktop       As Long
        lpTitle         As Long
        dwX             As Long
        dwY             As Long
        dwXSize         As Long
        dwYSize         As Long
        dwXCountChars   As Long
        dwYCountChars   As Long
        dwFillAttribute As Long
        dwFlags         As Long
        wShowWindow     As Integer
        cbReserved2     As Integer
        lpReserved2     As Long
        hStdInput       As Long
        hStdOutput      As Long
        hStdError       As Long
    End Type
    
    Private Type PROCESS_INFORMATION
        hProcess        As Long
        hThread         As Long
        dwProcessId     As Long
        dwThreadId      As Long
    End Type
    
    Private Declare Function CreateProcess Lib "kernel32" _
                             Alias "CreateProcessW" ( _
                             ByVal lpApplicationName As Long, _
                             ByVal lpCommandLine As Long, _
                             ByRef lpProcessAttributes As Any, _
                             ByRef lpThreadAttributes As Any, _
                             ByVal bInheritHandles As Long, _
                             ByVal dwCreationFlags As Long, _
                             ByRef lpEnvironment As Any, _
                             ByVal lpCurrentDirectory As Long, _
                             ByRef lpStartupInfo As STARTUPINFO, _
                             ByRef lpProcessInformation As PROCESS_INFORMATION) As Long
    Private Declare Function CloseHandle Lib "kernel32" ( _
                             ByVal hObject As Long) As Long
    Private Declare Function WaitForInputIdle Lib "user32" ( _
                             ByVal hProcess As Long, _
                             ByVal dwMilliseconds As Long) As Long
    Private Declare Sub GetStartupInfo Lib "kernel32" _
                        Alias "GetStartupInfoW" ( _
                        ByRef lpStartupInfo As STARTUPINFO)
    
    Sub Main()
        Dim tSI             As STARTUPINFO
        Dim tPI             As PROCESS_INFORMATION
        Dim cVBGetGlobal    As Object
        Dim cForms          As Object
        Dim frmMain         As Object
        
        InitializeInjectLibrary
        
        tSI.cb = Len(tSI)
        
        GetStartupInfo tSI
        
        If CreateProcess(StrPtr(App.Path & "\..\dummy\dummy.exe"), 0, ByVal 0&, ByVal 0&, 0, 0, ByVal 0&, 0, tSI, tPI) = 0 Then
            MsgBox "CreateProcess failed"
            Exit Sub
        End If
        
        WaitForInputIdle tPI.hProcess, -1
        
        CloseHandle tPI.hProcess
        CloseHandle tPI.hThread
        
        Set cVBGetGlobal = CreateVBObjectInThread(tPI.dwThreadId, App.Path & "\..\dll\GetVBGlobal.dll", "CExtractor")
        Set cForms = cVBGetGlobal.Forms
        
        Set frmMain = cForms(0)
        
        ' // Change back color of picturebox
        frmMain.Controls("picTest").BackColor = vbRed
        
        ' // Draw line on picturebox
        frmMain.Controls("picTest").Line (0, 0)-Step(100, 50), vbGreen, BF
        
        UninitializeInjectLibrary
        
    End Sub
    The executable contains a PictureBox (picTest) and you can acccess to it. You can access to a VB.Global class from a AX-Dll without any restrictions (because no marshaling is needed).

    https://github.com/thetrik/InjectAndManipulateVB

  22. #22
    Frenzied Member
    Join Date
    Feb 2015
    Posts
    1,834

    Re: Injecting a VB6 dll and manipulating the UI

    Quote Originally Posted by Broder View Post
    The pointer I get back is not equal to the value I get from ObjPtr(VB.Global)

    no wonder it crashes...
    The proper code:
    Code:
    ' //
    ' // Access to Forms collection of a VB6-EXE
    ' // By The trick 2021
    ' //
    
    Option Explicit
    
    Private Type MODULEINFO
        lpBaseOfDll As Long
        SizeOfImage As Long
        EntryPoint  As Long
    End Type
    
    Private Declare Function GetModuleHandle Lib "kernel32" _
                             Alias "GetModuleHandleW" ( _
                             ByVal lpModuleName As Long) As Long
    Private Declare Function GetModuleInformation Lib "psapi" ( _
                             ByVal hProcess As Long, _
                             ByVal hModule As Long, _
                             ByRef lpmodinfo As MODULEINFO, _
                             ByVal cb As Long) As Long
    Private Declare Function GetCurrentProcess Lib "kernel32" () As Long
    Private Declare Function vbaObjSetAddref Lib "MSVBVM60.DLL" _
                             Alias "__vbaObjSetAddref" ( _
                             ByRef dstObject As Any, _
                             ByRef srcObjPtr As Any) As Long
    Private Declare Sub GetMem4 Lib "msvbvm60" ( _
                        ByRef Addr As Any, _
                        ByRef Dst As Any)
            
    Public Property Get Forms() As Object
        Dim hModule     As Long
        Dim pVbHdr      As Long
        Dim tModInfo    As MODULEINFO
        Dim pProjData   As Long
        Dim pExtTable   As Long
        Dim lExtCount   As Long
        Dim lIndex      As Long
        Dim lTag        As Long
        Dim pObjDesc    As Long
        Dim pVBGlobal   As Long
        Dim cVBGlobal   As VB.Global
        
        hModule = GetModuleHandle(0)
        
        If GetModuleInformation(GetCurrentProcess(), hModule, tModInfo, Len(tModInfo)) = 0 Then
            MsgBox "GetModuleInformation failed", vbCritical
            Exit Property
        End If
        
        GetMem4 ByVal tModInfo.EntryPoint + 1, pVbHdr
        GetMem4 ByVal pVbHdr + &H30, pProjData
        GetMem4 ByVal pProjData + &H234, pExtTable
        GetMem4 ByVal pProjData + &H238, lExtCount
        
        For lIndex = 0 To lExtCount - 1
            
            GetMem4 ByVal pExtTable + lIndex * 8, lTag
            
            If lTag = 6 Then
                
                GetMem4 ByVal pExtTable + lIndex * 8 + 4, pObjDesc
                GetMem4 ByVal pObjDesc + 4, pVBGlobal
                GetMem4 ByVal pVBGlobal, pVBGlobal
                
                vbaObjSetAddref cVBGlobal, ByVal pVBGlobal
                
                Set Forms = cVBGlobal.Forms
                
            End If
            
        Next
        
    End Property

  23. #23
    Hyperactive Member
    Join Date
    Jun 2016
    Location
    Espaņa
    Posts
    291

    Re: Injecting a VB6 dll and manipulating the UI

    wauuu you never stop hallucinating The Trick.
    You could access other objects that are not forms, for example classes, modules

    the test program works perfectly, but I have problems when trying with an existing program.

    in Set Forms = cVBGlobal.Forms in Forms ().
    error '-2147417851'
    error in the method object

    a greeting

  24. #24

  25. #25
    Hyperactive Member
    Join Date
    Jun 2016
    Location
    Espaņa
    Posts
    291

    Re: Injecting a VB6 dll and manipulating the UI

    works perfectly and CreateVBObjectInThread perfectly
    Code:
    Sub Main()
        Dim tSI             As STARTUPINFO
        Dim tPI             As PROCESS_INFORMATION
        Dim cVBGetGlobal    As Object
        Dim cForms          As Object
        Dim frmMain         As Object
        
        InitializeInjectLibrary
        
        tSI.cb = Len(tSI)
        
        GetStartupInfo tSI
    
        If CreateProcess(StrPtr("F:\0VB6\TheBestTrick\prj.exe"), 0, ByVal 0&, ByVal 0&, 0, 0, ByVal 0&, 0, tSI, tPI) = 0 Then
            MsgBox "CreateProcess failed"
            Exit Sub
        End If
        
        WaitForInputIdle tPI.hProcess, -1
        
        CloseHandle tPI.hProcess
        CloseHandle tPI.hThread
        
        Set cVBGetGlobal = CreateVBObjectInThread(tPI.dwThreadId, App.Path & "\..\dll\GetVBGlobal.dll", "CExtractor")
    but from here on, it is where the error occurs
    Code:
        Set cForms = cVBGetGlobal.Forms
        Set frmMain = cForms(0)
    
        UninitializeInjectLibrary
        
    End Sub
    thank you very much

    sorry for bothering the thread, I love this topic very much

  26. #26
    Lively Member
    Join Date
    Feb 2015
    Posts
    84

    Re: Injecting a VB6 dll and manipulating the UI

    Quote Originally Posted by Shaggy Hiker View Post
    If this is still being updated, and with multiple versions, then it doesn't sound so much like legacy. In that case, you need to be sure that you are not violating any legal agreement with the approach you are taking.
    Exactly.

  27. #27
    Frenzied Member
    Join Date
    Feb 2014
    Location
    Norfolk UK (inbred)
    Posts
    1,166

    Re: Injecting a VB6 dll and manipulating the UI

    The O.P. does need to comment on this.

  28. #28

  29. #29
    Super Moderator Shaggy Hiker's Avatar
    Join Date
    Aug 2002
    Location
    Idaho
    Posts
    36,629

    Re: Injecting a VB6 dll and manipulating the UI

    The OP hasn't returned to this thread in a few days. Admittedly, a few days that were a US holiday, so they may have had other priorities, depending on where they are located (I didn't go looking).

    My view is that there are a lot of abandoned VB6 programs around that still work, after a fashion. I have a few, myself, most of which should have been retired/replaced decades back, but that's a different story. Trying to extend an abandoned VB6 program is understandable, if far from the ideal solution. However, if the application is still being maintained, then extending it in this fashion could be illegal. I'd like the OP to clarify that (I can be reached via PM) before this discussion goes on further. I'm leaning towards it being fine, in this case, but I'd like a bit of clarity.
    My usual boring signature: Nothing

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width