diff --git a/mhook-lib/mhook.cpp b/mhook-lib/mhook.cpp deleted file mode 100644 index 3380dce..0000000 --- a/mhook-lib/mhook.cpp +++ /dev/null @@ -1,918 +0,0 @@ -//Copyright (c) 2007-2008, Marton Anka -// -//Permission is hereby granted, free of charge, to any person obtaining a -//copy of this software and associated documentation files (the "Software"), -//to deal in the Software without restriction, including without limitation -//the rights to use, copy, modify, merge, publish, distribute, sublicense, -//and/or sell copies of the Software, and to permit persons to whom the -//Software is furnished to do so, subject to the following conditions: -// -//The above copyright notice and this permission notice shall be included -//in all copies or substantial portions of the Software. -// -//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -//OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -//THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -//IN THE SOFTWARE. - -#include -#include -#include -#include "mhook.h" -#include "../disasm-lib/disasm.h" - -//========================================================================= -#ifndef cntof -#define cntof(a) (sizeof(a)/sizeof(a[0])) -#endif - -//========================================================================= -#ifndef GOOD_HANDLE -#define GOOD_HANDLE(a) ((a!=INVALID_HANDLE_VALUE)&&(a!=NULL)) -#endif - -//========================================================================= -#ifndef gle -#define gle GetLastError -#endif - -//========================================================================= -#ifndef ODPRINTF - -#ifdef _DEBUG -#define ODPRINTF(a) odprintf a -#else -#define ODPRINTF(a) -#endif - -inline void __cdecl odprintf(PCSTR format, ...) { - va_list args; - va_start(args, format); - int len = _vscprintf(format, args); - if (len > 0) { - len += (1 + 2); - PSTR buf = (PSTR) malloc(len); - if (buf) { - len = vsprintf_s(buf, len, format, args); - if (len > 0) { - while (len && isspace(buf[len-1])) len--; - buf[len++] = '\r'; - buf[len++] = '\n'; - buf[len] = 0; - OutputDebugStringA(buf); - } - free(buf); - } - va_end(args); - } -} - -inline void __cdecl odprintf(PCWSTR format, ...) { - va_list args; - va_start(args, format); - int len = _vscwprintf(format, args); - if (len > 0) { - len += (1 + 2); - PWSTR buf = (PWSTR) malloc(sizeof(WCHAR)*len); - if (buf) { - len = vswprintf_s(buf, len, format, args); - if (len > 0) { - while (len && iswspace(buf[len-1])) len--; - buf[len++] = L'\r'; - buf[len++] = L'\n'; - buf[len] = 0; - OutputDebugStringW(buf); - } - free(buf); - } - va_end(args); - } -} - -#endif //#ifndef ODPRINTF - -//========================================================================= -#define MHOOKS_MAX_CODE_BYTES 32 -#define MHOOKS_MAX_RIPS 4 - -//========================================================================= -// The trampoline structure - stores every bit of info about a hook -struct MHOOKS_TRAMPOLINE { - PBYTE pSystemFunction; // the original system function - DWORD cbOverwrittenCode; // number of bytes overwritten by the jump - PBYTE pHookFunction; // the hook function that we provide - BYTE codeJumpToHookFunction[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that jumps to the hook function - BYTE codeTrampoline[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that holds the first few - // bytes from the system function and a jump to the remainder - // in the original location - BYTE codeUntouched[MHOOKS_MAX_CODE_BYTES]; // placeholder for unmodified original code - // (we patch IP-relative addressing) - MHOOKS_TRAMPOLINE* pPrevTrampoline; // When in the free list, thess are pointers to the prev and next entry. - MHOOKS_TRAMPOLINE* pNextTrampoline; // When not in the free list, this is a pointer to the prev and next trampoline in use. -}; - -//========================================================================= -// The patch data structures - store info about rip-relative instructions -// during hook placement -struct MHOOKS_RIPINFO -{ - DWORD dwOffset; - S64 nDisplacement; -}; - -struct MHOOKS_PATCHDATA -{ - S64 nLimitUp; - S64 nLimitDown; - DWORD nRipCnt; - MHOOKS_RIPINFO rips[MHOOKS_MAX_RIPS]; -}; - -//========================================================================= -// Global vars -static BOOL g_bVarsInitialized = FALSE; -static CRITICAL_SECTION g_cs; -static MHOOKS_TRAMPOLINE* g_pHooks = NULL; -static MHOOKS_TRAMPOLINE* g_pFreeList = NULL; -static DWORD g_nHooksInUse = 0; -static HANDLE* g_hThreadHandles = NULL; -static DWORD g_nThreadHandles = 0; -#define MHOOK_JMPSIZE 5 -#define MHOOK_MINALLOCSIZE 4096 - -//========================================================================= -// Toolhelp defintions so the functions can be dynamically bound to -typedef HANDLE (WINAPI * _CreateToolhelp32Snapshot)( - DWORD dwFlags, - DWORD th32ProcessID - ); - -typedef BOOL (WINAPI * _Thread32First)( - HANDLE hSnapshot, - LPTHREADENTRY32 lpte - ); - -typedef BOOL (WINAPI * _Thread32Next)( - HANDLE hSnapshot, - LPTHREADENTRY32 lpte - ); - -//========================================================================= -// Bring in the toolhelp functions from kernel32 -_CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapshot) GetProcAddress(GetModuleHandle(L"kernel32"), "CreateToolhelp32Snapshot"); -_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32First"); -_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32Next"); - -//========================================================================= -// Internal function: -// -// Remove the trampoline from the specified list, updating the head pointer -// if necessary. -//========================================================================= -static VOID ListRemove(MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) { - if (pNode->pPrevTrampoline) { - pNode->pPrevTrampoline->pNextTrampoline = pNode->pNextTrampoline; - } - - if (pNode->pNextTrampoline) { - pNode->pNextTrampoline->pPrevTrampoline = pNode->pPrevTrampoline; - } - - if ((*pListHead) == pNode) { - (*pListHead) = pNode->pNextTrampoline; - assert((*pListHead)->pPrevTrampoline == NULL); - } - - pNode->pPrevTrampoline = NULL; - pNode->pNextTrampoline = NULL; -} - -//========================================================================= -// Internal function: -// -// Prepend the trampoline from the specified list and update the head pointer. -//========================================================================= -static VOID ListPrepend(MHOOKS_TRAMPOLINE** pListHead, MHOOKS_TRAMPOLINE* pNode) { - pNode->pPrevTrampoline = NULL; - pNode->pNextTrampoline = (*pListHead); - if ((*pListHead)) { - (*pListHead)->pPrevTrampoline = pNode; - } - (*pListHead) = pNode; -} - -//========================================================================= -static VOID EnterCritSec() { - if (!g_bVarsInitialized) { - InitializeCriticalSection(&g_cs); - g_bVarsInitialized = TRUE; - } - EnterCriticalSection(&g_cs); -} - -//========================================================================= -static VOID LeaveCritSec() { - LeaveCriticalSection(&g_cs); -} - -//========================================================================= -// Internal function: -// -// Skip over jumps that lead to the real function. Gets around import -// jump tables, etc. -//========================================================================= -static PBYTE SkipJumps(PBYTE pbCode) { - PBYTE pbOrgCode = pbCode; -#ifdef _M_IX86_X64 -#ifdef _M_IX86 - //mov edi,edi: hot patch point - if (pbCode[0] == 0x8b && pbCode[1] == 0xff) - pbCode += 2; - // push ebp; mov ebp, esp; pop ebp; - // "collapsed" stackframe generated by MSVC - if (pbCode[0] == 0x55 && pbCode[1] == 0x8b && pbCode[2] == 0xec && pbCode[3] == 0x5d) - pbCode += 4; -#endif - if (pbCode[0] == 0xff && pbCode[1] == 0x25) { -#ifdef _M_IX86 - // on x86 we have an absolute pointer... - PBYTE pbTarget = *(PBYTE *)&pbCode[2]; - // ... that shows us an absolute pointer. - return SkipJumps(*(PBYTE *)pbTarget); -#elif defined _M_X64 - // on x64 we have a 32-bit offset... - INT32 lOffset = *(INT32 *)&pbCode[2]; - // ... that shows us an absolute pointer - return SkipJumps(*(PBYTE*)(pbCode + 6 + lOffset)); - } else if (pbCode[0] == 0x48 && pbCode[1] == 0xff && pbCode[2] == 0x25) { - // or we can have the same with a REX prefix - INT32 lOffset = *(INT32 *)&pbCode[3]; - // ... that shows us an absolute pointer - return SkipJumps(*(PBYTE*)(pbCode + 7 + lOffset)); -#endif - } else if (pbCode[0] == 0xe9) { - // here the behavior is identical, we have... - // ...a 32-bit offset to the destination. - return SkipJumps(pbCode + 5 + *(INT32 *)&pbCode[1]); - } else if (pbCode[0] == 0xeb) { - // and finally an 8-bit offset to the destination - return SkipJumps(pbCode + 2 + *(CHAR *)&pbCode[1]); - } -#else -#error unsupported platform -#endif - return pbOrgCode; -} - -//========================================================================= -// Internal function: -// -// Writes code at pbCode that jumps to pbJumpTo. Will attempt to do this -// in as few bytes as possible. Important on x64 where the long jump -// (0xff 0x25 ....) can take up 14 bytes. -//========================================================================= -static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) { -#ifdef _M_IX86_X64 - PBYTE pbJumpFrom = pbCode + 5; - SIZE_T cbDiff = pbJumpFrom > pbJumpTo ? pbJumpFrom - pbJumpTo : pbJumpTo - pbJumpFrom; - ODPRINTF((L"mhooks: EmitJump: Jumping from %p to %p, diff is %p", pbJumpFrom, pbJumpTo, cbDiff)); - if (cbDiff <= 0x7fff0000) { - pbCode[0] = 0xe9; - pbCode += 1; - *((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbJumpTo - pbJumpFrom); - pbCode += sizeof(DWORD); - } else { - pbCode[0] = 0xff; - pbCode[1] = 0x25; - pbCode += 2; -#ifdef _M_IX86 - // on x86 we write an absolute address (just behind the instruction) - *((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbCode + sizeof(DWORD)); -#elif defined _M_X64 - // on x64 we write the relative address of the same location - *((PDWORD)pbCode) = (DWORD)0; -#endif - pbCode += sizeof(DWORD); - *((PDWORD_PTR)pbCode) = (DWORD_PTR)(pbJumpTo); - pbCode += sizeof(DWORD_PTR); - } -#else -#error unsupported platform -#endif - return pbCode; -} - - -//========================================================================= -// Internal function: -// -// Round down to the next multiple of rndDown -//========================================================================= -static size_t RoundDown(size_t addr, size_t rndDown) -{ - return (addr / rndDown) * rndDown; -} - -//========================================================================= -// Internal function: -// -// Will attempt allocate a block of memory within the specified range, as -// near as possible to the specified function. -//========================================================================= -static MHOOKS_TRAMPOLINE* BlockAlloc(PBYTE pSystemFunction, PBYTE pbLower, PBYTE pbUpper) { - SYSTEM_INFO sSysInfo = {0}; - ::GetSystemInfo(&sSysInfo); - - // Always allocate in bulk, in case the system actually has a smaller allocation granularity than MINALLOCSIZE. - const ptrdiff_t cAllocSize = max(sSysInfo.dwAllocationGranularity, MHOOK_MINALLOCSIZE); - - MHOOKS_TRAMPOLINE* pRetVal = NULL; - PBYTE pModuleGuess = (PBYTE) RoundDown((size_t)pSystemFunction, cAllocSize); - int loopCount = 0; - for (PBYTE pbAlloc = pModuleGuess; pbLower < pbAlloc && pbAlloc < pbUpper; ++loopCount) { - // determine current state - MEMORY_BASIC_INFORMATION mbi; - ODPRINTF((L"mhooks: BlockAlloc: Looking at address %p", pbAlloc)); - if (!VirtualQuery(pbAlloc, &mbi, sizeof(mbi))) - break; - // free & large enough? - if (mbi.State == MEM_FREE && mbi.RegionSize >= (unsigned)cAllocSize) { - // and then try to allocate it - pRetVal = (MHOOKS_TRAMPOLINE*) VirtualAlloc(pbAlloc, cAllocSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); - if (pRetVal) { - size_t trampolineCount = cAllocSize / sizeof(MHOOKS_TRAMPOLINE); - ODPRINTF((L"mhooks: BlockAlloc: Allocated block at %p as %d trampolines", pRetVal, trampolineCount)); - - pRetVal[0].pPrevTrampoline = NULL; - pRetVal[0].pNextTrampoline = &pRetVal[1]; - - // prepare them by having them point down the line at the next entry. - for (size_t s = 1; s < trampolineCount; ++s) { - pRetVal[s].pPrevTrampoline = &pRetVal[s - 1]; - pRetVal[s].pNextTrampoline = &pRetVal[s + 1]; - } - - // last entry points to the current head of the free list - pRetVal[trampolineCount - 1].pNextTrampoline = g_pFreeList; - break; - } - } - - // This is a spiral, should be -1, 1, -2, 2, -3, 3, etc. (* cAllocSize) - ptrdiff_t bytesToOffset = (cAllocSize * (loopCount + 1) * ((loopCount % 2 == 0) ? -1 : 1)); - pbAlloc = pbAlloc + bytesToOffset; - } - - return pRetVal; -} - -//========================================================================= -// Internal function: -// -// Will try to allocate a big block of memory inside the required range. -//========================================================================= -static MHOOKS_TRAMPOLINE* FindTrampolineInRange(PBYTE pLower, PBYTE pUpper) { - if (!g_pFreeList) { - return NULL; - } - - // This is a standard free list, except we're doubly linked to deal with soem return shenanigans. - MHOOKS_TRAMPOLINE* curEntry = g_pFreeList; - while (curEntry) { - if ((MHOOKS_TRAMPOLINE*) pLower < curEntry && curEntry < (MHOOKS_TRAMPOLINE*) pUpper) { - ListRemove(&g_pFreeList, curEntry); - - return curEntry; - } - - curEntry = curEntry->pNextTrampoline; - } - - return NULL; -} - -//========================================================================= -// Internal function: -// -// Will try to allocate the trampoline structure within 2 gigabytes of -// the target function. -//========================================================================= -static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) { - - MHOOKS_TRAMPOLINE* pTrampoline = NULL; - - // determine lower and upper bounds for the allocation locations. - // in the basic scenario this is +/- 2GB but IP-relative instructions - // found in the original code may require a smaller window. - PBYTE pLower = pSystemFunction + nLimitUp; - pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ? - (PBYTE)(0x1) : (PBYTE)(pLower - (PBYTE)0x7fff0000); - PBYTE pUpper = pSystemFunction + nLimitDown; - pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ? - (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000) : (PBYTE)(DWORD_PTR)0xfffffffffff80000; - ODPRINTF((L"mhooks: TrampolineAlloc: Allocating for %p between %p and %p", pSystemFunction, pLower, pUpper)); - - // try to find a trampoline in the specified range - pTrampoline = FindTrampolineInRange(pLower, pUpper); - if (!pTrampoline) { - // if it we can't find it, then we need to allocate a new block and - // try again. Just fail if that doesn't work - g_pFreeList = BlockAlloc(pSystemFunction, pLower, pUpper); - pTrampoline = FindTrampolineInRange(pLower, pUpper); - } - - // found and allocated a trampoline? - if (pTrampoline) { - ListPrepend(&g_pHooks, pTrampoline); - } - - return pTrampoline; -} - -//========================================================================= -// Internal function: -// -// Return the internal trampoline structure that belongs to a hooked function. -//========================================================================= -static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) { - MHOOKS_TRAMPOLINE* pCurrent = g_pHooks; - - while (pCurrent) { - if (pCurrent->pHookFunction == pHookedFunction) { - return pCurrent; - } - - pCurrent = pCurrent->pNextTrampoline; - } - - return NULL; -} - -//========================================================================= -// Internal function: -// -// Free a trampoline structure. -//========================================================================= -static VOID TrampolineFree(MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) { - ListRemove(&g_pHooks, pTrampoline); - - // If a thread could feasinbly have some of our trampoline code - // on its stack and we yank the region from underneath it then it will - // surely crash upon returning. So instead of freeing the - // memory we just let it leak. Ugly, but safe. - if (bNeverUsed) { - ListPrepend(&g_pFreeList, pTrampoline); - } - - g_nHooksInUse--; -} - -//========================================================================= -// Internal function: -// -// Suspend a given thread and try to make sure that its instruction -// pointer is not in the given range. -//========================================================================= -static HANDLE SuspendOneThread(DWORD dwThreadId, PBYTE pbCode, DWORD cbBytes) { - // open the thread - HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadId); - if (GOOD_HANDLE(hThread)) { - // attempt suspension - DWORD dwSuspendCount = SuspendThread(hThread); - if (dwSuspendCount != -1) { - // see where the IP is - CONTEXT ctx; - ctx.ContextFlags = CONTEXT_CONTROL; - int nTries = 0; - while (GetThreadContext(hThread, &ctx)) { -#ifdef _M_IX86 - PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Eip; -#elif defined _M_X64 - PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Rip; -#endif - if (pIp >= pbCode && pIp < (pbCode + cbBytes)) { - if (nTries < 3) { - // oops - we should try to get the instruction pointer out of here. - ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE", dwThreadId, pIp)); - ResumeThread(hThread); - Sleep(100); - SuspendThread(hThread); - nTries++; - } else { - // we gave it all we could. (this will probably never - // happen - unless the thread has already been suspended - // to begin with) - ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE - CAN'T FIX", dwThreadId, pIp)); - ResumeThread(hThread); - CloseHandle(hThread); - hThread = NULL; - break; - } - } else { - // success, the IP is not conflicting - ODPRINTF((L"mhooks: SuspendOneThread: Successfully suspended thread %d - IP is at %p", dwThreadId, pIp)); - break; - } - } - } else { - // couldn't suspend - CloseHandle(hThread); - hThread = NULL; - } - } - return hThread; -} - -//========================================================================= -// Internal function: -// -// Resumes all previously suspended threads in the current process. -//========================================================================= -static VOID ResumeOtherThreads() { - // make sure things go as fast as possible - INT nOriginalPriority = GetThreadPriority(GetCurrentThread()); - SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); - // go through our list - for (DWORD i=0; inRipCnt; i++) { - DWORD dwNewDisplacement = (DWORD)(pdata->rips[i].nDisplacement - diff); - ODPRINTF((L"mhooks: fixing up RIP instruction operand for code at 0x%p: " - L"old displacement: 0x%8.8x, new displacement: 0x%8.8x", - pbNew + pdata->rips[i].dwOffset, - (DWORD)pdata->rips[i].nDisplacement, - dwNewDisplacement)); - *(PDWORD)(pbNew + pdata->rips[i].dwOffset) = dwNewDisplacement; - } -#endif -} - -//========================================================================= -// Examine the machine code at the target function's entry point, and -// skip bytes in a way that we'll always end on an instruction boundary. -// We also detect branches and subroutine calls (as well as returns) -// at which point disassembly must stop. -// Finally, detect and collect information on IP-relative instructions -// that we can patch. -static DWORD DisassembleAndSkip(PVOID pFunction, DWORD dwMinLen, MHOOKS_PATCHDATA* pdata) { - DWORD dwRet = 0; - pdata->nLimitDown = 0; - pdata->nLimitUp = 0; - pdata->nRipCnt = 0; -#ifdef _M_IX86 - ARCHITECTURE_TYPE arch = ARCH_X86; -#elif defined _M_X64 - ARCHITECTURE_TYPE arch = ARCH_X64; -#else - #error unsupported platform -#endif - DISASSEMBLER dis; - if (InitDisassembler(&dis, arch)) { - INSTRUCTION* pins = NULL; - U8* pLoc = (U8*)pFunction; - DWORD dwFlags = DISASM_DECODE | DISASM_DISASSEMBLE | DISASM_ALIGNOUTPUT; - - ODPRINTF((L"mhooks: DisassembleAndSkip: Disassembling %p", pLoc)); - while ( (dwRet < dwMinLen) && (pins = GetInstruction(&dis, (ULONG_PTR)pLoc, pLoc, dwFlags)) ) { - ODPRINTF(("mhooks: DisassembleAndSkip: %p:(0x%2.2x) %s", pLoc, pins->Length, pins->String)); - if (pins->Type == ITYPE_RET ) break; - if (pins->Type == ITYPE_BRANCH ) break; - if (pins->Type == ITYPE_BRANCHCC) break; - if (pins->Type == ITYPE_CALL ) break; - if (pins->Type == ITYPE_CALLCC ) break; - - #if defined _M_X64 - BOOL bProcessRip = FALSE; - // mov or lea to register from rip+imm32 - if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) && - (pins->X86.OperandSize == 8) && (pins->OperandCount == 2) && - (pins->Operands[1].Flags & OP_IPREL) && (pins->Operands[1].Register == AMD64_REG_RIP)) - { - // rip-addressing "mov reg, [rip+imm32]" - ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 1, pins->X86.Displacement, *(PDWORD)(pLoc+3))); - bProcessRip = TRUE; - } - // mov or lea to rip+imm32 from register - else if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) && - (pins->X86.OperandSize == 8) && (pins->OperandCount == 2) && - (pins->Operands[0].Flags & OP_IPREL) && (pins->Operands[0].Register == AMD64_REG_RIP)) - { - // rip-addressing "mov [rip+imm32], reg" - ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 0, pins->X86.Displacement, *(PDWORD)(pLoc+3))); - bProcessRip = TRUE; - } - else if ( (pins->OperandCount >= 1) && (pins->Operands[0].Flags & OP_IPREL) ) - { - // unsupported rip-addressing - ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 0)); - // dump instruction bytes to the debug output - for (DWORD i=0; iLength; i++) { - ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); - } - break; - } - else if ( (pins->OperandCount >= 2) && (pins->Operands[1].Flags & OP_IPREL) ) - { - // unsupported rip-addressing - ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 1)); - // dump instruction bytes to the debug output - for (DWORD i=0; iLength; i++) { - ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); - } - break; - } - else if ( (pins->OperandCount >= 3) && (pins->Operands[2].Flags & OP_IPREL) ) - { - // unsupported rip-addressing - ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 2)); - // dump instruction bytes to the debug output - for (DWORD i=0; iLength; i++) { - ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); - } - break; - } - // follow through with RIP-processing if needed - if (bProcessRip) { - // calculate displacement relative to function start - S64 nAdjustedDisplacement = pins->X86.Displacement + (pLoc - (U8*)pFunction); - // store displacement values furthest from zero (both positive and negative) - if (nAdjustedDisplacement < pdata->nLimitDown) - pdata->nLimitDown = nAdjustedDisplacement; - if (nAdjustedDisplacement > pdata->nLimitUp) - pdata->nLimitUp = nAdjustedDisplacement; - // store patch info - if (pdata->nRipCnt < MHOOKS_MAX_RIPS) { - pdata->rips[pdata->nRipCnt].dwOffset = dwRet + 3; - pdata->rips[pdata->nRipCnt].nDisplacement = pins->X86.Displacement; - pdata->nRipCnt++; - } else { - // no room for patch info, stop disassembly - break; - } - } - #endif - - dwRet += pins->Length; - pLoc += pins->Length; - } - - CloseDisassembler(&dis); - } - - return dwRet; -} - -//========================================================================= -BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction) { - MHOOKS_TRAMPOLINE* pTrampoline = NULL; - PVOID pSystemFunction = *ppSystemFunction; - // ensure thread-safety - EnterCritSec(); - ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction)); - // find the real functions (jump over jump tables, if any) - pSystemFunction = SkipJumps((PBYTE)pSystemFunction); - pHookFunction = SkipJumps((PBYTE)pHookFunction); - ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction)); - // figure out the length of the overwrite zone - MHOOKS_PATCHDATA patchdata = {0}; - DWORD dwInstructionLength = DisassembleAndSkip(pSystemFunction, MHOOK_JMPSIZE, &patchdata); - if (dwInstructionLength >= MHOOK_JMPSIZE) { - ODPRINTF((L"mhooks: Mhook_SetHook: disassembly signals %d bytes", dwInstructionLength)); - // suspend every other thread in this process, and make sure their IP - // is not in the code we're about to overwrite. - SuspendOtherThreads((PBYTE)pSystemFunction, dwInstructionLength); - // allocate a trampoline structure (TODO: it is pretty wasteful to get - // VirtualAlloc to grab chunks of memory smaller than 100 bytes) - pTrampoline = TrampolineAlloc((PBYTE)pSystemFunction, patchdata.nLimitUp, patchdata.nLimitDown); - if (pTrampoline) { - ODPRINTF((L"mhooks: Mhook_SetHook: allocated structure at %p", pTrampoline)); - DWORD dwOldProtectSystemFunction = 0; - DWORD dwOldProtectTrampolineFunction = 0; - // set the system function to PAGE_EXECUTE_READWRITE - if (VirtualProtect(pSystemFunction, dwInstructionLength, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) { - ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on system function")); - // mark our trampoline buffer to PAGE_EXECUTE_READWRITE - if (VirtualProtect(pTrampoline, sizeof(MHOOKS_TRAMPOLINE), PAGE_EXECUTE_READWRITE, &dwOldProtectTrampolineFunction)) { - ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on trampoline structure")); - - // create our trampoline function - PBYTE pbCode = pTrampoline->codeTrampoline; - // save original code.. - for (DWORD i = 0; icodeUntouched[i] = pbCode[i] = ((PBYTE)pSystemFunction)[i]; - } - pbCode += dwInstructionLength; - // plus a jump to the continuation in the original location - pbCode = EmitJump(pbCode, ((PBYTE)pSystemFunction) + dwInstructionLength); - ODPRINTF((L"mhooks: Mhook_SetHook: updated the trampoline")); - - // fix up any IP-relative addressing in the code - FixupIPRelativeAddressing(pTrampoline->codeTrampoline, (PBYTE)pSystemFunction, &patchdata); - - DWORD_PTR dwDistance = (PBYTE)pHookFunction < (PBYTE)pSystemFunction ? - (PBYTE)pSystemFunction - (PBYTE)pHookFunction : (PBYTE)pHookFunction - (PBYTE)pSystemFunction; - if (dwDistance > 0x7fff0000) { - // create a stub that jumps to the replacement function. - // we need this because jumping from the API to the hook directly - // will be a long jump, which is 14 bytes on x64, and we want to - // avoid that - the API may or may not have room for such stuff. - // (remember, we only have 5 bytes guaranteed in the API.) - // on the other hand we do have room, and the trampoline will always be - // within +/- 2GB of the API, so we do the long jump in there. - // the API will jump to the "reverse trampoline" which - // will jump to the user's hook code. - pbCode = pTrampoline->codeJumpToHookFunction; - pbCode = EmitJump(pbCode, (PBYTE)pHookFunction); - ODPRINTF((L"mhooks: Mhook_SetHook: created reverse trampoline")); - FlushInstructionCache(GetCurrentProcess(), pTrampoline->codeJumpToHookFunction, - pbCode - pTrampoline->codeJumpToHookFunction); - - // update the API itself - pbCode = (PBYTE)pSystemFunction; - pbCode = EmitJump(pbCode, pTrampoline->codeJumpToHookFunction); - } else { - // the jump will be at most 5 bytes so we can do it directly - // update the API itself - pbCode = (PBYTE)pSystemFunction; - pbCode = EmitJump(pbCode, (PBYTE)pHookFunction); - } - - // update data members - pTrampoline->cbOverwrittenCode = dwInstructionLength; - pTrampoline->pSystemFunction = (PBYTE)pSystemFunction; - pTrampoline->pHookFunction = (PBYTE)pHookFunction; - - // flush instruction cache and restore original protection - FlushInstructionCache(GetCurrentProcess(), pTrampoline->codeTrampoline, dwInstructionLength); - VirtualProtect(pTrampoline, sizeof(MHOOKS_TRAMPOLINE), dwOldProtectTrampolineFunction, &dwOldProtectTrampolineFunction); - } else { - ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtect 2: %d", gle())); - } - // flush instruction cache and restore original protection - FlushInstructionCache(GetCurrentProcess(), pSystemFunction, dwInstructionLength); - VirtualProtect(pSystemFunction, dwInstructionLength, dwOldProtectSystemFunction, &dwOldProtectSystemFunction); - } else { - ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtect 1: %d", gle())); - } - if (pTrampoline->pSystemFunction) { - // this is what the application will use as the entry point - // to the "original" unhooked function. - *ppSystemFunction = pTrampoline->codeTrampoline; - ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!")); - } else { - // if we failed discard the trampoline (forcing VirtualFree) - TrampolineFree(pTrampoline, TRUE); - pTrampoline = NULL; - } - } - // resume everybody else - ResumeOtherThreads(); - } else { - ODPRINTF((L"mhooks: disassembly signals %d bytes (unacceptable)", dwInstructionLength)); - } - LeaveCritSec(); - return (pTrampoline != NULL); -} - -//========================================================================= -BOOL Mhook_Unhook(PVOID *ppHookedFunction) { - ODPRINTF((L"mhooks: Mhook_Unhook: %p", *ppHookedFunction)); - BOOL bRet = FALSE; - EnterCritSec(); - // get the trampoline structure that corresponds to our function - MHOOKS_TRAMPOLINE* pTrampoline = TrampolineGet((PBYTE)*ppHookedFunction); - if (pTrampoline) { - // make sure nobody's executing code where we're about to overwrite a few bytes - SuspendOtherThreads(pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode); - ODPRINTF((L"mhooks: Mhook_Unhook: found struct at %p", pTrampoline)); - DWORD dwOldProtectSystemFunction = 0; - // make memory writable - if (VirtualProtect(pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) { - ODPRINTF((L"mhooks: Mhook_Unhook: readwrite set on system function")); - PBYTE pbCode = (PBYTE)pTrampoline->pSystemFunction; - for (DWORD i = 0; icbOverwrittenCode; i++) { - pbCode[i] = pTrampoline->codeUntouched[i]; - } - // flush instruction cache and make memory unwritable - FlushInstructionCache(GetCurrentProcess(), pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode); - VirtualProtect(pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, dwOldProtectSystemFunction, &dwOldProtectSystemFunction); - // return the original function pointer - *ppHookedFunction = pTrampoline->pSystemFunction; - bRet = TRUE; - ODPRINTF((L"mhooks: Mhook_Unhook: sysfunc: %p", *ppHookedFunction)); - // free the trampoline while not really discarding it from memory - TrampolineFree(pTrampoline, FALSE); - ODPRINTF((L"mhooks: Mhook_Unhook: unhook successful")); - } else { - ODPRINTF((L"mhooks: Mhook_Unhook: failed VirtualProtect 1: %d", gle())); - } - // make the other guys runnable - ResumeOtherThreads(); - } - LeaveCritSec(); - return bRet; -} - -//========================================================================= diff --git a/mhook-test.sln b/mhook-test.sln deleted file mode 100644 index 95fbcce..0000000 --- a/mhook-test.sln +++ /dev/null @@ -1,29 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mhook-test", "mhook-test.vcxproj", "{0E055CAF-C68B-42CB-A302-F775CA5A917F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|Win32.ActiveCfg = Debug|Win32 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|Win32.Build.0 = Debug|Win32 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x64.ActiveCfg = Debug|x64 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x64.Build.0 = Debug|x64 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|Win32.ActiveCfg = Release|Win32 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|Win32.Build.0 = Release|Win32 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x64.ActiveCfg = Release|x64 - {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x64.Build.0 = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(DPCodeReviewSolutionGUID) = preSolution - DPCodeReviewSolutionGUID = {00000000-0000-0000-0000-000000000000} - EndGlobalSection -EndGlobal diff --git a/mhook-test.vcproj b/mhook-test.vcproj deleted file mode 100644 index ecbd5a9..0000000 --- a/mhook-test.vcproj +++ /dev/null @@ -1,445 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/mhook-test.vcxproj b/mhook-test/mhook-test.vcxproj similarity index 79% rename from mhook-test.vcxproj rename to mhook-test/mhook-test.vcxproj index de107dd..533fc2a 100644 --- a/mhook-test.vcxproj +++ b/mhook-test/mhook-test.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -18,29 +18,41 @@ x64 + + + + + + + {0E055CAF-C68B-42CB-A302-F775CA5A917F} mhooktest Win32Proj + 8.1 Application Unicode true + v140 Application Unicode + v140 Application Unicode true + v140 Application Unicode + v140 @@ -60,16 +72,16 @@ <_ProjectFileVersion>10.0.40219.1 - $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)bin\$(PlatformTarget)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ true - $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)bin\$(PlatformTarget)\$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ true - $(SolutionDir)$(Configuration)\ + $(SolutionDir)bin\$(PlatformTarget)\$(Configuration)\ $(Configuration)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)bin\$(PlatformTarget)\$(Configuration)\ $(Platform)\$(Configuration)\ false AllRules.ruleset @@ -92,15 +104,17 @@ true EnableFastChecks MultiThreadedDebug - - + NotUsing Level3 EditAndContinue + $(SolutionDir)include;%(AdditionalIncludeDirectories) true Console MachineX86 + mhook.lib;%(AdditionalDependencies) + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ @@ -109,13 +123,12 @@ Disabled - %(AdditionalIncludeDirectories) + $(SolutionDir)include;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks MultiThreadedDebug - - + NotUsing Level3 ProgramDatabase @@ -123,15 +136,18 @@ true Console MachineX64 + mhook.lib;%(AdditionalDependencies) + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded - Use + NotUsing Level3 ProgramDatabase + $(SolutionDir)include;%(AdditionalIncludeDirectories) true @@ -139,6 +155,8 @@ true true MachineX86 + mhook.lib;%(AdditionalDependencies) + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ @@ -148,9 +166,10 @@ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded - Use + NotUsing Level3 ProgramDatabase + $(SolutionDir)include;%(AdditionalIncludeDirectories) true @@ -158,31 +177,10 @@ true true MachineX64 + mhook.lib;%(AdditionalDependencies) + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ - - - - Create - Create - Create - Create - - - - - - - - - - - - - - - - diff --git a/mhook-test/mhook-test.vcxproj.filters b/mhook-test/mhook-test.vcxproj.filters new file mode 100644 index 0000000..89af4d9 --- /dev/null +++ b/mhook-test/mhook-test.vcxproj.filters @@ -0,0 +1,30 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + + + Header Files + + + \ No newline at end of file diff --git a/mhook-test.cpp b/mhook-test/src/mhook-test.cpp similarity index 99% rename from mhook-test.cpp rename to mhook-test/src/mhook-test.cpp index 79dc767..d0a76b9 100644 --- a/mhook-test.cpp +++ b/mhook-test/src/mhook-test.cpp @@ -19,7 +19,7 @@ //IN THE SOFTWARE. #include "stdafx.h" -#include "mhook-lib/mhook.h" +#include //========================================================================= // Define _NtOpenProcess so we can dynamically bind to the function diff --git a/stdafx.cpp b/mhook-test/src/stdafx.cpp similarity index 100% rename from stdafx.cpp rename to mhook-test/src/stdafx.cpp diff --git a/stdafx.h b/mhook-test/src/stdafx.h similarity index 100% rename from stdafx.h rename to mhook-test/src/stdafx.h diff --git a/mhook.sln b/mhook.sln new file mode 100644 index 0000000..f03c31a --- /dev/null +++ b/mhook.sln @@ -0,0 +1,41 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.25420.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mhook", "mhook\mhook.vcxproj", "{DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "mhook-test", "mhook-test\mhook-test.vcxproj", "{0E055CAF-C68B-42CB-A302-F775CA5A917F}" + ProjectSection(ProjectDependencies) = postProject + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7} = {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Debug|x64.ActiveCfg = Debug|x64 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Debug|x64.Build.0 = Debug|x64 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Debug|x86.ActiveCfg = Debug|Win32 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Debug|x86.Build.0 = Debug|Win32 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Release|x64.ActiveCfg = Release|x64 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Release|x64.Build.0 = Release|x64 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Release|x86.ActiveCfg = Release|Win32 + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7}.Release|x86.Build.0 = Release|Win32 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x64.ActiveCfg = Debug|x64 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x64.Build.0 = Debug|x64 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x86.ActiveCfg = Debug|Win32 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Debug|x86.Build.0 = Debug|Win32 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x64.ActiveCfg = Release|x64 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x64.Build.0 = Release|x64 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x86.ActiveCfg = Release|Win32 + {0E055CAF-C68B-42CB-A302-F775CA5A917F}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/mhook-lib/mhook.h b/mhook/include/mhook/mhook.h similarity index 73% rename from mhook-lib/mhook.h rename to mhook/include/mhook/mhook.h index 1d7cfff..0040534 100644 --- a/mhook-lib/mhook.h +++ b/mhook/include/mhook/mhook.h @@ -26,3 +26,14 @@ BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction); BOOL Mhook_Unhook(PVOID *ppHookedFunction); + +// optimisation - when setting many hooks CreateToolHelp32Snapshot to enumerate threads +// can become a bottleneck, allow apps to suspend threads across multiple hooks +// +// note - it's the responsibility of user code to ensure that the threads don't have +// their instruction pointer near any of the hooks - as this would normally be handled +// on a per-hook basis. +// +// these functions are also not thread safe. +void Mhook_SuspendOtherThreads(); +void Mhook_ResumeOtherThreads(); diff --git a/mhook/mhook.vcxproj b/mhook/mhook.vcxproj new file mode 100644 index 0000000..62b967f --- /dev/null +++ b/mhook/mhook.vcxproj @@ -0,0 +1,185 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {DFBE49D1-548A-4CF9-B9AC-705642E0CFF7} + Win32Proj + mhook + 8.1 + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ + + + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ + + + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ + + + $(SolutionDir)lib\$(PlatformTarget)\$(Configuration)\ + + + + + + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + $(ProjectDir)\include;%(AdditionalIncludeDirectories) + + + Windows + + + mkdir $(SolutionDir)include +xcopy /s /f /y $(ProjectDir)include $(SolutionDir)include + + + + + + + Level3 + Disabled + _DEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDebug + $(ProjectDir)\include;%(AdditionalIncludeDirectories) + + + Windows + + + mkdir $(SolutionDir)include +xcopy /s /f /y $(ProjectDir)include $(SolutionDir)include + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + $(ProjectDir)\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + + + mkdir $(SolutionDir)include +xcopy /s /f /y $(ProjectDir)include $(SolutionDir)include + + + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreaded + $(ProjectDir)\include;%(AdditionalIncludeDirectories) + + + Windows + true + true + + + mkdir $(SolutionDir)include +xcopy /s /f /y $(ProjectDir)include $(SolutionDir)include + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mhook-test.vcxproj.filters b/mhook/mhook.vcxproj.filters similarity index 61% rename from mhook-test.vcxproj.filters rename to mhook/mhook.vcxproj.filters index f267eb7..b29d536 100644 --- a/mhook-test.vcxproj.filters +++ b/mhook/mhook.vcxproj.filters @@ -5,65 +5,59 @@ {4FC737F1-C7A5-4376-A066-2A32D752A2FF} cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - {8cb5c05a-64f7-4f61-a670-df3f82769fe0} - - - {be9cd60b-52fc-47e7-a645-e6545493fed8} - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hpp;hxx;hm;inl;inc;xsd + h;hh;hpp;hxx;hm;inl;inc;xsd {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {b9ea35cb-622a-470b-887b-554631d95c1a} + + + {11af8eb6-ab7f-4127-9f8e-a7a02c3ef372} - - Source Files - - - Source Files - - - Source Files\mhook-lib - - - Source Files\disasm-lib - - - Source Files\disasm-lib - - - Source Files\disasm-lib - - - Source Files\disasm-lib - + - - Source Files\mhook-lib - - + Source Files\disasm-lib - + Source Files\disasm-lib - + Source Files\disasm-lib - + Source Files\disasm-lib - + Source Files\disasm-lib - + Header Files + + + Source Files\mhook-lib + + + Source Files\disasm-lib + + + Source Files\disasm-lib + + + Source Files\disasm-lib + + + Source Files\disasm-lib + + \ No newline at end of file diff --git a/disasm-lib/cpu.c b/mhook/src/disasm-lib/cpu.c similarity index 100% rename from disasm-lib/cpu.c rename to mhook/src/disasm-lib/cpu.c diff --git a/disasm-lib/cpu.h b/mhook/src/disasm-lib/cpu.h similarity index 100% rename from disasm-lib/cpu.h rename to mhook/src/disasm-lib/cpu.h diff --git a/disasm-lib/disasm.c b/mhook/src/disasm-lib/disasm.c similarity index 100% rename from disasm-lib/disasm.c rename to mhook/src/disasm-lib/disasm.c diff --git a/disasm-lib/disasm.h b/mhook/src/disasm-lib/disasm.h similarity index 100% rename from disasm-lib/disasm.h rename to mhook/src/disasm-lib/disasm.h diff --git a/disasm-lib/disasm_x86.c b/mhook/src/disasm-lib/disasm_x86.c similarity index 100% rename from disasm-lib/disasm_x86.c rename to mhook/src/disasm-lib/disasm_x86.c diff --git a/disasm-lib/disasm_x86.h b/mhook/src/disasm-lib/disasm_x86.h similarity index 100% rename from disasm-lib/disasm_x86.h rename to mhook/src/disasm-lib/disasm_x86.h diff --git a/disasm-lib/disasm_x86_tables.h b/mhook/src/disasm-lib/disasm_x86_tables.h similarity index 100% rename from disasm-lib/disasm_x86_tables.h rename to mhook/src/disasm-lib/disasm_x86_tables.h diff --git a/disasm-lib/misc.c b/mhook/src/disasm-lib/misc.c similarity index 100% rename from disasm-lib/misc.c rename to mhook/src/disasm-lib/misc.c diff --git a/disasm-lib/misc.h b/mhook/src/disasm-lib/misc.h similarity index 100% rename from disasm-lib/misc.h rename to mhook/src/disasm-lib/misc.h diff --git a/mhook/src/mhook-lib/mhook.cpp b/mhook/src/mhook-lib/mhook.cpp new file mode 100644 index 0000000..c14d2c7 --- /dev/null +++ b/mhook/src/mhook-lib/mhook.cpp @@ -0,0 +1,909 @@ +//Copyright (c) 2007-2008, Marton Anka +// +//Permission is hereby granted, free of charge, to any person obtaining a +//copy of this software and associated documentation files (the "Software"), +//to deal in the Software without restriction, including without limitation +//the rights to use, copy, modify, merge, publish, distribute, sublicense, +//and/or sell copies of the Software, and to permit persons to whom the +//Software is furnished to do so, subject to the following conditions: +// +//The above copyright notice and this permission notice shall be included +//in all copies or substantial portions of the Software. +// +//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +//OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +//THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +//IN THE SOFTWARE. + +#include +#include +#include +#include +#include "mhook/mhook.h" +#include "../disasm-lib/disasm.h" + +//========================================================================= +#ifndef cntof +#define cntof(a) (sizeof(a)/sizeof(a[0])) +#endif + +//========================================================================= +#ifndef GOOD_HANDLE +#define GOOD_HANDLE(a) ((a!=INVALID_HANDLE_VALUE)&&(a!=NULL)) +#endif + +//========================================================================= +#ifndef gle +#define gle GetLastError +#endif + +//========================================================================= +#ifndef ODPRINTF + +// uncomment this line to enable debug output +//#define DEBUG_PRINT + +#ifdef DEBUG_PRINT +#define ODPRINTF(a) odprintf a + +inline void __cdecl odprintf(PCSTR format, ...) { + va_list args; + va_start(args, format); + int len = _vscprintf(format, args); + if (len > 0) { + len += (1 + 2); + PSTR buf = (PSTR)malloc(len); + if (buf) { + len = vsprintf_s(buf, len, format, args); + if (len > 0) { + while (len && isspace(buf[len - 1])) len--; + buf[len++] = '\r'; + buf[len++] = '\n'; + buf[len] = 0; + OutputDebugStringA(buf); + } + free(buf); + } + va_end(args); + } +} + +inline void __cdecl odprintf(PCWSTR format, ...) { + va_list args; + va_start(args, format); + int len = _vscwprintf(format, args); + if (len > 0) { + len += (1 + 2); + PWSTR buf = (PWSTR)malloc(sizeof(WCHAR)*len); + if (buf) { + len = vswprintf_s(buf, len, format, args); + if (len > 0) { + while (len && iswspace(buf[len - 1])) len--; + buf[len++] = L'\r'; + buf[len++] = L'\n'; + buf[len] = 0; + OutputDebugStringW(buf); + } + free(buf); + } + va_end(args); + } +} + +#else +#define ODPRINTF(a) +#endif + +#endif //#ifndef ODPRINTF + +//========================================================================= +#define MHOOKS_MAX_CODE_BYTES 32 +#define MHOOKS_MAX_RIPS 4 +#define MHOOKS_MAX_SUPPORTED_HOOKS 512 + +//========================================================================= +// The trampoline structure - stores every bit of info about a hook +struct MHOOKS_TRAMPOLINE { + PBYTE pSystemFunction; // the original system function + DWORD cbOverwrittenCode; // number of bytes overwritten by the jump + PBYTE pHookFunction; // the hook function that we provide + BYTE codeJumpToHookFunction[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that jumps to the hook function + BYTE codeTrampoline[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that holds the first few + // bytes from the system function and a jump to the remainder + // in the original location + BYTE codeUntouched[MHOOKS_MAX_CODE_BYTES]; // placeholder for unmodified original code + // (we patch IP-relative addressing) +}; + + +//========================================================================= +// The patch data structures - store info about rip-relative instructions +// during hook placement +struct MHOOKS_RIPINFO +{ + DWORD dwOffset; + S64 nDisplacement; +}; + +struct MHOOKS_PATCHDATA +{ + S64 nLimitUp; + S64 nLimitDown; + DWORD nRipCnt; + MHOOKS_RIPINFO rips[MHOOKS_MAX_RIPS]; +}; + +//========================================================================= +// Global vars +static BOOL g_bVarsInitialized = FALSE; +static CRITICAL_SECTION g_cs; +static MHOOKS_TRAMPOLINE* g_pHooks[MHOOKS_MAX_SUPPORTED_HOOKS]; +static DWORD g_nHooksInUse = 0; +static BOOL g_bThreadsSuspended = FALSE; +static HANDLE* g_hThreadHandles = NULL; +static DWORD g_nThreadHandles = 0; +#define MHOOK_JMPSIZE 5 + +//========================================================================= +// Toolhelp defintions so the functions can be dynamically bound to +typedef HANDLE (WINAPI * _CreateToolhelp32Snapshot)( + DWORD dwFlags, DWORD th32ProcessID); + +typedef BOOL (WINAPI * _Thread32First)( + HANDLE hSnapshot, LPTHREADENTRY32 lpte); + +typedef BOOL (WINAPI * _Thread32Next)( + HANDLE hSnapshot, LPTHREADENTRY32 lpte); + +//========================================================================= +// Bring in the toolhelp functions from kernel32 +#ifdef UNICODE +_CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapshot) GetProcAddress(GetModuleHandle(L"kernel32"), "CreateToolhelp32Snapshot"); +_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32First"); +_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle(L"kernel32"), "Thread32Next"); +#else +_CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapshot) GetProcAddress(GetModuleHandle("kernel32"), "CreateToolhelp32Snapshot"); +_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle("kernel32"), "Thread32First"); +_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle("kernel32"), "Thread32Next"); +#endif + +//========================================================================= +static VOID EnterCritSec() { + if (!g_bVarsInitialized) { + InitializeCriticalSection(&g_cs); + ZeroMemory(g_pHooks, sizeof(g_pHooks)); + g_bVarsInitialized = TRUE; + } + EnterCriticalSection(&g_cs); +} + +//========================================================================= +static VOID LeaveCritSec() { + LeaveCriticalSection(&g_cs); +} + +//========================================================================= +// Internal function: +// +// Skip over jumps that lead to the real function. Gets around import +// jump tables, etc. +//========================================================================= +static PBYTE SkipJumps(PBYTE pbCode) { +#ifdef _M_IX86_X64 + if (pbCode[0] == 0xff && pbCode[1] == 0x25) { +#ifdef _M_IX86 + // on x86 we have an absolute pointer... + PBYTE pbTarget = *(PBYTE *)&pbCode[2]; + // ... that shows us an absolute pointer. + return SkipJumps(*(PBYTE *)pbTarget); +#elif defined _M_X64 + // on x64 we have a 32-bit offset... + INT32 lOffset = *(INT32 *)&pbCode[2]; + // ... that shows us an absolute pointer + return SkipJumps(*(PBYTE*)(pbCode + 6 + lOffset)); + // AMD: on 64-bit, reinstate the indirect jump check and skip it if necessary + } else if (pbCode[0] == 0x48 && pbCode[1] == 0xff && pbCode[2] == 0x25) { + // or we can have the same with a REX prefix + INT32 lOffset = *(INT32 *)&pbCode[3]; + // ... that shows us an absolute pointer + return SkipJumps(*(PBYTE*)(pbCode + 7 + lOffset)); +#endif + } else if (pbCode[0] == 0xe9) { + // here the behavior is identical, we have... + // ...a 32-bit offset to the destination. + return SkipJumps(pbCode + 5 + *(INT32 *)&pbCode[1]); + } else if (pbCode[0] == 0xeb) { + // and finally an 8-bit offset to the destination + return SkipJumps(pbCode + 2 + *(CHAR *)&pbCode[1]); + } +#else +#error unsupported platform +#endif + return pbCode; +} + +//========================================================================= +// Internal function: +// +// Writes code at pbCode that jumps to pbJumpTo. Will attempt to do this +// in as few bytes as possible. Important on x64 where the long jump +// (0xff 0x25 ....) can take up 14 bytes. +//========================================================================= +static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) { +#ifdef _M_IX86_X64 + PBYTE pbJumpFrom = pbCode + 5; + SIZE_T cbDiff = pbJumpFrom > pbJumpTo ? pbJumpFrom - pbJumpTo : pbJumpTo - pbJumpFrom; + ODPRINTF((L"mhooks: EmitJump: Jumping from %p to %p, diff is %p", pbJumpFrom, pbJumpTo, cbDiff)); + if (cbDiff <= 0x7fff0000) { + pbCode[0] = 0xe9; + pbCode += 1; + *((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbJumpTo - pbJumpFrom); + pbCode += sizeof(DWORD); + } else { + pbCode[0] = 0xff; + pbCode[1] = 0x25; + pbCode += 2; +#ifdef _M_IX86 + // on x86 we write an absolute address (just behind the instruction) + *((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbCode + sizeof(DWORD)); +#elif defined _M_X64 + // on x64 we write the relative address of the same location + *((PDWORD)pbCode) = (DWORD)0; +#endif + pbCode += sizeof(DWORD); + *((PDWORD_PTR)pbCode) = (DWORD_PTR)(pbJumpTo); + pbCode += sizeof(DWORD_PTR); + } +#else +#error unsupported platform +#endif + return pbCode; +} + +//========================================================================= +// Internal function: +// +// Will try to allocate the trampoline structure within 2 gigabytes of +// the target function. +//========================================================================= +static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) { + + MHOOKS_TRAMPOLINE* pTrampoline = NULL; + + // do we have room to store this guy? + if (g_nHooksInUse < MHOOKS_MAX_SUPPORTED_HOOKS) { + + // determine lower and upper bounds for the allocation locations. + // in the basic scenario this is +/- 2GB but IP-relative instructions + // found in the original code may require a smaller window. + PBYTE pLower = pSystemFunction + nLimitUp; + pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ? + (PBYTE)(0x1) : (PBYTE)(pLower - (PBYTE)0x7fff0000); + PBYTE pUpper = pSystemFunction + nLimitDown; + pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ? + (PBYTE)(pUpper + (DWORD_PTR)0x7ff80000) : (PBYTE)(DWORD_PTR)0xfffffffffff80000; + ODPRINTF((L"mhooks: TrampolineAlloc: Allocating for %p between %p and %p", pSystemFunction, pLower, pUpper)); + + SYSTEM_INFO sSysInfo = {0}; + ::GetSystemInfo(&sSysInfo); + + // go through the available memory blocks and try to allocate a chunk for us + for (PBYTE pbAlloc = pLower; pbAlloc < pUpper;) { + // determine current state + MEMORY_BASIC_INFORMATION mbi; + ODPRINTF((L"mhooks: TrampolineAlloc: Looking at address %p", pbAlloc)); + if (!VirtualQuery(pbAlloc, &mbi, sizeof(mbi))) + break; + // free & large enough? + if (mbi.State == MEM_FREE && mbi.RegionSize >= sizeof(MHOOKS_TRAMPOLINE) && mbi.RegionSize >= sSysInfo.dwAllocationGranularity) { + // yes, align the pointer to the 64K boundary first + pbAlloc = (PBYTE)(ULONG_PTR((ULONG_PTR(pbAlloc) + (sSysInfo.dwAllocationGranularity-1)) / sSysInfo.dwAllocationGranularity) * sSysInfo.dwAllocationGranularity); + // and then try to allocate it + pTrampoline = (MHOOKS_TRAMPOLINE*)VirtualAlloc(pbAlloc, sizeof(MHOOKS_TRAMPOLINE), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READ); + if (pTrampoline) { + ODPRINTF((L"mhooks: TrampolineAlloc: Allocated block at %p as the trampoline", pTrampoline)); + break; + } + } + // continue the search + pbAlloc = (PBYTE)mbi.BaseAddress + mbi.RegionSize; + } + + // found and allocated a trampoline? + if (pTrampoline) { + // put it into our list so we know we'll have to free it + for (DWORD i=0; icodeTrampoline == pHookedFunction) + return g_pHooks[i]; + } + } + return NULL; +} + +//========================================================================= +// Internal function: +// +// Free a trampoline structure. +//========================================================================= +static VOID TrampolineFree(MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) { + for (DWORD i=0; i= pbCode && pIp < (pbCode + cbBytes)) { + if (nTries < 3) { + // oops - we should try to get the instruction pointer out of here. + ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE", dwThreadId, pIp)); + ResumeThread(hThread); + Sleep(100); + SuspendThread(hThread); + nTries++; + } else { + // we gave it all we could. (this will probably never + // happen - unless the thread has already been suspended + // to begin with) + ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE - CAN'T FIX", dwThreadId, pIp)); + ResumeThread(hThread); + CloseHandle(hThread); + hThread = NULL; + break; + } + } else { + // success, the IP is not conflicting + ODPRINTF((L"mhooks: SuspendOneThread: Successfully suspended thread %d - IP is at %p", dwThreadId, pIp)); + break; + } + } + } else { + // couldn't suspend + CloseHandle(hThread); + hThread = NULL; + } + } + return hThread; +} + +//========================================================================= +// Internal function: +// +// Resumes all previously suspended threads in the current process. +//========================================================================= +static VOID ResumeOtherThreads() { + if (!g_bThreadsSuspended) + return; + // make sure things go as fast as possible + INT nOriginalPriority = GetThreadPriority(GetCurrentThread()); + SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); + // go through our list + for (DWORD i=0; inRipCnt; i++) { + DWORD dwNewDisplacement = (DWORD)(pdata->rips[i].nDisplacement - diff); + ODPRINTF((L"mhooks: fixing up RIP instruction operand for code at 0x%p: " + L"old displacement: 0x%8.8x, new displacement: 0x%8.8x", + pbNew + pdata->rips[i].dwOffset, + (DWORD)pdata->rips[i].nDisplacement, + dwNewDisplacement)); + *(PDWORD)(pbNew + pdata->rips[i].dwOffset) = dwNewDisplacement; + } +} + +//========================================================================= +// Examine the machine code at the target function's entry point, and +// skip bytes in a way that we'll always end on an instruction boundary. +// We also detect branches and subroutine calls (as well as returns) +// at which point disassembly must stop. +// Finally, detect and collect information on IP-relative instructions +// that we can patch. +static DWORD DisassembleAndSkip(PVOID pFunction, DWORD dwMinLen, MHOOKS_PATCHDATA* pdata) { + DWORD dwRet = 0; + pdata->nLimitDown = 0; + pdata->nLimitUp = 0; + pdata->nRipCnt = 0; +#ifdef _M_IX86 + ARCHITECTURE_TYPE arch = ARCH_X86; +#elif defined _M_X64 + ARCHITECTURE_TYPE arch = ARCH_X64; +#else + #error unsupported platform +#endif + DISASSEMBLER dis; + if (InitDisassembler(&dis, arch)) { + INSTRUCTION* pins = NULL; + U8* pLoc = (U8*)pFunction; + DWORD dwFlags = DISASM_DECODE | DISASM_DISASSEMBLE | DISASM_ALIGNOUTPUT; +#ifndef _DEBUG + // suppress errors in release build + dwFlags |= DISASM_SUPPRESSERRORS; +#endif + + ODPRINTF((L"mhooks: DisassembleAndSkip: Disassembling %p", pLoc)); + while ( (dwRet < dwMinLen) && ((pins = GetInstruction(&dis, (ULONG_PTR)pLoc, pLoc, dwFlags)) != NULL ) ) + { + ODPRINTF(("mhooks: DisassembleAndSkip: %p: %s", pLoc, pins->String)); + BOOL bProcessRip = FALSE; + DWORD displacementOffset = 3; // number of bytes from the start of the instruction where offset is located + + if (pins->Type == ITYPE_RET) + break; + else if (pins->Type == ITYPE_BRANCH) + { + if (pLoc[0] == 0xe9) + { + if (pins->OperandCount == 1 && (pins->Operands[0].Flags & OP_IPREL) && pins->X86.Relative && (pins->X86.OperandSize == 4 || pins->X86.OperandSize == 8)) + { + // jmp [rip + ilen + offset] (0xE9, [offset]) + displacementOffset = 1; + bProcessRip = TRUE; + } + } +#if defined _M_X64 + else if (pLoc[0] == 0x65 && pLoc[1] == 0xff && pLoc[2] == 0x24 && pLoc[3] == 0x25) + { + // jmp qword_ptr gs:[offset] (0x65, 0xFF, 0x24, 0x25, [offset]) + // Nothing to do here - no relocation needed; offset is relative to gs + } +#endif + else + { + break; + } + } + else if (pins->Type == ITYPE_BRANCHCC) + break; + else if (pins->Type == ITYPE_CALL) + { + if (pLoc[0] == 0xe8) + { + if (pins->OperandCount == 1 && (pins->Operands[0].Flags & OP_IPREL) && pins->X86.Relative && (pins->X86.OperandSize == 4 || pins->X86.OperandSize == 8)) + { + // call [rip + ilen + offset] (0xE8, [offset]) + displacementOffset = 1; + bProcessRip = TRUE; + } + } + else if (pLoc[0] == 0xff && pLoc[1] == 0x15) + { + if (pins->OperandCount == 1 && (pins->Operands[0].Flags & OP_IPREL) && pins->X86.Relative && (pins->X86.OperandSize == 4 || pins->X86.OperandSize == 8)) + { + // call dword_ptr [offset] (0xff, 0x15, [offset]) + displacementOffset = 2; + bProcessRip = TRUE; + } + } + else + { + break; + } + } + else if (pins->Type == ITYPE_CALLCC ) + break; + + #if defined _M_X64 + // mov or lea to register from rip+imm32 + else if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) && + (pins->X86.OperandSize == 8) && (pins->OperandCount == 2) && + (pins->Operands[1].Flags & OP_IPREL) && (pins->Operands[1].Register == AMD64_REG_RIP)) + { + // rip-addressing "mov reg, [rip+imm32]" + ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 1, pins->X86.Displacement, *(PDWORD)(pLoc+3))); + bProcessRip = TRUE; + } + // mov or lea to register from eip+imm32 + else if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) && + (pins->X86.OperandSize == 4) && (pins->OperandCount == 2) && + (pins->Operands[1].Flags & OP_IPREL) && (pins->Operands[1].Register == X86_REG_EIP)) + { + // rip-addressing "mov reg, [rip+imm32]" + ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 1, pins->X86.Displacement, *(PDWORD)(pLoc+2))); + displacementOffset = 2; + bProcessRip = TRUE; + } + // mov or lea to rip+imm32 from register + else if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) && + (pins->X86.OperandSize == 8) && (pins->OperandCount == 2) && + (pins->Operands[0].Flags & OP_IPREL) && (pins->Operands[0].Register == AMD64_REG_RIP)) + { + // rip-addressing "mov [rip+imm32], reg" + ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 0, pins->X86.Displacement, *(PDWORD)(pLoc+3))); + bProcessRip = TRUE; + } + // cmp from eip+imm32, value + else if (pins->Type == ITYPE_CMP && (pins->X86.Relative) && + (pins->X86.OperandSize == 4) && (pins->OperandCount == 2) && + (pins->Operands[0].Flags & OP_IPREL) && (pins->Operands[0].Register == X86_REG_EIP)) + { + // rip-addressing "mov reg, [rip+imm32]" + ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 1, pins->X86.Displacement, *(PDWORD)(pLoc + 2))); + displacementOffset = 2; + bProcessRip = TRUE; + } + else if ((pins->OperandCount >= 1) && (pins->Operands[0].Flags & OP_IPREL)) + { + // unsupported rip-addressing + ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 0)); + // dump instruction bytes to the debug output + for (DWORD i=0; iLength; i++) { + ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); + } + break; + } + else if ( (pins->OperandCount >= 2) && (pins->Operands[1].Flags & OP_IPREL) ) + { + // unsupported rip-addressing + ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 1)); + // dump instruction bytes to the debug output + for (DWORD i=0; iLength; i++) { + ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); + } + break; + } + else if ( (pins->OperandCount >= 3) && (pins->Operands[2].Flags & OP_IPREL) ) + { + // unsupported rip-addressing + ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 2)); + // dump instruction bytes to the debug output + for (DWORD i=0; iLength; i++) { + ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i])); + } + break; + } + #endif + // follow through with RIP-processing if needed + if (bProcessRip) { + // calculate displacement relative to function start + S64 nAdjustedDisplacement = pins->X86.Displacement + (pLoc - (U8*)pFunction); + // store displacement values furthest from zero (both positive and negative) + if (nAdjustedDisplacement < pdata->nLimitDown) + pdata->nLimitDown = nAdjustedDisplacement; + if (nAdjustedDisplacement > pdata->nLimitUp) + pdata->nLimitUp = nAdjustedDisplacement; + // store patch info + if (pdata->nRipCnt < MHOOKS_MAX_RIPS) { + pdata->rips[pdata->nRipCnt].dwOffset = dwRet + displacementOffset; + pdata->rips[pdata->nRipCnt].nDisplacement = pins->X86.Displacement; + pdata->nRipCnt++; + } else { + // no room for patch info, stop disassembly + break; + } + } + + dwRet += pins->Length; + pLoc += pins->Length; + } + + CloseDisassembler(&dis); + } + + return dwRet; +} + +//========================================================================= +void Mhook_SuspendOtherThreads() { + SuspendOtherThreads(NULL, 0); + g_bThreadsSuspended = TRUE; +} + +//========================================================================= +void Mhook_ResumeOtherThreads() { + ResumeOtherThreads(); + g_bThreadsSuspended = FALSE; +} + +//========================================================================= +BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction) { + MHOOKS_TRAMPOLINE* pTrampoline = NULL; + PVOID pSystemFunction = *ppSystemFunction; + // ensure thread-safety + EnterCritSec(); + ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction)); + // find the real functions (jump over jump tables, if any) + pSystemFunction = SkipJumps((PBYTE)pSystemFunction); + pHookFunction = SkipJumps((PBYTE)pHookFunction); + ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction)); + // figure out the length of the overwrite zone + MHOOKS_PATCHDATA patchdata = {0}; + + DWORD dwInstructionLength = DisassembleAndSkip(pSystemFunction, MHOOK_JMPSIZE, &patchdata); + if (dwInstructionLength >= MHOOK_JMPSIZE) { + ODPRINTF((L"mhooks: Mhook_SetHook: disassembly signals %d bytes", dwInstructionLength)); + // suspend every other thread in this process, and make sure their IP + // is not in the code we're about to overwrite. + SuspendOtherThreads((PBYTE)pSystemFunction, dwInstructionLength); + // allocate a trampoline structure (TODO: it is pretty wasteful to get + // VirtualAlloc to grab chunks of memory smaller than 100 bytes) + pTrampoline = TrampolineAlloc((PBYTE)pSystemFunction, patchdata.nLimitUp, patchdata.nLimitDown); + if (pTrampoline) { + ODPRINTF((L"mhooks: Mhook_SetHook: allocated structure at %p", pTrampoline)); + // open ourselves so we can VirtualProtectEx + HANDLE hProc = GetCurrentProcess(); + DWORD dwOldProtectSystemFunction = 0; + DWORD dwOldProtectTrampolineFunction = 0; + // set the system function to PAGE_EXECUTE_READWRITE + if (VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) { + ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on system function")); + // mark our trampoline buffer to PAGE_EXECUTE_READWRITE + if (VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), PAGE_EXECUTE_READWRITE, &dwOldProtectTrampolineFunction)) { + ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on trampoline structure")); + + // create our trampoline function + PBYTE pbCode = pTrampoline->codeTrampoline; + // save original code.. + for (DWORD i = 0; icodeUntouched[i] = pbCode[i] = ((PBYTE)pSystemFunction)[i]; + } + pbCode += dwInstructionLength; + // plus a jump to the continuation in the original location + pbCode = EmitJump(pbCode, ((PBYTE)pSystemFunction) + dwInstructionLength); + ODPRINTF((L"mhooks: Mhook_SetHook: updated the trampoline")); + + // fix up any IP-relative addressing in the code + FixupIPRelativeAddressing(pTrampoline->codeTrampoline, (PBYTE)pSystemFunction, &patchdata); + + DWORD_PTR dwDistance = (PBYTE)pHookFunction < (PBYTE)pSystemFunction ? + (PBYTE)pSystemFunction - (PBYTE)pHookFunction : (PBYTE)pHookFunction - (PBYTE)pSystemFunction; + if (dwDistance > 0x7fff0000) { + // create a stub that jumps to the replacement function. + // we need this because jumping from the API to the hook directly + // will be a long jump, which is 14 bytes on x64, and we want to + // avoid that - the API may or may not have room for such stuff. + // (remember, we only have 5 bytes guaranteed in the API.) + // on the other hand we do have room, and the trampoline will always be + // within +/- 2GB of the API, so we do the long jump in there. + // the API will jump to the "reverse trampoline" which + // will jump to the user's hook code. + pbCode = pTrampoline->codeJumpToHookFunction; + pbCode = EmitJump(pbCode, (PBYTE)pHookFunction); + ODPRINTF((L"mhooks: Mhook_SetHook: created reverse trampoline")); + FlushInstructionCache(hProc, pTrampoline->codeJumpToHookFunction, + pbCode - pTrampoline->codeJumpToHookFunction); + + // update the API itself + pbCode = (PBYTE)pSystemFunction; + pbCode = EmitJump(pbCode, pTrampoline->codeJumpToHookFunction); + ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!")); + } else { + // the jump will be at most 5 bytes so we can do it directly + ODPRINTF((L"mhooks: Mhook_SetHook: no need for reverse trampoline")); + // update the API itself + pbCode = (PBYTE)pSystemFunction; + pbCode = EmitJump(pbCode, (PBYTE)pHookFunction); + ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!")); + } + + // update data members + pTrampoline->cbOverwrittenCode = dwInstructionLength; + pTrampoline->pSystemFunction = (PBYTE)pSystemFunction; + pTrampoline->pHookFunction = (PBYTE)pHookFunction; + + // flush instruction cache and restore original protection + FlushInstructionCache(hProc, pTrampoline->codeTrampoline, dwInstructionLength); + VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), dwOldProtectTrampolineFunction, &dwOldProtectTrampolineFunction); + } else { + ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 2: %d", gle())); + } + // flush instruction cache and restore original protection + FlushInstructionCache(hProc, pSystemFunction, dwInstructionLength); + VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, dwOldProtectSystemFunction, &dwOldProtectSystemFunction); + } else { + ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 1: %d", gle())); + } + if (pTrampoline->pSystemFunction) { + // this is what the application will use as the entry point + // to the "original" unhooked function. + *ppSystemFunction = pTrampoline->codeTrampoline; + ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!")); + } + else { + // if we failed discard the trampoline (forcing VirtualFree) + TrampolineFree(pTrampoline, TRUE); + pTrampoline = NULL; + } + } + } else { + ODPRINTF((L"mhooks: disassembly signals %d bytes (unacceptable)", dwInstructionLength)); + } + LeaveCritSec(); + return (pTrampoline != NULL); +} + +//========================================================================= +BOOL Mhook_Unhook(PVOID *ppHookedFunction) { + ODPRINTF((L"mhooks: Mhook_Unhook: %p", *ppHookedFunction)); + BOOL bRet = FALSE; + EnterCritSec(); + // get the trampoline structure that corresponds to our function + MHOOKS_TRAMPOLINE* pTrampoline = TrampolineGet((PBYTE)*ppHookedFunction); + if (pTrampoline) { + // make sure nobody's executing code where we're about to overwrite a few bytes + SuspendOtherThreads(pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode); + ODPRINTF((L"mhooks: Mhook_Unhook: found struct at %p", pTrampoline)); + // open ourselves so we can VirtualProtectEx + HANDLE hProc = GetCurrentProcess(); + DWORD dwOldProtectSystemFunction = 0; + // make memory writable + if (VirtualProtectEx(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) { + ODPRINTF((L"mhooks: Mhook_Unhook: readwrite set on system function")); + PBYTE pbCode = (PBYTE)pTrampoline->pSystemFunction; + for (DWORD i = 0; icbOverwrittenCode; i++) { + pbCode[i] = pTrampoline->codeUntouched[i]; + } + // flush instruction cache and make memory unwritable + FlushInstructionCache(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode); + VirtualProtectEx(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, dwOldProtectSystemFunction, &dwOldProtectSystemFunction); + // return the original function pointer + *ppHookedFunction = pTrampoline->pSystemFunction; + bRet = TRUE; + ODPRINTF((L"mhooks: Mhook_Unhook: sysfunc: %p", *ppHookedFunction)); + // free the trampoline while not really discarding it from memory + TrampolineFree(pTrampoline, FALSE); + ODPRINTF((L"mhooks: Mhook_Unhook: unhook successful")); + } else { + ODPRINTF((L"mhooks: Mhook_Unhook: failed VirtualProtectEx 1: %d", gle())); + } + } + LeaveCritSec(); + return bRet; +} + +//=========================================================================