Try our affiliated browser extension - redirect to BreezeWiki automatically!

AsmPatcher.bas

Download
Edit
Compatibility:
VB4 Yes
VB5 untested
VB6 untested
VB.NET untested

A module that uses a neat trick to use assembler in VB. It's rather easy to use. First you generate your assembler function and write it down as an array of Longs. WinDbg is your friend. Then you insert it in your program just like in the following example. It's very easy to use too!

Note: the section "Code" doesn't show up correctly. It will be okay when you download it.

Example

Note: when I wrote this I hadn't studied the calling convention extensively. Officially the registers ebx, ebp, esi and edi should be left alone. If you test this code and your version of VB borks because of this, let me know, and I'll change the examples to use ecx instead.

To try it, add a class module to your project and call it "Asm" and insert the text below. Also add this module, AsmPatcher.bas.

Option Explicit

Dim A(1 To 5) As Long 
Dim B(1 To 4) As Long
Dim C(1 To 2) As Long
Dim D(1 To 6) As Long
Dim E(1 To 3) As Long

Function GetEIP(ByVal A As Long) As Long
Error 1
End Function

Function GetArg(ByVal A As Long) As Long
Error 1
End Function

Function RaiseError42(ByVal A As Long) As Long
Error 1
End Function

Function ShowVBMsgBox(ByVal A As Long) As Long
Error 1
End Function

Function VBMsgBox(ByVal A As Long) As Long
VBMsgBox = MsgBox(Hex(A), vbYesNoCancel, "Choose wisely...")
End Function

Function ShowVBMsgBox2(ByVal A As Long) As Long
Error 1
End Function

Private Sub Class_Initialize()
'call    +0
'pop     eax
'mov     ebx,[esp+0xc]
'mov     [ebx],eax
'xor     eax,eax
'ret     0xc
A(1) = 232
A(2) = 1552635904
A(3) = 59313188
A(4) = 214089777
A(5) = 0

'mov     eax,[esp+0x8]
'mov     ebx,[esp+0xc]
'mov     [ebx],eax
'xor     eax,eax
'ret     0xc
B(1) = 136594571
B(2) = 203709579
B(3) = -1070529655
B(4) = 3266

'mov     eax,0x800a002a
'ret     0xc
C(1) = 167783096
C(2) = 836224

'mov     ebx,[esp+0x4]
'push    dword ptr [esp+0xc]
'push    dword ptr [esp+0xc]
'push    ebx
'mov     ebx,[ebx]
'call    dword ptr [ebx+0x2c]
'ret     0xc
D(1) = 69491851
D(2) = 203715839
D(3) = 203715839
D(4) = -14972077
D(5) = 214051923
D(6) = 0

'mov     ebx,[esp+0x4]
'mov     ebx,[ebx]
'jmp     dword ptr [ebx+0x2c]
E(1) = 69491851
E(2) = 1677663115
E(3) = 44

Set Asm = Me
AsmFunction 1, A
AsmFunction 2, B
AsmFunction 3, C
AsmFunction 4, D
AsmFunction 6, E
End Sub

You can now call the functions GetEIP etc. like this:

Print Hex(Asm.GetArg(&HCAFEBABE))

No initialization is needed, everything has been taken care of. Note that the stub functions contain "Error 1" for a reason: this way you'll know when something went wrong.

AsmPatcher_NoAsm (compiler flag)
Disables the AsmFunction function.
AsmPatcher_NoObj (compiler flag)
Disables the AsmMethod function.
Sub AsmFunction(ByVal Index As Long, Data() As Long)
Replace public method number Index from the Asm object with the assembler function contained in Data.
Sub AsmMethod(ByVal Object As Object, ByVal Index As Long, Data() As Long)
Replace public method number Index from Object with the assembler function contained in Data.

Writing assembler routines for VB

When your assembler routine starts, the stack looks like this:

[esp]   Return address. When you use ret, be sure that esp points here.
[esp+4] Me.
[esp+8] Argument.
[esp+c] Return value.

