Jump to content

This site uses cookies. By continuing to browse the site you are agreeing to our use of cookies. Find out more here. X
X


Photo

Hooking into the IDispatch interface.


  • Please log in to reply
70 replies to this topic

#1 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 03 January 2010 - 12:51 AM

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.
AutoIt         
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, 03 January 2010 - 11:17 AM.

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








#2 AdmiralAlkex

AdmiralAlkex

    Here be dragons

  • Active Members
  • PipPipPipPipPipPip
  • 4,607 posts

Posted 03 January 2010 - 02:44 AM

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 ;)

#3 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 03 January 2010 - 03:47 PM

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!


#4 AdmiralAlkex

AdmiralAlkex

    Here be dragons

  • Active Members
  • PipPipPipPipPipPip
  • 4,607 posts

Posted 03 January 2010 - 07:25 PM

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

Can't you do both? ;)

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

#5 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 03 January 2010 - 07:52 PM

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:
Plain Text         
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!


#6 AdmiralAlkex

AdmiralAlkex

    Here be dragons

  • Active Members
  • PipPipPipPipPipPip
  • 4,607 posts

Posted 03 January 2010 - 08:07 PM

That thing is ugly :evil:
It's even worse than SDL_RWops.... I think, I never understood that ;)

#7 trancexx

trancexx

    Queen F. Elizabeth MCXI

  • Active Members
  • PipPipPipPipPipPip
  • 5,989 posts

Posted 03 January 2010 - 09:56 PM

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.

Maybe I'm in love (Sshh, it's a secret)

.

 

eMyvnE


#8 Datenshi

Datenshi

    Prodigy

  • Active Members
  • PipPipPip
  • 179 posts

Posted 03 January 2010 - 09:59 PM

I'm not surprised, monoceres and trancexx at it again. You sure know how to deliver quality code. ;)

#9 James

James

    Universalist

  • MVPs
  • 10,164 posts

Posted 03 January 2010 - 10:29 PM

After monoceres explain why this could would be a useful asset to AutoIt, I sat there looking like this Posted Image for a good few minutes.

If what he told me he is doing at the moment, works... Then... Wow, AutoIt will be an even better language.

#10 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 04 January 2010 - 07:56 AM

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!


#11 trancexx

trancexx

    Queen F. Elizabeth MCXI

  • Active Members
  • PipPipPipPipPipPip
  • 5,989 posts

Posted 04 January 2010 - 10:17 AM

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.

Maybe I'm in love (Sshh, it's a secret)

.

 

eMyvnE


#12 Manadar

Manadar

         

  • MVPs
  • 10,723 posts

Posted 04 January 2010 - 10:30 AM

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


Spoiler

Edited by Manadar, 04 January 2010 - 10:31 AM.


#13 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 04 January 2010 - 03:14 PM

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


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


Spoiler

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!


#14 Manadar

Manadar

         

  • MVPs
  • 10,723 posts

Posted 04 January 2010 - 03:52 PM

I have full faith in your abilities as a programmer, you will do this just fine. Probably even better than me..

#15 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 04 January 2010 - 07:58 PM

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.

AutoIt         
#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, 04 January 2010 - 08:02 PM.

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


#16 trancexx

trancexx

    Queen F. Elizabeth MCXI

  • Active Members
  • PipPipPipPipPipPip
  • 5,989 posts

Posted 04 January 2010 - 08:05 PM

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

Maybe I'm in love (Sshh, it's a secret)

.

 

eMyvnE


#17 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 04 January 2010 - 08:07 PM

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!


#18 ProgAndy

ProgAndy

    You need AutoItObject

  • MVPs
  • 2,508 posts

Posted 04 January 2010 - 08:15 PM

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* Posted Image [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

#19 monoceres

monoceres

    asdf

  • MVPs
  • 3,957 posts

Posted 04 January 2010 - 08:20 PM

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!


#20 James

James

    Universalist

  • MVPs
  • 10,164 posts

Posted 04 January 2010 - 08:23 PM

YAYAYAYAY I'm going to have some fun with this ;)




0 user(s) are reading this topic

0 members, 0 guests, 0 anonymous users