#include #if (_MSC_VER < 1299) #pragma warning(disable: 4710) #endif //#define DETOUR_DEBUG 1 #define DETOURS_INTERNAL #include "detours.h" #include "detoured.h" #define DETOURS_X86 #if !defined(DETOURS_X86) && !defined(DETOURS_X64) && !defined(DETOURS_IA64) #error Must define one of DETOURS_X86, DETOURS_X64, or DETOURS_IA64 #endif static bool detour_is_imported( PBYTE pbCode , PBYTE pbAddress ) { MEMORY_BASIC_INFORMATION mbi; VirtualQuery( ( PVOID ) pbCode,&mbi,sizeof( mbi ) ); __try { PIMAGE_DOS_HEADER pDosHeader = ( PIMAGE_DOS_HEADER ) mbi.AllocationBase; if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { return false; } PIMAGE_NT_HEADERS pNtHeader = ( PIMAGE_NT_HEADERS ) ( ( PBYTE ) pDosHeader + pDosHeader->e_lfanew ); if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE ) { return false; } if ( pbAddress >= ( ( PBYTE ) pDosHeader + pNtHeader->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress ) && pbAddress < ( ( PBYTE ) pDosHeader + pNtHeader->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress + pNtHeader->OptionalHeader .DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size ) ) { return true; } return false; } __except( EXCEPTION_EXECUTE_HANDLER ) { return false; } } // X86. #ifdef DETOURS_X86 struct _DETOUR_TRAMPOLINE { BYTE rbCode[23]; // target code + jmp to pbRemain BYTE cbTarget; // size of target code moved. PBYTE pbRemain; // first instruction after moved code. [free list] PBYTE pbDetour; // first instruction of detour function. }; enum { SIZE_OF_JMP = 5 }; inline PBYTE detour_gen_jmp_immediate( PBYTE pbCode , PBYTE pbJmpVal ) { PBYTE pbJmpSrc = pbCode + 5; *pbCode++ = 0xE9; // jmp +imm32 *( ( INT32 * & ) pbCode )++ = ( INT32 ) ( pbJmpVal - pbJmpSrc ); return pbCode; } inline PBYTE detour_gen_jmp_indirect( PBYTE pbCode , PBYTE * ppbJmpVal ) { PBYTE pbJmpSrc = pbCode + 6; *pbCode++ = 0xff; // jmp [+imm32] *pbCode++ = 0x25; *( ( INT32 * & ) pbCode )++ = ( INT32 ) ( ( PBYTE ) ppbJmpVal - pbJmpSrc ); return pbCode; } inline PBYTE detour_gen_brk( PBYTE pbCode , PBYTE pbLimit ) { while ( pbCode < pbLimit ) { *pbCode++ = 0xcc; // brk; } return pbCode; } 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 BOOL detour_does_code_end_function( PBYTE pbCode ) { if ( pbCode[0] == 0xe9 || // jmp +imm32 pbCode[0] == 0xe0 || // jmp eax pbCode[0] == 0xc2 || // ret +imm8 pbCode[0] == 0xc3 || // ret pbCode[0] == 0xcc ) { // brk return TRUE; } else if ( pbCode[0] == 0xff && pbCode[1] == 0x25 ) { // jmp [+imm32] return TRUE; } else if ( ( pbCode[0] == 0x26 || // jmp es: pbCode[0] == 0x2e || // jmp cs: pbCode[0] == 0x36 || // jmp ss: pbCode[0] == 0xe3 || // jmp ds: pbCode[0] == 0x64 || // jmp fs: pbCode[0] == 0x65 ) && // jmp gs: pbCode[1] == 0xff && // jmp [+imm32] pbCode[2] == 0x25 ) { return TRUE; } return FALSE; } #endif // DETOURS_X86 ///////////////////////////////////////////////////////////////////////// X64. // #ifdef DETOURS_X64 #error Feature not supported in this release. #endif // DETOURS_X64 //////////////////////////////////////////////////////////////////////// IA64. // #ifdef DETOURS_IA64 #error Feature not supported in this release. #endif //////////////////////////////////////////////// Trampoline Memory Management. // struct DETOUR_REGION { ULONG dwSignature; DETOUR_REGION * pNext; // Next region in list of regions. DETOUR_TRAMPOLINE * pFree; // List of free trampolines in this region. }; typedef DETOUR_REGION * PDETOUR_REGION; const ULONG DETOUR_REGION_SIGNATURE = 'Rrtd'; const ULONG DETOUR_REGION_SIZE = 0x10000; const ULONG DETOUR_TRAMPOLINES_PER_REGION = ( DETOUR_REGION_SIZE / sizeof( DETOUR_TRAMPOLINE ) ) - 1; static PDETOUR_REGION s_pRegions = NULL; // List of all regions. static PDETOUR_REGION s_pRegion = NULL; // Default region. static void detour_writable_trampoline_regions() { // Mark all of the regions as writable. for ( PDETOUR_REGION pRegion = s_pRegions; pRegion != NULL; pRegion = pRegion->pNext ) { DWORD dwOld; VirtualProtect( pRegion,DETOUR_REGION_SIZE,PAGE_EXECUTE_READWRITE,&dwOld ); } } static void detour_runnable_trampoline_regions() { // Mark all of the regions as executable. for ( PDETOUR_REGION pRegion = s_pRegions; pRegion != NULL; pRegion = pRegion->pNext ) { DWORD dwOld; VirtualProtect( pRegion,DETOUR_REGION_SIZE,PAGE_EXECUTE_READ,&dwOld ); } } 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; } static VOID detour_free_trampoline( PDETOUR_TRAMPOLINE pTrampoline ) { PDETOUR_REGION pRegion = ( PDETOUR_REGION ) ( ( ULONG_PTR ) pTrampoline & ~( ULONG_PTR ) 0xffff ); memset( pTrampoline,0,sizeof( *pTrampoline ) ); pTrampoline->pbRemain = ( PBYTE ) pRegion->pFree; pRegion->pFree = pTrampoline; } ////////////////////////////////////////////////////////////////////////////// // static PIMAGE_DOS_HEADER detour_find_header( PBYTE pbTarget ) { MEMORY_BASIC_INFORMATION mbi; if ( !VirtualQuery( pbTarget,&mbi,sizeof( mbi ) ) ) { return NULL; } PIMAGE_DOS_HEADER pDosHeader = ( PIMAGE_DOS_HEADER ) mbi.AllocationBase; __try { if ( pDosHeader->e_magic != IMAGE_DOS_SIGNATURE ) { return NULL; } PIMAGE_NT_HEADERS pNtHeader = ( PIMAGE_NT_HEADERS ) ( ( PBYTE ) pDosHeader + pDosHeader->e_lfanew ); if ( pNtHeader->Signature != IMAGE_NT_SIGNATURE ) { return NULL; } if ( pNtHeader->FileHeader.SizeOfOptionalHeader == 0 ) { return NULL; } return pDosHeader; } __except( EXCEPTION_EXECUTE_HANDLER ) { return NULL; } } ///////////////////////////////////////////////////////// Transaction Structs. // struct DetourThread { DetourThread * pNext; HANDLE hThread; }; struct DetourOperation { DetourOperation * pNext; BOOL fIsRemove; PBYTE * ppbPointer; PBYTE pbTarget; PDETOUR_TRAMPOLINE pTrampoline; ULONG dwPerm; }; static BOOL s_fIgnoreTooSmall = FALSE; static LONG s_nPendingThreadId = 0; // Thread owning pending transaction. static LONG s_nPendingError = NO_ERROR; static PVOID * s_ppPendingError = NULL; static DetourThread * s_pPendingThreads = NULL; static DetourOperation *s_pPendingOperations = NULL; ////////////////////////////////////////////////////////////////////////////// // PVOID WINAPI DetourCodeFromPointer( PVOID pPointer , PVOID * ppGlobals ) { return detour_skip_jmp( ( PBYTE ) pPointer,ppGlobals ); } //////////////////////////////////////////////////////////// Transaction APIs. // VOID WINAPI DetourSetIgnoreTooSmall( BOOL fIgnore ) { s_fIgnoreTooSmall = fIgnore; } LONG WINAPI DetourTransactionBegin() { // Only one transaction is allowed at a time. if ( s_nPendingThreadId != 0 ) { return ERROR_INVALID_OPERATION; } // Make sure only one thread can start a transaction. if ( InterlockedCompareExchange( &s_nPendingThreadId,( LONG ) GetCurrentThreadId(),0 ) != 0 ) { return ERROR_INVALID_OPERATION; } s_fIgnoreTooSmall = FALSE; s_pPendingOperations = NULL; s_pPendingThreads = NULL; s_nPendingError = NO_ERROR; s_ppPendingError = NULL; // Make sure the trampoline pages are writable. detour_writable_trampoline_regions(); return NO_ERROR; } LONG WINAPI DetourTransactionAbort() { if ( s_nPendingThreadId != ( LONG ) GetCurrentThreadId() ) { return ERROR_INVALID_OPERATION; } // Restore all of the page permissions. for ( DetourOperation *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 ); if ( !o->fIsRemove ) { if ( 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 ( DetourThread *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; return NO_ERROR; } LONG WINAPI DetourTransactionCommit() { return DetourTransactionCommitEx( 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 DetourUpdateThread( HANDLE hThread ) { LONG error; // If any of the pending operations failed, then we don't need to do this. if ( s_nPendingError != NO_ERROR ) { return s_nPendingError; } // Silently (and safely) drop any attempt to suspend our own thread. if ( hThread == GetCurrentThread() ) { return NO_ERROR; } DetourThread * t = new DetourThread; if ( t == NULL ) { error = ERROR_NOT_ENOUGH_MEMORY; fail: if ( t != NULL ) { delete t; t = NULL; } s_nPendingError = error; s_ppPendingError = NULL; DETOUR_BREAK(); return error; } if ( SuspendThread( hThread ) == ( DWORD ) - 1 ) { error = GetLastError(); DETOUR_BREAK(); goto fail; } t->hThread = hThread; t->pNext = s_pPendingThreads; s_pPendingThreads = t; return NO_ERROR; } ///////////////////////////////////////////////////////////// Transacted APIs. // LONG WINAPI DetourAttach( PVOID * ppPointer , PVOID pDetour ) { return DetourAttachEx( ppPointer,pDetour,NULL,NULL,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 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; } LONG WINAPI DetourDetach( PVOID * ppPointer , PVOID pDetour ) { LONG error = NO_ERROR; if ( s_nPendingThreadId != ( LONG ) GetCurrentThreadId() ) { return ERROR_INVALID_OPERATION; } // If any of the pending operations failed, then we don't need to do this. if ( s_nPendingError != NO_ERROR ) { return s_nPendingError; } if ( ppPointer == NULL ) { return ERROR_INVALID_HANDLE; } if ( *ppPointer == NULL ) { error = ERROR_INVALID_HANDLE; s_nPendingError = error; s_ppPendingError = ppPointer; DETOUR_BREAK(); return error; } DetourOperation * o = new DetourOperation; if ( o == NULL ) { error = ERROR_NOT_ENOUGH_MEMORY; fail: s_nPendingError = error; DETOUR_BREAK(); stop: if ( o != NULL ) { delete o; o = NULL; } s_ppPendingError = ppPointer; return error; } PDETOUR_TRAMPOLINE pTrampoline = ( PDETOUR_TRAMPOLINE ) * ppPointer; pDetour = DetourCodeFromPointer( pDetour,NULL ); ////////////////////////////////////// Verify that Trampoline is in place. // PBYTE pbTarget = pTrampoline->pbRemain - pTrampoline->cbTarget; LONG cbTarget = pTrampoline->cbTarget; if ( cbTarget == 0 || cbTarget > sizeof( pTrampoline->rbCode ) ) { error = ERROR_INVALID_BLOCK; if ( s_fIgnoreTooSmall ) { goto stop; } else { DETOUR_BREAK(); goto fail; } } if ( pTrampoline->pbDetour != pDetour ) { error = ERROR_INVALID_BLOCK; if ( s_fIgnoreTooSmall ) { goto stop; } else { DETOUR_BREAK(); goto fail; } } DWORD dwOld = 0; if ( !VirtualProtect( pbTarget,cbTarget,PAGE_EXECUTE_READWRITE,&dwOld ) ) { error = GetLastError(); DETOUR_BREAK(); goto fail; } o->fIsRemove = TRUE; o->ppbPointer = ( PBYTE * ) ppPointer; o->pTrampoline = pTrampoline; o->pbTarget = pbTarget; o->dwPerm = dwOld; o->pNext = s_pPendingOperations; s_pPendingOperations = o; return NO_ERROR; } HMODULE WINAPI DetourGetDetouredMarker() { #ifdef DETOURS_INTERNAL_USAGE #error Feature not supported in this release. #else return Detoured(); #endif }