Results 1 to 2 of 2

Thread: Binary Code Thunk Experiment

  1. #1

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Binary Code Thunk Experiment

    Here's the VB6 code for Form1
    Code:
    Private Declare Function CallProc Lib "user32.dll" Alias "CallWindowProcA" (ByVal ProcAddr As Long, ByVal Param1 As Long, ByVal Param2 As Long, ByVal Param3 As Long, ByVal Param4 As Long) As Long
    Private Declare Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
    
    Private Const ThunkStrConst As String = "55 89 E5 8B 45 08 03 45 0C 03 45 10 03 45 14 C9 C2 10 00"
    Dim VM As New clsVirtualMemory
    Dim MemAddr As Long
    Dim ThunkRetVal As Long
    
    
    
    Private Sub Form_Load()
    Main
    End Sub
    
    
    
    Private Sub Main()
    AllocateMem
    CheckAllocation
    CopyMem
    ExecuteMem
    DeallocateMem
    PrintRetVal
    End Sub
    
    
    
    Private Sub AllocateMem()
    MemAddr = VM.Allocate(&H10000000, &H1000, True)
    End Sub
    
    
    
    Private Sub CheckAllocation()
    If MemAddr = 0 Then End
    If MemAddr <> &H10000000 Then
        DeallocateMem
        End
    End If
    End Sub
    
    
    
    Private Sub CopyMem()
    Dim ThunkStr As String
    Dim Thunk() As Byte
    Dim n As Long
    
    ThunkStr = Replace(ThunkStrConst, " ", "")
    ReDim Thunk(Len(ThunkStr) \ 2 - 1)
    
    For n = 0 To UBound(Thunk)
        Thunk(n) = "&h" & Mid$(ThunkStr, n * 2 + 1, 2)
    Next n
    
    CopyMemory ByVal MemAddr, Thunk(0), UBound(Thunk) + 1
    End Sub
    
    
    
    Private Sub ExecuteMem()
    ThunkRetVal = CallProc(MemAddr, 1, 2, 3, 4)
    End Sub
    
    
    
    Private Sub PrintRetVal()
    Print ThunkRetVal
    End Sub
    
    
    
    Private Sub DeallocateMem()
    VM.Free
    End Sub
    
    
    
    Private Sub Form_Unload(Cancel As Integer)
    DeallocateMem
    End Sub


    Here's the code for the class module.
    Code:
    Private Declare Function VirtualAlloc Lib "kernel32.dll" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
    Private Declare Function VirtualFree Lib "kernel32.dll" (ByVal lpAddress As Long, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
    Private Declare Function VirtualQuery Lib "kernel32.dll" (ByVal lpAddress As Long, ByRef lpBuffer As MEMORY_BASIC_INFORMATION, ByVal dwLength As Long) As Long
    Private Type MEMORY_BASIC_INFORMATION
        BaseAddress As Long
        AllocationBase As Long
        AllocationProtect As Long
        RegionSize As Long
        State As Long
        Protect As Long
        lType As Long
    End Type
    
    
    
    Dim MemAddr As Long
    Dim MemSize As Long
    Dim MemExec As Boolean
    
    
    
    Public Property Get Address() As Long
    Address = MemAddr
    End Property
    
    Public Property Get Size() As Long
    Size = MemSize
    End Property
    
    Public Property Get IsExecutable() As Boolean
    IsExecutable = MemExec
    End Property
    
    
    
    Public Function Allocate(ByVal DesiredAddress As Long, ByVal DesiredSize As Long, Optional ByVal MakeExecutable As Boolean) As Long
    Dim AccessType As Long
    Dim MemInfo As MEMORY_BASIC_INFORMATION
    
    If MemAddr <> 0 Then Exit Function
    If MakeExecutable Then AccessType = &H40 Else AccessType = 4
    Allocate = VirtualAlloc(DesiredAddress, DesiredSize, &H3000, AccessType)
    If Allocate = 0 Then Exit Function
    MemAddr = Allocate
    If VirtualQuery(MemAddr, MemInfo, Len(MemInfo)) = 0 Then
        Free
        Allocate = 0
        Exit Function
    End If
    MemSize = MemInfo.RegionSize
    MemExec = MakeExecutable
    End Function
    
    
    
    Public Function Free() As Long
    If MemAddr = 0 Then Exit Function
    Free = VirtualFree(MemAddr, 0, &H8000&)
    If Free = 0 Then Exit Function
    MemAddr = 0
    MemSize = 0
    End Function


    Note that Form1's AutoRedraw property should be set to True for this project, so that the text that is Printed to the form before the form is shown won't be gone when it is shown. Note also that this project may or may not work if run from the VB6 IDE. It depends on if a 4096 byte block of memory starting at virtual memory address 0x10000000 is available when the allocation attempt occurs, or whether that memory happens to be being used by the VB6 IDE at the time. It has ALWAYS worked when running the compiled EXE file. Note that I'm assuming it's being compiled in Native (not P-Code) mode when making that statement, as that's how I've tested it.


    Some interesting things you may notice include that the only thing in Form1's Load event is a call to a sub called Main. This is because I wanted to be able to easily debug the EXE file in an external debugger (OllyDbg 2.01 in my case), and there's quite a bit of overhead in terms of the amount of machine-code present in the Load event, but not so much with a sub. So I didn't "pollute" the already crowded Load event with even more code. This makes looking at the code in a debugger much easier. You'll also note that the Main sub simply contains a series of calls to other subs, and that the actual work is divided up into these other subs. The reason for this is so that if you want to only debug certain portions of the code, it's easy to look through the short list of calls, as these calls to other subs are commands that have very little overhead (compared to something like even a simple VB command like a Print statement, which has a TON of overhead in machine-code). Again, this makes looking at it in a debugger much easier.

    I'll quickly describe here what each of these subs do:

    AllocateMem allocates an executable block of virtual memory, via the Allocate function in an instance of the class module clsVirtualMemory.

    CheckAllocation verifies that the memory allocation was successful, and that it successfully put it in the allocated it in the requested location. There's no guaranty that if it is successful, that it will actually be put where you want it. If a given location is already in use, it may do one of 2 things. It may fail, but not necessarily. Alternatively it may succeed but instead of putting it where you wanted it, it may put it somewhere else nearby, and return the corresponding memory address (not good for my thunk, which if it used more advanced code than it currently does, it could fail if not located at the expected address). If the location is not where it is expected, or if the allocation failed altogether, the program is immediately terminated (though if the allocation didn't fail but was at the wrong address, it deallocates the memory just before terminating the program).

    CopyMem converts the hex string for the thunk into raw data, and copies it to the allocated executable memory block.

    ExecuteMem uses the function CallProc (which aliases to the actual API function CallWindowProcA) to start execution of the thunk code, as well as providing it with 4 parameters. The thunk code in this very simple case just adds the 4 numbers together and returns the sum.

    DeallocateMem deallocates the virtual memory block via the Free function in the instance of the clsVirtualMemory class module.

    PrintRetVal prints the value returned by the thunk code to the form.



    If this program is working properly on your computer, when your form appears you should see the number 10 printed on it.
    Last edited by Ben321; Jan 9th, 2016 at 01:22 AM.

  2. #2

    Thread Starter
    Frenzied Member
    Join Date
    Oct 2008
    Posts
    1,181

    Re: Binary Code Thunk Experiment

    To draw a 256x256 pixel black square on the form, you can use the below thunk string constant in place of the one in the code sample in this thread's opening post.
    Code:
    Private Const ThunkStrConst As String = "55 89 E5 6A FF 6A FF A1 44 00 00 10 03 05 48 00 00 10 50 FF 45 F8 C7 45 FC FF FF FF FF FF 45 FC 6A 00 FF 75 F8 FF 75 FC FF 75 08 FF 55 F4 81 7D FC FF 00 00 00 72 E6 81 7D F8 FF 00 00 00 72 D3 C9 C2 10 00 00 00 16 76 70 EA 02 00"

    And then use this line to call it in place of the one in the code sample in this thread's opening post, in the ExecuteMem sub.
    Code:
    CallProc MemAddr, Me.hDC, 0, 0, 0

    As a result, of it no longer making use of the return value of the thunk, you can now remove this line of code from the Main sub.
    Code:
    PrintRetVal


    By the way, here's the ASM code I used to generate this particular thunk. Note that it's in NASM format (which means that it will work in NASM, but no guaranty it will work in other assemblers). A : (colon) after a piece of text designates that piece of text as a label or variable name. A ; (semicolon) before a piece of text designates it as a comment (is ignored by the assembler). A label or variable name between [] (square brackets) in a line of ASM code means "the value stored at the address who's name this is". Except for a few cases, the size of the data to be acted on must be specified with the brackets, such as
    cmp dword[VarName],100
    which means "compare the 4byte value stored at VarName to the decimal number 100". 0x is used to specify the use of hexidecimal, otherwise decimal is the default in NASM. The dd means Data Dword (db would be Data Byte, and dw would be Data Word), and is an instruction simply tells the assembler to write that 4byte integer (or 1byte for db, or 2byte for dw) value directly to the file at that location. BITS 32 means to use 32 bit opcodes. ORG 0x10000000 means that the code should be compiled as if the first byte is located at 0x10000000, which is required for certain calls or jumps (though I think this has more to do with jumps or calls that use an immediate constant, which aren't used here, so it may or may not actually be used here, but it's good to make sure that your ORG is always set to the first byte of where in memory your thunk will be, just to be sure). Now you can see why the memory location of the allocated virtual memory must be very precise. The label start: is not used in this particular example, but in other even more advanced thunks, it could be used to allow looping of the entire function, or a way to jump to that function from other functions in the thunk. Well, now without further adeu, I present to you the assembly code for the thunk that draws that black square.

    Code:
    BITS 32
    
    ORG 0x10000000
    
    
    
    start:
    push ebp
    mov ebp,esp
    push -1 ;[ebp-4] = X coordinate
    push -1 ;[ebp-8] = Y coordinate
    mov eax,[GDI32_VirAddr]
    add eax,[SetPix_RelVirAddr]
    push eax ;[ebp-12] = Virtual address of GDI32 SetPix function
    
    LoopY:
      inc dword[ebp-8]
      mov dword[ebp-4],-1
      LoopX:
        inc dword[ebp-4]
        push 0
        push dword[ebp-8]
        push dword[ebp-4]
        push dword[ebp+8]
        call [ebp-12]
        cmp dword[ebp-4],255
      jb LoopX
      cmp dword[ebp-8],255
    jb LoopY
    
    leave
    ret 16
    
    
    
    GDI32_VirAddr: dd 0x76160000
    SetPix_RelVirAddr: dd 0x2EA70


    To get the assembled raw code in the file into hex text, I simply opened the file in HxD hex editor and copied the hex string from there into my VB6 program between the quote marks where the constant ThunkStrConst is defined.
    Last edited by Ben321; Jan 9th, 2016 at 04:43 AM.

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