Jump to content

Recommended Posts

Posted (edited)

So, today my mind has been 100 % focused on something that on the surface looks really boring. The IDispatch interface.

A lot of you probably heard the name IDispatch before. And you may even know that it's required by COM-objects used in autoit with the ObjCreate() function. So we're dealing with COM.

Before reading any further I would recommend that you read the following wikipedia articles since that's what this article really is about. Link 1 Link 2.

So what is IDispatch good for? It's a way for applications to resolve methods, properties and arguments during runtime instead when compiled. This is an extremely powerful feature that means that applications like AutoIt, VBScript and others that don't come shipped with thousands of lines of code from microsoft can utilize powerful code.

We're going to take advantage of IDispatch's method of work. IDispatch exposes 4 methods for outside use. In our hooking application we will hook two of them. GetIdsFromNames and Invoke. These two are used to resolve member names in objects and finally use them.

Why would we want to hook them? Because it provides some interesting opportunities, we can for example modify objects during runtime to bend over to our will.

In the example below we're going to select one innocent victim. The only requirement for this victim is that it's an IDispatch interface. In my almighty wisdom I chose Shell.Application.

So what will we do to poor Shell.Application? We will explore his vtable (you did read the wiki links?) and simply change the pointers that points to the real methods to custom autoit made callbacks.

Now we have a problem. If we try to use ObjCreate function we will only get an abstract autoit object, this is no good since we wanna edit his inner workings, we need a pointer.

So we simply create the object manually. Is this a free deal? No, because now we have a pointer, not the autoit object. Fortunately we exploit the "idispatch*" dllcall data type to overcome this problem.

What we will do next is to examine the data coming form autoit and tell autoit that certain new names exists in the object - but in reality they really aren't. When autoit then tries to call this new method we're there too to execute some custom code.

Enough chit chat. Here's the code. It's completely stand alone, no third party tools whatsoever, no assembly or nothing. Just Dll* functions.

Global $hOle32=0

Global Const $tagGUID   = "ulong;ushort;ushort;byte[8]"
Global Const $tagCLSID = $tagGUID
Global Const $tagUUID = $tagGUID
Global Const $CLSCTX_INPROC_SERVER  = 0x1
Global Const $S_OK = 0
Global Const $DISP_E_UNKNOWNNAME = 2147614726
Global Const $DISPID_UNKNOWN=4294967295
Global Const $DISP_E_MEMBERNOTFOUND=2147614723

; Could be anything really.
Global Const $HELLO_THERE_ID = 1337

; Starts COM
Func CoInitialize()
    $hOle32=DllOpen("Ole32.dll")
    DllCall($hOle32,"ulong","CoInitialize","ptr",0)
EndFunc

; This will make a registry lookup to find out what the passed
; string really means. That is a GUID
Func CLSIDFromProgID($ProgID)
    $clsid = DllStructCreate($tagCLSID)
    DllCall($hOle32,"long","CLSIDFromProgID","wstr",$ProgID,"ptr",DllStructGetPtr($clsid))
    Return $clsid
EndFunc

; Returns the UUID of IDispatch
Func IDispatch_UUID()
    ; Autoit sets it all to null
    $uuid = DllStructCreate($tagUUID)
    DllStructSetData($uuid,1,132096)
    DllStructSetData($uuid,4,192,1)
    DllStructSetData($uuid,4,70,8)
    Return $uuid
EndFunc

; Creates a instance of a COM object from a clsid and iid
; Usually done internally by autoit but we need the pointer.
Func CoCreateInstance($clsid,$pUnkOuter,$ClsContext,$iid,$pOutObj)
    Return DllCall($hOle32,"long","CoCreateInstance","ptr",DllStructGetPtr($clsid),"ptr",$pUnkOuter,"dword",$ClsContext,"ptr",DllStructGetPtr($iid),"ptr",$pOutObj)
EndFunc

; WRAPPER FOR IDISPATCH INTERFACES.
; This is like ObjCreate except this returns the raw pointer to the IDispatch interface.
; This is essential for hooking the methods of the IDispatch object.
Func CreateIDispatchFromProgID($ProgID)
    ; Ptr to IDispatch object
    $pObj = DllStructCreate("ptr")
    $aCall = CoCreateInstance(CLSIDFromProgID($ProgID),0,$CLSCTX_INPROC_SERVER,IDispatch_UUID(),DllStructGetPtr($pObj))
    Return DllStructGetData($pObj,1)
EndFunc

; In the end we still want the autoit object. This function converts a raw pointer to an autoit object
Func ConvertPtrToIDispatch($IDispatch_Ptr)
    ; This would have been 10000x easier if autoit had supported the idispatch* type in dllstructs...
    ; Fortunetely memcpy can copy the pointer into a idispatch*, lucky us.
    $ptr_struct=DllStructCreate("ptr")
    DllStructSetData($ptr_struct,1,$IDispatch_Ptr)
    $aCall = DllCall("ntdll.dll","ptr:cdecl","memcpy","idispatch*","","ptr",DllStructGetPtr($ptr_struct),"long",4)
    return $aCall[1]
