mirror of
https://gitlab.com/Mr_Goldberg/goldberg_emulator.git
synced 2024-12-27 04:48:44 +01:00
2461 lines
82 KiB
C++
2461 lines
82 KiB
C++
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Core Detours Functionality (detours.cpp of detours.lib)
|
|
//
|
|
// Microsoft Research Detours Package, Version 4.0.1
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
|
|
#pragma warning(disable:4068) // unknown pragma (suppress)
|
|
|
|
#if _MSC_VER >= 1900
|
|
#pragma warning(push)
|
|
#pragma warning(disable:4091) // empty typedef
|
|
#endif
|
|
|
|
#define _ARM_WINAPI_PARTITION_DESKTOP_SDK_AVAILABLE 1
|
|
#include <windows.h>
|
|
|
|
#if (_MSC_VER < 1299)
|
|
#pragma warning(disable: 4710)
|
|
#endif
|
|
|
|
//#define DETOUR_DEBUG 1
|
|
#define DETOURS_INTERNAL
|
|
|
|
#include "detours.h"
|
|
|
|
#if DETOURS_VERSION != 0x4c0c1 // 0xMAJORcMINORcPATCH
|
|
#error detours.h version mismatch
|
|
#endif
|
|
|
|
#if _MSC_VER >= 1900
|
|
#pragma warning(pop)
|
|
#endif
|
|
|
|
#define NOTHROW
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
struct _DETOUR_ALIGN
|
|
{
|
|
BYTE obTarget : 3;
|
|
BYTE obTrampoline : 5;
|
|
};
|
|
|
|
C_ASSERT(sizeof(_DETOUR_ALIGN) == 1);
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Region reserved for system DLLs, which cannot be used for trampolines.
|
|
//
|
|
static PVOID s_pSystemRegionLowerBound = (PVOID)(ULONG_PTR)0x70000000;
|
|
static PVOID s_pSystemRegionUpperBound = (PVOID)(ULONG_PTR)0x80000000;
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
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;
|
|
}
|
|
}
|
|
#pragma prefast(suppress:28940, "A bad pointer means this probably isn't a PE header.")
|
|
__except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
|
|
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline ULONG_PTR detour_2gb_below(ULONG_PTR address)
|
|
{
|
|
return (address > (ULONG_PTR)0x7ff80000) ? address - 0x7ff80000 : 0x80000;
|
|
}
|
|
|
|
inline ULONG_PTR detour_2gb_above(ULONG_PTR address)
|
|
{
|
|
#if defined(DETOURS_64BIT)
|
|
return (address < (ULONG_PTR)0xffffffff80000000) ? address + 0x7ff80000 : (ULONG_PTR)0xfffffffffff80000;
|
|
#else
|
|
return (address < (ULONG_PTR)0x80000000) ? address + 0x7ff80000 : (ULONG_PTR)0xfff80000;
|
|
#endif
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////// X86.
|
|
//
|
|
#ifdef DETOURS_X86
|
|
|
|
struct _DETOUR_TRAMPOLINE
|
|
{
|
|
BYTE rbCode[30]; // target code + jmp to pbRemain
|
|
BYTE cbCode; // size of moved target code.
|
|
BYTE cbCodeBreak; // padding to make debugging easier.
|
|
BYTE rbRestore[22]; // original target code.
|
|
BYTE cbRestore; // size of original target code.
|
|
BYTE cbRestoreBreak; // padding to make debugging easier.
|
|
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
|
|
PBYTE pbRemain; // first instruction after moved code. [free list]
|
|
PBYTE pbDetour; // first instruction of detour function.
|
|
};
|
|
|
|
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 72);
|
|
|
|
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)
|
|
{
|
|
*pbCode++ = 0xff; // jmp [+imm32]
|
|
*pbCode++ = 0x25;
|
|
*((INT32*&)pbCode)++ = (INT32)((PBYTE)ppbJmpVal);
|
|
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;
|
|
}
|
|
|
|
// 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 = *(UNALIGNED PBYTE *)&pbCode[2];
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
PBYTE pbNew = *(UNALIGNED 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;
|
|
|
|
// 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 = *(UNALIGNED PBYTE *)&pbCode[2];
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
pbNew = *(UNALIGNED PBYTE *)pbTarget;
|
|
DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew));
|
|
pbCode = pbNew;
|
|
}
|
|
}
|
|
// Finally, skip over a long jump if it is the target of the patch jump.
|
|
else if (pbCode[0] == 0xe9) { // jmp +imm32
|
|
pbNew = pbCode + 5 + *(UNALIGNED INT32 *)&pbCode[1];
|
|
DETOUR_TRACE(("%p->%p: skipped over long jump.\n", pbCode, pbNew));
|
|
pbCode = pbNew;
|
|
}
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline void detour_find_jmp_bounds(PBYTE pbCode,
|
|
PDETOUR_TRAMPOLINE *ppLower,
|
|
PDETOUR_TRAMPOLINE *ppUpper)
|
|
{
|
|
// We have to place trampolines within +/- 2GB of code.
|
|
ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode);
|
|
ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode);
|
|
DETOUR_TRACE(("[%p..%p..%p]\n", lo, pbCode, hi));
|
|
|
|
// And, within +/- 2GB of relative jmp targets.
|
|
if (pbCode[0] == 0xe9) { // jmp +imm32
|
|
PBYTE pbNew = pbCode + 5 + *(UNALIGNED INT32 *)&pbCode[1];
|
|
|
|
if (pbNew < pbCode) {
|
|
hi = detour_2gb_above((ULONG_PTR)pbNew);
|
|
}
|
|
else {
|
|
lo = detour_2gb_below((ULONG_PTR)pbNew);
|
|
}
|
|
DETOUR_TRACE(("[%p..%p..%p] +imm32\n", lo, pbCode, hi));
|
|
}
|
|
|
|
*ppLower = (PDETOUR_TRAMPOLINE)lo;
|
|
*ppUpper = (PDETOUR_TRAMPOLINE)hi;
|
|
}
|
|
|
|
inline BOOL detour_does_code_end_function(PBYTE pbCode)
|
|
{
|
|
if (pbCode[0] == 0xeb || // jmp +imm8
|
|
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] == 0xf3 && pbCode[1] == 0xc3) { // rep ret
|
|
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] == 0x3e || // jmp ds:
|
|
pbCode[0] == 0x64 || // jmp fs:
|
|
pbCode[0] == 0x65) && // jmp gs:
|
|
pbCode[1] == 0xff && // jmp [+imm32]
|
|
pbCode[2] == 0x25) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
inline ULONG detour_is_code_filler(PBYTE pbCode)
|
|
{
|
|
// 1-byte through 11-byte NOPs.
|
|
if (pbCode[0] == 0x90) {
|
|
return 1;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x90) {
|
|
return 2;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x00) {
|
|
return 3;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x40 &&
|
|
pbCode[3] == 0x00) {
|
|
return 4;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x44 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00) {
|
|
return 5;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F &&
|
|
pbCode[3] == 0x44 && pbCode[4] == 0x00 && pbCode[5] == 0x00) {
|
|
return 6;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x80 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00) {
|
|
return 7;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x84 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00) {
|
|
return 8;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F &&
|
|
pbCode[3] == 0x84 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00) {
|
|
return 9;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x0F &&
|
|
pbCode[3] == 0x1F && pbCode[4] == 0x84 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 &&
|
|
pbCode[9] == 0x00) {
|
|
return 10;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x66 &&
|
|
pbCode[3] == 0x0F && pbCode[4] == 0x1F && pbCode[5] == 0x84 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 &&
|
|
pbCode[9] == 0x00 && pbCode[10] == 0x00) {
|
|
return 11;
|
|
}
|
|
|
|
// int 3.
|
|
if (pbCode[0] == 0xcc) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif // DETOURS_X86
|
|
|
|
///////////////////////////////////////////////////////////////////////// X64.
|
|
//
|
|
#ifdef DETOURS_X64
|
|
|
|
struct _DETOUR_TRAMPOLINE
|
|
{
|
|
// An X64 instuction can be 15 bytes long.
|
|
// In practice 11 seems to be the limit.
|
|
BYTE rbCode[30]; // target code + jmp to pbRemain.
|
|
BYTE cbCode; // size of moved target code.
|
|
BYTE cbCodeBreak; // padding to make debugging easier.
|
|
BYTE rbRestore[30]; // original target code.
|
|
BYTE cbRestore; // size of original target code.
|
|
BYTE cbRestoreBreak; // padding to make debugging easier.
|
|
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
|
|
PBYTE pbRemain; // first instruction after moved code. [free list]
|
|
PBYTE pbDetour; // first instruction of detour function.
|
|
BYTE rbCodeIn[8]; // jmp [pbDetour]
|
|
};
|
|
|
|
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 96);
|
|
|
|
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;
|
|
}
|
|
|
|
// 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 = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2];
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
PBYTE pbNew = *(UNALIGNED 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;
|
|
|
|
// 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 = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2];
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
pbNew = *(UNALIGNED PBYTE *)pbTarget;
|
|
DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew));
|
|
pbCode = pbNew;
|
|
}
|
|
}
|
|
// Finally, skip over a long jump if it is the target of the patch jump.
|
|
else if (pbCode[0] == 0xe9) { // jmp +imm32
|
|
pbNew = pbCode + 5 + *(UNALIGNED INT32 *)&pbCode[1];
|
|
DETOUR_TRACE(("%p->%p: skipped over long jump.\n", pbCode, pbNew));
|
|
pbCode = pbNew;
|
|
}
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline void detour_find_jmp_bounds(PBYTE pbCode,
|
|
PDETOUR_TRAMPOLINE *ppLower,
|
|
PDETOUR_TRAMPOLINE *ppUpper)
|
|
{
|
|
// We have to place trampolines within +/- 2GB of code.
|
|
ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode);
|
|
ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode);
|
|
DETOUR_TRACE(("[%p..%p..%p]\n", lo, pbCode, hi));
|
|
|
|
// And, within +/- 2GB of relative jmp vectors.
|
|
if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [+imm32]
|
|
PBYTE pbNew = pbCode + 6 + *(UNALIGNED INT32 *)&pbCode[2];
|
|
|
|
if (pbNew < pbCode) {
|
|
hi = detour_2gb_above((ULONG_PTR)pbNew);
|
|
}
|
|
else {
|
|
lo = detour_2gb_below((ULONG_PTR)pbNew);
|
|
}
|
|
DETOUR_TRACE(("[%p..%p..%p] [+imm32]\n", lo, pbCode, hi));
|
|
}
|
|
// And, within +/- 2GB of relative jmp targets.
|
|
else if (pbCode[0] == 0xe9) { // jmp +imm32
|
|
PBYTE pbNew = pbCode + 5 + *(UNALIGNED INT32 *)&pbCode[1];
|
|
|
|
if (pbNew < pbCode) {
|
|
hi = detour_2gb_above((ULONG_PTR)pbNew);
|
|
}
|
|
else {
|
|
lo = detour_2gb_below((ULONG_PTR)pbNew);
|
|
}
|
|
DETOUR_TRACE(("[%p..%p..%p] +imm32\n", lo, pbCode, hi));
|
|
}
|
|
|
|
*ppLower = (PDETOUR_TRAMPOLINE)lo;
|
|
*ppUpper = (PDETOUR_TRAMPOLINE)hi;
|
|
}
|
|
|
|
inline BOOL detour_does_code_end_function(PBYTE pbCode)
|
|
{
|
|
if (pbCode[0] == 0xeb || // jmp +imm8
|
|
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] == 0xf3 && pbCode[1] == 0xc3) { // rep ret
|
|
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] == 0x3e || // jmp ds:
|
|
pbCode[0] == 0x64 || // jmp fs:
|
|
pbCode[0] == 0x65) && // jmp gs:
|
|
pbCode[1] == 0xff && // jmp [+imm32]
|
|
pbCode[2] == 0x25) {
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
inline ULONG detour_is_code_filler(PBYTE pbCode)
|
|
{
|
|
// 1-byte through 11-byte NOPs.
|
|
if (pbCode[0] == 0x90) {
|
|
return 1;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x90) {
|
|
return 2;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x00) {
|
|
return 3;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x40 &&
|
|
pbCode[3] == 0x00) {
|
|
return 4;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x44 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00) {
|
|
return 5;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F &&
|
|
pbCode[3] == 0x44 && pbCode[4] == 0x00 && pbCode[5] == 0x00) {
|
|
return 6;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x80 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00) {
|
|
return 7;
|
|
}
|
|
if (pbCode[0] == 0x0F && pbCode[1] == 0x1F && pbCode[2] == 0x84 &&
|
|
pbCode[3] == 0x00 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00) {
|
|
return 8;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x0F && pbCode[2] == 0x1F &&
|
|
pbCode[3] == 0x84 && pbCode[4] == 0x00 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00) {
|
|
return 9;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x0F &&
|
|
pbCode[3] == 0x1F && pbCode[4] == 0x84 && pbCode[5] == 0x00 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 &&
|
|
pbCode[9] == 0x00) {
|
|
return 10;
|
|
}
|
|
if (pbCode[0] == 0x66 && pbCode[1] == 0x66 && pbCode[2] == 0x66 &&
|
|
pbCode[3] == 0x0F && pbCode[4] == 0x1F && pbCode[5] == 0x84 &&
|
|
pbCode[6] == 0x00 && pbCode[7] == 0x00 && pbCode[8] == 0x00 &&
|
|
pbCode[9] == 0x00 && pbCode[10] == 0x00) {
|
|
return 11;
|
|
}
|
|
|
|
// int 3.
|
|
if (pbCode[0] == 0xcc) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif // DETOURS_X64
|
|
|
|
//////////////////////////////////////////////////////////////////////// IA64.
|
|
//
|
|
#ifdef DETOURS_IA64
|
|
|
|
struct _DETOUR_TRAMPOLINE
|
|
{
|
|
// On the IA64, a trampoline is used for both incoming and outgoing calls.
|
|
//
|
|
// The trampoline contains the following bundles for the outgoing call:
|
|
// movl gp=target_gp;
|
|
// <relocated target bundle>
|
|
// brl target_code;
|
|
//
|
|
// The trampoline contains the following bundles for the incoming call:
|
|
// alloc r41=ar.pfs, b, 0, 8, 0
|
|
// mov r40=rp
|
|
//
|
|
// adds r50=0, r39
|
|
// adds r49=0, r38
|
|
// adds r48=0, r37 ;;
|
|
//
|
|
// adds r47=0, r36
|
|
// adds r46=0, r35
|
|
// adds r45=0, r34
|
|
//
|
|
// adds r44=0, r33
|
|
// adds r43=0, r32
|
|
// adds r42=0, gp ;;
|
|
//
|
|
// movl gp=ffffffff`ffffffff ;;
|
|
//
|
|
// brl.call.sptk.few rp=disas!TestCodes+20e0 (00000000`00404ea0) ;;
|
|
//
|
|
// adds gp=0, r42
|
|
// mov rp=r40, +0 ;;
|
|
// mov.i ar.pfs=r41
|
|
//
|
|
// br.ret.sptk.many rp ;;
|
|
//
|
|
// This way, we only have to relocate a single bundle.
|
|
//
|
|
// The complicated incoming trampoline is required because we have to
|
|
// create an additional stack frame so that we save and restore the gp.
|
|
// We must do this because gp is a caller-saved register, but not saved
|
|
// if the caller thinks the target is in the same DLL, which changes
|
|
// when we insert a detour.
|
|
//
|
|
DETOUR_IA64_BUNDLE bMovlTargetGp; // Bundle which sets target GP
|
|
BYTE rbCode[sizeof(DETOUR_IA64_BUNDLE)]; // moved bundle.
|
|
DETOUR_IA64_BUNDLE bBrlRemainEip; // Brl to pbRemain
|
|
// This must be adjacent to bBranchIslands.
|
|
|
|
// Each instruction in the moved bundle could be a IP-relative chk or branch or call.
|
|
// Any such instructions are changed to point to a brl in bBranchIslands.
|
|
// This must be adjacent to bBrlRemainEip -- see "pbPool".
|
|
DETOUR_IA64_BUNDLE bBranchIslands[DETOUR_IA64_INSTRUCTIONS_PER_BUNDLE];
|
|
|
|
// Target of brl inserted in target function
|
|
DETOUR_IA64_BUNDLE bAllocFrame; // alloc frame
|
|
DETOUR_IA64_BUNDLE bSave37to39; // save r37, r38, r39.
|
|
DETOUR_IA64_BUNDLE bSave34to36; // save r34, r35, r36.
|
|
DETOUR_IA64_BUNDLE bSaveGPto33; // save gp, r32, r33.
|
|
DETOUR_IA64_BUNDLE bMovlDetourGp; // set detour GP.
|
|
DETOUR_IA64_BUNDLE bCallDetour; // call detour.
|
|
DETOUR_IA64_BUNDLE bPopFrameGp; // pop frame and restore gp.
|
|
DETOUR_IA64_BUNDLE bReturn; // return to caller.
|
|
|
|
PLABEL_DESCRIPTOR pldTrampoline;
|
|
|
|
BYTE rbRestore[sizeof(DETOUR_IA64_BUNDLE)]; // original target bundle.
|
|
BYTE cbRestore; // size of original target code.
|
|
BYTE cbCode; // size of moved target code.
|
|
_DETOUR_ALIGN rAlign[14]; // instruction alignment array.
|
|
PBYTE pbRemain; // first instruction after moved code. [free list]
|
|
PBYTE pbDetour; // first instruction of detour function.
|
|
PPLABEL_DESCRIPTOR ppldDetour; // [pbDetour,gpDetour]
|
|
PPLABEL_DESCRIPTOR ppldTarget; // [pbTarget,gpDetour]
|
|
};
|
|
|
|
C_ASSERT(sizeof(DETOUR_IA64_BUNDLE) == 16);
|
|
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 256 + DETOUR_IA64_INSTRUCTIONS_PER_BUNDLE * 16);
|
|
|
|
enum {
|
|
SIZE_OF_JMP = sizeof(DETOUR_IA64_BUNDLE)
|
|
};
|
|
|
|
inline PBYTE detour_skip_jmp(PBYTE pPointer, PVOID *ppGlobals)
|
|
{
|
|
PBYTE pGlobals = NULL;
|
|
PBYTE pbCode = NULL;
|
|
|
|
if (pPointer != NULL) {
|
|
PPLABEL_DESCRIPTOR ppld = (PPLABEL_DESCRIPTOR)pPointer;
|
|
pbCode = (PBYTE)ppld->EntryPoint;
|
|
pGlobals = (PBYTE)ppld->GlobalPointer;
|
|
}
|
|
if (ppGlobals != NULL) {
|
|
*ppGlobals = pGlobals;
|
|
}
|
|
if (pbCode == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
DETOUR_IA64_BUNDLE *pb = (DETOUR_IA64_BUNDLE *)pbCode;
|
|
|
|
// IA64 Local Import Jumps look like:
|
|
// addl r2=ffffffff`ffe021c0, gp ;;
|
|
// ld8 r2=[r2]
|
|
// nop.i 0 ;;
|
|
//
|
|
// ld8 r3=[r2], 8 ;;
|
|
// ld8 gp=[r2]
|
|
// mov b6=r3, +0
|
|
//
|
|
// nop.m 0
|
|
// nop.i 0
|
|
// br.cond.sptk.few b6
|
|
//
|
|
|
|
// 002024000200100b
|
|
if ((pb[0].wide[0] & 0xfffffc000603ffff) == 0x002024000200100b &&
|
|
pb[0].wide[1] == 0x0004000000203008 &&
|
|
pb[1].wide[0] == 0x001014180420180a &&
|
|
pb[1].wide[1] == 0x07000830c0203008 &&
|
|
pb[2].wide[0] == 0x0000000100000010 &&
|
|
pb[2].wide[1] == 0x0080006000000200) {
|
|
|
|
ULONG64 offset =
|
|
((pb[0].wide[0] & 0x0000000001fc0000) >> 18) | // imm7b
|
|
((pb[0].wide[0] & 0x000001ff00000000) >> 25) | // imm9d
|
|
((pb[0].wide[0] & 0x00000000f8000000) >> 11); // imm5c
|
|
if (pb[0].wide[0] & 0x0000020000000000) { // sign
|
|
offset |= 0xffffffffffe00000;
|
|
}
|
|
PBYTE pbTarget = pGlobals + offset;
|
|
DETOUR_TRACE(("%p: potential import jump, target=%p\n", pb, pbTarget));
|
|
|
|
if (detour_is_imported(pbCode, pbTarget) && *(PBYTE*)pbTarget != NULL) {
|
|
DETOUR_TRACE(("%p: is import jump, label=%p\n", pb, *(PBYTE *)pbTarget));
|
|
|
|
PPLABEL_DESCRIPTOR ppld = (PPLABEL_DESCRIPTOR)*(PBYTE *)pbTarget;
|
|
pbCode = (PBYTE)ppld->EntryPoint;
|
|
pGlobals = (PBYTE)ppld->GlobalPointer;
|
|
if (ppGlobals != NULL) {
|
|
*ppGlobals = pGlobals;
|
|
}
|
|
}
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
|
|
inline void detour_find_jmp_bounds(PBYTE pbCode,
|
|
PDETOUR_TRAMPOLINE *ppLower,
|
|
PDETOUR_TRAMPOLINE *ppUpper)
|
|
{
|
|
(void)pbCode;
|
|
*ppLower = (PDETOUR_TRAMPOLINE)(ULONG_PTR)0x0000000000080000;
|
|
*ppUpper = (PDETOUR_TRAMPOLINE)(ULONG_PTR)0xfffffffffff80000;
|
|
}
|
|
|
|
inline BOOL detour_does_code_end_function(PBYTE pbCode)
|
|
{
|
|
// Routine not needed on IA64.
|
|
(void)pbCode;
|
|
return FALSE;
|
|
}
|
|
|
|
inline ULONG detour_is_code_filler(PBYTE pbCode)
|
|
{
|
|
// Routine not needed on IA64.
|
|
(void)pbCode;
|
|
return 0;
|
|
}
|
|
|
|
#endif // DETOURS_IA64
|
|
|
|
#ifdef DETOURS_ARM
|
|
|
|
struct _DETOUR_TRAMPOLINE
|
|
{
|
|
// A Thumb-2 instruction can be 2 or 4 bytes long.
|
|
BYTE rbCode[62]; // target code + jmp to pbRemain
|
|
BYTE cbCode; // size of moved target code.
|
|
BYTE cbCodeBreak; // padding to make debugging easier.
|
|
BYTE rbRestore[22]; // original target code.
|
|
BYTE cbRestore; // size of original target code.
|
|
BYTE cbRestoreBreak; // padding to make debugging easier.
|
|
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
|
|
PBYTE pbRemain; // first instruction after moved code. [free list]
|
|
PBYTE pbDetour; // first instruction of detour function.
|
|
};
|
|
|
|
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 104);
|
|
|
|
enum {
|
|
SIZE_OF_JMP = 8
|
|
};
|
|
|
|
inline PBYTE align4(PBYTE pValue)
|
|
{
|
|
return (PBYTE)(((ULONG)pValue) & ~(ULONG)3u);
|
|
}
|
|
|
|
inline ULONG fetch_thumb_opcode(PBYTE pbCode)
|
|
{
|
|
ULONG Opcode = *(UINT16 *)&pbCode[0];
|
|
if (Opcode >= 0xe800) {
|
|
Opcode = (Opcode << 16) | *(UINT16 *)&pbCode[2];
|
|
}
|
|
return Opcode;
|
|
}
|
|
|
|
inline void write_thumb_opcode(PBYTE &pbCode, ULONG Opcode)
|
|
{
|
|
if (Opcode >= 0x10000) {
|
|
*((UINT16*&)pbCode)++ = Opcode >> 16;
|
|
}
|
|
*((UINT16*&)pbCode)++ = (UINT16)Opcode;
|
|
}
|
|
|
|
PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE *ppPool, PBYTE pbJmpVal)
|
|
{
|
|
PBYTE pbLiteral;
|
|
if (ppPool != NULL) {
|
|
*ppPool = *ppPool - 4;
|
|
pbLiteral = *ppPool;
|
|
}
|
|
else {
|
|
pbLiteral = align4(pbCode + 6);
|
|
}
|
|
|
|
*((PBYTE*&)pbLiteral) = DETOURS_PBYTE_TO_PFUNC(pbJmpVal);
|
|
LONG delta = pbLiteral - align4(pbCode + 4);
|
|
|
|
write_thumb_opcode(pbCode, 0xf8dff000 | delta); // LDR PC,[PC+n]
|
|
|
|
if (ppPool == NULL) {
|
|
if (((ULONG)pbCode & 2) != 0) {
|
|
write_thumb_opcode(pbCode, 0xdefe); // BREAK
|
|
}
|
|
pbCode += 4;
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit)
|
|
{
|
|
while (pbCode < pbLimit) {
|
|
write_thumb_opcode(pbCode, 0xdefe);
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals)
|
|
{
|
|
if (pbCode == NULL) {
|
|
return NULL;
|
|
}
|
|
if (ppGlobals != NULL) {
|
|
*ppGlobals = NULL;
|
|
}
|
|
|
|
// Skip over the import jump if there is one.
|
|
pbCode = (PBYTE)DETOURS_PFUNC_TO_PBYTE(pbCode);
|
|
ULONG Opcode = fetch_thumb_opcode(pbCode);
|
|
|
|
if ((Opcode & 0xfbf08f00) == 0xf2400c00) { // movw r12,#xxxx
|
|
ULONG Opcode2 = fetch_thumb_opcode(pbCode+4);
|
|
|
|
if ((Opcode2 & 0xfbf08f00) == 0xf2c00c00) { // movt r12,#xxxx
|
|
ULONG Opcode3 = fetch_thumb_opcode(pbCode+8);
|
|
if (Opcode3 == 0xf8dcf000) { // ldr pc,[r12]
|
|
PBYTE pbTarget = (PBYTE)(((Opcode2 << 12) & 0xf7000000) |
|
|
((Opcode2 << 1) & 0x08000000) |
|
|
((Opcode2 << 16) & 0x00ff0000) |
|
|
((Opcode >> 4) & 0x0000f700) |
|
|
((Opcode >> 15) & 0x00000800) |
|
|
((Opcode >> 0) & 0x000000ff));
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
PBYTE pbNew = *(PBYTE *)pbTarget;
|
|
pbNew = DETOURS_PFUNC_TO_PBYTE(pbNew);
|
|
DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew));
|
|
return pbNew;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline void detour_find_jmp_bounds(PBYTE pbCode,
|
|
PDETOUR_TRAMPOLINE *ppLower,
|
|
PDETOUR_TRAMPOLINE *ppUpper)
|
|
{
|
|
// We have to place trampolines within +/- 2GB of code.
|
|
ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode);
|
|
ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode);
|
|
DETOUR_TRACE(("[%p..%p..%p]\n", lo, pbCode, hi));
|
|
|
|
*ppLower = (PDETOUR_TRAMPOLINE)lo;
|
|
*ppUpper = (PDETOUR_TRAMPOLINE)hi;
|
|
}
|
|
|
|
|
|
inline BOOL detour_does_code_end_function(PBYTE pbCode)
|
|
{
|
|
ULONG Opcode = fetch_thumb_opcode(pbCode);
|
|
if ((Opcode & 0xffffff87) == 0x4700 || // bx <reg>
|
|
(Opcode & 0xf800d000) == 0xf0009000) { // b <imm20>
|
|
return TRUE;
|
|
}
|
|
if ((Opcode & 0xffff8000) == 0xe8bd8000) { // pop {...,pc}
|
|
__debugbreak();
|
|
return TRUE;
|
|
}
|
|
if ((Opcode & 0xffffff00) == 0x0000bd00) { // pop {...,pc}
|
|
__debugbreak();
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
inline ULONG detour_is_code_filler(PBYTE pbCode)
|
|
{
|
|
if (pbCode[0] == 0x00 && pbCode[1] == 0xbf) { // nop.
|
|
return 2;
|
|
}
|
|
if (pbCode[0] == 0x00 && pbCode[1] == 0x00) { // zero-filled padding.
|
|
return 2;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif // DETOURS_ARM
|
|
|
|
#ifdef DETOURS_ARM64
|
|
|
|
struct _DETOUR_TRAMPOLINE
|
|
{
|
|
// An ARM64 instruction is 4 bytes long.
|
|
//
|
|
// The overwrite is always 2 instructions plus a literal, so 16 bytes, 4 instructions.
|
|
//
|
|
// Copied instructions can expand.
|
|
//
|
|
// The scheme using MovImmediate can cause an instruction
|
|
// to grow as much as 6 times.
|
|
// That would be Bcc or Tbz with a large address space:
|
|
// 4 instructions to form immediate
|
|
// inverted tbz/bcc
|
|
// br
|
|
//
|
|
// An expansion of 4 is not uncommon -- bl/blr and small address space:
|
|
// 3 instructions to form immediate
|
|
// br or brl
|
|
//
|
|
// A theoretical maximum for rbCode is thefore 4*4*6 + 16 = 112 (another 16 for jmp to pbRemain).
|
|
//
|
|
// With literals, the maximum expansion is 5, including the literals: 4*4*5 + 16 = 96.
|
|
//
|
|
// The number is rounded up to 128. m_rbScratchDst should match this.
|
|
//
|
|
BYTE rbCode[128]; // target code + jmp to pbRemain
|
|
BYTE cbCode; // size of moved target code.
|
|
BYTE cbCodeBreak[3]; // padding to make debugging easier.
|
|
BYTE rbRestore[24]; // original target code.
|
|
BYTE cbRestore; // size of original target code.
|
|
BYTE cbRestoreBreak[3]; // padding to make debugging easier.
|
|
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
|
|
PBYTE pbRemain; // first instruction after moved code. [free list]
|
|
PBYTE pbDetour; // first instruction of detour function.
|
|
};
|
|
|
|
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 184);
|
|
|
|
enum {
|
|
SIZE_OF_JMP = 16
|
|
};
|
|
|
|
inline ULONG fetch_opcode(PBYTE pbCode)
|
|
{
|
|
return *(ULONG *)pbCode;
|
|
}
|
|
|
|
inline void write_opcode(PBYTE &pbCode, ULONG Opcode)
|
|
{
|
|
*(ULONG *)pbCode = Opcode;
|
|
pbCode += 4;
|
|
}
|
|
|
|
PBYTE detour_gen_jmp_immediate(PBYTE pbCode, PBYTE *ppPool, PBYTE pbJmpVal)
|
|
{
|
|
PBYTE pbLiteral;
|
|
if (ppPool != NULL) {
|
|
*ppPool = *ppPool - 8;
|
|
pbLiteral = *ppPool;
|
|
}
|
|
else {
|
|
pbLiteral = pbCode + 8;
|
|
}
|
|
|
|
*((PBYTE*&)pbLiteral) = pbJmpVal;
|
|
LONG delta = (LONG)(pbLiteral - pbCode);
|
|
|
|
write_opcode(pbCode, 0x58000011 | ((delta / 4) << 5)); // LDR X17,[PC+n]
|
|
write_opcode(pbCode, 0xd61f0000 | (17 << 5)); // BR X17
|
|
|
|
if (ppPool == NULL) {
|
|
pbCode += 8;
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline PBYTE detour_gen_brk(PBYTE pbCode, PBYTE pbLimit)
|
|
{
|
|
while (pbCode < pbLimit) {
|
|
write_opcode(pbCode, 0xd4100000 | (0xf000 << 5));
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline INT64 detour_sign_extend(UINT64 value, UINT bits)
|
|
{
|
|
const UINT left = 64 - bits;
|
|
const INT64 m1 = -1;
|
|
const INT64 wide = (INT64)(value << left);
|
|
const INT64 sign = (wide < 0) ? (m1 << left) : 0;
|
|
return value | sign;
|
|
}
|
|
|
|
inline PBYTE detour_skip_jmp(PBYTE pbCode, PVOID *ppGlobals)
|
|
{
|
|
if (pbCode == NULL) {
|
|
return NULL;
|
|
}
|
|
if (ppGlobals != NULL) {
|
|
*ppGlobals = NULL;
|
|
}
|
|
|
|
// Skip over the import jump if there is one.
|
|
pbCode = (PBYTE)pbCode;
|
|
ULONG Opcode = fetch_opcode(pbCode);
|
|
|
|
if ((Opcode & 0x9f00001f) == 0x90000010) { // adrp x16, IAT
|
|
ULONG Opcode2 = fetch_opcode(pbCode + 4);
|
|
|
|
if ((Opcode2 & 0xffe003ff) == 0xf9400210) { // ldr x16, [x16, IAT]
|
|
ULONG Opcode3 = fetch_opcode(pbCode + 8);
|
|
|
|
if (Opcode3 == 0xd61f0200) { // br x16
|
|
|
|
/* https://static.docs.arm.com/ddi0487/bb/DDI0487B_b_armv8_arm.pdf
|
|
The ADRP instruction shifts a signed, 21-bit immediate left by 12 bits, adds it to the value of the program counter with
|
|
the bottom 12 bits cleared to zero, and then writes the result to a general-purpose register. This permits the
|
|
calculation of the address at a 4KB aligned memory region. In conjunction with an ADD (immediate) instruction, or
|
|
a Load/Store instruction with a 12-bit immediate offset, this allows for the calculation of, or access to, any address
|
|
within �4GB of the current PC.
|
|
|
|
PC-rel. addressing
|
|
This section describes the encoding of the PC-rel. addressing instruction class. The encodings in this section are
|
|
decoded from Data Processing -- Immediate on page C4-226.
|
|
Add/subtract (immediate)
|
|
This section describes the encoding of the Add/subtract (immediate) instruction class. The encodings in this section
|
|
are decoded from Data Processing -- Immediate on page C4-226.
|
|
Decode fields
|
|
Instruction page
|
|
op
|
|
0 ADR
|
|
1 ADRP
|
|
|
|
C6.2.10 ADRP
|
|
Form PC-relative address to 4KB page adds an immediate value that is shifted left by 12 bits, to the PC value to
|
|
form a PC-relative address, with the bottom 12 bits masked out, and writes the result to the destination register.
|
|
ADRP <Xd>, <label>
|
|
imm = SignExtend(immhi:immlo:Zeros(12), 64);
|
|
|
|
31 30 29 28 27 26 25 24 23 5 4 0
|
|
1 immlo 1 0 0 0 0 immhi Rd
|
|
9 0
|
|
|
|
Rd is hardcoded as 0x10 above.
|
|
Immediate is 21 signed bits split into 2 bits and 19 bits, and is scaled by 4K.
|
|
*/
|
|
UINT64 const pageLow2 = (Opcode >> 29) & 3;
|
|
UINT64 const pageHigh19 = (Opcode >> 5) & ~(~0ui64 << 19);
|
|
INT64 const page = detour_sign_extend((pageHigh19 << 2) | pageLow2, 21) << 12;
|
|
|
|
/* https://static.docs.arm.com/ddi0487/bb/DDI0487B_b_armv8_arm.pdf
|
|
|
|
C6.2.101 LDR (immediate)
|
|
Load Register (immediate) loads a word or doubleword from memory and writes it to a register. The address that is
|
|
used for the load is calculated from a base register and an immediate offset.
|
|
The Unsigned offset variant scales the immediate offset value by the size of the value accessed before adding it
|
|
to the base register value.
|
|
|
|
Unsigned offset
|
|
64-bit variant Applies when size == 11.
|
|
31 30 29 28 27 26 25 24 23 22 21 10 9 5 4 0
|
|
1 x 1 1 1 0 0 1 0 1 imm12 Rn Rt
|
|
F 9 4 200 10
|
|
|
|
That is, two low 5 bit fields are registers, hardcoded as 0x10 and 0x10 << 5 above,
|
|
then unsigned size-unscaled (8) 12-bit offset, then opcode bits 0xF94.
|
|
*/
|
|
UINT64 const offset = ((Opcode2 >> 10) & ~(~0ui64 << 12)) << 3;
|
|
|
|
PBYTE const pbTarget = (PBYTE)((ULONG64)pbCode & 0xfffffffffffff000ULL) + page + offset;
|
|
|
|
if (detour_is_imported(pbCode, pbTarget)) {
|
|
PBYTE pbNew = *(PBYTE *)pbTarget;
|
|
DETOUR_TRACE(("%p->%p: skipped over import table.\n", pbCode, pbNew));
|
|
return pbNew;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return pbCode;
|
|
}
|
|
|
|
inline void detour_find_jmp_bounds(PBYTE pbCode,
|
|
PDETOUR_TRAMPOLINE *ppLower,
|
|
PDETOUR_TRAMPOLINE *ppUpper)
|
|
{
|
|
// We have to place trampolines within +/- 2GB of code.
|
|
ULONG_PTR lo = detour_2gb_below((ULONG_PTR)pbCode);
|
|
ULONG_PTR hi = detour_2gb_above((ULONG_PTR)pbCode);
|
|
DETOUR_TRACE(("[%p..%p..%p]\n", lo, pbCode, hi));
|
|
|
|
*ppLower = (PDETOUR_TRAMPOLINE)lo;
|
|
*ppUpper = (PDETOUR_TRAMPOLINE)hi;
|
|
}
|
|
|
|
inline BOOL detour_does_code_end_function(PBYTE pbCode)
|
|
{
|
|
ULONG Opcode = fetch_opcode(pbCode);
|
|
if ((Opcode & 0xfffffc1f) == 0xd65f0000 || // br <reg>
|
|
(Opcode & 0xfc000000) == 0x14000000) { // b <imm26>
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
inline ULONG detour_is_code_filler(PBYTE pbCode)
|
|
{
|
|
if (*(ULONG *)pbCode == 0xd503201f) { // nop.
|
|
return 4;
|
|
}
|
|
if (*(ULONG *)pbCode == 0x00000000) { // zero-filled padding.
|
|
return 4;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#endif // DETOURS_ARM64
|
|
|
|
//////////////////////////////////////////////// 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 DWORD detour_writable_trampoline_regions()
|
|
{
|
|
// Mark all of the regions as writable.
|
|
for (PDETOUR_REGION pRegion = s_pRegions; pRegion != NULL; pRegion = pRegion->pNext) {
|
|
DWORD dwOld;
|
|
if (!VirtualProtect(pRegion, DETOUR_REGION_SIZE, PAGE_EXECUTE_READWRITE, &dwOld)) {
|
|
return GetLastError();
|
|
}
|
|
}
|
|
return NO_ERROR;
|
|
}
|
|
|
|
static void detour_runnable_trampoline_regions()
|
|
{
|
|
HANDLE hProcess = GetCurrentProcess();
|
|
|
|
// 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);
|
|
FlushInstructionCache(hProcess, pRegion, DETOUR_REGION_SIZE);
|
|
}
|
|
}
|
|
|
|
static PBYTE detour_alloc_round_down_to_region(PBYTE pbTry)
|
|
{
|
|
// WinXP64 returns free areas that aren't REGION aligned to 32-bit applications.
|
|
ULONG_PTR extra = ((ULONG_PTR)pbTry) & (DETOUR_REGION_SIZE - 1);
|
|
if (extra != 0) {
|
|
pbTry -= extra;
|
|
}
|
|
return pbTry;
|
|
}
|
|
|
|
static PBYTE detour_alloc_round_up_to_region(PBYTE pbTry)
|
|
{
|
|
// WinXP64 returns free areas that aren't REGION aligned to 32-bit applications.
|
|
ULONG_PTR extra = ((ULONG_PTR)pbTry) & (DETOUR_REGION_SIZE - 1);
|
|
if (extra != 0) {
|
|
ULONG_PTR adjust = DETOUR_REGION_SIZE - extra;
|
|
pbTry += adjust;
|
|
}
|
|
return pbTry;
|
|
}
|
|
|
|
// Starting at pbLo, try to allocate a memory region, continue until pbHi.
|
|
|
|
static PVOID detour_alloc_region_from_lo(PBYTE pbLo, PBYTE pbHi)
|
|
{
|
|
PBYTE pbTry = detour_alloc_round_up_to_region(pbLo);
|
|
|
|
DETOUR_TRACE((" Looking for free region in %p..%p from %p:\n", pbLo, pbHi, pbTry));
|
|
|
|
for (; pbTry < pbHi;) {
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
|
|
if (pbTry >= s_pSystemRegionLowerBound && pbTry <= s_pSystemRegionUpperBound) {
|
|
// Skip region reserved for system DLLs, but preserve address space entropy.
|
|
pbTry += 0x08000000;
|
|
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_up_to_region((PBYTE)mbi.BaseAddress + mbi.RegionSize);
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
// Starting at pbHi, try to allocate a memory region, continue until pbLo.
|
|
|
|
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 >= s_pSystemRegionLowerBound && pbTry <= s_pSystemRegionUpperBound) {
|
|
// Skip region reserved for system DLLs, but preserve address space entropy.
|
|
pbTry -= 0x08000000;
|
|
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;
|
|
}
|
|
|
|
static PDETOUR_TRAMPOLINE detour_alloc_trampoline(PBYTE pbTarget)
|
|
{
|
|
// We have to place trampolines within +/- 2GB of target.
|
|
|
|
PDETOUR_TRAMPOLINE pLo;
|
|
PDETOUR_TRAMPOLINE pHi;
|
|
|
|
detour_find_jmp_bounds(pbTarget, &pLo, &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;
|
|
|
|
// NB: We must always also start the search at an offset from pbTarget
|
|
// in order to maintain ASLR entropy.
|
|
|
|
#if defined(DETOURS_64BIT)
|
|
// 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);
|
|
}
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
|
|
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 BOOL detour_is_region_empty(PDETOUR_REGION pRegion)
|
|
{
|
|
// Stop if the region isn't a region (this would be bad).
|
|
if (pRegion->dwSignature != DETOUR_REGION_SIGNATURE) {
|
|
return FALSE;
|
|
}
|
|
|
|
PBYTE pbRegionBeg = (PBYTE)pRegion;
|
|
PBYTE pbRegionLim = pbRegionBeg + DETOUR_REGION_SIZE;
|
|
|
|
// Stop if any of the trampolines aren't free.
|
|
PDETOUR_TRAMPOLINE pTrampoline = ((PDETOUR_TRAMPOLINE)pRegion) + 1;
|
|
for (int i = 0; i < DETOUR_TRAMPOLINES_PER_REGION; i++) {
|
|
if (pTrampoline[i].pbRemain != NULL &&
|
|
(pTrampoline[i].pbRemain < pbRegionBeg ||
|
|
pTrampoline[i].pbRemain >= pbRegionLim)) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
// OK, the region is empty.
|
|
return TRUE;
|
|
}
|
|
|
|
static void detour_free_unused_trampoline_regions()
|
|
{
|
|
PDETOUR_REGION *ppRegionBase = &s_pRegions;
|
|
PDETOUR_REGION pRegion = s_pRegions;
|
|
|
|
while (pRegion != NULL) {
|
|
if (detour_is_region_empty(pRegion)) {
|
|
*ppRegionBase = pRegion->pNext;
|
|
|
|
VirtualFree(pRegion, 0, MEM_RELEASE);
|
|
s_pRegion = NULL;
|
|
}
|
|
else {
|
|
ppRegionBase = &pRegion->pNext;
|
|
}
|
|
pRegion = *ppRegionBase;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////// 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 BOOL s_fRetainRegions = 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(_In_ PVOID pPointer,
|
|
_Out_opt_ PVOID *ppGlobals)
|
|
{
|
|
return detour_skip_jmp((PBYTE)pPointer, ppGlobals);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////// Transaction APIs.
|
|
//
|
|
BOOL WINAPI DetourSetIgnoreTooSmall(_In_ BOOL fIgnore)
|
|
{
|
|
BOOL fPrevious = s_fIgnoreTooSmall;
|
|
s_fIgnoreTooSmall = fIgnore;
|
|
return fPrevious;
|
|
}
|
|
|
|
BOOL WINAPI DetourSetRetainRegions(_In_ BOOL fRetain)
|
|
{
|
|
BOOL fPrevious = s_fRetainRegions;
|
|
s_fRetainRegions = fRetain;
|
|
return fPrevious;
|
|
}
|
|
|
|
PVOID WINAPI DetourSetSystemRegionLowerBound(_In_ PVOID pSystemRegionLowerBound)
|
|
{
|
|
PVOID pPrevious = s_pSystemRegionLowerBound;
|
|
s_pSystemRegionLowerBound = pSystemRegionLowerBound;
|
|
return pPrevious;
|
|
}
|
|
|
|
PVOID WINAPI DetourSetSystemRegionUpperBound(_In_ PVOID pSystemRegionUpperBound)
|
|
{
|
|
PVOID pPrevious = s_pSystemRegionUpperBound;
|
|
s_pSystemRegionUpperBound = pSystemRegionUpperBound;
|
|
return pPrevious;
|
|
}
|
|
|
|
LONG WINAPI DetourTransactionBegin()
|
|
{
|
|
// Only one transaction is allowed at a time.
|
|
_Benign_race_begin_
|
|
if (s_nPendingThreadId != 0) {
|
|
return ERROR_INVALID_OPERATION;
|
|
}
|
|
_Benign_race_end_
|
|
|
|
// Make sure only one thread can start a transaction.
|
|
if (InterlockedCompareExchange(&s_nPendingThreadId, (LONG)GetCurrentThreadId(), 0) != 0) {
|
|
return ERROR_INVALID_OPERATION;
|
|
}
|
|
|
|
s_pPendingOperations = NULL;
|
|
s_pPendingThreads = NULL;
|
|
s_ppPendingError = NULL;
|
|
|
|
// Make sure the trampoline pages are writable.
|
|
s_nPendingError = detour_writable_trampoline_regions();
|
|
|
|
return s_nPendingError;
|
|
}
|
|
|
|
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->cbRestore,
|
|
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);
|
|
}
|
|
|
|
static BYTE detour_align_from_trampoline(PDETOUR_TRAMPOLINE pTrampoline, BYTE obTrampoline)
|
|
{
|
|
for (LONG n = 0; n < ARRAYSIZE(pTrampoline->rAlign); n++) {
|
|
if (pTrampoline->rAlign[n].obTrampoline == obTrampoline) {
|
|
return pTrampoline->rAlign[n].obTarget;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static LONG detour_align_from_target(PDETOUR_TRAMPOLINE pTrampoline, LONG obTarget)
|
|
{
|
|
for (LONG n = 0; n < ARRAYSIZE(pTrampoline->rAlign); n++) {
|
|
if (pTrampoline->rAlign[n].obTarget == obTarget) {
|
|
return pTrampoline->rAlign[n].obTrampoline;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
LONG WINAPI DetourTransactionCommitEx(_Out_opt_ 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
|
|
*o->ppbPointer = (PBYTE)o->pTrampoline->ppldTarget;
|
|
#endif // DETOURS_IA64
|
|
|
|
#ifdef DETOURS_X86
|
|
*o->ppbPointer = o->pbTarget;
|
|
#endif // DETOURS_X86
|
|
|
|
#ifdef DETOURS_X64
|
|
*o->ppbPointer = o->pbTarget;
|
|
#endif // DETOURS_X64
|
|
|
|
#ifdef DETOURS_ARM
|
|
*o->ppbPointer = DETOURS_PBYTE_TO_PFUNC(o->pbTarget);
|
|
#endif // DETOURS_ARM
|
|
|
|
#ifdef DETOURS_ARM64
|
|
*o->ppbPointer = o->pbTarget;
|
|
#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
|
|
((DETOUR_IA64_BUNDLE*)o->pbTarget)
|
|
->SetBrl((UINT64)&o->pTrampoline->bAllocFrame);
|
|
*o->ppbPointer = (PBYTE)&o->pTrampoline->pldTrampoline;
|
|
#endif // DETOURS_IA64
|
|
|
|
#ifdef DETOURS_X64
|
|
detour_gen_jmp_indirect(o->pTrampoline->rbCodeIn, &o->pTrampoline->pbDetour);
|
|
PBYTE pbCode = detour_gen_jmp_immediate(o->pbTarget, o->pTrampoline->rbCodeIn);
|
|
pbCode = detour_gen_brk(pbCode, o->pTrampoline->pbRemain);
|
|
*o->ppbPointer = o->pTrampoline->rbCode;
|
|
UNREFERENCED_PARAMETER(pbCode);
|
|
#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;
|
|
UNREFERENCED_PARAMETER(pbCode);
|
|
#endif // DETOURS_X86
|
|
|
|
#ifdef DETOURS_ARM
|
|
PBYTE pbCode = detour_gen_jmp_immediate(o->pbTarget, NULL, o->pTrampoline->pbDetour);
|
|
pbCode = detour_gen_brk(pbCode, o->pTrampoline->pbRemain);
|
|
*o->ppbPointer = DETOURS_PBYTE_TO_PFUNC(o->pTrampoline->rbCode);
|
|
UNREFERENCED_PARAMETER(pbCode);
|
|
#endif // DETOURS_ARM
|
|
|
|
#ifdef DETOURS_ARM64
|
|
PBYTE pbCode = detour_gen_jmp_immediate(o->pbTarget, NULL, o->pTrampoline->pbDetour);
|
|
pbCode = detour_gen_brk(pbCode, o->pTrampoline->pbRemain);
|
|
*o->ppbPointer = o->pTrampoline->rbCode;
|
|
UNREFERENCED_PARAMETER(pbCode);
|
|
#endif // DETOURS_ARM64
|
|
|
|
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
|
|
DETOUR_TRACE(("\n"));
|
|
DETOUR_TRACE(("detours: &pldTrampoline =%p\n",
|
|
&o->pTrampoline->pldTrampoline));
|
|
DETOUR_TRACE(("detours: &bMovlTargetGp =%p [%p]\n",
|
|
&o->pTrampoline->bMovlTargetGp,
|
|
o->pTrampoline->bMovlTargetGp.GetMovlGp()));
|
|
DETOUR_TRACE(("detours: &rbCode =%p [%p]\n",
|
|
&o->pTrampoline->rbCode,
|
|
((DETOUR_IA64_BUNDLE&)o->pTrampoline->rbCode).GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: &bBrlRemainEip =%p [%p]\n",
|
|
&o->pTrampoline->bBrlRemainEip,
|
|
o->pTrampoline->bBrlRemainEip.GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: &bMovlDetourGp =%p [%p]\n",
|
|
&o->pTrampoline->bMovlDetourGp,
|
|
o->pTrampoline->bMovlDetourGp.GetMovlGp()));
|
|
DETOUR_TRACE(("detours: &bBrlDetourEip =%p [%p]\n",
|
|
&o->pTrampoline->bCallDetour,
|
|
o->pTrampoline->bCallDetour.GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: pldDetour =%p [%p]\n",
|
|
o->pTrampoline->ppldDetour->EntryPoint,
|
|
o->pTrampoline->ppldDetour->GlobalPointer));
|
|
DETOUR_TRACE(("detours: pldTarget =%p [%p]\n",
|
|
o->pTrampoline->ppldTarget->EntryPoint,
|
|
o->pTrampoline->ppldTarget->GlobalPointer));
|
|
DETOUR_TRACE(("detours: pbRemain =%p\n",
|
|
o->pTrampoline->pbRemain));
|
|
DETOUR_TRACE(("detours: pbDetour =%p\n",
|
|
o->pTrampoline->pbDetour));
|
|
DETOUR_TRACE(("\n"));
|
|
#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
|
|
|
|
#ifdef DETOURS_X86
|
|
#define DETOURS_EIP Eip
|
|
#endif // DETOURS_X86
|
|
|
|
#ifdef DETOURS_X64
|
|
#define DETOURS_EIP Rip
|
|
#endif // DETOURS_X64
|
|
|
|
#ifdef DETOURS_IA64
|
|
#define DETOURS_EIP StIIP
|
|
#endif // DETOURS_IA64
|
|
|
|
#ifdef DETOURS_ARM
|
|
#define DETOURS_EIP Pc
|
|
#endif // DETOURS_ARM
|
|
|
|
#ifdef DETOURS_ARM64
|
|
#define DETOURS_EIP Pc
|
|
#endif // DETOURS_ARM64
|
|
|
|
typedef ULONG_PTR DETOURS_EIP_TYPE;
|
|
|
|
if (GetThreadContext(t->hThread, &cxt)) {
|
|
for (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;
|
|
}
|
|
|
|
LONG WINAPI DetourUpdateThread(_In_ 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 NOTHROW 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(_Inout_ PVOID *ppPointer,
|
|
_In_ PVOID pDetour)
|
|
{
|
|
return DetourAttachEx(ppPointer, pDetour, NULL, NULL, NULL);
|
|
}
|
|
|
|
LONG WINAPI DetourAttachEx(_Inout_ PVOID *ppPointer,
|
|
_In_ PVOID pDetour,
|
|
_Out_opt_ PDETOUR_TRAMPOLINE *ppRealTrampoline,
|
|
_Out_opt_ PVOID *ppRealTarget,
|
|
_Out_opt_ PVOID *ppRealDetour)
|
|
{
|
|
LONG error = NO_ERROR;
|
|
|
|
if (ppRealTrampoline != NULL) {
|
|
*ppRealTrampoline = NULL;
|
|
}
|
|
if (ppRealTarget != NULL) {
|
|
*ppRealTarget = NULL;
|
|
}
|
|
if (ppRealDetour != NULL) {
|
|
*ppRealDetour = NULL;
|
|
}
|
|
if (pDetour == NULL) {
|
|
DETOUR_TRACE(("empty detour\n"));
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
|
|
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
|
|
PPLABEL_DESCRIPTOR ppldDetour = (PPLABEL_DESCRIPTOR)pDetour;
|
|
PPLABEL_DESCRIPTOR ppldTarget = (PPLABEL_DESCRIPTOR)pbTarget;
|
|
PVOID pDetourGlobals = NULL;
|
|
PVOID pTargetGlobals = NULL;
|
|
|
|
pDetour = (PBYTE)DetourCodeFromPointer(ppldDetour, &pDetourGlobals);
|
|
pbTarget = (PBYTE)DetourCodeFromPointer(ppldTarget, &pTargetGlobals);
|
|
DETOUR_TRACE((" ppldDetour=%p, code=%p [gp=%p]\n",
|
|
ppldDetour, pDetour, pDetourGlobals));
|
|
DETOUR_TRACE((" ppldTarget=%p, code=%p [gp=%p]\n",
|
|
ppldTarget, pbTarget, pTargetGlobals));
|
|
#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 NOTHROW 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;
|
|
#ifdef DETOURS_IA64
|
|
PBYTE pbPool = (PBYTE)(&pTrampoline->bBranchIslands + 1);
|
|
#else
|
|
PBYTE pbPool = pbTrampoline + sizeof(pTrampoline->rbCode);
|
|
#endif
|
|
ULONG cbTarget = 0;
|
|
ULONG cbJump = SIZE_OF_JMP;
|
|
ULONG nAlign = 0;
|
|
|
|
#ifdef DETOURS_ARM
|
|
// On ARM, we need an extra instruction when the function isn't 32-bit aligned.
|
|
// Check if the existing code is another detour (or at least a similar
|
|
// "ldr pc, [PC+0]" jump.
|
|
if ((ULONG)pbTarget & 2) {
|
|
cbJump += 2;
|
|
|
|
ULONG op = fetch_thumb_opcode(pbSrc);
|
|
if (op == 0xbf00) {
|
|
op = fetch_thumb_opcode(pbSrc + 2);
|
|
if (op == 0xf8dff000) { // LDR PC,[PC]
|
|
*((PUSHORT&)pbTrampoline)++ = *((PUSHORT&)pbSrc)++;
|
|
*((PULONG&)pbTrampoline)++ = *((PULONG&)pbSrc)++;
|
|
*((PULONG&)pbTrampoline)++ = *((PULONG&)pbSrc)++;
|
|
cbTarget = (LONG)(pbSrc - pbTarget);
|
|
// We will fall through the "while" because cbTarget is now >= cbJump.
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
ULONG op = fetch_thumb_opcode(pbSrc);
|
|
if (op == 0xf8dff000) { // LDR PC,[PC]
|
|
*((PULONG&)pbTrampoline)++ = *((PULONG&)pbSrc)++;
|
|
*((PULONG&)pbTrampoline)++ = *((PULONG&)pbSrc)++;
|
|
cbTarget = (LONG)(pbSrc - pbTarget);
|
|
// We will fall through the "while" because cbTarget is now >= cbJump.
|
|
}
|
|
}
|
|
#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;
|
|
nAlign++;
|
|
|
|
if (nAlign >= ARRAYSIZE(pTrampoline->rAlign)) {
|
|
break;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
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
|
|
pTrampoline->ppldDetour = ppldDetour;
|
|
pTrampoline->ppldTarget = ppldTarget;
|
|
pTrampoline->pldTrampoline.EntryPoint = (UINT64)&pTrampoline->bMovlTargetGp;
|
|
pTrampoline->pldTrampoline.GlobalPointer = (UINT64)pDetourGlobals;
|
|
|
|
((DETOUR_IA64_BUNDLE *)pTrampoline->rbCode)->SetStop();
|
|
|
|
pTrampoline->bMovlTargetGp.SetMovlGp((UINT64)pTargetGlobals);
|
|
pTrampoline->bBrlRemainEip.SetBrl((UINT64)pTrampoline->pbRemain);
|
|
|
|
// Alloc frame: alloc r41=ar.pfs,11,0,8,0; mov r40=rp
|
|
pTrampoline->bAllocFrame.wide[0] = 0x00000580164d480c;
|
|
pTrampoline->bAllocFrame.wide[1] = 0x00c4000500000200;
|
|
// save r36, r37, r38.
|
|
pTrampoline->bSave37to39.wide[0] = 0x031021004e019001;
|
|
pTrampoline->bSave37to39.wide[1] = 0x8401280600420098;
|
|
// save r34,r35,r36: adds r47=0,r36; adds r46=0,r35; adds r45=0,r34
|
|
pTrampoline->bSave34to36.wide[0] = 0x02e0210048017800;
|
|
pTrampoline->bSave34to36.wide[1] = 0x84011005a042008c;
|
|
// save gp,r32,r33" adds r44=0,r33; adds r43=0,r32; adds r42=0,gp ;;
|
|
pTrampoline->bSaveGPto33.wide[0] = 0x02b0210042016001;
|
|
pTrampoline->bSaveGPto33.wide[1] = 0x8400080540420080;
|
|
// set detour GP.
|
|
pTrampoline->bMovlDetourGp.SetMovlGp((UINT64)pDetourGlobals);
|
|
// call detour: brl.call.sptk.few rp=detour ;;
|
|
pTrampoline->bCallDetour.wide[0] = 0x0000000100000005;
|
|
pTrampoline->bCallDetour.wide[1] = 0xd000001000000000;
|
|
pTrampoline->bCallDetour.SetBrlTarget((UINT64)pDetour);
|
|
// pop frame & gp: adds gp=0,r42; mov rp=r40,+0;; mov.i ar.pfs=r41
|
|
pTrampoline->bPopFrameGp.wide[0] = 0x4000210054000802;
|
|
pTrampoline->bPopFrameGp.wide[1] = 0x00aa029000038005;
|
|
// return to caller: br.ret.sptk.many rp ;;
|
|
pTrampoline->bReturn.wide[0] = 0x0000000100000019;
|
|
pTrampoline->bReturn.wide[1] = 0x0084000880000200;
|
|
|
|
DETOUR_TRACE(("detours: &bMovlTargetGp=%p\n", &pTrampoline->bMovlTargetGp));
|
|
DETOUR_TRACE(("detours: &bMovlDetourGp=%p\n", &pTrampoline->bMovlDetourGp));
|
|
#endif // DETOURS_IA64
|
|
|
|
pbTrampoline = pTrampoline->rbCode + pTrampoline->cbCode;
|
|
#ifdef DETOURS_X64
|
|
pbTrampoline = detour_gen_jmp_indirect(pbTrampoline, &pTrampoline->pbRemain);
|
|
pbTrampoline = detour_gen_brk(pbTrampoline, pbPool);
|
|
#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
|
|
pbTrampoline = detour_gen_jmp_immediate(pbTrampoline, &pbPool, pTrampoline->pbRemain);
|
|
pbTrampoline = detour_gen_brk(pbTrampoline, pbPool);
|
|
#endif // DETOURS_ARM
|
|
|
|
#ifdef DETOURS_ARM64
|
|
pbTrampoline = detour_gen_jmp_immediate(pbTrampoline, &pbPool, pTrampoline->pbRemain);
|
|
pbTrampoline = detour_gen_brk(pbTrampoline, pbPool);
|
|
#endif // DETOURS_ARM64
|
|
|
|
(void)pbTrampoline;
|
|
|
|
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 DetourDetach(_Inout_ PVOID *ppPointer,
|
|
_In_ 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 (pDetour == NULL) {
|
|
return ERROR_INVALID_PARAMETER;
|
|
}
|
|
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 NOTHROW 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;
|
|
}
|
|
|
|
|
|
#ifdef DETOURS_IA64
|
|
PPLABEL_DESCRIPTOR ppldTrampo = (PPLABEL_DESCRIPTOR)*ppPointer;
|
|
PPLABEL_DESCRIPTOR ppldDetour = (PPLABEL_DESCRIPTOR)pDetour;
|
|
PVOID pDetourGlobals = NULL;
|
|
PVOID pTrampoGlobals = NULL;
|
|
|
|
pDetour = (PBYTE)DetourCodeFromPointer(ppldDetour, &pDetourGlobals);
|
|
PDETOUR_TRAMPOLINE pTrampoline = (PDETOUR_TRAMPOLINE)
|
|
DetourCodeFromPointer(ppldTrampo, &pTrampoGlobals);
|
|
DETOUR_TRACE((" ppldDetour=%p, code=%p [gp=%p]\n",
|
|
ppldDetour, pDetour, pDetourGlobals));
|
|
DETOUR_TRACE((" ppldTrampo=%p, code=%p [gp=%p]\n",
|
|
ppldTrampo, pTrampoline, pTrampoGlobals));
|
|
|
|
|
|
DETOUR_TRACE(("\n"));
|
|
DETOUR_TRACE(("detours: &pldTrampoline =%p\n",
|
|
&pTrampoline->pldTrampoline));
|
|
DETOUR_TRACE(("detours: &bMovlTargetGp =%p [%p]\n",
|
|
&pTrampoline->bMovlTargetGp,
|
|
pTrampoline->bMovlTargetGp.GetMovlGp()));
|
|
DETOUR_TRACE(("detours: &rbCode =%p [%p]\n",
|
|
&pTrampoline->rbCode,
|
|
((DETOUR_IA64_BUNDLE&)pTrampoline->rbCode).GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: &bBrlRemainEip =%p [%p]\n",
|
|
&pTrampoline->bBrlRemainEip,
|
|
pTrampoline->bBrlRemainEip.GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: &bMovlDetourGp =%p [%p]\n",
|
|
&pTrampoline->bMovlDetourGp,
|
|
pTrampoline->bMovlDetourGp.GetMovlGp()));
|
|
DETOUR_TRACE(("detours: &bBrlDetourEip =%p [%p]\n",
|
|
&pTrampoline->bCallDetour,
|
|
pTrampoline->bCallDetour.GetBrlTarget()));
|
|
DETOUR_TRACE(("detours: pldDetour =%p [%p]\n",
|
|
pTrampoline->ppldDetour->EntryPoint,
|
|
pTrampoline->ppldDetour->GlobalPointer));
|
|
DETOUR_TRACE(("detours: pldTarget =%p [%p]\n",
|
|
pTrampoline->ppldTarget->EntryPoint,
|
|
pTrampoline->ppldTarget->GlobalPointer));
|
|
DETOUR_TRACE(("detours: pbRemain =%p\n",
|
|
pTrampoline->pbRemain));
|
|
DETOUR_TRACE(("detours: pbDetour =%p\n",
|
|
pTrampoline->pbDetour));
|
|
DETOUR_TRACE(("\n"));
|
|
#else // !DETOURS_IA64
|
|
PDETOUR_TRAMPOLINE pTrampoline =
|
|
(PDETOUR_TRAMPOLINE)DetourCodeFromPointer(*ppPointer, NULL);
|
|
pDetour = DetourCodeFromPointer(pDetour, NULL);
|
|
#endif // !DETOURS_IA64
|
|
|
|
////////////////////////////////////// Verify that Trampoline is in place.
|
|
//
|
|
LONG cbTarget = pTrampoline->cbRestore;
|
|
PBYTE pbTarget = pTrampoline->pbRemain - 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;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Helpers for manipulating page protection.
|
|
//
|
|
|
|
// For reference:
|
|
// PAGE_NOACCESS 0x01
|
|
// PAGE_READONLY 0x02
|
|
// PAGE_READWRITE 0x04
|
|
// PAGE_WRITECOPY 0x08
|
|
// PAGE_EXECUTE 0x10
|
|
// PAGE_EXECUTE_READ 0x20
|
|
// PAGE_EXECUTE_READWRITE 0x40
|
|
// PAGE_EXECUTE_WRITECOPY 0x80
|
|
// PAGE_GUARD ...
|
|
// PAGE_NOCACHE ...
|
|
// PAGE_WRITECOMBINE ...
|
|
|
|
#define DETOUR_PAGE_EXECUTE_ALL (PAGE_EXECUTE | \
|
|
PAGE_EXECUTE_READ | \
|
|
PAGE_EXECUTE_READWRITE | \
|
|
PAGE_EXECUTE_WRITECOPY)
|
|
|
|
#define DETOUR_PAGE_NO_EXECUTE_ALL (PAGE_NOACCESS | \
|
|
PAGE_READONLY | \
|
|
PAGE_READWRITE | \
|
|
PAGE_WRITECOPY)
|
|
|
|
#define DETOUR_PAGE_ATTRIBUTES (~(DETOUR_PAGE_EXECUTE_ALL | DETOUR_PAGE_NO_EXECUTE_ALL))
|
|
|
|
C_ASSERT((DETOUR_PAGE_NO_EXECUTE_ALL << 4) == DETOUR_PAGE_EXECUTE_ALL);
|
|
|
|
static DWORD DetourPageProtectAdjustExecute(_In_ DWORD dwOldProtect,
|
|
_In_ DWORD dwNewProtect)
|
|
// Copy EXECUTE from dwOldProtect to dwNewProtect.
|
|
{
|
|
bool const fOldExecute = ((dwOldProtect & DETOUR_PAGE_EXECUTE_ALL) != 0);
|
|
bool const fNewExecute = ((dwNewProtect & DETOUR_PAGE_EXECUTE_ALL) != 0);
|
|
|
|
if (fOldExecute && !fNewExecute) {
|
|
dwNewProtect = ((dwNewProtect & DETOUR_PAGE_NO_EXECUTE_ALL) << 4)
|
|
| (dwNewProtect & DETOUR_PAGE_ATTRIBUTES);
|
|
}
|
|
else if (!fOldExecute && fNewExecute) {
|
|
dwNewProtect = ((dwNewProtect & DETOUR_PAGE_EXECUTE_ALL) >> 4)
|
|
| (dwNewProtect & DETOUR_PAGE_ATTRIBUTES);
|
|
}
|
|
return dwNewProtect;
|
|
}
|
|
|
|
_Success_(return != FALSE)
|
|
BOOL WINAPI DetourVirtualProtectSameExecuteEx(_In_ HANDLE hProcess,
|
|
_In_ PVOID pAddress,
|
|
_In_ SIZE_T nSize,
|
|
_In_ DWORD dwNewProtect,
|
|
_Out_ PDWORD pdwOldProtect)
|
|
// Some systems do not allow executability of a page to change. This function applies
|
|
// dwNewProtect to [pAddress, nSize), but preserving the previous executability.
|
|
// This function is meant to be a drop-in replacement for some uses of VirtualProtectEx.
|
|
// When "restoring" page protection, there is no need to use this function.
|
|
{
|
|
MEMORY_BASIC_INFORMATION mbi;
|
|
|
|
// Query to get existing execute access.
|
|
|
|
ZeroMemory(&mbi, sizeof(mbi));
|
|
|
|
if (VirtualQueryEx(hProcess, pAddress, &mbi, sizeof(mbi)) == 0) {
|
|
return FALSE;
|
|
}
|
|
return VirtualProtectEx(hProcess, pAddress, nSize,
|
|
DetourPageProtectAdjustExecute(mbi.Protect, dwNewProtect),
|
|
pdwOldProtect);
|
|
}
|
|
|
|
_Success_(return != FALSE)
|
|
BOOL WINAPI DetourVirtualProtectSameExecute(_In_ PVOID pAddress,
|
|
_In_ SIZE_T nSize,
|
|
_In_ DWORD dwNewProtect,
|
|
_Out_ PDWORD pdwOldProtect)
|
|
{
|
|
return DetourVirtualProtectSameExecuteEx(GetCurrentProcess(),
|
|
pAddress, nSize, dwNewProtect, pdwOldProtect);
|
|
}
|
|
|
|
// End of File
|