1407 lines
36 KiB
C++
1407 lines
36 KiB
C++
|
//////////////////////////////////////////////////////////////////////////////
|
||
|
//
|
||
|
// Core Detours Functionality (detours.cpp of detours.lib)
|
||
|
//
|
||
|
// Microsoft Research Detours Package, Version 2.1.
|
||
|
//
|
||
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||
|
//
|
||
|
#include <windows.h>
|
||
|
|
||
|
#if (_MSC_VER < 1299)
|
||
|
#pragma warning(disable: 4710)
|
||
|
#endif
|
||
|
|
||
|
//#define DETOUR_DEBUG 1
|
||
|
#define DETOURS_INTERNAL
|
||
|
|
||
|
#include "detours.h"
|
||
|
#include "detoured.h"
|
||
|
|
||
|
#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
|
||
|
}
|
||
|
|
||
|
// End of File
|