EndFunc

; Sets the MEM_EXECUTE_READWRITE flag on the specified memory. Use with care, I use because I'm lazy.
Func UnprotectMemory($pointer,$size)
    DllCall("Kernel32.dll","int","VirtualProtect","ptr",$pointer,"long",$size,"dword",0x40,"dword*",0)
EndFunc

; Returns the pointer the passed pointer points to ;)
Func DereferencePointer($ptr)
    $tempstruct = DllStructCreate("ptr",$ptr)
    Return DllStructGetData($tempstruct,1)
Endfunc


; Takes a pointer to the v-table in a class and replaces specified pointer in it to a new one.
Func ReplaceVTableEntry($vtable_ptr,$offset,$new_ptr)
    ; Dereference the pointer
    $first_entry_ptr=DereferencePointer($vtable_ptr)

    $entry_pointer = $first_entry_ptr+$offset
    ; Make the memory free for all. Yay!
    UnprotectMemory($entry_pointer,4)
    $entry_struct = DllStructCreate("ptr",$entry_pointer)
    DllStructSetData($entry_struct,1,$new_ptr)

EndFunc

; Everytime autoit wants to call a method, get or set a property in a object it needs to go to
; IDispatch::GetIDsFromNames. This is our version of that function, note that by defining this ourselves
; we can fool autoit to believe that the object supports a lot of different properties/methods.
;
Func IDispatch_GetIDsFromNames($self,$refiid,$str_array,$array_size,$context,$out_array)
    ; It's self explainable that autoit only asks for one member
    $str = DllStructCreate("wchar[256]",DereferencePointer($str_array))
    ConsoleWrite("AutoIt wants to look up: "&DllStructGetData($str,1)&@CRLF)

    ; Autoit gave us an array with one element ready to accept the id of the member it requested.
    $ids = DllStructCreate("long",$out_array)

    ; If autoit tried to call method "HelloThere" then supply a valid ID and indicate success
    if DllStructGetData($str,1)=="HelloThere" Then
        DllStructSetData($ids,1,$HELLO_THERE_ID)
        return $S_OK
    ; ....else say it was an unknown name.
    ; We really should redirect execution to the original function here, but meh!
    Else
        DllStructSetData($ids,1,$DISPID_UNKNOWN)
        return $DISP_E_UNKNOWNNAME
    EndIf



EndFunc
; Create the callback so we have a pointer to this function.
$IDispatch_GetIDsFromNames_Callback = DllCallbackRegister("IDispatch_GetIDsFromNames","long","idispatch;ptr;ptr;int;int;ptr")
$IDispatch_GetIDsFromNames_Callback_Ptr = DllCallbackGetPtr($IDispatch_GetIDsFromNames_Callback)


; This is called when a method is called, a property is set or get.
; This call also contains arguments returns and a lot other stuff.
; However in this trivial example we don't return anything and we don't take any arguments. Puh, could get messy
Func IDispatch_Invoke($self,$dispID,$riid,$lcid,$wFlags,$pDispParams,$pVarResult,$pExceptInfo,$puArgErr)
    ;; Dump all parameters to console.
    ConsoleWrite("DispID: "&$dispID&@CRLF&"RIID: "&$riid&@CRLF&"LCID: "&$lcid&@CRLF&"wFlags: "&$wFlags&@CRLF& _
                    "pDispParams: "&$pDispParams&@CRLF&"pVarResult: "&$pVarResult&@CRLF&"pExceptInfo: "&$pExceptInfo&@CRLF&"puArgError: "&$puArgErr&@CRLF)

    ; Oh, autoit tries to use a macro we know! Lets do something
    If $dispID=$HELLO_THERE_ID Then
        MsgBox(0,"Hi","Hello There!")
        ; Give autoit the message that everything went according to plan
        return $S_OK
    Else
        ; Here we should Redirect execution to the original IDispatch::Invoke.
        ; But I'm lazy and we have avoid any assembly so for so lets make it stay that way
        Return $DISP_E_MEMBERNOTFOUND
    EndIf

EndFunc
; Create callback
$IDispatch_Invoke_Callback = DllCallbackRegister("IDispatch_Invoke","long","idispatch;long;dword;ushort;ptr;ptr;ptr;ptr;ptr")
$IDispatch_Invoke_Callback_Ptr = DllCallbackGetPtr($IDispatch_Invoke_Callback)


; Initalize COM
CoInitialize()

; Create a victim. Could be any COM object that inherits from IDispatch
$obj_ptr = CreateIDispatchFromProgID("shell.application")
; Create autoit object as well.
$obj = ConvertPtrToIDispatch($obj_ptr)

; Hook into the object
; Offset 20 & 24 is fifth entry in vtable. Look at IDispatch and IUnknown interfaces to see why
ReplaceVTableEntry($obj_ptr,20,$IDispatch_GetIDsFromNames_Callback_Ptr)
ReplaceVTableEntry($obj_ptr,24,$IDispatch_Invoke_Callback_Ptr)

; Perform magic. All this work has been so this little line would work.
; Computer science is truly amazing.
$obj.HelloThere()

