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.
- 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).
- Push the return value and all arguments, right to left.
- Push the object reference. For the called method this reference is Me.
- Dereference the object reference to get at a pointer to the method list (vtable).
- 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