Results 1 to 35 of 35

Thread: [VB6] - Multithreading in VB6 part 4 - multithreading in Standart EXE.

Threaded View

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Feb 2015

    [VB6] - Multithreading in VB6 part 4 - multithreading in a Standart EXE.

    This one is obsolete. The stable solution without any dependencies you can get here.

    Hello everyone. Now I have a little time, so I have not often been programming BASIC and less likely to appear on the forum. Today again I will be talking about multi-threading, this time in the Standart EXE. I must say that all of what I write is my personal study, and may in some way does not correspond to reality; also due to my lack of time I will complement this post with further progress in the study of this issue. So here we go.
    As I said before, to multithreading worked need to initialize the runtime. Without initialization we can work very limited in the sense that the COM will not work, ie roughly all the power of BASIC is not available. You can work with the API, declared in tlb, some functions, also removing the check __vbaSetSystemError, you can use Declared-function. All previous publications showing work in separate DLL, and we could easily initialize runtime using VBDllGetClassObject function for this. Today we will try to initialize the runtime in the usual EXE, ie without using external dependencies. It's no secret that any application written in VB6 has a project header, which contains a lot of information about the project that the runtime uses to work:
    Type VbHeader
        szVbMagic               As String * 4
        wRuntimeBuild           As Integer
        szLangDll               As String * 14
        szSecLangDll            As String * 14
        wRuntimeRevision        As Integer
        dwLCID                  As Long
        dwSecLCID               As Long
        lpSubMain               As Long
        lpProjectInfo           As Long
        fMdlIntCtls             As Long
        fMdlIntCtls2            As Long
        dwThreadFlags           As Long
        dwThreadCount           As Long
        wFormCount              As Integer
        wExternalCount          As Integer
        dwThunkCount            As Long
        lpGuiTable              As Long
        lpExternalCompTable     As Long
        lpComRegisterData       As Long
        bszProjectDescription   As Long
        bszProjectExeName       As Long
        bszProjectHelpFile      As Long
        bszProjectName          As Long
    End Type
    In this structure, a lot of fields to describe all I will not, I will note only that this structure refers to a variety of other structures. Some of them will be needed in the future, such as a field lpSubMain, which contains the address of the procedure Main, if it is defined, otherwise there is 0. The vast majority of EXE files begin with the following code:
    PUSH xxxxxxxx
    CALL MSVBVM60.ThunRTMain
    Just xxxxxxxx points to structure VBHeader. This feature will allow to find the structure inside the EXE for initializing runtime. In a previous article, I described how to get from the ActiveX DLL that structure - for this it was necessary to read the data in one of the exported functions (eg DllGetClassObject). To get from EXE - we also make use of the same method. First you need to find an entry point (entry point), ie address that starts the EXE. This address can be obtained from the structure IMAGE_OPTIONAL_HEADER - field AddressOfEntryPoint. This structure (IMAGE_OPTIONAL_HEADER) is located in the PE header, and the PE header is located at offset specified in the field e_lfanew from structure IMAGE_DOS_HEADER, well, IMAGE_DOS_HEADER structure located in the address specified in App.hInstance (or GetModuleHandle). Pointer to VbHeader is located at offset AddressOfEntryPoint + 1, because push-opcode in this case equal 0x68h. So, gathering all together, we get the function to get the Header:
    ' // Get VBHeader structure
    Private Function GetVBHeader() As Long
        Dim ptr     As Long
        ' 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
    Now, if you pass this structure VBDllGetClassObject function in a new thread, then, roughly speaking, this function will start our project for execution according to this structure. Of course in this sense is not enough - it is the same as re-start the application in the new thread. For example, if the function has been set Main, and then start again with the execution of it, and if the form has, then this form. Must somehow make the project was carried out on the other, do we need in the function. To do this, you can change the field "lpSubMain" in the structure vbHeader. I also did so at first, but it has given nothing. As it turned out, in runtime, there is one global object that stores a reference to projects and related objects, and if you pass the same header at VBDllGetClassObject, then the runtime checks are not loaded if such a project, and if loaded, simply launch a new copy without parse structure vbHeader, based on the previous analysis. So I decided to do so - you can copy the structure vbHeader to another location and use it. Immediately, I note that in this structure the last 4 fields - is offset with respect to the structure, so when copying the structure they need to be adjusted. If we now try to pass this structure to VBDllGetClassObject, then everything will be fine if installed as a startup Sub Main, if the form, it will be launched and the shape and after the Main. To exclude such behavior need to fix some data referenced by the title. I do not know exactly what kind of data, as did not understand this, but "dig deeper" inside the runtime I found their place position. Field "lpGuiTable" in the structure "vbHeader" refers to a list of structures tGuiTable, which describe froms in the project. Structures are sequentially the number of structures has a field "wFormCount" in the structure "vbHeader". In the network, I have not found the normal description of the structure tGuiTable, that's what is:
    Type tGuiTable
        lStructSize          As Long
        uuidObjectGUI        As uuid
        Unknown1             As Long
        Unknown2             As Long
        Unknown3             As Long
        Unknown4             As Long
        lObjectID            As Long
        Unknown5             As Long
        fOLEMisc             As Long
        uuidObject           As uuid
        Unknown6             As Long
        Unknown7             As Long
        aFormPointer         As Long
        Unknown8             As Long
    End Type
    As it turned out there inside the runtime code that checks the field "Unknown5" in each structure:

    I've added comments; They show that "Unknown5" contains flags and if you have installed the 5th bit, the recording is a reference to some object defined register EAX, in the field at offset 0x30 within the object specified register EDX. What kind of objects - I do not know, maybe later will deal with this, we have the important fact of the recording of a value in the field at offset 0x30. Now, if you start to explore more code you can stumble on such a fragment:

    I will say that the object pointed to by ESI, the same object in the previous procedure under consideration (register EDX). It can be seen that the value of this field is tested for 0 and -1, and if there is any of the numbers that starts the procedure Main (unless specified); otherwise runs the first form. So, now that is guaranteed to run only Sub Main, we change the flag lpGuiTable.Unknown5, resetting the fifth bit. To install a new Sub Main and modification flag I created a separate procedure:
    ' // Modify VBHeader to replace Sub Main
    Private Sub ModifyVBHeader(ByVal newAddress As Long)
        Dim ptr     As Long
        Dim old     As Long
        Dim flag    As Long
        Dim count   As Long
        Dim size    As Long
        ptr = lpVBHeader + &H2C
        ' Are allowed to write in the page
        VirtualProtect ByVal ptr, 4, PAGE_READWRITE, old
        ' Set a new address of Sub Main
        GetMem4 newAddress, ByVal ptr
        VirtualProtect ByVal ptr, 4, old, 0
        ' Remove startup form
        GetMem4 ByVal lpVBHeader + &H4C, ptr
        ' Get forms count
        GetMem4 ByVal lpVBHeader + &H44, count
        Do While count > 0
            ' Get structure size
            GetMem4 ByVal ptr, size
            ' Get flag (unknown5) from current form
            GetMem4 ByVal ptr + &H28, flag
            ' When set, bit 5,
            If flag And &H10 Then
                ' Unset bit 5
                flag = flag And &HFFFFFFEF
                ' Are allowed to write in the page
                VirtualProtect ByVal ptr, 4, PAGE_READWRITE, old
                ' Write changet flag
                GetMem4 flag, ByVal ptr + &H28
                ' Restoring the memory attributes
                VirtualProtect ByVal ptr, 4, old, 0
            End If
            count = count - 1
            ptr = ptr + size
    End Sub
    Now, if you try to run this procedure before sending the header at VBDllGetClassObject, it will run the procedure defined by us. However multithreading have will work, but it is not convenient because there is no mechanism to pass a parameter to the thread as it is implemented in the CreateThread. In order to make a complete analog CreateThread I decided to create a similar function that will perform all initialization and then execute the call is transferred to the thread function with parameter. In order to be able to pass a parameter to the Sub Main, I used a thread local storage (TLS). We distinguish index for TLS. After allocation of the index, we can set the value of this index, specific for each thread. In general, the idea is, create a new thread where the starting function is a special feature ThreadProc, a parameter which transmits the structure of two fields - addresses the user function and address parameter. In this procedure, we will initialize the runtime for the new thread and stored in TLS parameter passed. As the procedure Main create a binary code that will get data from TLS, forming a stack and jump to a user function. The result had such a module:

Tags for this Thread

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