What can we do with this newly found power?

First off one can quite easily overcome the no ByRef in Mehtod calls limitation autoit have had forever.

But most important: With this, AutoIt3 can, and will be an object oriented language, yes you read it right. Without even touching the core of autoit we can extend it into new heights. Inheritance, polymorphism and instances, just with some clever thinking.

If anyone feel like they can contribute to the OO project and preferable knows at least basic COM feel free to contact me.

Lastly a would like to thanks trancexx a bunch for ideas, code and clever thinking!

Still reading? You're probably kinda alone ;)

Edited by monoceres

Broken link? PM me and I'll send you the file!

  • Replies 70
  • Created
  • Last Reply

Top Posters In This Topic

Top Posters In This Topic

Posted

  On 1/3/2010 at 2:44 AM, 'AdmiralAlkex said:

This sounds awesome!

11 months ago I started on a UDF only to crash inte the ByRef limit, will be fun to see if I can get longer now :evil:

Well it's possible now to build around it. Not saying I'm going to do it though ;)

I'm more into the other thing. Doing some incredible progress right now.

Broken link? PM me and I'll send you the file!

Posted

  On 1/3/2010 at 7:25 PM, 'AdmiralAlkex said:

Can't you do both? ;)

How hard would it be anyway? Should I even bother?

I don't think you want to until my OO work is done.

Stuff like this need to be implemented, and that's not too easy :evil:

struct tagVARIANT
    {
    union 
    {
    struct __tagVARIANT
    {
    VARTYPE vt;
    WORD wReserved1;
    WORD wReserved2;
    WORD wReserved3;
    union 
    {
    LONGLONG llVal;
    LONG lVal;
    BYTE bVal;
    SHORT iVal;
    FLOAT fltVal;
    DOUBLE dblVal;
    VARIANT_BOOL boolVal;
    _VARIANT_BOOL bool;
    SCODE scode;
    CY cyVal;
    DATE date;
    BSTR bstrVal;
    IUnknown *punkVal;
    IDispatch *pdispVal;
    SAFEARRAY *parray;
    BYTE *pbVal;
    SHORT *piVal;
    LONG *plVal;
    LONGLONG *pllVal;
    FLOAT *pfltVal;
    DOUBLE *pdblVal;
    VARIANT_BOOL *pboolVal;
    _VARIANT_BOOL *pbool;
    SCODE *pscode;
    CY *pcyVal;
    DATE *pdate;
    BSTR *pbstrVal;
    IUnknown **ppunkVal;
    IDispatch **ppdispVal;
    SAFEARRAY **pparray;
    VARIANT *pvarVal;
    PVOID byref;
    CHAR cVal;
    USHORT uiVal;
    ULONG ulVal;
    ULONGLONG ullVal;
    INT intVal;
    UINT uintVal;
    DECIMAL *pdecVal;
    CHAR *pcVal;
    USHORT *puiVal;
    ULONG *pulVal;
    ULONGLONG *pullVal;
    INT *pintVal;
    UINT *puintVal;
    struct __tagBRECORD
    {
    PVOID pvRecord;
    IRecordInfo *pRecInfo;
    }   __VARIANT_NAME_4;
    }   __VARIANT_NAME_3;
    }   __VARIANT_NAME_2;
    DECIMAL decVal;
    }   __VARIANT_NAME_1;
    } ;

Broken link? PM me and I'll send you the file!

Posted

monoceres made a great breakthrough with this. He opened a new door for AutoIt.

And since I saw a bit of what followed the code from the first post I would say this - AutoIt will have classes built-in in the near future if there would be enough wisdom by the devs.

Brilliant.

♡♡♡

.

eMyvnE

Posted

I just got arguments to work.

At the moment the following syntax is correct.

$obj.Awesome("This is a MessageBox with automatic title")
$obj.MessageBox("Regular","MessageBox")

Func Awesome($self, $arg)
    Return $self.MessageBox("Awesome",$arg)
EndFunc ;==>Awesome

Func MessageBox($self,$arg,$arg2)
    MsgBox(0, $arg, $arg2)
EndFunc ;==>MessageBox

Properties should be next. They will be tricky.

Broken link? PM me and I'll send you the file!

Posted

  On 1/4/2010 at 7:56 AM, 'monoceres said:

I just got arguments to work.

At the moment the following syntax is correct.

$obj.Awesome("This is a MessageBox with automatic title")
$obj.MessageBox("Regular","MessageBox")

Func Awesome($self, $arg)
    Return $self.MessageBox("Awesome",$arg)
EndFunc ;==>Awesome

Func MessageBox($self,$arg,$arg2)
    MsgBox(0, $arg, $arg2)
EndFunc ;==>MessageBox

Properties should be next. They will be tricky.

Awesome!

This is also a place where it would be necessary to introduce new syntax elements in AutoIt.

♡♡♡

.

eMyvnE

Posted (edited)

monoceres, could you give some practical appliances of this code other than introducing OO?

  Reveal hidden contents

I did a Lua implementation of OO once. It allows you to take no particular order of which classes you include (or load) first.

