Game Accessibility Library logo SourceForge.net Logo
Game Accessibility Suite: CAT/CATIntercept.h Source File

CATIntercept.h

Go to the documentation of this file.
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_

Generated on Mon Feb 11 04:09:44 2008 for Game Accessibility Suite by doxygen 1.5.4