windows-nt/Source/XPSP1/NT/windows/appcompat/doc/msvcsampleshim/lib/shimhook.cpp
2020-09-26 16:20:57 +08:00

988 lines
22 KiB
C++

/*++
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;
}