/*++ Copyright (c) 2000 Microsoft Corporation Module Name: ShimHook.cpp Abstract: Utils for all modules and COM hooking mechanism Notes: None History: 11/01/1999 markder Created 11/11/1999 markder Added comments 01/10/2000 linstev Format to new style --*/ // Have to do this to keep shims simpler #define LIB_BUILD_FLAG #include "ShimHook.h" #undef LIB_BUILD_FLAG #define APPBreakPoint() ; // Global variables for api hook support PHOOKAPI GetHookAPIs( IN LPSTR pszCmdLine, IN PFNPATCHNEWMODULES pfnPatchNewModules, IN OUT DWORD *pdwHooksCount ); void PatchFunction( PVOID* pVtbl, DWORD dwVtblIndex, PVOID pfnNew ); ULONG COMHook_AddRef( PVOID pThis ); ULONG COMHook_Release( PVOID pThis ); HRESULT COMHook_QueryInterface( PVOID pThis, REFIID iid, PVOID* ppvObject ); HRESULT COMHook_IClassFactory_CreateInstance( PVOID pThis, IUnknown * pUnkOuter, REFIID riid, void ** ppvObject ); VOID HookObject(IN CLSID *pCLSID, IN REFIID riid, OUT LPVOID *ppv, OUT PSHIM_HOOKED_OBJECT pOb, IN BOOL bClassFactory); /*++ Global variables for COM hook support The following variables are pointers to the first entry in linked lists that are maintained by the mechanism in order to properly manage the hooking process. There will be one SHIM_IFACE_FN_MAP for every COM interface function pointer that was overwritten with one of our hooks. There will be one SHIM_HOOKED_OBJECT entry every COM interface that is handed out. This is required to differentiate between different classes that expose the same interface, but one is hooked and one isn't. --*/ PSHIM_IFACE_FN_MAP g_pIFaceFnMaps; PSHIM_HOOKED_OBJECT g_pObjectCache; #ifdef DBG DEBUGLEVEL GetDebugLevel() { CHAR cEnv[MAX_PATH]; DEBUGLEVEL dlRet = eDbgLevelError; if (GetEnvironmentVariableA( szDebugEnvironmentVariable, cEnv, MAX_PATH)) { CHAR c = cEnv[0]; if ((c >= '0') || (c <= '9')) { dlRet = (DEBUGLEVEL)((int)(c - '0')); } } return dlRet; } #endif /*++ Function Description: Called by the shim mechanism. Initializes the global APIHook array and returns necessary information to the shim mechanism. Arguments: IN dwGetProcAddress - Function pointer to GetProcAddress IN dwLoadLibraryA - Function pointer to LoadLibraryA IN dwFreeLibrary - Function pointer to FreeLibrary IN OUT pdwHooksCount - Receive the number of APIHooks in the returned array Return Value: Pointer to global HOOKAPI array. History: 11/01/1999 markder Created --*/ PHOOKAPI GetHookAPIs( IN LPSTR /*pszCmdLine*/, IN PFNPATCHNEWMODULES /*pfnPatchNewModules */, IN OUT DWORD * pdwHooksCount ) { // Initialize Global variables here. // At present (1/11/00) we do not have static initialization g_bAPIHooksInited = FALSE; g_pAPIHooks = NULL; g_dwAPIHookCount = 0; g_bHasCOMHooks = FALSE; g_dwCOMHookCount = 0; g_pCOMHooks = NULL; g_pIFaceFnMaps = NULL; g_pObjectCache = NULL; // exists in user shims InitializeHooks(DLL_PROCESS_ATTACH); PHOOKAPI p_ReportedShimBlockAddress = g_pAPIHooks; if (!g_bHasCOMHooks) { p_ReportedShimBlockAddress = g_pAPIHooks + USERAPIHOOKSTART; g_dwAPIHookCount -= USERAPIHOOKSTART; } *pdwHooksCount = g_dwAPIHookCount; DPF(eDbgLevelBase, "Hooks = %d, DebugLevel = %d\n\n", g_dwAPIHookCount, GetDebugLevel()); return p_ReportedShimBlockAddress; } /*++ Function Description: Adds an entry to the g_IFaceFnMaps linked list. Arguments: IN pVtbl - Pointer to an interface vtable to file under IN pfnNew - Pointer to the new (stub) function IN pfnOld - Pointer to the old (original) function Return Value: None History: 11/01/1999 markder Created --*/ VOID AddIFaceFnMap( IN PVOID pVtbl, IN PVOID pfnNew, IN PVOID pfnOld ) { PSHIM_IFACE_FN_MAP pNewMap = (PSHIM_IFACE_FN_MAP) VirtualAlloc( NULL, sizeof(SHIM_IFACE_FN_MAP), MEM_COMMIT, PAGE_READWRITE); DPF(eDbgLevelInfo, "[AddMap] pVtbl: 0x%p pfnNew: 0x%p pfnOld: 0x%p\n", pVtbl, pfnNew, pfnOld); pNewMap->pVtbl = pVtbl; pNewMap->pfnNew = pfnNew; pNewMap->pfnOld = pfnOld; pNewMap->pNext = g_pIFaceFnMaps; g_pIFaceFnMaps = pNewMap; } /*++ Function Description: Searches the g_pIFaceFnMaps linked list for a match on pVtbl and pfnNew, and returns the corresponding pfnOld. This is typically called from inside a stubbed function to determine what original function pointer to call for the particular vtable that was used by the caller. It is also used by PatchFunction to determine if a vtable's function pointer has already been stubbed. Arguments: IN pVtbl - Pointer to an interface vtable to file under IN pfnNew - Pointer to the new (stub) function IN bThrowExceptionIfNull - Flag that specifies whether it should be possible to not find the original function in our function map Return Value: Returns the original function pointer History: 11/01/1999 markder Created --*/ PVOID LookupOldCOMIntf( IN PVOID pVtbl, IN PVOID pfnNew, IN BOOL bThrowExceptionIfNull ) { PSHIM_IFACE_FN_MAP pMap = g_pIFaceFnMaps; PVOID pReturn = NULL; DPF(eDbgLevelInfo, "[LookUpOldCOMIntf] pVtbl: 0x%p pfnNew: 0x%p ", pVtbl, pfnNew); // Scan the linked list for a match and return if found. while (pMap) { if (pMap->pVtbl == pVtbl && pMap->pfnNew == pfnNew) { pReturn = pMap->pfnOld; break; } pMap = (PSHIM_IFACE_FN_MAP) pMap->pNext; } DPF(eDbgLevelInfo, " --> Returned: 0x%p\n", pReturn); if (!pReturn && bThrowExceptionIfNull) { // If we have hit this point, there is something seriously wrong. // Either there is a bug in the AddRef/Release stubs or the app // obtained an interface pointer in some way that we don't catch. DPF(eDbgLevelError,"ERROR: Shim COM APIHooking mechanism failed.\n"); APPBreakPoint(); } return pReturn; } /*++ Function Description: Stores the original function pointer in the function map and overwrites it in the vtable with the new one. Arguments: IN pVtbl - Pointer to an interface vtable to file under IN dwVtblIndex - The index of the target function within the vtable. IN pfnNew - Pointer to the new (stub) function Return Value: None History: 11/01/1999 markder Created --*/ VOID PatchFunction( IN PVOID* pVtbl, IN DWORD dwVtblIndex, IN PVOID pfnNew ) { DWORD dwOldProtect = 0; DWORD dwOldProtect2 = 0; DPF(eDbgLevelInfo, "[PatchFunction] pVtbl: 0x%p, dwVtblIndex: %d, pfnOld: 0x%p, pfnNew: 0x%p\n", pVtbl, dwVtblIndex, pVtbl[dwVtblIndex], pfnNew); // if not patched yet if (!LookupOldCOMIntf( pVtbl, pfnNew, FALSE)) { AddIFaceFnMap( pVtbl, pfnNew, pVtbl[dwVtblIndex]); // Make the code page writable and overwrite function pointers in vtable if (VirtualProtect(pVtbl + dwVtblIndex, sizeof(DWORD), PAGE_READWRITE, &dwOldProtect)) { pVtbl[dwVtblIndex] = pfnNew; // Return the code page to its original state VirtualProtect(pVtbl + dwVtblIndex, sizeof(DWORD), dwOldProtect, &dwOldProtect2); } } } /*++ Function Description: This stub exists to keep track of an interface's reference count changes. Note that the bAddRefTrip flag is cleared, which allows APIHook_QueryInterface to determine whether an AddRef was performed inside the original QueryInterface function call. Arguments: IN pThis - The object's 'this' pointer Return Value: Return value is obtained from original function History: 11/01/1999 markder Created --*/ ULONG APIHook_AddRef( IN PVOID pThis ) { PSHIM_HOOKED_OBJECT pHookedOb = g_pObjectCache; _pfn_AddRef pfnOld; ULONG ulReturn; pfnOld = (_pfn_AddRef) LookupOldCOMIntf( *((PVOID*)(pThis)), APIHook_AddRef, TRUE); ulReturn = (*pfnOld)(pThis); while (pHookedOb) { if (pHookedOb->pThis == pThis) { pHookedOb->dwRef++; pHookedOb->bAddRefTrip = FALSE; DPF(eDbgLevelInfo, "[AddRef] pThis: 0x%p dwRef: %d ulReturn: %d\n", pThis, pHookedOb->dwRef, ulReturn); break; } pHookedOb = (PSHIM_HOOKED_OBJECT) pHookedOb->pNext; } return ulReturn; } /*++ Function Description: This stub exists to keep track of an interface's reference count changes. Arguments: IN pThis - The object's 'this' pointer Return Value: Return value is obtained from original function History: 11/01/1999 markder Created --*/ ULONG APIHook_Release( IN PVOID pThis ) { PSHIM_HOOKED_OBJECT *ppHookedOb = &g_pObjectCache; PSHIM_HOOKED_OBJECT pTemp; _pfn_Release pfnOld; ULONG ulReturn; pfnOld = (_pfn_Release) LookupOldCOMIntf(*((PVOID*)(pThis)), APIHook_Release, TRUE); ulReturn = (*pfnOld)( pThis ); while ((*ppHookedOb)) { if ((*ppHookedOb)->pThis == pThis) { (*ppHookedOb)->dwRef--; DPF(eDbgLevelInfo, "[Release] pThis: 0x%p dwRef: %d ulReturn: %d %s\n", pThis, (*ppHookedOb)->dwRef, ulReturn, ((*ppHookedOb)->dwRef?"":" --> Deleted")); if (!((*ppHookedOb)->dwRef)) { pTemp = (*ppHookedOb); *ppHookedOb = (PSHIM_HOOKED_OBJECT) (*ppHookedOb)->pNext; VirtualFree(pTemp, 0, MEM_RELEASE); } break; } ppHookedOb = (PSHIM_HOOKED_OBJECT*) &((*ppHookedOb)->pNext); } return ulReturn; } /*++ Function Description: This stub catches the application attempting to obtain a new interface pointer to the same object. The function searches the object cache to obtain a CLSID for the object and, if found, APIHooks all required functions in the new vtable (via the HookObject call). Arguments: IN pThis - The object's 'this' pointer IN iid - Reference to the identifier of the requested interface IN ppvObject - Address of output variable that receives the interface pointer requested in riid. Return Value: Return value is obtained from original function History: 11/01/1999 markder Created --*/ HRESULT APIHook_QueryInterface( PVOID pThis, REFIID iid, PVOID* ppvObject ) { HRESULT hrReturn = E_FAIL; _pfn_QueryInterface pfnOld = NULL; PSHIM_HOOKED_OBJECT pOb = g_pObjectCache; pfnOld = (_pfn_QueryInterface) LookupOldCOMIntf( *((PVOID*)pThis), APIHook_QueryInterface, TRUE); while (pOb) { if (pOb->pThis == pThis) { pOb->bAddRefTrip = TRUE; break; } pOb = (PSHIM_HOOKED_OBJECT) pOb->pNext; } if (S_OK == (hrReturn = (*pfnOld) (pThis, iid, ppvObject))) { if (pOb) { if (pOb->pThis == *((PVOID*)ppvObject)) { // Same object. Detect whether QueryInterface used IUnknown::AddRef // or an internal function. DPF( eDbgLevelInfo,"[HookObject] Existing object%s. pThis: 0x%p\n", (pOb->bAddRefTrip?" (AddRef'd) ":""), pOb->pThis); if (pOb->bAddRefTrip) { (pOb->dwRef)++; // AddRef the object pOb->bAddRefTrip = FALSE; } // We are assured that the CLSID for the object will be the same. HookObject(pOb->pCLSID, iid, ppvObject, pOb, pOb->bClassFactory); } else { HookObject(pOb->pCLSID, iid, ppvObject, NULL, pOb->bClassFactory); } } } return hrReturn; } /*++ Function Description: This stub catches the most interesting part of the object creation process: The actual call to IClassFactory::CreateInstance. Since no CLSID is passed in to this function, the stub must decide whether to APIHook the object by looking up the instance of the class factory in the object cache. IF IT EXISTS IN THE CACHE, that indicates that it creates an object that we wish to APIHook. Arguments: IN pThis - The object's 'this' pointer IN pUnkOuter - Pointer to whether object is or isn't part of an aggregate IN riid - Reference to the identifier of the interface OUT ppvObject - Address of output variable that receives the interface pointer requested in riid Return Value: Return value is obtained from original function History: 11/01/1999 markder Created --*/ HRESULT APIHook_IClassFactory_CreateInstance( PVOID pThis, IUnknown *pUnkOuter, REFIID riid, VOID **ppvObject ) { HRESULT hrReturn = E_FAIL; _pfn_CreateInstance pfnOldCreateInst = NULL; PSHIM_HOOKED_OBJECT pOb = g_pObjectCache; pfnOldCreateInst = (_pfn_CreateInstance) LookupOldCOMIntf( *((PVOID*)pThis), APIHook_IClassFactory_CreateInstance, FALSE); if (S_OK == (hrReturn = (*pfnOldCreateInst)(pThis, pUnkOuter, riid, ppvObject))) { while (pOb) { if (pOb->pThis == pThis) { // This class factory instance creates an object that we APIHook. DPF(eDbgLevelInfo, "[CreateInstance] Hooking object! pThis: 0x%p\n", pThis); HookObject(pOb->pCLSID, riid, ppvObject, NULL, FALSE); break; } pOb = (PSHIM_HOOKED_OBJECT) pOb->pNext; } } return hrReturn; } /*++ Function Description: This stub intercepts the one and only exported function in a DLL server that OLE/COM uses. It is at this point that we start APIHooking, first by APIHooking the class factory (which this function returns) and then later the actual object(s) we are interested in. Arguments: IN rclsid CLSID for the class object IN riid - Reference to the identifier of the interface that communicateswith the class object IN riid - Reference to the identifier of the interface OUT ppvObject - Address of output variable that receives the interface pointer requested in riid Return Value: Return value is obtained from original function History: 11/01/1999 markder Created --*/ HRESULT APIHook_DllGetClassObject( REFCLSID rclsid, REFIID riid, LPVOID * ppv ) { HRESULT hrReturn = E_FAIL; DWORD i = 0; hrReturn = LOOKUP_APIHOOK(DllGetClassObject)( rclsid, riid, ppv); if (S_OK == hrReturn) { // Determine if we need to APIHook this object for (i = 0; i < g_dwCOMHookCount; i++) { if (g_pCOMHooks[i].pCLSID && IsEqualGUID( (REFCLSID) *(g_pCOMHooks[i].pCLSID), rclsid)) { // Yes, we are APIHooking an interface on this object. Hook // IClassFactory::CreateInstance. HookObject((CLSID*) &rclsid, riid, ppv, NULL, TRUE); break; } } } return hrReturn; } /*++ Function Description: This stub intercepts the DirectDrawCreate. Arguments: See DirectDrawCreate on MSDN Return Value: See DirectDrawCreate on MSDN History: 11/01/1999 markder Created --*/ HRESULT APIHook_DirectDrawCreate( IN GUID FAR *lpGUID, OUT LPVOID *lplpDD, OUT IUnknown* pUnkOuter ) { HRESULT hrReturn = E_FAIL; DWORD i = 0; hrReturn = LOOKUP_APIHOOK(DirectDrawCreate)(lpGUID, lplpDD, pUnkOuter); if (S_OK == hrReturn) { // Determine if we need to APIHook this object for (i = 0; i < g_dwCOMHookCount; i++) { if (g_pCOMHooks[i].pCLSID && IsEqualGUID( (REFCLSID) *(g_pCOMHooks[i].pCLSID), CLSID_DirectDraw)) { // Yes, we are APIHooking an interface on this object. HookObject((CLSID*) &CLSID_DirectDraw, IID_IDirectDraw, lplpDD, NULL, FALSE); break; } } } return hrReturn; } /*++ Function Description: This stub intercepts DirectDrawCreateEx. Arguments: See DirectDrawCreateEx on MSDN Return Value: See DirectDrawCreateEx on MSDN History: 11/01/1999 markder Created --*/ HRESULT APIHook_DirectDrawCreateEx( GUID FAR *lpGUID, LPVOID *lplpDD, REFIID iid, IUnknown* pUnkOuter ) { HRESULT hrReturn = E_FAIL; DWORD i = 0; hrReturn = LOOKUP_APIHOOK(DirectDrawCreateEx)( lpGUID, lplpDD, iid, pUnkOuter); if (S_OK == hrReturn) { // Determine if we need to APIHook this object for (i = 0; i < g_dwCOMHookCount; i++) { if (g_pCOMHooks[i].pCLSID && IsEqualGUID( (REFCLSID) *(g_pCOMHooks[i].pCLSID), CLSID_DirectDraw)) { // Yes, we are APIHooking an interface on this object. HookObject((CLSID*) &CLSID_DirectDraw, iid, lplpDD, NULL, FALSE); break; } } } return hrReturn; } /*++ Function Description: Free memory associated with Hooks and dump info Arguments: None Return Value: None History: 11/01/1999 markder Created --*/ VOID DumpCOMHooks() { PSHIM_IFACE_FN_MAP pMap = g_pIFaceFnMaps; PSHIM_HOOKED_OBJECT pHookedOb = g_pObjectCache; // Dump function map DPF(eDbgLevelInfo, "\n--- Shim COM Hook Function Map ---\n\n"); while (pMap) { DPF(eDbgLevelInfo, "pVtbl: 0x%p pfnNew: 0x%p pfnOld: 0x%p\n", pMap->pVtbl, pMap->pfnNew, pMap->pfnOld); pMap = (PSHIM_IFACE_FN_MAP) pMap->pNext; } // Dump class factory cache DPF(eDbgLevelInfo, "\n--- Shim Object Cache (SHOULD BE EMPTY!!) ---\n\n"); while (pHookedOb) { DPF(eDbgLevelInfo, "pThis: 0x%p dwRef: %d\n", pHookedOb->pThis, pHookedOb->dwRef); pHookedOb = (PSHIM_HOOKED_OBJECT) pHookedOb->pNext; } } /*++ Function Description: This function adds the object's important info to the object cache and then patches all required functions. IUnknown is APIHooked for all objects regardless. Arguments: IN rclsid - CLSID for the class object IN riid - Reference to the identifier of the interface that communicates with the class object OUT ppv - Address of the pThis pointer that uniquely identifies an instance of the COM interface OUT pOb - New obj pointer IN bClassFactory - Is this a class factory call Return Value: None History: 11/01/1999 markder Created --*/ VOID HookObject( IN CLSID *pCLSID, IN REFIID riid, OUT LPVOID *ppv, OUT PSHIM_HOOKED_OBJECT pOb, IN BOOL bClassFactory ) { PVOID *pVtbl = ((PVOID*)(*((PVOID*)(*ppv)))); DWORD i = 0; if (!pOb) { DPF(eDbgLevelInfo, "[HookObject] New %s! pThis: 0x%p\n", (bClassFactory?"class factory":"object"), *ppv); pOb = (PSHIM_HOOKED_OBJECT) VirtualAlloc( NULL, sizeof(PSHIM_HOOKED_OBJECT), MEM_COMMIT, PAGE_READWRITE); pOb->pCLSID = pCLSID; pOb->pThis = *ppv; pOb->dwRef = 1; pOb->bAddRefTrip = FALSE; pOb->pNext = g_pObjectCache; pOb->bClassFactory = bClassFactory; g_pObjectCache = pOb; } // IUnknown must always be APIHooked since it is possible to get // a new interface pointer using it, and we need to process each interface // handed out. We must also keep track of the reference count so that // we can clean up our interface function map. PatchFunction(pVtbl, 0, APIHook_QueryInterface); PatchFunction(pVtbl, 1, APIHook_AddRef); PatchFunction(pVtbl, 2, APIHook_Release); if (bClassFactory && IsEqualGUID(IID_IClassFactory, riid)) { PatchFunction(pVtbl, 3, APIHook_IClassFactory_CreateInstance); } else { for (i = 0; i < g_dwCOMHookCount; i++) { if (!(g_pCOMHooks[i].pCLSID) || !pCLSID) { if (IsEqualGUID( (REFIID) *(g_pCOMHooks[i].pIID), riid)) { PatchFunction( pVtbl, g_pCOMHooks[i].dwVtblIndex, g_pCOMHooks[i].pfnNew); } } else { if (IsEqualGUID((REFCLSID) *(g_pCOMHooks[i].pCLSID), *pCLSID) && IsEqualGUID((REFIID) *(g_pCOMHooks[i].pIID), riid)) { PatchFunction( pVtbl, g_pCOMHooks[i].dwVtblIndex, g_pCOMHooks[i].pfnNew); } } } } } void InitHooks(DWORD dwCount) { g_bAPIHooksInited = TRUE; g_dwAPIHookCount = dwCount; g_pAPIHooks = (PHOOKAPI) VirtualAlloc( NULL, g_dwAPIHookCount * sizeof(HOOKAPI), MEM_COMMIT, PAGE_READWRITE); } void InitComHooks(DWORD dwCount) { DECLARE_APIHOOK(DDraw.dll, DirectDrawCreate); DECLARE_APIHOOK(DDraw.dll, DirectDrawCreateEx); g_bHasCOMHooks = TRUE; g_dwCOMHookCount = dwCount; g_pCOMHooks = (PSHIM_COM_HOOK) VirtualAlloc( NULL, g_dwCOMHookCount * sizeof(SHIM_COM_HOOK), MEM_COMMIT, PAGE_READWRITE); } /*++ Function Description: Called on process detach with old shim mechanism. Arguments: See MSDN Return Value: See MSDN History: 11/01/1999 markder Created --*/ BOOL __stdcall DllMain( HINSTANCE /*hinstDLL*/, DWORD fdwReason, LPVOID /*lpvReserved*/ ) { if (DLL_PROCESS_DETACH == fdwReason) { if (g_bHasCOMHooks) { DumpCOMHooks(); } InitializeHooks(DLL_PROCESS_DETACH); } return TRUE; }