Edit: You will probably not understand this if you don't know about metatables and metamethods: http://www.lua.org/pil/13.html

Library (ignore annoying double white lines):

Example:

Edited by Manadar
Posted

  On 1/4/2010 at 10:17 AM, 'trancexx said:

Awesome!

This is also a place where it would be necessary to introduce new syntax elements in AutoIt.

Yeah, trhe syntax for defining objects will not be very good. But it will work nevertheless

  On 1/4/2010 at 10:30 AM, 'Manadar said:

monoceres, could you give some practical appliances of this code other than introducing OO?

  Reveal hidden contents

I did a Lua implementation of OO once. It allows you to take no particular order of which classes you include (or load) first.

Edit: You will probably not understand this if you don't know about metatables and metamethods: http://www.lua.org/pil/13.html

Library (ignore annoying double white lines):

Example:

Interesting. You wouldn't be interesting in helping my a little with this? I'm not sure that current lookup table is as flexible as I want it to.

Another use for the code is as mentioned before, adding ByRef support fr parameters. I'm sure more uses can be found.

Broken link? PM me and I'll send you the file!

Posted (edited)

Read and wheep. Here's the first example of Object orientation in pure autoit without any preprocessor or anything.

The code is unstable (I'm getting a random crash somewhere, annoying, freed memory is probably the error), not functional for x64 and the namespace is raped beyond recognition. But it works god damn it!

For those not interested in the technical implementation just look at the last 20 lines. There's magic going on right there.

#include <Array.au3>

Global $hOle32 = 0

Global Const $VT_EMPTY = 0
Global Const $VT_NULL = 1
Global Const $VT_I2 = 2
Global Const $VT_I4 = 3
Global Const $VT_R4 = 4
Global Const $VT_R8 = 5
Global Const $VT_CY = 6
Global Const $VT_DATE = 7
Global Const $VT_BSTR = 8
Global Const $VT_DISPATCH = 9
Global Const $VT_ERROR = 10
Global Const $VT_BOOL = 11
Global Const $VT_VARIANT = 12
Global Const $VT_UNKNOWN = 13
Global Const $VT_DECIMAL = 14
Global Const $VT_I1 = 16
Global Const $VT_UI1 = 17
Global Const $VT_UI2 = 18
Global Const $VT_UI4 = 19
Global Const $VT_I8 = 20
Global Const $VT_UI8 = 21
Global Const $VT_INT = 22
Global Const $VT_UINT = 23
Global Const $VT_VOID = 24
Global Const $VT_HRESULT = 25
Global Const $VT_PTR = 26
Global Const $VT_SAFEARRAY = 27
Global Const $VT_CARRAY = 28
Global Const $VT_USERDEFINED = 29
Global Const $VT_LPSTR = 30
Global Const $VT_LPWSTR = 31
Global Const $VT_RECORD = 36
Global Const $VT_INT_PTR = 37
Global Const $VT_UINT_PTR = 38
Global Const $VT_FILETIME = 64
Global Const $VT_BLOB = 65
Global Const $VT_STREAM = 66
Global Const $VT_STORAGE = 67
Global Const $VT_STREAMED_OBJECT = 68
Global Const $VT_STORED_OBJECT = 69
Global Const $VT_BLOB_OBJECT = 70
Global Const $VT_CF = 71
Global Const $VT_CLSID = 72
Global Const $VT_VERSIONED_STREAM = 73
Global Const $VT_BSTR_BLOB = 0xfff
Global Const $VT_VECTOR = 0x1000
Global Const $VT_ARRAY = 0x2000
Global Const $VT_BYREF = 0x4000
Global Const $VT_RESERVED = 0x8000
Global Const $VT_ILLEGAL = 0xffff
Global Const $VT_ILLEGALMASKED = 0xfff
Global Const $VT_TYPEMASK = 0xfff

Global Const $tagGUID = "ulong;ushort;ushort;byte[8]"
Global Const $tagCLSID = $tagGUID
Global Const $tagUUID = $tagGUID
Global Const $CLSCTX_INPROC_SERVER = 0x1
Global Const $S_OK = 0
Global Const $DISP_E_UNKNOWNNAME = 2147614726
Global Const $DISPID_UNKNOWN = 4294967295
Global Const $DISP_E_MEMBERNOTFOUND = 2147614723
Global Const $tagVARIANT = "ushort vt;ushort r1;ushort r2;ushort r3;uint64 data"
Global Const $tagDISPPARAMS = "ptr rgvargs;ptr rgdispidNamedArgs;dword cArgs;dword cNamedArgs;"


Global Const $DISPATCH_METHOD = 0x1
Global Const $DISPATCH_PROPERTYGET = 0x2
Global Const $DISPATCH_PROPERTYPUT = 0x4
Global Const $DISPATCH_PROPERTYPUTREF = 0x8



; Starts COM
Func CoInitialize()
    $hOle32 = DllOpen("Ole32.dll")
    DllCall($hOle32, "ulong", "CoInitialize", "ptr", 0)
EndFunc ;==>CoInitialize

; This will make a registry lookup to find out what the passed
; string really means. That is a GUID
Func CLSIDFromProgID($ProgID)
    $clsid = DllStructCreate($tagCLSID)
    DllCall($hOle32, "long", "CLSIDFromProgID", "wstr", $ProgID, "ptr", DllStructGetPtr($clsid))
    Return $clsid
EndFunc ;==>CLSIDFromProgID

; Returns the UUID of IDispatch
Func IDispatch_UUID()
    ; Autoit sets it all to null
    $uuid = DllStructCreate($tagUUID)
    DllStructSetData($uuid, 1, 132096)
    DllStructSetData($uuid, 4, 192, 1)
    DllStructSetData($uuid, 4, 70, 8)
    Return $uuid
EndFunc ;==>IDispatch_UUID

; Creates a instance of a COM object from a clsid and iid
; Usually done internally by autoit but we need the pointer.
Func CoCreateInstance($clsid, $pUnkOuter, $ClsContext, $iid, $pOutObj)
    Return DllCall($hOle32, "long", "CoCreateInstance", "ptr", DllStructGetPtr($clsid), "ptr", $pUnkOuter, "dword", $ClsContext, "ptr", DllStructGetPtr($iid), "ptr", $pOutObj)
EndFunc ;==>CoCreateInstance

; WRAPPER FOR IDISPATCH INTERFACES.
; This is like ObjCreate except this returns the raw pointer to the IDispatch interface.
; This is essential for hooking the methods of the IDispatch object.
Func CreateIDispatchFromProgID($ProgID)
    ; Ptr to IDispatch object
    $pObj = DllStructCreate("ptr")
    $aCall = CoCreateInstance(CLSIDFromProgID($ProgID), 0, $CLSCTX_INPROC_SERVER, IDispatch_UUID(), DllStructGetPtr($pObj))
    Return DllStructGetData($pObj, 1)
EndFunc ;==>CreateIDispatchFromProgID

; In the end we still want the autoit object. This function converts a raw pointer to an autoit object
Func ConvertPtrToIDispatch($pIDispatch)
    ; This would have been 10000x easier if autoit had supported the idispatch* type in dllstructs...
    ; Fortunetely memcpy can copy the pointer into a idispatch*, lucky us.
    Local $aCall = DllCall("kernel32.dll", "none", "RtlMoveMemory", _
            "idispatch*", 0, _
            "ptr*", $pIDispatch, _
            "dword", 4)

    If @error Then
        Return SetError(1, 0, 0)
    EndIf

    Return $aCall[1]

EndFunc ;==>ConvertPtrToIDispatch

; Sets the MEM_EXECUTE_READWRITE flag on the specified memory. Use with care, I use because I'm lazy.
Func UnprotectMemory($pointer, $size)
    DllCall("Kernel32.dll", "int", "VirtualProtect", "ptr", $pointer, "long", $size, "dword", 0x40, "dword*", 0)
EndFunc ;==>UnprotectMemory

; Returns the pointer the passed pointer points to ;)
Func DereferencePointer($ptr)
    $tempstruct = DllStructCreate("ptr", $ptr)
    Return DllStructGetData($tempstruct, 1)
