//========================================================================= // 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; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) { if (g_pHooks[i] == NULL) { g_pHooks[i] = pTrampoline; g_nHooksInUse++; break; } } } } return pTrampoline; }
//========================================================================= // 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; if (g_pFreeList) { g_pFreeList->pPrevTrampoline = &pRetVal[trampolineCount - 1]; } 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: // // 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: // // 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: // // 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; }
//========================================================================= // if IP-relative addressing has been detected, fix up the code so the // offset points to the original location static void FixupIPRelativeAddressing(PBYTE pbNew, PBYTE pbOriginal, MHOOKS_PATCHDATA* pdata) { #if defined _M_X64 S64 diff = pbNew - pbOriginal; for (DWORD i = 0; i < pdata->nRipCnt; 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 }
//========================================================================= 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; i<pTrampoline->cbOverwrittenCode; 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())); } // make the other guys runnable ResumeOtherThreads(); } LeaveCritSec(); return bRet; }
//========================================================================= 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; i<dwInstructionLength; i++) { pTrampoline->codeUntouched[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); }
//========================================================================= // 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; i<pins->Length; 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; i<pins->Length; 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; i<pins->Length; 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; }
//========================================================================= // Internal function: // // Suspend all threads in this process while trying to make sure that their // instruction pointer is not in the given range. //========================================================================= static BOOL SuspendOtherThreads(PBYTE pbCode, DWORD cbBytes) { BOOL bRet = FALSE; // make sure we're the most important thread in the process INT nOriginalPriority = GetThreadPriority(GetCurrentThread()); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL); // get a view of the threads in the system HANDLE hSnap = fnCreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId()); if (GOOD_HANDLE(hSnap)) { THREADENTRY32 te; te.dwSize = sizeof(te); // count threads in this process (except for ourselves) DWORD nThreadsInProcess = 0; if (fnThread32First(hSnap, &te)) { do { if (te.th32OwnerProcessID == GetCurrentProcessId()) { if (te.th32ThreadID != GetCurrentThreadId()) { nThreadsInProcess++; } } te.dwSize = sizeof(te); } while(fnThread32Next(hSnap, &te)); } ODPRINTF((L"mhooks: SuspendOtherThreads: counted %d other threads", nThreadsInProcess)); if (nThreadsInProcess) { // alloc buffer for the handles we really suspended g_hThreadHandles = (HANDLE*)malloc(nThreadsInProcess*sizeof(HANDLE)); if (g_hThreadHandles) { ZeroMemory(g_hThreadHandles, nThreadsInProcess*sizeof(HANDLE)); DWORD nCurrentThread = 0; BOOL bFailed = FALSE; te.dwSize = sizeof(te); // go through every thread if (fnThread32First(hSnap, &te)) { do { if (te.th32OwnerProcessID == GetCurrentProcessId()) { if (te.th32ThreadID != GetCurrentThreadId()) { // attempt to suspend it g_hThreadHandles[nCurrentThread] = SuspendOneThread(te.th32ThreadID, pbCode, cbBytes); if (GOOD_HANDLE(g_hThreadHandles[nCurrentThread])) { ODPRINTF((L"mhooks: SuspendOtherThreads: successfully suspended %d", te.th32ThreadID)); nCurrentThread++; } else { ODPRINTF((L"mhooks: SuspendOtherThreads: error while suspending thread %d: %d", te.th32ThreadID, gle())); // TODO: this might not be the wisest choice // but we can choose to ignore failures on // thread suspension. It's pretty unlikely that // we'll fail - and even if we do, the chances // of a thread's IP being in the wrong place // is pretty small. // bFailed = TRUE; } } } te.dwSize = sizeof(te); } while(fnThread32Next(hSnap, &te) && !bFailed); } g_nThreadHandles = nCurrentThread; bRet = !bFailed; } } CloseHandle(hSnap); //TODO: we might want to have another pass to make sure all threads // in the current process (including those that might have been // created since we took the original snapshot) have been // suspended. } else { ODPRINTF((L"mhooks: SuspendOtherThreads: can't CreateToolhelp32Snapshot: %d", gle())); } SetThreadPriority(GetCurrentThread(), nOriginalPriority); if (!bRet) { ODPRINTF((L"mhooks: SuspendOtherThreads: Had a problem (or not running multithreaded), resuming all threads.")); ResumeOtherThreads(); } return bRet; }