If there is more than one argument, the second argument follows the first, etc. until the last is followed by the return value. The return value in turn behaves just like a ByRef argument, with one important exception: when you change a ByRef argument, the change is immediately reflected in your VB program. The return value however is only assigned if your routine succeeds, that is if it returns with eax set to zero.

A few examples follow. All signatures are like this:

Function ... (ByVal Argument As Long) As Long

This is a trick to get the value of eip, a register that you cannot manipulate using normal means. Note that +0 means "the next instruction". Depending on the assembler you use you may need to enter an address or label.

call    +0
pop     eax
mov     ebx,[esp+0xc]
mov     [ebx],eax
xor     eax,eax
ret     0xc

This code simply returns its only argument:

mov     eax,[esp+0x8]
mov     ebx,[esp+0xc]
mov     [ebx],eax
xor     eax,eax
ret     0xc

This raises run-time error 42 (2a in hexadecimal)

mov     eax,0x800a002a
ret     0xc

This calls a method on Me. Calling methods on other objects works the same.

  1. First grab the object reference from the stack (and dereference it if it's ByRef - but Me is always passed ByVal, so that doesn't apply here).
  2. Push the return value and all arguments, right to left.
  3. Push the object reference. For the called method this reference is Me.
  4. Dereference the object reference to get at a pointer to the method list (vtable).
  5. Call the method using "call dword ptr [reg+offset]" where offset is Index * 4 + 24. You can omit "dword ptr" in many assemblers.

Just remember that the first seven public methods are the members of IDispatch; the method with Index 1 will be at offset 1c (hexadecimal).

mov     ebx,[esp+0x4]
push    dword ptr [esp+0xc]
push    dword ptr [esp+0xc]
push    ebx
mov     ebx,[ebx]
call    dword ptr [ebx+0x2c]
ret     0xc

Of course, if you plan to simply return the value of the called method, you can sometimes use a "jmp" instead, if the function has the right signature.

mov     ebx,[esp+0x4]
mov     ebx,[ebx]
jmp     dword ptr [ebx+0x2c]

Why it works

Todo: lengthy explanation.B9A171

License

It's rather straightforward when you know how it works. Since only creative works are copyrightable, this code is in the public domain.B9A171

Code

Attribute VB_Name = "AsmPatcher" Option Explicit Private Declare Function GetSafeArrayAddress Lib "Kernel32" Alias "RtlMoveMemory" ( _ Dest As Long, Source() As Long, ByVal Size As Long) As Long Private Declare Function GetArrayDataAddress Lib "Kernel32" Alias "RtlMoveMemory" ( _ Dest As Long, ByVal SafeArrayAdressPlus12 As Long, ByVal Size As Long) As Long #If Not AsmPatcher_NoObj Then Private Declare Function GetPointerToVtable Lib "Kernel32" Alias "RtlMoveMemory" ( _ Dest As Long, ByVal Object As Object, ByVal Size As Long) As Long #End If #If Not AsmPatcher_NoAsm Then Private Declare Function GetPointerToVtableAsm Lib "Kernel32" Alias "RtlMoveMemory" ( _ Dest As Long, ByVal Object As Asm, ByVal Size As Long) As Long #End If Private Declare Function PointVtableEntryToArray Lib "Kernel32" Alias "RtlMoveMemory" ( _ ByVal PointerToVtable As Long, FunctionPointer As Long, ByVal Size As Long) As Long #If Not AsmPatcher_NoAsm Then Public Asm As New Asm Sub AsmFunction(ByVal Index As Long, Data() As Long) Dim A As Long, V As Long GetSafeArrayAddress A, Data, 4 GetArrayDataAddress A, A + 12, 4 GetPointerToVtableAsm V, Asm, 4 PointVtableEntryToArray 4 * Index + V + 24, A, 4 End Sub #End If #If Not AsmPatcher_NoObj Then Sub AsmMethod(ByVal Object As Object, ByVal Index As Long, Data() As Long) Dim A As Long, V As Long GetSafeArrayAddress A, Data, 4 GetArrayDataAddress A, A + 12, 4 GetPointerToVtable V, Object, 4 PointVtableEntryToArray 4 * Index + V + 24, A, 4 End Sub #End If

B9A171