EndFunc ;==>DereferencePointer


; Moves the vtable to a new position. useful if you want to add more entries.
Func RelocateVTable($pObj, $pNew, $VTable_size)
    $vtable = DllStructCreate("ptr", $pObj)
    $vtable_ptr = DllStructGetData($vtable, 1)
    DllCall("kernel32.dll", "none", "RtlMoveMemory", "ptr", $pNew, "ptr", $vtable_ptr, "dword", $VTable_size)
    DllStructSetData($vtable, 1, $pNew)
EndFunc ;==>RelocateVTable

; Allocate memory on the heap
Func DynAlloc($dwSize)
    $hHeap = DllCall("Kernel32.dll", "ptr", "GetProcessHeap")
    $hHeap = $hHeap[0]
    $hMem = DllCall("Kernel32.dll", "ptr", "HeapAlloc", "ptr", $hHeap, "dword", 0x8, "dword", $dwSize)
    DllCall("Kernel32.dll", "none", "CloseHandle", "ptr", $hHeap)
    Return $hMem[0]
EndFunc ;==>DynAlloc



; Takes a pointer to the v-table in a class and replaces specified pointer in it to a new one.
Func ReplaceVTableEntry($vtable_ptr, $offset, $new_ptr)
    ; Dereference the pointer
    $first_entry_ptr = DereferencePointer($vtable_ptr)

    $entry_pointer = $first_entry_ptr + $offset
    ; Make the memory free for all. Yay!
    UnprotectMemory($entry_pointer, 4)
    $entry_struct = DllStructCreate("ptr", $entry_pointer)
    DllStructSetData($entry_struct, 1, $new_ptr)

EndFunc ;==>ReplaceVTableEntry

Func MakeQWORD($val1, $val2)
    $qword = DllStructCreate("uint64")
    $dwords = DllStructCreate("dword;dword", DllStructGetPtr($qword))
    DllStructSetData($dwords, 1, $val1)
    DllStructSetData($dwords, 2, $val2)
    Return DllStructGetData($qword, 1)
