00001 /// \file CATIntercept.h 00002 /// \brief Function interception base class 00003 /// \ingroup CAT 00004 /// 00005 /// Copyright (c) 2007-2008 by Michael Ellison. 00006 /// See COPYING.txt for the \ref gaslicense License (MIT License). 00007 /// 00008 // $Author: mikeellison $ 00009 // $Date: 2008-01-29 07:01:23 -0600 (Tue, 29 Jan 2008) $ 00010 // $Revision: $ 00011 // $NoKeywords: $ 00012 00013 #ifndef _CATIntercept_H_ 00014 #define _CATIntercept_H_ 00015 00016 #include "CATInternal.h" 00017 00018 #ifdef CAT_CONFIG_WIN32 00019 #include <psapi.h> 00020 00021 class CATIntercept; 00022 00023 00024 #pragma pack(push) 00025 #pragma pack(1) 00026 /// The CATHOOK structure contains all the information about a hook, plus directly executable code 00027 /// to bootstrap our hook functions and help return to the original function from them. 00028 struct CATHOOK 00029 { 00030 CATUInt8 PrePushHook; ///< First, we push the CATHOOK address onto the stack for the 00031 CATUInt32 PrePushHookAddress; ///< receiving hook function. 00032 00033 CATUInt32 StackSwap1[2]; ///< Then, we swap the caller's return address with the CATHOOK 00034 CATUInt16 StackSwap2; ///< address, so the latter may be read as a parameter. 00035 00036 CATUInt8 HookJmp; ///< Finally, jump to the user-defined hook function 00037 CATUInt32 HookJmpLoc; 00038 00039 CATUInt8 OrgInst[32]; ///< These are the original instructions that were at the location 00040 ///< we patched with a jump to us. All additional space is filled 00041 ///< with NOPs. We'll execute this directly. 00042 00043 CATUInt8 PostPatchJump; ///< This jump jumps back into the original target right *after* 00044 CATUInt32 PostPatchJumpLoc; ///< the jump patch we applied. 00045 00046 void* Target; ///< Start address of target function 00047 void* HookFunc; ///< Start address of hook function 00048 CATUInt32 OrgInstLen; ///< Number of bytes grabbed into OrgInst 00049 void* UserParam; ///< User-specified context 00050 CATIntercept* InterceptObj; ///< Parent CATIntercept object. 00051 }; 00052 #pragma pack(pop) 00053 00054 /// Request the compiler to NOT introduce prologue/epilogue code into our hook functions 00055 #define CATHOOKFUNC _declspec(naked) 00056 00057 /// Our hook function prologue takes the *original* number of parameters for the function 00058 /// being hooked. It stores all the registers and sets up our stack frame and return values. 00059 /// We then repush relevant parameters onto the stack so that our C/C++ can in hook functions 00060 /// can be pretty. Also note that it has a trailing '{' that must be closed by a 00061 /// CATHOOK_EPILOGUE macro. 00062 /// 00063 #define CATHOOK_PROLOGUE(numParams) \ 00064 __asm {push ebp} \ 00065 __asm {mov ebp,esp} \ 00066 __asm {push eax} \ 00067 __asm {pushfd} \ 00068 __asm {pushad} \ 00069 __asm {sub esp,__LOCAL_SIZE} \ 00070 __asm {mov ecx,numParams+1 } \ 00071 __asm {mov ebx,(numParams+1)*4} \ 00072 __asm {repush_regs:} \ 00073 __asm {push [ebp+ebx]} \ 00074 __asm {sub ebx,4} \ 00075 __asm {loop repush_regs}{ 00076 00077 // __asm {sub esp,8} \ 00078 // __asm {fstp qword ptr [esp]} \ 00079 00080 /// The epilogue restore all the registers saved by CATHOOK_PROLOGUE and sets the stack 00081 /// up to return properly to the original caller. Note that numParams refers to the number 00082 /// of parameters in the *original* function, not the hook function. 00083 #define CATHOOK_EPILOGUE_RAW(numParams) \ 00084 } \ 00085 __asm {add esp,(numParams+1)*4} \ 00086 __asm {add esp,__LOCAL_SIZE} \ 00087 __asm {popad} \ 00088 __asm {popfd} \ 00089 __asm {pop eax} \ 00090 __asm {mov esp,ebp} \ 00091 __asm {pop ebp} \ 00092 __asm {xchg eax,[esp+4]} \ 00093 __asm {pop eax} \ 00094 __asm {xchg eax,[esp]} 00095 00096 // __asm {fld qword ptr [esp]} \ 00097 // __asm {add esp,8} \ 00098 00099 #define CATHOOK_EPILOGUE_WINAPI(numParams) \ 00100 CATHOOK_EPILOGUE_RAW(numParams) \ 00101 __asm {ret numParams*4} 00102 00103 /// This is the CDECL version - only use with CDECL functions! 00104 #define CATHOOK_EPILOGUE_CDECL(numParams) \ 00105 CATHOOK_EPILOGUE_RAW(numParams) \ 00106 __asm {ret 0} 00107 00108 /// CATHOOK_CALLORIGINAL() calls the original function from within a hook function. The return 00109 /// value for EAX is automatically updated, but may be overridden using CATHOOK_SETRETURN. 00110 /// This may only be used between CATHOOK_PROLOGUE and CATHOOK_EPILOGUE. 00111 #define CATHOOK_CALLORIGINAL_WINAPI(hookInst,numParams) \ 00112 void* func = &hookInst->OrgInst; \ 00113 __asm {mov ecx,numParams} \ 00114 __asm {or ecx,ecx} \ 00115 __asm {jz skip_push} \ 00116 __asm {mov ebx,(numParams+2)*4} \ 00117 __asm {repush_host:} \ 00118 __asm {push [ebp+ebx]} \ 00119 __asm {sub ebx,4} \ 00120 __asm {loop repush_host} \ 00121 __asm {skip_push:} \ 00122 __asm {call func} \ 00123 __asm {mov [ebp-4],eax} 00124 00125 /// CDECL version of call original - removes params off stack after call 00126 #define CATHOOK_CALLORIGINAL_CDECL(hookInst,numParams) \ 00127 CATHOOK_CALLORIGINAL_WINAPI(hookInst,numParams) \ 00128 __asm {add esp,(numParams)*4} 00129 00130 /// Sets the return value from a hook function. 00131 #define CATHOOK_SETRETURN(retVal) \ 00132 __asm {mov eax,retVal} \ 00133 __asm {mov [ebp-4],eax} 00134 00135 /// Entry for tables used by InterceptCOMObject(). Specifies the virtual table index of the 00136 /// target function and the target hook function to receive the call. 00137 /// 00138 /// The last entry should have a value of -1 for its VTableIndex to end the table. 00139 struct CATINTERCEPT_COM_TABLE_ENTRY 00140 { 00141 CATUInt32 VTableIndex; 00142 void* HookFunction; 00143 CATUInt32 StubLength; 00144 }; 00145 00146 /// Entry for tables used by InterceptDLL(). Specifies the exported function name of 00147 /// the target function and the target hook to receive the call. 00148 /// 00149 /// The last entry should have a NULL FunctionName entry to specify the end of the table. 00150 struct CATINTERCEPT_DLL_TABLE_ENTRY 00151 { 00152 const CATChar* FunctionName; 00153 void* HookFunction; 00154 CATUInt32 StubLength; 00155 }; 00156 00157 /// \class CATIntercept 00158 /// \brief Function interception class for Win32. 00159 /// \ingroup CAT 00160 /// 00161 /// CATIntercept provides a way to directly hook functions within the current process. 00162 /// To use it, first create a hook function for the function you wish to hook. 00163 /// It needs to be akin to the following: 00164 /// 00165 /// \code 00166 /// CATHOOKFUNC int HookedFunc(CATHOOK* hookInfo, PARAMTYPE param_1, ..., PARAMTYPE param_n) 00167 /// { 00168 /// CATHOOK_PROLOGUE(numParams); 00169 /// 00170 /// /* Your code here to execute prior to original function */ 00171 /// 00172 /// CATHOOK_CALLORIGINAL(hookInfo,numParams); 00173 /// 00174 /// /* Your code here for post-execution */ 00175 /// 00176 /// /* Set the return value if desired... */ 00177 /// CATHOOK_SETRETURN(returnValue); 00178 /// 00179 /// CATHOOK_EPILOGUE(numParams); 00180 /// } 00181 /// \endcode 00182 /// 00183 /// 00184 /// Once you've created the hook function for each function you wish to intercept, you may instantiate a 00185 /// CATIntercept object and call Intercept() for each function. 00186 /// 00187 /// CATIntercept works by overwriting the target function's first 5 with a jump directly into the 00188 /// returned CATHOOK structure. The CATHOOK structure then sets up the registers for the hook function and 00189 /// passes control to it. To call the original function within the hook function, CATHOOK_CALLORIGINAL 00190 /// executes the original bytes from the start of the target function, then jumps to just *after* the jump 00191 /// it patched the function with. 00192 /// 00193 /// While this allows us to be reentrant on hooked functions and not have to beat up import tables, it does 00194 /// present a problem - the code at the start of the target function may not be exactly 5 bytes in length 00195 /// for a proper decode. 00196 /// 00197 /// At the moment, you'll need to debug into the target function and figure out how many bytes to save, then 00198 /// pass that as the stubLength to Intercept(). Eventually, this is screaming to have a basic disassembler 00199 /// written for it to determine the proper number of bytes automatically. Note that we do currently follow 00200 /// 0xe9 jumps, so it will find the actual function within the jump table (or other similar hooks!) 00201 /// 00202 /// \todo 00203 /// Add disassembler component to determine number of bytes to use for stubLength automatically. 00204 /// 00205 /// \note 00206 /// The hook functions should be find for multiple threads (at least, the skeleton provided should be), 00207 /// but currently the CATIntercept object is not. Instantiate a new one for use on each thread or serialize 00208 /// calls to Intercept/Restore. Also note that intercepting/restoring a function while it is being called 00209 /// will probably crash as well. 00210 /// 00211 class CATIntercept 00212 { 00213 public: 00214 CATIntercept(); 00215 virtual ~CATIntercept(); 00216 00217 static void* GetFunctionFromVTable(void* objectPtr, CATUInt32 vtableIndex); 00218 /// Intercept the targetFunc so that hookFunc gets called instead. See the docs above 00219 /// for the CATIntercept class and the CATHOOK_PROLOGUE, CATHOOK_CALLORIGINAL, and 00220 /// CATHOOK_EPILOGUE macros for additional information. 00221 /// 00222 /// \param targetFunc Ptr to the function to hook. 00223 /// \param hookFunc Ptr to hook function to receive calls 00224 /// \param stubLength Number of bytes (minimum of 5) to save from the target function 00225 /// to be executed within our hook prior to calling the target. The 00226 /// length MUST specify full instructions, or it will crash. Relative 00227 /// addressing functions will also cause crashes. 00228 /// \param newHook On success, this is set to point to the CATHOOK struct created for 00229 /// hook. 00230 /// \param userParam Optional context that will be added to CATHOOK structure. 00231 /// \return CATResult CAT_SUCCESS on success. 00232 CATResult Intercept ( void* targetFunc, 00233 void* hookFunc, 00234 CATUInt32 stubLength, 00235 CATHOOK*& newHook, 00236 void* userParam = 0); 00237 00238 /// Restores the target function and removes the hook. 00239 /// 00240 /// \param hookInfo The hook to remove. Set ot 0 on successful return. 00241 /// \return CATResult CAT_SUCCESS on success. 00242 CATResult Restore ( CATHOOK*& hookInfo); 00243 00244 void RestoreAll(); 00245 00246 /// Save the interception data for a COM intercept table to the registry, 00247 /// so we don't have to do all the nasty COM creation later. 00248 CATResult SaveInterceptData ( const CATWChar* objectName, 00249 void* comObject, 00250 CATINTERCEPT_COM_TABLE_ENTRY* interceptTable, 00251 void* userParam); 00252 /// Load interception data from the registry if it's available 00253 CATResult LoadAndHook ( const CATWChar* objectName, 00254 CATINTERCEPT_COM_TABLE_ENTRY* interceptTable, 00255 void* userParam); 00256 00257 /// Hooks all the functions in a COM interface that are specified in a table. 00258 CATResult InterceptCOMObject( void* comObject, 00259 CATINTERCEPT_COM_TABLE_ENTRY* interceptTable, 00260 void* userParam); 00261 00262 CATResult InterceptDLL ( HMODULE module, 00263 CATINTERCEPT_DLL_TABLE_ENTRY* interceptTable, 00264 void* userParam); 00265 00266 protected: 00267 std::vector<CATHOOK*> fHooks; 00268 }; 00269 00270 #endif // CAT_CONFIG_WIN32 00271 #endif // _CATIntercept_H_