inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) { if (pbCode == NULL) { return NULL; } if (ppGlobals != NULL) { *ppGlobals = NULL; } if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32] // Looks like an import alias jump, then get the code it points to. PBYTE pbTarget = *(PBYTE *)&pbCode[2]; if (detour_is_imported(pbCode, pbTarget)) { PBYTE pbNew = *(PBYTE *)pbTarget; DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); return pbNew; } } else if (pbCode[0] == 0xeb) { // jmp +imm8 // These just started appearing with CL13. PBYTE pbNew = pbCode + 2 + *(CHAR *)&pbCode[1]; DETOUR_TRACE(("%p->%p: skipped over short jump.\n", pbCode, pbNew)); if (pbNew[0] == 0xe9) { // jmp +imm32 pbCode = pbNew; pbNew = pbCode + *(INT32 *)&pbCode[1]; DETOUR_TRACE(("%p->%p: skipped over short jump.\n", pbCode, pbNew)); } return pbNew; } return pbCode; }
inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals) { if (pbCode == NULL) { return NULL; } if (ppGlobals != NULL) { *ppGlobals = NULL; } // First, skip over the import vector if there is one. if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [imm32] // Looks like an import alias jump, then get the code it points to. PBYTE pbTarget = *(PBYTE *)&pbCode[2]; if (detour_is_imported(pbCode, pbTarget)) { PBYTE pbNew = *(PBYTE *)pbTarget; DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew)); pbCode = pbNew; } } // Then, skip over a patch jump if (pbCode[0] == 0xeb) { // jmp +imm8 PBYTE pbNew = pbCode + 2 + *(CHAR *)&pbCode[1]; DETOUR_TRACE(("%p->%p: skipped over short jump.\n", pbCode, pbNew)); pbCode = pbNew; // Finally, skip over a long jump if it is the target of the patch jump. if (pbCode[0] == 0xe9) { // jmp +imm32 PBYTE pbNew = pbCode + 5 + *(INT32 *)&pbCode[1]; DETOUR_TRACE(("%p->%p: skipped over long jump.\n", pbCode, pbNew)); pbCode = pbNew; } } return pbCode; }
static PDETOUR_TRAMPOLINE detour_alloc_trampoline(PBYTE pbTarget) { // We have to place trampolines within +/- 2GB of target. // The allocation code assumes that PDETOUR_TRAMPOLINE pLo = (PDETOUR_TRAMPOLINE) ((pbTarget > (PBYTE)0x7ff80000) ? pbTarget - 0x7ff80000 : (PBYTE)(ULONG_PTR)DETOUR_REGION_SIZE); PDETOUR_TRAMPOLINE pHi = (PDETOUR_TRAMPOLINE) ((pbTarget < (PBYTE)0xffffffff80000000) ? pbTarget + 0x7ff80000 : (PBYTE)0xfffffffffff80000); DETOUR_TRACE(("[%p..%p..%p]\n", pLo, pbTarget, pHi)); PDETOUR_TRAMPOLINE pTrampoline = NULL; // Insure that there is a default region. if (s_pRegion == NULL && s_pRegions != NULL) { s_pRegion = s_pRegions; } // First check the default region for an valid free block. if (s_pRegion != NULL && s_pRegion->pFree != NULL && s_pRegion->pFree >= pLo && s_pRegion->pFree <= pHi) { found_region: pTrampoline =
static PVOID detour_alloc_region_from_hi(PBYTE pbLo, PBYTE pbHi) { PBYTE pbTry = detour_alloc_round_down_to_region(pbHi - DETOUR_REGION_SIZE); DETOUR_TRACE((" Looking for free region in %p..%p from %p:\n", pbLo, pbHi, pbTry)); for (; pbTry > pbLo;) { MEMORY_BASIC_INFORMATION mbi; DETOUR_TRACE((" Try %p\n", pbTry)); if (pbTry >= (PBYTE)(ULONG_PTR)0x50000000 && pbTry <= (PBYTE)(ULONG_PTR)0x80000000) { // Skip region reserved for system DLLs. pbTry = (PBYTE)(ULONG_PTR)(0x50000000 - DETOUR_REGION_SIZE); continue; } ZeroMemory(&mbi, sizeof(mbi)); if (!VirtualQuery(pbTry, &mbi, sizeof(mbi))) { break; } DETOUR_TRACE((" Try %p => %p..%p %6x\n", pbTry, mbi.BaseAddress, (PBYTE)mbi.BaseAddress + mbi.RegionSize - 1, mbi.State)); if (mbi.State == MEM_FREE && mbi.RegionSize >= DETOUR_REGION_SIZE) { PVOID pv = VirtualAlloc(pbTry, DETOUR_REGION_SIZE, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (pv != NULL) { return pv; } pbTry -= DETOUR_REGION_SIZE; } else { pbTry = detour_alloc_round_down_to_region((PBYTE)mbi.AllocationBase - DETOUR_REGION_SIZE); } } return NULL; }
LONG WINAPI DetourTransactionCommitEx(PVOID **pppFailedPointer) { if (pppFailedPointer != NULL) { // Used to get the last error. *pppFailedPointer = s_ppPendingError; } if (s_nPendingThreadId != (LONG)GetCurrentThreadId()) { return ERROR_INVALID_OPERATION; } // If any of the pending operations failed, then we abort the whole transaction. if (s_nPendingError != NO_ERROR) { DETOUR_BREAK(); DetourTransactionAbort(); return s_nPendingError; } // Common variables. DetourOperation *o; DetourThread *t; // Insert or remove each of the detours. for (o = s_pPendingOperations; o != NULL; o = o->pNext) { if (o->fIsRemove) { PBYTE pbSrc = o->pTrampoline->rbCode; LONG cbCopy = 0; for (; cbCopy < o->pTrampoline->cbTarget;) { LONG lExtra = 0; pbSrc = (PBYTE)DetourCopyInstructionEx(o->pbTarget + cbCopy, pbSrc, NULL, &lExtra); if (lExtra != 0) { break; // Abort if offset doesn't fit. } cbCopy = (LONG)(pbSrc - o->pTrampoline->rbCode); } if (cbCopy != o->pTrampoline->cbTarget) { // Count came out different! // This is a drastic error as the backward copy should never fail. s_nPendingError = ERROR_INVALID_DATA; s_ppPendingError = (PVOID*)o->ppbPointer; DETOUR_BREAK(); } #ifdef DETOURS_IA64 #error Feature not supported in this release. #else // DETOURS_IA64 *o->ppbPointer = o->pbTarget; #endif } else { DETOUR_TRACE(("detours: pbTramp =%p, pbRemain=%p, pbDetour=%p, cbTarget=%d\n", o->pTrampoline, o->pTrampoline->pbRemain, o->pTrampoline->pbDetour, o->pTrampoline->cbTarget)); DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x [before]\n", o->pbTarget, o->pbTarget[0], o->pbTarget[1], o->pbTarget[2], o->pbTarget[3], o->pbTarget[4], o->pbTarget[5], o->pbTarget[6], o->pbTarget[7], o->pbTarget[8], o->pbTarget[9], o->pbTarget[10], o->pbTarget[11])); #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_X86 PBYTE pbCode = detour_gen_jmp_immediate(o->pbTarget, o->pTrampoline->pbDetour); pbCode = detour_gen_brk(pbCode, o->pTrampoline->pbRemain); *o->ppbPointer = o->pTrampoline->rbCode; #endif // DETOURS_X86 DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x [after]\n", o->pbTarget, o->pbTarget[0], o->pbTarget[1], o->pbTarget[2], o->pbTarget[3], o->pbTarget[4], o->pbTarget[5], o->pbTarget[6], o->pbTarget[7], o->pbTarget[8], o->pbTarget[9], o->pbTarget[10], o->pbTarget[11])); DETOUR_TRACE(("detours: pbTramp =%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", o->pTrampoline, o->pTrampoline->rbCode[0], o->pTrampoline->rbCode[1], o->pTrampoline->rbCode[2], o->pTrampoline->rbCode[3], o->pTrampoline->rbCode[4], o->pTrampoline->rbCode[5], o->pTrampoline->rbCode[6], o->pTrampoline->rbCode[7], o->pTrampoline->rbCode[8], o->pTrampoline->rbCode[9], o->pTrampoline->rbCode[10], o->pTrampoline->rbCode[11])); #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 } } // Update any suspended threads. for (t = s_pPendingThreads; t != NULL; t = t->pNext) { CONTEXT cxt; cxt.ContextFlags = CONTEXT_CONTROL; #undef DETOURS_EIP #undef DETOURS_EIP_TYPE #ifdef DETOURS_X86 #define DETOURS_EIP Eip #define DETOURS_EIP_TYPE DWORD #endif // DETOURS_X86 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 if (GetThreadContext(t->hThread, &cxt)) { for (DetourOperation *o = s_pPendingOperations; o != NULL; o = o->pNext) { if (o->fIsRemove) { if (cxt.DETOURS_EIP >= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pTrampoline && cxt.DETOURS_EIP < (DETOURS_EIP_TYPE)(ULONG_PTR)o->pTrampoline + sizeof(o->pTrampoline)) { cxt.DETOURS_EIP -= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pTrampoline; cxt.DETOURS_EIP += (DETOURS_EIP_TYPE)(ULONG_PTR)o->pbTarget; SetThreadContext(t->hThread, &cxt); } } else { if (cxt.DETOURS_EIP >= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pbTarget && cxt.DETOURS_EIP < ((DETOURS_EIP_TYPE)(ULONG_PTR)o->pbTarget + o->pTrampoline->cbTarget)) { cxt.DETOURS_EIP -= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pbTarget; cxt.DETOURS_EIP += (DETOURS_EIP_TYPE)(ULONG_PTR)o->pTrampoline; SetThreadContext(t->hThread, &cxt); } } } } #undef DETOURS_EIP } // Restore all of the page permissions and flush the icache. HANDLE hProcess = GetCurrentProcess(); for (o = s_pPendingOperations; o != NULL;) { // We don't care if this fails, because the code is still accessible. DWORD dwOld; VirtualProtect(o->pbTarget, o->pTrampoline->cbTarget, o->dwPerm, &dwOld); FlushInstructionCache(hProcess, o->pbTarget, o->pTrampoline->cbTarget); if (o->fIsRemove && o->pTrampoline) { detour_free_trampoline(o->pTrampoline); o->pTrampoline = NULL; } DetourOperation *n = o->pNext; delete o; o = n; } s_pPendingOperations = NULL; // Make sure the trampoline pages are no longer writable. detour_runnable_trampoline_regions(); // Resume any suspended threads. for (t = s_pPendingThreads; t != NULL;) { // There is nothing we can do if this fails. ResumeThread(t->hThread); DetourThread *n = t->pNext; delete t; t = n; } s_pPendingThreads = NULL; s_nPendingThreadId = 0; if (pppFailedPointer != NULL) { *pppFailedPointer = s_ppPendingError; } return s_nPendingError; }
LONG WINAPI DetourAttachEx(PVOID *ppPointer, PVOID pDetour, PDETOUR_TRAMPOLINE *ppRealTrampoline, PVOID *ppRealTarget, PVOID *ppRealDetour) { LONG error = NO_ERROR; if (ppRealTrampoline != NULL) { *ppRealTrampoline = NULL; } if (ppRealTarget != NULL) { *ppRealTarget = NULL; } if (ppRealDetour != NULL) { *ppRealDetour = NULL; } if (s_nPendingThreadId != (LONG)GetCurrentThreadId()) { DETOUR_TRACE(("transaction conflict with thread id=%d\n", s_nPendingThreadId)); return ERROR_INVALID_OPERATION; } // If any of the pending operations failed, then we don't need to do this. if (s_nPendingError != NO_ERROR) { DETOUR_TRACE(("pending transaction error=%d\n", s_nPendingError)); return s_nPendingError; } if (ppPointer == NULL) { DETOUR_TRACE(("ppPointer is null\n")); return ERROR_INVALID_HANDLE; } if (*ppPointer == NULL) { error = ERROR_INVALID_HANDLE; s_nPendingError = error; s_ppPendingError = ppPointer; DETOUR_TRACE(("*ppPointer is null (ppPointer=%p)\n", ppPointer)); DETOUR_BREAK(); return error; } PBYTE pbTarget = (PBYTE)*ppPointer; PDETOUR_TRAMPOLINE pTrampoline = NULL; DetourOperation *o = NULL; #ifdef DETOURS_IA64 #error Feature not supported in this release. #else pbTarget = (PBYTE)DetourCodeFromPointer(pbTarget, NULL); pDetour = DetourCodeFromPointer(pDetour, NULL); #endif // Don't follow a jump if its destination is the target function. // This happens when the detour does nothing other than call the target. if (pDetour == (PVOID)pbTarget) { if (s_fIgnoreTooSmall) { goto stop; } else { DETOUR_BREAK(); goto fail; } } if (ppRealTarget != NULL) { *ppRealTarget = pbTarget; } if (ppRealDetour != NULL) { *ppRealDetour = pDetour; } o = new DetourOperation; if (o == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; fail: s_nPendingError = error; DETOUR_BREAK(); stop: if (pTrampoline != NULL) { detour_free_trampoline(pTrampoline); pTrampoline = NULL; } if (o != NULL) { delete o; o = NULL; } s_ppPendingError = ppPointer; return error; } // Mark process as having detoured code. #ifdef DETOURS_INTERNAL_USAGE #error Feature not supported in this release. #else // Detoured(); #endif pTrampoline = detour_alloc_trampoline(pbTarget); if (pTrampoline == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; DETOUR_BREAK(); goto fail; } if (ppRealTrampoline != NULL) { *ppRealTrampoline = pTrampoline; } DETOUR_TRACE(("detours: pbTramp=%p, pDetour=%p\n", pTrampoline, pDetour)); // Determine the number of movable target instructions. PBYTE pbSrc = pbTarget; LONG cbTarget = 0; while (cbTarget < SIZE_OF_JMP) { PBYTE pbOp = pbSrc; LONG lExtra = 0; DETOUR_TRACE((" DetourCopyInstructionEx(%p,%p)\n", pTrampoline->rbCode + cbTarget, pbSrc)); pbSrc = (PBYTE)DetourCopyInstructionEx(pTrampoline->rbCode + cbTarget, pbSrc, NULL, &lExtra); DETOUR_TRACE((" DetourCopyInstructionEx() = %p (%d bytes)\n", pbSrc, (int)(pbSrc - pbOp))); if (lExtra != 0) { break; // Abort if offset doesn't fit. } cbTarget = (LONG)(pbSrc - pbTarget); if (detour_does_code_end_function(pbOp)) { break; } } if (cbTarget < SIZE_OF_JMP) { // Too few instructions. error = ERROR_INVALID_BLOCK; if (s_fIgnoreTooSmall) { goto stop; } else { DETOUR_BREAK(); goto fail; } } #if !defined(DETOURS_IA64) if (cbTarget > sizeof(pTrampoline->rbCode) - SIZE_OF_JMP) { // Too many instructions. error = ERROR_INVALID_HANDLE; DETOUR_BREAK(); goto fail; } #endif pTrampoline->pbRemain = pbTarget + cbTarget; pTrampoline->pbDetour = (PBYTE)pDetour; pTrampoline->cbTarget = (BYTE)cbTarget; #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_X86 pbSrc = detour_gen_jmp_immediate(pTrampoline->rbCode + cbTarget, pTrampoline->pbRemain); pbSrc = detour_gen_brk(pbSrc, pTrampoline->rbCode + sizeof(pTrampoline->rbCode)); #endif // DETOURS_X86 DWORD dwOld = 0; if (!VirtualProtect(pbTarget, cbTarget, PAGE_EXECUTE_READWRITE, &dwOld)) { error = GetLastError(); DETOUR_BREAK(); goto fail; } DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", pbTarget, pbTarget[0], pbTarget[1], pbTarget[2], pbTarget[3], pbTarget[4], pbTarget[5], pbTarget[6], pbTarget[7], pbTarget[8], pbTarget[9], pbTarget[10], pbTarget[11])); DETOUR_TRACE(("detours: pbTramp =%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", pTrampoline, pTrampoline->rbCode[0], pTrampoline->rbCode[1], pTrampoline->rbCode[2], pTrampoline->rbCode[3], pTrampoline->rbCode[4], pTrampoline->rbCode[5], pTrampoline->rbCode[6], pTrampoline->rbCode[7], pTrampoline->rbCode[8], pTrampoline->rbCode[9], pTrampoline->rbCode[10], pTrampoline->rbCode[11])); /////////////////////////////////////////// Mark binary as being detoured. // PIMAGE_DOS_HEADER pDosHeader = detour_find_header(pbTarget); if (pDosHeader != NULL && pDosHeader->e_res[0] != 'eD') { DWORD dwDos = 0; if (!VirtualProtect(pDosHeader, sizeof(*pDosHeader), PAGE_EXECUTE_READWRITE, &dwDos)) { error = GetLastError(); DETOUR_BREAK(); goto fail; } pDosHeader->e_res[0] = 'eD'; pDosHeader->e_res[1] = 'ot'; pDosHeader->e_res[2] = 'ru'; pDosHeader->e_res[3] = '!s'; } o->fIsRemove = FALSE; o->ppbPointer = (PBYTE*)ppPointer; o->pTrampoline = pTrampoline; o->pbTarget = pbTarget; o->dwPerm = dwOld; o->pNext = s_pPendingOperations; s_pPendingOperations = o; return NO_ERROR; }
PVOID WINAPI DetourFindFunction(PCSTR pszModule, PCSTR pszFunction) { /////////////////////////////////////////////// First, try GetProcAddress. // HMODULE hModule = LoadLibraryExA(pszModule, NULL, 0); if (hModule == NULL) { return NULL; } PBYTE pbCode = (PBYTE)GetProcAddress(hModule, pszFunction); if (pbCode) { return pbCode; } ////////////////////////////////////////////////////// Then try ImageHelp. // DETOUR_TRACE(("DetourFindFunction(%s, %s)\n", pszModule, pszFunction)); PDETOUR_SYM_INFO pSymInfo = DetourLoadImageHlp(); if (pSymInfo == NULL) { DETOUR_TRACE(("DetourLoadImageHlp failed: %d\n", GetLastError())); return NULL; } if (pSymInfo->pfSymLoadModule64(pSymInfo->hProcess, NULL, (PCHAR)pszModule, NULL, (DWORD64)hModule, 0) == 0) { if (ERROR_SUCCESS != GetLastError()) { DETOUR_TRACE(("SymLoadModule64(%p) failed: %d\n", pSymInfo->hProcess, GetLastError())); return NULL; } } HRESULT hrRet; CHAR szFullName[512]; IMAGEHLP_MODULE64 modinfo; ZeroMemory(&modinfo, sizeof(modinfo)); modinfo.SizeOfStruct = sizeof(modinfo); if (!pSymInfo->pfSymGetModuleInfo64(pSymInfo->hProcess, (DWORD64)hModule, &modinfo)) { DETOUR_TRACE(("SymGetModuleInfo64(%p, %p) failed: %d\n", pSymInfo->hProcess, hModule, GetLastError())); return NULL; } hrRet = StringCchCopyA(szFullName, sizeof(szFullName)/sizeof(CHAR), modinfo.ModuleName); if (FAILED(hrRet)) { DETOUR_TRACE(("StringCchCopyA failed: %08x\n", hrRet)); return NULL; } hrRet = StringCchCatA(szFullName, sizeof(szFullName)/sizeof(CHAR), "!"); if (FAILED(hrRet)) { DETOUR_TRACE(("StringCchCatA failed: %08x\n", hrRet)); return NULL; } hrRet = StringCchCatA(szFullName, sizeof(szFullName)/sizeof(CHAR), pszFunction); if (FAILED(hrRet)) { DETOUR_TRACE(("StringCchCatA failed: %08x\n", hrRet)); return NULL; } struct CFullSymbol : SYMBOL_INFO { CHAR szRestOfName[512]; } symbol; ZeroMemory(&symbol, sizeof(symbol)); //symbol.ModBase = (ULONG64)hModule; symbol.SizeOfStruct = sizeof(SYMBOL_INFO); #ifdef DBHLPAPI symbol.MaxNameLen = sizeof(symbol.szRestOfName)/sizeof(symbol.szRestOfName[0]); #else symbol.MaxNameLength = sizeof(symbol.szRestOfName)/sizeof(symbol.szRestOfName[0]); #endif if (!pSymInfo->pfSymFromName(pSymInfo->hProcess, szFullName, &symbol)) { DETOUR_TRACE(("SymFromName(%s) failed: %d\n", szFullName, GetLastError())); return NULL; } #ifdef DETOURS_IA64 #error Feature not supported in this release. #else return (PBYTE)symbol.Address; #endif }
static PDETOUR_TRAMPOLINE detour_alloc_trampoline(PBYTE pbTarget) { // We have to place trampolines within +/- 2GB of target. PDETOUR_TRAMPOLINE pLo = (PDETOUR_TRAMPOLINE) ((pbTarget > (PBYTE)0x7ff80000) ? pbTarget - 0x7ff80000 : (PBYTE)(ULONG_PTR)DETOUR_REGION_SIZE); PDETOUR_TRAMPOLINE pHi = (PDETOUR_TRAMPOLINE) ((pbTarget < (PBYTE)0xffffffff80000000) ? pbTarget + 0x7ff80000 : (PBYTE)0xfffffffffff80000); DETOUR_TRACE(("[%p..%p..%p]\n", pLo, pbTarget, pHi)); PDETOUR_TRAMPOLINE pTrampoline = NULL; // Insure that there is a default region. if (s_pRegion == NULL && s_pRegions != NULL) { s_pRegion = s_pRegions; } // First check the default region for an valid free block. if (s_pRegion != NULL && s_pRegion->pFree != NULL && s_pRegion->pFree >= pLo && s_pRegion->pFree <= pHi) { found_region: pTrampoline = s_pRegion->pFree; // do a last sanity check on region. if (pTrampoline < pLo || pTrampoline > pHi) { return NULL; } s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pTrampoline->pbRemain; memset(pTrampoline, 0xcc, sizeof(*pTrampoline)); return pTrampoline; } // Then check the existing regions for a valid free block. for (s_pRegion = s_pRegions; s_pRegion != NULL; s_pRegion = s_pRegion->pNext) { if (s_pRegion != NULL && s_pRegion->pFree != NULL && s_pRegion->pFree >= pLo && s_pRegion->pFree <= pHi) { goto found_region; } } // We need to allocate a new region. // Round pbTarget down to 64KB block. pbTarget = pbTarget - (PtrToUlong(pbTarget) & 0xffff); PVOID pbTry = NULL; // Try looking 1GB below or lower. if (pbTry == NULL && pbTarget > (PBYTE)0x40000000) { pbTry = detour_alloc_region_from_hi((PBYTE)pLo, pbTarget - 0x40000000); } // Try looking 1GB above or higher. if (pbTry == NULL && pbTarget < (PBYTE)0xffffffff40000000) { pbTry = detour_alloc_region_from_lo(pbTarget + 0x40000000, (PBYTE)pHi); } // Try looking 1GB below or higher. if (pbTry == NULL && pbTarget > (PBYTE)0x40000000) { pbTry = detour_alloc_region_from_lo(pbTarget - 0x40000000, pbTarget); } // Try looking 1GB above or lower. if (pbTry == NULL && pbTarget < (PBYTE)0xffffffff40000000) { pbTry = detour_alloc_region_from_hi(pbTarget, pbTarget + 0x40000000); } // Try anything below. if (pbTry == NULL) { pbTry = detour_alloc_region_from_hi((PBYTE)pLo, pbTarget); } // try anything above. if (pbTry == NULL) { pbTry = detour_alloc_region_from_lo(pbTarget, (PBYTE)pHi); } if (pbTry != NULL) { s_pRegion = (DETOUR_REGION*)pbTry; s_pRegion->dwSignature = DETOUR_REGION_SIGNATURE; s_pRegion->pFree = NULL; s_pRegion->pNext = s_pRegions; s_pRegions = s_pRegion; DETOUR_TRACE((" Allocated region %p..%p\n\n", s_pRegion, ((PBYTE)s_pRegion) + DETOUR_REGION_SIZE - 1)); // Put everything but the first trampoline on the free list. PBYTE pFree = NULL; pTrampoline = ((PDETOUR_TRAMPOLINE)s_pRegion) + 1; for (int i = DETOUR_TRAMPOLINES_PER_REGION - 1; i > 1; i--) { pTrampoline[i].pbRemain = pFree; pFree = (PBYTE)&pTrampoline[i]; } s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pFree; goto found_region; } DETOUR_TRACE(("Couldn't find available memory region!\n")); return NULL; }
LONG WINAPI DetourAttachEx(PVOID *ppPointer, PVOID pDetour, PDETOUR_TRAMPOLINE *ppRealTrampoline, PVOID *ppRealTarget, PVOID *ppRealDetour) { LONG error = NO_ERROR; if (ppRealTrampoline != NULL) { *ppRealTrampoline = NULL; } if (ppRealTarget != NULL) { *ppRealTarget = NULL; } if (ppRealDetour != NULL) { *ppRealDetour = NULL; } if (s_nPendingThreadId != (LONG)GetCurrentThreadId()) { DETOUR_TRACE(("transaction conflict with thread id=%d\n", s_nPendingThreadId)); return ERROR_INVALID_OPERATION; } // If any of the pending operations failed, then we don't need to do this. if (s_nPendingError != NO_ERROR) { DETOUR_TRACE(("pending transaction error=%d\n", s_nPendingError)); return s_nPendingError; } if (ppPointer == NULL) { DETOUR_TRACE(("ppPointer is null\n")); return ERROR_INVALID_HANDLE; } if (*ppPointer == NULL) { error = ERROR_INVALID_HANDLE; s_nPendingError = error; s_ppPendingError = ppPointer; DETOUR_TRACE(("*ppPointer is null (ppPointer=%p)\n", ppPointer)); DETOUR_BREAK(); return error; } PBYTE pbTarget = (PBYTE)*ppPointer; PDETOUR_TRAMPOLINE pTrampoline = NULL; DetourOperation *o = NULL; #ifdef DETOURS_IA64 #error Feature not supported in this release. #else // DETOURS_IA64 pbTarget = (PBYTE)DetourCodeFromPointer(pbTarget, NULL); pDetour = DetourCodeFromPointer(pDetour, NULL); #endif // !DETOURS_IA64 // Don't follow a jump if its destination is the target function. // This happens when the detour does nothing other than call the target. if (pDetour == (PVOID)pbTarget) { if (s_fIgnoreTooSmall) { goto stop; } else { DETOUR_BREAK(); goto fail; } } if (ppRealTarget != NULL) { *ppRealTarget = pbTarget; } if (ppRealDetour != NULL) { *ppRealDetour = pDetour; } o = new DetourOperation; if (o == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; fail: s_nPendingError = error; DETOUR_BREAK(); stop: if (pTrampoline != NULL) { detour_free_trampoline(pTrampoline); pTrampoline = NULL; if (ppRealTrampoline != NULL) { *ppRealTrampoline = NULL; } } if (o != NULL) { delete o; o = NULL; } s_ppPendingError = ppPointer; return error; } pTrampoline = detour_alloc_trampoline(pbTarget); if (pTrampoline == NULL) { error = ERROR_NOT_ENOUGH_MEMORY; DETOUR_BREAK(); goto fail; } if (ppRealTrampoline != NULL) { *ppRealTrampoline = pTrampoline; } DETOUR_TRACE(("detours: pbTramp=%p, pDetour=%p\n", pTrampoline, pDetour)); memset(pTrampoline->rAlign, 0, sizeof(pTrampoline->rAlign)); // Determine the number of movable target instructions. PBYTE pbSrc = pbTarget; PBYTE pbTrampoline = pTrampoline->rbCode; PBYTE pbPool = pbTrampoline + sizeof(pTrampoline->rbCode); ULONG cbTarget = 0; ULONG cbJump = SIZE_OF_JMP; ULONG nAlign = 0; #ifdef DETOURS_ARM #error Feature not supported in this release. #endif while (cbTarget < cbJump) { PBYTE pbOp = pbSrc; LONG lExtra = 0; DETOUR_TRACE((" DetourCopyInstruction(%p,%p)\n", pbTrampoline, pbSrc)); pbSrc = (PBYTE) DetourCopyInstruction(pbTrampoline, (PVOID*)&pbPool, pbSrc, NULL, &lExtra); DETOUR_TRACE((" DetourCopyInstruction() = %p (%d bytes)\n", pbSrc, (int)(pbSrc - pbOp))); pbTrampoline += (pbSrc - pbOp) + lExtra; cbTarget = (LONG)(pbSrc - pbTarget); pTrampoline->rAlign[nAlign].obTarget = cbTarget; pTrampoline->rAlign[nAlign].obTrampoline = pbTrampoline - pTrampoline->rbCode; if (detour_does_code_end_function(pbOp)) { break; } } // Consume, but don't duplicate padding if it is needed and available. while (cbTarget < cbJump) { LONG cFiller = detour_is_code_filler(pbSrc); if (cFiller == 0) { break; } pbSrc += cFiller; cbTarget = (LONG)(pbSrc - pbTarget); } #if DETOUR_DEBUG { DETOUR_TRACE((" detours: rAlign [")); LONG n = 0; for (n = 0; n < ARRAYSIZE(pTrampoline->rAlign); n++) { if (pTrampoline->rAlign[n].obTarget == 0 && pTrampoline->rAlign[n].obTrampoline == 0) { break; } DETOUR_TRACE((" %d/%d", pTrampoline->rAlign[n].obTarget, pTrampoline->rAlign[n].obTrampoline )); } DETOUR_TRACE((" ]\n")); } #endif if (cbTarget < cbJump || nAlign > ARRAYSIZE(pTrampoline->rAlign)) { // Too few instructions. error = ERROR_INVALID_BLOCK; if (s_fIgnoreTooSmall) { goto stop; } else { DETOUR_BREAK(); goto fail; } } if (pbTrampoline > pbPool) { __debugbreak(); } #if 0 // [GalenH] if (cbTarget < pbTrampoline - pTrampoline->rbCode) { __debugbreak(); } #endif pTrampoline->cbCode = (BYTE)(pbTrampoline - pTrampoline->rbCode); pTrampoline->cbRestore = (BYTE)cbTarget; CopyMemory(pTrampoline->rbRestore, pbTarget, cbTarget); #if !defined(DETOURS_IA64) if (cbTarget > sizeof(pTrampoline->rbCode) - cbJump) { // Too many instructions. error = ERROR_INVALID_HANDLE; DETOUR_BREAK(); goto fail; } #endif // !DETOURS_IA64 pTrampoline->pbRemain = pbTarget + cbTarget; pTrampoline->pbDetour = (PBYTE)pDetour; #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 pbTrampoline = pTrampoline->rbCode + pTrampoline->cbCode; #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_X86 pbTrampoline = detour_gen_jmp_immediate(pbTrampoline, pTrampoline->pbRemain); pbTrampoline = detour_gen_brk(pbTrampoline, pbPool); #endif // DETOURS_X86 #ifdef DETOURS_ARM #error Feature not supported in this release. #endif // DETOURS_ARM DWORD dwOld = 0; if (!VirtualProtect(pbTarget, cbTarget, PAGE_EXECUTE_READWRITE, &dwOld)) { error = GetLastError(); DETOUR_BREAK(); goto fail; } DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", pbTarget, pbTarget[0], pbTarget[1], pbTarget[2], pbTarget[3], pbTarget[4], pbTarget[5], pbTarget[6], pbTarget[7], pbTarget[8], pbTarget[9], pbTarget[10], pbTarget[11])); DETOUR_TRACE(("detours: pbTramp =%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", pTrampoline, pTrampoline->rbCode[0], pTrampoline->rbCode[1], pTrampoline->rbCode[2], pTrampoline->rbCode[3], pTrampoline->rbCode[4], pTrampoline->rbCode[5], pTrampoline->rbCode[6], pTrampoline->rbCode[7], pTrampoline->rbCode[8], pTrampoline->rbCode[9], pTrampoline->rbCode[10], pTrampoline->rbCode[11])); o->fIsRemove = FALSE; o->ppbPointer = (PBYTE*)ppPointer; o->pTrampoline = pTrampoline; o->pbTarget = pbTarget; o->dwPerm = dwOld; o->pNext = s_pPendingOperations; s_pPendingOperations = o; return NO_ERROR; }
LONG WINAPI DetourTransactionCommitEx(PVOID **pppFailedPointer) { if (pppFailedPointer != NULL) { // Used to get the last error. *pppFailedPointer = s_ppPendingError; } if (s_nPendingThreadId != (LONG)GetCurrentThreadId()) { return ERROR_INVALID_OPERATION; } // If any of the pending operations failed, then we abort the whole transaction. if (s_nPendingError != NO_ERROR) { DETOUR_BREAK(); DetourTransactionAbort(); return s_nPendingError; } // Common variables. DetourOperation *o; DetourThread *t; BOOL freed = FALSE; // Insert or remove each of the detours. for (o = s_pPendingOperations; o != NULL; o = o->pNext) { if (o->fIsRemove) { CopyMemory(o->pbTarget, o->pTrampoline->rbRestore, o->pTrampoline->cbRestore); #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 #ifdef DETOURS_X86 *o->ppbPointer = o->pbTarget; #endif // DETOURS_X86 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_ARM #error Feature not supported in this release. #endif // DETOURS_ARM } else { DETOUR_TRACE(("detours: pbTramp =%p, pbRemain=%p, pbDetour=%p, cbRestore=%d\n", o->pTrampoline, o->pTrampoline->pbRemain, o->pTrampoline->pbDetour, o->pTrampoline->cbRestore)); DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x [before]\n", o->pbTarget, o->pbTarget[0], o->pbTarget[1], o->pbTarget[2], o->pbTarget[3], o->pbTarget[4], o->pbTarget[5], o->pbTarget[6], o->pbTarget[7], o->pbTarget[8], o->pbTarget[9], o->pbTarget[10], o->pbTarget[11])); #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_X86 PBYTE pbCode = detour_gen_jmp_immediate(o->pbTarget, o->pTrampoline->pbDetour); pbCode = detour_gen_brk(pbCode, o->pTrampoline->pbRemain); *o->ppbPointer = o->pTrampoline->rbCode; #endif // DETOURS_X86 #ifdef DETOURS_ARM #error Feature not supported in this release. #endif // DETOURS_ARM DETOUR_TRACE(("detours: pbTarget=%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x [after]\n", o->pbTarget, o->pbTarget[0], o->pbTarget[1], o->pbTarget[2], o->pbTarget[3], o->pbTarget[4], o->pbTarget[5], o->pbTarget[6], o->pbTarget[7], o->pbTarget[8], o->pbTarget[9], o->pbTarget[10], o->pbTarget[11])); DETOUR_TRACE(("detours: pbTramp =%p: " "%02x %02x %02x %02x " "%02x %02x %02x %02x " "%02x %02x %02x %02x\n", o->pTrampoline, o->pTrampoline->rbCode[0], o->pTrampoline->rbCode[1], o->pTrampoline->rbCode[2], o->pTrampoline->rbCode[3], o->pTrampoline->rbCode[4], o->pTrampoline->rbCode[5], o->pTrampoline->rbCode[6], o->pTrampoline->rbCode[7], o->pTrampoline->rbCode[8], o->pTrampoline->rbCode[9], o->pTrampoline->rbCode[10], o->pTrampoline->rbCode[11])); #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 } } // Update any suspended threads. for (t = s_pPendingThreads; t != NULL; t = t->pNext) { CONTEXT cxt; cxt.ContextFlags = CONTEXT_CONTROL; #undef DETOURS_EIP #undef DETOURS_EIP_TYPE #ifdef DETOURS_X86 #define DETOURS_EIP Eip #define DETOURS_EIP_TYPE DWORD #endif // DETOURS_X86 #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif // DETOURS_IA64 #ifdef DETOURS_ARM #error Feature not supported in this release. #endif // DETOURS_ARM if (GetThreadContext(t->hThread, &cxt)) { for (DetourOperation *o = s_pPendingOperations; o != NULL; o = o->pNext) { if (o->fIsRemove) { if (cxt.DETOURS_EIP >= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pTrampoline && cxt.DETOURS_EIP < (DETOURS_EIP_TYPE)((ULONG_PTR)o->pTrampoline + sizeof(o->pTrampoline)) ) { cxt.DETOURS_EIP = (DETOURS_EIP_TYPE) ((ULONG_PTR)o->pbTarget + detour_align_from_trampoline(o->pTrampoline, (BYTE)(cxt.DETOURS_EIP - (DETOURS_EIP_TYPE)(ULONG_PTR) o->pTrampoline))); SetThreadContext(t->hThread, &cxt); } } else { if (cxt.DETOURS_EIP >= (DETOURS_EIP_TYPE)(ULONG_PTR)o->pbTarget && cxt.DETOURS_EIP < (DETOURS_EIP_TYPE)((ULONG_PTR)o->pbTarget + o->pTrampoline->cbRestore) ) { cxt.DETOURS_EIP = (DETOURS_EIP_TYPE) ((ULONG_PTR)o->pTrampoline + detour_align_from_target(o->pTrampoline, (BYTE)(cxt.DETOURS_EIP - (DETOURS_EIP_TYPE)(ULONG_PTR) o->pbTarget))); SetThreadContext(t->hThread, &cxt); } } } } #undef DETOURS_EIP } // Restore all of the page permissions and flush the icache. HANDLE hProcess = GetCurrentProcess(); for (o = s_pPendingOperations; o != NULL;) { // We don't care if this fails, because the code is still accessible. DWORD dwOld; VirtualProtect(o->pbTarget, o->pTrampoline->cbRestore, o->dwPerm, &dwOld); FlushInstructionCache(hProcess, o->pbTarget, o->pTrampoline->cbRestore); if (o->fIsRemove && o->pTrampoline) { detour_free_trampoline(o->pTrampoline); o->pTrampoline = NULL; freed = true; } DetourOperation *n = o->pNext; delete o; o = n; } s_pPendingOperations = NULL; // Free any trampoline regions that are now unused. if (freed && !s_fRetainRegions) { detour_free_unused_trampoline_regions(); } // Make sure the trampoline pages are no longer writable. detour_runnable_trampoline_regions(); // Resume any suspended threads. for (t = s_pPendingThreads; t != NULL;) { // There is nothing we can do if this fails. ResumeThread(t->hThread); DetourThread *n = t->pNext; delete t; t = n; } s_pPendingThreads = NULL; s_nPendingThreadId = 0; if (pppFailedPointer != NULL) { *pppFailedPointer = s_ppPendingError; } return s_nPendingError; }
// UpdateImports32 aka UpdateImports64 static BOOL UPDATE_IMPORTS_XX(HANDLE hProcess, HMODULE hModule, LPCSTR *plpDlls, DWORD nDlls) { BOOL fSucceeded = FALSE; BYTE * pbNew = NULL; DWORD i; PBYTE pbModule = (PBYTE)hModule; IMAGE_DOS_HEADER idh; ZeroMemory(&idh, sizeof(idh)); if (!ReadProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) { DETOUR_TRACE(("ReadProcessMemory(idh@%p..%p) failed: %d\n", pbModule, pbModule + sizeof(idh), GetLastError())); finish: if (pbNew != NULL) { delete[] pbNew; pbNew = NULL; } return fSucceeded; } IMAGE_NT_HEADERS_XX inh; ZeroMemory(&inh, sizeof(inh)); if (!ReadProcessMemory(hProcess, pbModule + idh.e_lfanew, &inh, sizeof(inh), NULL)) { DETOUR_TRACE(("ReadProcessMemory(inh@%p..%p) failed: %d\n", pbModule + idh.e_lfanew, pbModule + idh.e_lfanew + sizeof(inh), GetLastError())); goto finish; } if (inh.OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC_XX) { DETOUR_TRACE(("Wrong size image (%04x != %04x).\n", inh.OptionalHeader.Magic, IMAGE_NT_OPTIONAL_HDR_MAGIC_XX)); SetLastError(ERROR_INVALID_BLOCK); goto finish; } // Zero out the bound table so loader doesn't use it instead of our new table. inh.BOUND_DIRECTORY.VirtualAddress = 0; inh.BOUND_DIRECTORY.Size = 0; // Find the size of the mapped file. DWORD dwFileSize = 0; DWORD dwSec = idh.e_lfanew + FIELD_OFFSET(IMAGE_NT_HEADERS_XX, OptionalHeader) + inh.FileHeader.SizeOfOptionalHeader; for (i = 0; i < inh.FileHeader.NumberOfSections; i++) { IMAGE_SECTION_HEADER ish; ZeroMemory(&ish, sizeof(ish)); if (!ReadProcessMemory(hProcess, pbModule + dwSec + sizeof(ish) * i, &ish, sizeof(ish), NULL)) { DETOUR_TRACE(("ReadProcessMemory(ish@%p..%p) failed: %d\n", pbModule + dwSec + sizeof(ish) * i, pbModule + dwSec + sizeof(ish) * (i + 1), GetLastError())); goto finish; } DETOUR_TRACE(("ish[%d] : va=%08x sr=%d\n", i, ish.VirtualAddress, ish.SizeOfRawData)); // If the file didn't have an IAT_DIRECTORY, we assign it... if (inh.IAT_DIRECTORY.VirtualAddress == 0 && inh.IMPORT_DIRECTORY.VirtualAddress >= ish.VirtualAddress && inh.IMPORT_DIRECTORY.VirtualAddress < ish.VirtualAddress + ish.SizeOfRawData) { inh.IAT_DIRECTORY.VirtualAddress = ish.VirtualAddress; inh.IAT_DIRECTORY.Size = ish.SizeOfRawData; } // Find the end of the file... if (dwFileSize < ish.PointerToRawData + ish.SizeOfRawData) { dwFileSize = ish.PointerToRawData + ish.SizeOfRawData; } } DETOUR_TRACE(("dwFileSize = %08x\n", dwFileSize)); #if IGNORE_CHECKSUMS // Find the current checksum. WORD wBefore = ComputeChkSum(hProcess, pbModule, &inh); DETOUR_TRACE(("ChkSum: %04x + %08x => %08x\n", wBefore, dwFileSize, wBefore + dwFileSize)); #endif DETOUR_TRACE((" Imports: %p..%p\n", (DWORD_PTR)pbModule + inh.IMPORT_DIRECTORY.VirtualAddress, (DWORD_PTR)pbModule + inh.IMPORT_DIRECTORY.VirtualAddress + inh.IMPORT_DIRECTORY.Size)); DWORD obRem = sizeof(IMAGE_IMPORT_DESCRIPTOR) * nDlls; DWORD obTab = PadToDwordPtr(obRem + inh.IMPORT_DIRECTORY.Size + sizeof(IMAGE_IMPORT_DESCRIPTOR)); DWORD obDll = obTab + sizeof(DWORD_XX) * 4 * nDlls; DWORD obStr = obDll; DWORD cbNew = obStr; DWORD n; for (n = 0; n < nDlls; n++) { cbNew += PadToDword((DWORD)strlen(plpDlls[n]) + 1); } pbNew = new BYTE [cbNew]; if (pbNew == NULL) { DETOUR_TRACE(("new BYTE [cbNew] failed.\n")); goto finish; } ZeroMemory(pbNew, cbNew); PBYTE pbBase = pbModule; PBYTE pbNext = pbBase + inh.OptionalHeader.BaseOfCode + inh.OptionalHeader.SizeOfCode + inh.OptionalHeader.SizeOfInitializedData + inh.OptionalHeader.SizeOfUninitializedData; if (pbBase < pbNext) { pbBase = pbNext; } DETOUR_TRACE(("pbBase = %p\n", pbBase)); PBYTE pbNewIid = FindAndAllocateNearBase(hProcess, pbBase, cbNew); if (pbNewIid == NULL) { DETOUR_TRACE(("FindAndAllocateNearBase failed.\n")); goto finish; } DWORD obBase = (DWORD)(pbNewIid - pbModule); DWORD dwProtect = 0; if (inh.IMPORT_DIRECTORY.VirtualAddress != 0) { // Read the old import directory if it exists. if (!VirtualProtectEx(hProcess, pbModule + inh.IMPORT_DIRECTORY.VirtualAddress, inh.IMPORT_DIRECTORY.Size, PAGE_EXECUTE_READWRITE, &dwProtect)) { DETOUR_TRACE(("VirtualProtectEx(import) write failed: %d\n", GetLastError())); goto finish; } DETOUR_TRACE(("IMPORT_DIRECTORY perms=%x\n", dwProtect)); if (!ReadProcessMemory(hProcess, pbModule + inh.IMPORT_DIRECTORY.VirtualAddress, pbNew + obRem, inh.IMPORT_DIRECTORY.Size, NULL)) { DETOUR_TRACE(("ReadProcessMemory(imports) failed: %d\n", GetLastError())); goto finish; } } PIMAGE_IMPORT_DESCRIPTOR piid = (PIMAGE_IMPORT_DESCRIPTOR)pbNew; DWORD_XX *pt; for (n = 0; n < nDlls; n++) { HRESULT hrRet = StringCchCopyA((char*)pbNew + obStr, cbNew - obStr, plpDlls[n]); if (FAILED(hrRet)) { DETOUR_TRACE(("StringCchCopyA failed: %d\n", GetLastError())); goto finish; } DWORD nOffset = obTab + (sizeof(DWORD_XX) * (4 * n)); piid[n].OriginalFirstThunk = obBase + nOffset; pt = ((DWORD_XX*)(pbNew + nOffset)); pt[0] = IMAGE_ORDINAL_FLAG_XX + 1; pt[1] = 0; nOffset = obTab + (sizeof(DWORD_XX) * ((4 * n) + 2)); piid[n].FirstThunk = obBase + nOffset; pt = ((DWORD_XX*)(pbNew + nOffset)); pt[0] = IMAGE_ORDINAL_FLAG_XX + 1; pt[1] = 0; piid[n].TimeDateStamp = 0; piid[n].ForwarderChain = 0; piid[n].Name = obBase + obStr; obStr += PadToDword((DWORD)strlen(plpDlls[n]) + 1); } for (i = 0; i < nDlls + (inh.IMPORT_DIRECTORY.Size / sizeof(*piid)); i++) { DETOUR_TRACE(("%8d. Look=%08x Time=%08x Fore=%08x Name=%08x Addr=%08x\n", i, piid[i].OriginalFirstThunk, piid[i].TimeDateStamp, piid[i].ForwarderChain, piid[i].Name, piid[i].FirstThunk)); if (piid[i].OriginalFirstThunk == 0 && piid[i].FirstThunk == 0) { break; } } if (!WriteProcessMemory(hProcess, pbNewIid, pbNew, obStr, NULL)) { DETOUR_TRACE(("WriteProcessMemory(iid) failed: %d\n", GetLastError())); goto finish; } DETOUR_TRACE(("obBaseBef = %08x..%08x\n", inh.IMPORT_DIRECTORY.VirtualAddress, inh.IMPORT_DIRECTORY.VirtualAddress + inh.IMPORT_DIRECTORY.Size)); DETOUR_TRACE(("obBaseAft = %08x..%08x\n", obBase, obBase + obStr)); // If the file doesn't have an IAT_DIRECTORY, we create it... if (inh.IAT_DIRECTORY.VirtualAddress == 0) { inh.IAT_DIRECTORY.VirtualAddress = obBase; inh.IAT_DIRECTORY.Size = cbNew; } inh.IMPORT_DIRECTORY.VirtualAddress = obBase; inh.IMPORT_DIRECTORY.Size = cbNew; /////////////////////// Update the NT header for the new import directory. /////////////////////////////// Update the DOS header to fix the checksum. // if (!VirtualProtectEx(hProcess, pbModule, inh.OptionalHeader.SizeOfHeaders, PAGE_EXECUTE_READWRITE, &dwProtect)) { DETOUR_TRACE(("VirtualProtectEx(inh) write failed: %d\n", GetLastError())); goto finish; } #if IGNORE_CHECKSUMS idh.e_res[0] = 0; #else inh.OptionalHeader.CheckSum = 0; #endif // IGNORE_CHECKSUMS if (!WriteProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) { DETOUR_TRACE(("WriteProcessMemory(idh) failed: %d\n", GetLastError())); goto finish; } DETOUR_TRACE(("WriteProcessMemory(idh:%p..%p)\n", pbModule, pbModule + sizeof(idh))); if (!WriteProcessMemory(hProcess, pbModule + idh.e_lfanew, &inh, sizeof(inh), NULL)) { DETOUR_TRACE(("WriteProcessMemory(inh) failed: %d\n", GetLastError())); goto finish; } DETOUR_TRACE(("WriteProcessMemory(inh:%p..%p)\n", pbModule + idh.e_lfanew, pbModule + idh.e_lfanew + sizeof(inh))); #if IGNORE_CHECKSUMS WORD wDuring = ComputeChkSum(hProcess, pbModule, &inh); DETOUR_TRACE(("ChkSum: %04x + %08x => %08x\n", wDuring, dwFileSize, wDuring + dwFileSize)); idh.e_res[0] = detour_sum_minus(idh.e_res[0], detour_sum_minus(wDuring, wBefore)); if (!WriteProcessMemory(hProcess, pbModule, &idh, sizeof(idh), NULL)) { DETOUR_TRACE(("WriteProcessMemory(idh) failed: %d\n", GetLastError())); goto finish; } #endif // IGNORE_CHECKSUMS if (!VirtualProtectEx(hProcess, pbModule, inh.OptionalHeader.SizeOfHeaders, dwProtect, &dwProtect)) { DETOUR_TRACE(("VirtualProtectEx(idh) restore failed: %d\n", GetLastError())); goto finish; } #if IGNORE_CHECKSUMS WORD wAfter = ComputeChkSum(hProcess, pbModule, &inh); DETOUR_TRACE(("ChkSum: %04x + %08x => %08x\n", wAfter, dwFileSize, wAfter + dwFileSize)); DETOUR_TRACE(("Before: %08x, After: %08x\n", wBefore + dwFileSize, wAfter + dwFileSize)); if (wBefore != wAfter) { DETOUR_TRACE(("Restore of checksum failed %04x != %04x.\n", wBefore, wAfter)); goto finish; } #endif // IGNORE_CHECKSUMS fSucceeded = TRUE; goto finish; }
static PDETOUR_TRAMPOLINE detour_alloc_trampoline(PBYTE pbTarget) { // We have to place trampolines within +/- 2GB of target. // The allocation code assumes that PDETOUR_TRAMPOLINE pLo = (PDETOUR_TRAMPOLINE) ((pbTarget > (PBYTE)0x7ff80000) ? pbTarget - 0x7ff80000 : (PBYTE)(ULONG_PTR)DETOUR_REGION_SIZE); PDETOUR_TRAMPOLINE pHi = (PDETOUR_TRAMPOLINE) ((pbTarget < (PBYTE)0xffffffff80000000) ? pbTarget + 0x7ff80000 : (PBYTE)0xfffffffffff80000); DETOUR_TRACE(("[%p..%p..%p]\n", pLo, pbTarget, pHi)); PDETOUR_TRAMPOLINE pTrampoline = NULL; // Insure that there is a default region. if (s_pRegion == NULL && s_pRegions != NULL) { s_pRegion = s_pRegions; } // First check the default region for an valid free block. if (s_pRegion != NULL && s_pRegion->pFree != NULL && s_pRegion->pFree >= pLo && s_pRegion->pFree <= pHi) { found_region: pTrampoline = s_pRegion->pFree; // do a last sanity check on region. if (pTrampoline < pLo || pTrampoline > pHi) { return NULL; } s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pTrampoline->pbRemain; memset(pTrampoline, 0xcc, sizeof(*pTrampoline)); return pTrampoline; } // Then check the existing regions for a valid free block. for (s_pRegion = s_pRegions; s_pRegion != NULL; s_pRegion = s_pRegion->pNext) { if (s_pRegion != NULL && s_pRegion->pFree != NULL && s_pRegion->pFree >= pLo && s_pRegion->pFree <= pHi) { goto found_region; } } // We need to allocate a new region. // Round pbTarget down to 64K block. pbTarget = pbTarget - (PtrToUlong(pbTarget) & 0xffff); // First we search down (within the valid region) DETOUR_TRACE((" Looking for free region below %p:\n", pbTarget)); PBYTE pbTry; for (pbTry = pbTarget; pbTry > (PBYTE)pLo;) { MEMORY_BASIC_INFORMATION mbi; DETOUR_TRACE((" Try %p\n", pbTry)); if (pbTry >= (PBYTE)(ULONG_PTR)0x70000000 && pbTry <= (PBYTE)(ULONG_PTR)0x80000000) { // Skip region reserved for system DLLs. pbTry = (PBYTE)(ULONG_PTR)(0x70000000 - DETOUR_REGION_SIZE); } if (!VirtualQuery(pbTry, &mbi, sizeof(mbi))) { break; } DETOUR_TRACE((" Try %p => %p..%p %6x\n", pbTry, mbi.BaseAddress, (PBYTE)mbi.BaseAddress + mbi.RegionSize - 1, mbi.State)); if (mbi.State == MEM_FREE && mbi.RegionSize >= DETOUR_REGION_SIZE) { s_pRegion = (DETOUR_REGION*)VirtualAlloc(pbTry, DETOUR_REGION_SIZE, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (s_pRegion != NULL) { alloced_region: s_pRegion->dwSignature = DETOUR_REGION_SIGNATURE; s_pRegion->pFree = NULL; s_pRegion->pNext = s_pRegions; s_pRegions = s_pRegion; DETOUR_TRACE((" Allocated region %p..%p\n\n", s_pRegion, ((PBYTE)s_pRegion) + DETOUR_REGION_SIZE - 1)); // Put everything but the first trampoline on the free list. PBYTE pFree = NULL; pTrampoline = ((PDETOUR_TRAMPOLINE)s_pRegion) + 1; for (int i = DETOUR_TRAMPOLINES_PER_REGION - 1; i > 1; i--) { pTrampoline[i].pbRemain = pFree; pFree = (PBYTE)&pTrampoline[i]; } s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pFree; goto found_region; } else { DETOUR_TRACE(("Error: %p %d\n", pbTry, GetLastError())); break; } } pbTry = (PBYTE)mbi.AllocationBase - DETOUR_REGION_SIZE; } DETOUR_TRACE((" Looking for free region above %p:\n", pbTarget)); for (pbTry = pbTarget; pbTry < (PBYTE)pHi;) { MEMORY_BASIC_INFORMATION mbi; if (pbTry >= (PBYTE)(ULONG_PTR)0x70000000 && pbTry <= (PBYTE)(ULONG_PTR)0x80000000) { // Skip region reserved for system DLLs. pbTry = (PBYTE)(ULONG_PTR)(0x80000000 + DETOUR_REGION_SIZE); } if (!VirtualQuery(pbTry, &mbi, sizeof(mbi))) { break; } DETOUR_TRACE((" Try %p => %p..%p %6x\n", pbTry, mbi.BaseAddress, (PBYTE)mbi.BaseAddress + mbi.RegionSize - 1, mbi.State)); if (mbi.State == MEM_FREE && mbi.RegionSize >= DETOUR_REGION_SIZE) { ULONG_PTR extra = ((ULONG_PTR)pbTry) & (DETOUR_REGION_SIZE - 1); if (extra != 0) { // WinXP64 returns free areas that aren't REGION aligned to // 32-bit applications. ULONG_PTR adjust = DETOUR_REGION_SIZE - extra; mbi.RegionSize -= adjust; ((PBYTE&)mbi.BaseAddress) += adjust; DETOUR_TRACE(("--Try %p => %p..%p %6x\n", pbTry, mbi.BaseAddress, (PBYTE)mbi.BaseAddress + mbi.RegionSize - 1, mbi.State)); pbTry = (PBYTE)mbi.BaseAddress; } s_pRegion = (DETOUR_REGION*)VirtualAlloc(pbTry, DETOUR_REGION_SIZE, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (s_pRegion != NULL) { goto alloced_region; } else { DETOUR_TRACE(("Error: %p %d\n", pbTry, GetLastError())); } } pbTry = (PBYTE)mbi.BaseAddress + mbi.RegionSize; } DETOUR_TRACE(("Couldn't find available memory region!\n")); return NULL; }