EndFunc ;==>MakeQWORD

Func GetHIDWORD($qword_val)
    $qword = DllStructCreate("uint64")
    DllStructSetData($qword, 1, $qword_val)
    $dword = DllStructCreate("dword", DllStructGetPtr($qword) + 4)
    Return DllStructGetData($dword, 1)
EndFunc ;==>GetHIDWORD
Func GetLODWORD($qword_val)
    $qword = DllStructCreate("uint64")
    DllStructSetData($qword, 1, $qword_val)
    $dword = DllStructCreate("dword", DllStructGetPtr($qword))
    Return DllStructGetData($dword, 1)
EndFunc ;==>GetLODWORD

Func CreateDynamicString($str)
    $dynmem = DynAlloc(StringLen($str) + 1)
    DllStructSetData(DllStructCreate("char[" & StringLen($str) + 1 & "]", $dynmem), 1, $str)
    Return $dynmem
EndFunc ;==>CreateDynamicString



Func ConstructLookupTable($pObj, $aNames)
    ; Set point in vtable
    $vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7)
    ; Create dynamic memory for new lookup table
    $mem = DynAlloc(4 + (UBound($aNames) * 8))
    ; Create lookup table, first element is number of element
    ; Must be sorted!
    $lookup_table = DllStructCreate("int;uint64[" & UBound($aNames) & "]", $mem)
    ; Set size of lookup table
    DllStructSetData($lookup_table, 1, UBound($aNames))
    ; Modify vtable to point to the lookup table
    DllStructSetData($vtable_entry, 1, $mem)



    For $i = 0 To UBound($aNames) - 1

        If StringLeft($aNames[$i][0], 1) = "@" Then ; This is a method
            DllStructSetData($lookup_table, 2, MakeQWORD(CreateDynamicString($aNames[$i][0]), CreateDynamicString($aNames[$i][1])), $i + 1)
        Else ; It's a property then.
            $variant_ptr = DynAlloc(16)
            ValueToCOMVariant($variant_ptr, $aNames[$i][1])
            DllStructSetData($lookup_table, 2, MakeQWORD(CreateDynamicString($aNames[$i][0]), $variant_ptr), $i + 1)
        EndIf




    Next
EndFunc ;==>ConstructLookupTable

Func ChrPtrToString($ptr, $maxsize = 4096)
    $str_struct = DllStructCreate("char[" & $maxsize & "]", $ptr)
    Return DllStructGetData($str_struct, 1)
EndFunc ;==>ChrPtrToString


Func FindNameInLookupTable($pObj, $name)
    $vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7) ; sizeof(ptr)*(num entries in idispatch)
    $header = DllStructCreate("int", DllStructGetData($vtable_entry, 1))
    $lookup_table_size = DllStructGetData($header, 1)
    $lookup_table = DllStructCreate("int;uint64[" & $lookup_table_size & "]", DllStructGetData($vtable_entry, 1))



    For $i = 1 To $lookup_table_size
        If $name = ChrPtrToString(GetLODWORD(DllStructGetData($lookup_table, 2, $i))) Then Return $i
    Next


    Return -1

EndFunc ;==>FindNameInLookupTable

Func IDToValue($pObj, $id)
    $vtable_entry = DllStructCreate("ptr", DereferencePointer($pObj) + 4 * 7)
    $header = DllStructCreate("int", DllStructGetData($vtable_entry, 1))
    $lookup_table_size = DllStructGetData($header, 1)
    $lookup_table = DllStructCreate("int;uint64[" & $lookup_table_size & "]", DllStructGetData($vtable_entry, 1))

    If $id <= 0 Or $id > $lookup_table_size Then Return ""

    $member_name = ChrPtrToString(GetLODWORD(DllStructGetData($lookup_table, 2, $id)))
    If StringLeft($member_name, 1) = "@" Then
        Return ChrPtrToString(GetHIDWORD(DllStructGetData($lookup_table, 2, $id)))
    Else
        Return GetHIDWORD(DllStructGetData($lookup_table, 2, $id))
    EndIf
EndFunc ;==>IDToValue




Func VTType2AutoitType($vt_type)
    Switch $vt_type
        Case $VT_I1
            Return "byte"
        Case $VT_I2
            Return "short"
        Case $VT_I4
            Return "int"
        Case $VT_BSTR
            Return "wstr"
    EndSwitch
EndFunc ;==>VTType2AutoitType

Func AutoitType2VTType($autoit_type)
    Switch $autoit_type
        Case "byte"
            Return $VT_I1
        Case "short"
            Return $VT_I2
        Case "int"
            Return $VT_I4
        Case "wstr"
            Return $VT_BSTR
    EndSwitch

EndFunc ;==>AutoitType2VTType


; Find out length of string. I do not trust autoit to do this.
Func wcslen($pwchar)
    $aCall = DllCall("ntdll.dll", "dword:cdecl", "wcslen", "ptr", $pwchar)
    Return $aCall[0]
EndFunc ;==>wcslen

