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" 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 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",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.