Func SysAllocString($str)
    $aCall = DllCall("oleaut32.dll", "ptr", "SysAllocString", "wstr", $str)
    Return $aCall[0]
EndFunc ;==>SysAllocString



Func COMVariantToValue($pVariant)
    Local $var = DllStructCreate($tagVARIANT, $pVariant)
    ; Translate the vt id to a autoit dllcall type
    $type = VTType2AutoitType(DllStructGetData($var, "vt"))

    If $type = "wstr" Then
        $str_ptr = DllStructCreate("ptr", DllStructGetPtr($var, "data"))
        ; Getting random crashes when trusting autoit to automatically use right size.
        ; doing it myself instead (also, it should be a BSTR, but it's not. Is autoit not obeying the rules!?
        $str_size = wcslen(DllStructGetData($str_ptr, 1))
        $data = DllStructCreate("wchar[" & $str_size + 1 & "]", DllStructGetData($str_ptr, 1))
    Else
        $data = DllStructCreate($type, DllStructGetPtr($var, "data"))
    EndIf


    Return DllStructGetData($data, 1)
EndFunc ;==>COMVariantToValue

Func ValueToCOMVariant($pVariant, $vValue)
    $var = DllStructCreate($tagVARIANT, $pVariant)

    If IsInt($vValue) Then
        $vt_type = AutoitType2VTType("int")
        $var_data = $vValue
    ElseIf IsString($vValue) Then
        $vt_type = AutoitType2VTType("wstr")
        $var_data = SysAllocString($vValue)
    EndIf
    DllStructSetData($var, "vt", $vt_type)
    DllStructSetData(DllStructCreate("int", DllStructGetPtr($var, "data")), 1, $var_data)

EndFunc ;==>ValueToCOMVariant






; Everytime autoit wants to call a method, get or set a property in a object it needs to go to
; IDispatch::GetIDsFromNames. This is our version of that function, note that by defining this ourselves
; we can fool autoit to believe that the object supports a lot of different properties/methods.
Func IDispatch_GetIDsFromNames($self, $refiid, $str_array, $array_size, $context, $out_array)
    ; It's self explainable that autoit only asks for one member
    $str = DllStructCreate("wchar[256]", DereferencePointer($str_array))
    ConsoleWrite("AutoIt wants to look up: " & DllStructGetData($str, 1) & @CRLF)

    ; Autoit gave us an array with one element ready to accept the id of the member it requested.
    $ids = DllStructCreate("long", $out_array)

;~  ConsoleWrite("ZERO"&@CRLF)
    $id = FindNameInLookupTable($self, "@" & DllStructGetData($str, 1))
    ConsoleWrite("ONE" & @CRLF)
    If $id = -1 Then $id = FindNameInLookupTable($self, DllStructGetData($str, 1))
    ConsoleWrite("TWO" & @CRLF)
    If $id <> -1 Then
        DllStructSetData($ids, 1, $id)
        Return $S_OK
    Else
        DllStructSetData($ids, 1, $DISPID_UNKNOWN)
        Return $DISP_E_UNKNOWNNAME
    EndIf



EndFunc ;==>IDispatch_GetIDsFromNames
; Create the callback so we have a pointer to this function.
$IDispatch_GetIDsFromNames_Callback = DllCallbackRegister("IDispatch_GetIDsFromNames", "long", "ptr;ptr;ptr;int;int;ptr")
$IDispatch_GetIDsFromNames_Callback_Ptr = DllCallbackGetPtr($IDispatch_GetIDsFromNames_Callback)


; This is called when a method is called, a property is set or get.
; This call also contains arguments returns and a lot other stuff.
; However in this trivial example we don't return anything and we don't take any arguments. Puh, could get messy
Func IDispatch_Invoke($self, $dispID, $riid, $lcid, $wFlags, $pDispParams, $pVarResult, $pExceptInfo, $puArgErr)
    ;; Dump all parameters to console.
    ConsoleWrite("DispID: " & $dispID & @CRLF & "RIID: " & $riid & @CRLF & "LCID: " & $lcid & @CRLF & "wFlags: " & $wFlags & @CRLF & _
            "pDispParams: " & $pDispParams & @CRLF & "pVarResult: " & $pVarResult & @CRLF & "pExceptInfo: " & $pExceptInfo & @CRLF & "puArgError: " & $puArgErr & @CRLF)


    $memberval = IDToValue($self, $dispID)

    If $memberval = "" Then Return $DISP_E_MEMBERNOTFOUND
    $dispparams = DllStructCreate($tagDISPPARAMS, $pDispParams)
    If $wFlags = BitOR($DISPATCH_METHOD, $DISPATCH_PROPERTYGET) Then
        If IsString($memberval) Then



            $self_obj = ConvertPtrToIDispatch($self)
            $callstring = $memberval & "($self_obj"

            If $pDispParams <> 0 And DllStructGetData($dispparams, "cArgs") > 0 Then
                Local $params[DllStructGetData($dispparams, "cArgs")]
                ; Fetch all arguments
                For $i = 0 To UBound($params) - 1
                    ; Save the values backwards (that's how autoit do it)
                    $params[(UBound($params) - 1) - $i] = COMVariantToValue(DllStructGetData($dispparams, "rgvargs") + ($i * 16)) ; i*sizeof(VARIANT)
                    $callstring &= ",$params[" & $i & "]"
                Next
            EndIf
            $callstring &= ")"
            ConsoleWrite("Calling function: " & $callstring & @CRLF)
            $ret = Execute($callstring)
            ; Set return value.
            ValueToCOMVariant($pVarResult, $ret)
            ; Give autoit the message that everything went according to plan
            Return $S_OK
        Else
            ValueToCOMVariant($pVarResult, COMVariantToValue($memberval))
            Return $S_OK
        EndIf

    ElseIf $wFlags = $DISPATCH_PROPERTYPUT Then
;~      MsgBox(0,"","")
;~      ConsoleWrite(DereferencePointer($pDispParams+8)&@CRLF)
;~      ConsoleWrite(DllStructGetData($dispparams,"rgvargs")&@CRLF)
        ValueToCOMVariant($memberval, COMVariantToValue(DllStructGetData($dispparams, "rgvargs")))
        Return $S_OK
    EndIf




EndFunc ;==>IDispatch_Invoke
; Create callback
$IDispatch_Invoke_Callback = DllCallbackRegister("IDispatch_Invoke", "long", "ptr;dword;ptr;dword;ushort;ptr;ptr;ptr;ptr")
$IDispatch_Invoke_Callback_Ptr = DllCallbackGetPtr($IDispatch_Invoke_Callback)


Func CreateObject($aMembers)
    ; Create a victim. Could be any COM object that inherits from IDispatch
    $obj_ptr = CreateIDispatchFromProgID("ScriptControl")
    ; Create autoit object as well.


    ; Hook into the object
    ; Offset 20 & 24 is fifth entry in vtable. Look at IDispatch and IUnknown interfaces to see why
    ReplaceVTableEntry($obj_ptr, 20, $IDispatch_GetIDsFromNames_Callback_Ptr)
    ReplaceVTableEntry($obj_ptr, 24, $IDispatch_Invoke_Callback_Ptr)

    ; Create space for a new bigger vtable
    $p = DynAlloc(4 * 7 + 4) ; sizeof(ptr)*(num entirs in dispatch)+sizeof(ptr)
    RelocateVTable($obj_ptr, $p, 4 * 7)


    ConstructLookupTable($obj_ptr, $aMembers)

    Return ConvertPtrToIDispatch($obj_ptr)
EndFunc




; Initalize COM
CoInitialize()


$msgbox = MessageBox()

$msgbox.flag = 64
$msgbox.title = "Important Announcement"
$msgbox.text = "Object Orientation + AutoIt = True"
$msgbox.Display()



Func MessageBox()
    ; Construct a lookup table with properties and methods (methods are marked with @
    Local $members[4][2] = [["@Display", "MessageBox_Display"],["flag", 0],["title", ""],["text", ""]]
    Return CreateObject($members)
EndFunc ;==>MessageBox


Func MessageBox_Display($self)
    Return MsgBox($self.flag, $self.title, $self.text)
EndFunc ;==>MessageBox_Display

So what's next? Inheritance, stability, polymorphism (which the current implementation almost gives away for free. Nice!), more types (right now only ints and strings are supported) and general clean up.

Stay tuned!

Edit: If crashes, run again!

Edit2: Replaced shell.application with ScriptControl, since shell.application would just return same old instance.

Edited by monoceres

Broken link? PM me and I'll send you the file!

Posted

  On 1/4/2010 at 8:05 PM, 'trancexx said:

I was following you with the x64 compatibility (on blind ;) ).

Wanna see?

Yes! :evil:

Seeing any problems with the current implementation? I'm pretty fucking happy with it.

Broken link? PM me and I'll send you the file!

Posted

Do you really need to create a ScriptControl-Object wouldn't it be enough to implement all funcs and then memcpy the vtable-pointer to idispatch?

*GERMAN* [note: you are not allowed to remove author / modified info from my UDFs]My UDFs:[_SetImageBinaryToCtrl] [_TaskDialog] [AutoItObject] [Animated GIF (GDI+)] [ClipPut for Image] [FreeImage] [GDI32 UDFs] [GDIPlus Progressbar] [Hotkey-Selector] [Multiline Inputbox] [MySQL without ODBC] [RichEdit UDFs] [SpeechAPI Example] [WinHTTP]UDFs included in AutoIt: FTP_Ex (as FTPEx), _WinAPI_SetLayeredWindowAttributes

Posted

  On 1/4/2010 at 8:15 PM, 'ProgAndy said:

Do you really need to create a ScriptControl-Object wouldn't it be enough to implement all funcs and then memcpy the vtable-pointer to idispatch?

You're right, I didn't even think of that. However it was way easier to use a ready to use object when developing.

As long as autoit doesn't need important information from one of the methods it will work. Needing the call the real interfaces will make stuff more complicated than it needs to with function pointer call and stuff.

Broken link? PM me and I'll send you the file!

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...