Files
BeneficialCode-WinArk/KernelLibrary/detours.cpp
T
2023-02-06 10:18:27 +08:00

1133 lines
30 KiB
C++

#include "pch.h"
#include <ntimage.h>
#include "detours.h"
#include "Logging.h"
#include "Memory.h"
#include "disasm.h"
extern "C" {
NTSYSAPI NTSTATUS NTAPI ZwFlushInstructionCache(_In_ HANDLE ProcessHandle,
_In_ PVOID BaseAddress,
_In_ ULONG NumberOfBytesToFlush
);
}
static bool s_fIgnoreTooSmall = false;
static bool s_fRetainRegions = false;
static DetourOperation* s_pPendingOperations = nullptr;
bool DetourIsImported(PUCHAR pCode, PUCHAR pAddress) {
MEMORY_BASIC_INFORMATION mbi;
SIZE_T len;
NTSTATUS status = ZwQueryVirtualMemory(ZwCurrentProcess(),
pCode,
MemoryBasicInformation,
&mbi,
sizeof(mbi),
&len);
if (!NT_SUCCESS(status)) {
return false;
}
__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)((PUCHAR)pDosHeader +
pDosHeader->e_lfanew);
if (pNtHeader->Signature != IMAGE_NT_SIGNATURE) {
return false;
}
if (pAddress >= ((PUCHAR)pDosHeader +
pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress)
&& pAddress < ((PUCHAR)pDosHeader
+ pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].VirtualAddress
+ pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IAT].Size)) {
return true;
}
}
__except (GetExceptionCode() == STATUS_ACCESS_VIOLATION
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
return false;
}
return false;
}
ULONG_PTR Detour2gbBelow(ULONG_PTR address) {
return (address > (ULONG_PTR)0x7ff80000) ? address - 0x7ff80000 : 0x80000;
}
ULONG_PTR Detour2gbAbove(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
}
void MapLockedCopyMemory(PUCHAR pDst, PVOID pSrc, ULONG length) {
UCHAR code[4] = { 0 };
PHYSICAL_ADDRESS physicalAddress = { 0 };
PVOID virtualAddress = nullptr;
if (length > sizeof(ULONG)) {
RtlCopyMemory(code, pSrc, sizeof(ULONG));
}
else {
RtlCopyMemory(code, pSrc, length);
RtlCopyMemory(code + length, pDst + length, sizeof(ULONG) - length);
}
physicalAddress = MmGetPhysicalAddress(pDst);
virtualAddress = MmMapIoSpace(physicalAddress, length, MmNonCached);
if (virtualAddress != nullptr) {
if (length > sizeof(ULONG)) {
InterlockedExchange(reinterpret_cast<LONG*>(virtualAddress), 0xfeebUI32);
RtlCopyMemory(reinterpret_cast<PCHAR>(virtualAddress) + sizeof(ULONG),
reinterpret_cast<PCHAR>(pSrc) + sizeof(ULONG),
length - sizeof(ULONG));
}
InterlockedExchange(reinterpret_cast<LONG*>(virtualAddress), *reinterpret_cast<PLONG>(code));
MmUnmapIoSpace(virtualAddress, length);
}
}
#pragma pack(push,1)
struct JmpImmediate {
unsigned char jmp = 0xe9;
INT32 imm32 = 0;
};
struct JmpIndirect {
unsigned short int jmp = 0x25FF;
INT32 imm32 = 0;
};
struct Brk {
unsigned char int3[1];
};
struct JmpCode {
unsigned char push = 0x68;
ULONG lowAddr; // push xxxxxxxx
unsigned char op1 = 0xc7;
unsigned char op2 = 0x44;
unsigned char op3 = 0x24;
unsigned char op4 = 0x04;
ULONG highAddr;
unsigned char ret = 0xc3;
};
#pragma pack(pop)
///////////////////////////////////////////////////////////////////////// X86.
//
#ifdef DETOURS_X86
struct _DETOUR_TRAMPOLINE
{
UCHAR rbCode[30]; // target code + jmp to pbRemain
UCHAR cbCode; // size of moved target code.
UCHAR cbCodeBreak; // padding to make debugging easier.
UCHAR rbRestore[22]; // original target code.
UCHAR cbRestore; // size of original target code.
UCHAR cbRestoreBreak;// padding to make debugging easier.
_DETOUR_ALIGN rAlign[8]; // instruction alignment array.
PUCHAR pRemain; // first instruction after moved code. [free list]
PUCHAR pDetour; // first instruction of detour function.
};
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 72);
enum {
SIZE_OF_JUMP_CODE = 5
};
PUCHAR DetourGenJmpImmediate(PUCHAR pCode, PUCHAR pJmpVal) {
PUCHAR pJmpSrc = pCode + 5;
JmpImmediate code;
code.imm32 = (INT32)(pJmpVal - pJmpSrc);
MapLockedCopyMemory(pCode, &code, sizeof(code));
pCode++; // jmp +imm32
((INT32*&)pCode)++;
return pCode;
}
PUCHAR DetourGenJmpIndirect(PUCHAR pCode, PUCHAR* ppJmpVal) {
PUCHAR pJmpSrc = pCode + 6;
JmpIndirect code;
code.imm32 = (INT32)((PUCHAR)ppJmpVal - pJmpSrc);
MapLockedCopyMemory(pCode, &code, sizeof(code));
pCode++; // jmp [+imm32]
pCode++;
((INT32*&)pCode)++;
return pCode;
}
PUCHAR DetourGenBrk(PUCHAR pCode, PUCHAR pLimit) {
ULONG count = pLimit - pCode;
Brk* pBrk = (Brk*)ExAllocatePoolWithTag(NonPagedPool, sizeof(Brk) + count, 'oted');
if (pBrk != nullptr) {
MapLockedCopyMemory(pCode, pBrk->int3, count);
ExFreePool(pBrk);
while (pCode < pLimit) {
pCode++;
}
}
return pCode;
}
PUCHAR DetourSkipJmp(PUCHAR pCode, PVOID* ppGlobals) {
if (pCode == nullptr) {
return nullptr;
}
if (ppGlobals != nullptr) {
*ppGlobals = nullptr;
}
// First, skip over the import vector if there is one
if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
// Looks like an import alias jump, then get the code it points to.
PUCHAR pTarget = *(UNALIGNED PUCHAR*) & pCode[2];
if (DetourIsImported(pCode, pTarget)) {
PUCHAR pNew = *(UNALIGNED PUCHAR*)pTarget;
LogDebug("%p->%p: skipped over import table.", pCode, pNew);
pCode = pNew;
}
}
// Then, skip over a patch jump
if (pCode[0] == 0xeb) { // jmp +imm8
PUCHAR pNew = pCode + 2 + *(CHAR*)&pCode[1];
LogDebug("%p->%p: skipped over short jump.\n", pCode, pNew);
pCode = pNew;
// First, skip over the import vector if there is one.
if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
// Looks like an import alias jump, then get the code it points to.
PUCHAR pTarget = *(UNALIGNED PUCHAR*) & pCode[2];
if (DetourIsImported(pCode, pTarget)) {
pNew = *(UNALIGNED PUCHAR*)pTarget;
LogDebug("%p->%p: skipped over import table.\n", pCode, pNew);
pCode = pNew;
}
}
// Finally, skip over a long jump if it is the target of the patch jump.
else if (pCode[0] == 0xe9) {
pNew = pCode + 5 + *(UNALIGNED INT32*) & pCode[1];
LogDebug("%p->%p: skipped over long jump.\n", pCode, pNew);
pCode = pNew;
}
}
return pCode;
}
void DetourFindJmpBounds(PUCHAR pCode, PDETOUR_TRAMPOLINE* ppLower,
PDETOUR_TRAMPOLINE* ppUpper) {
// We have to place trampolines within +/- 2GB of code
ULONG_PTR lo = Detour2gbBelow((ULONG_PTR)pCode);
ULONG_PTR hi = Detour2gbAbove((ULONG_PTR)pCode);
LogDebug("[%p..%p..%p]\n", (PVOID)lo, pCode, (PVOID)hi);
// And, within +/- 2GB of relative jmp vectors.
if (pCode[0] == 0xe9) { // jmp +imm32
PUCHAR pNew = pCode + 5 + *(UNALIGNED INT32*) & pCode[1];
if (pNew < pCode) {
hi = Detour2gbAbove((ULONG_PTR)pNew);
}
else {
lo = Detour2gbBelow((ULONG_PTR)pNew);
}
LogDebug("[%p..%p..%p] +imm32\n", (PVOID)lo, pCode, (PVOID)hi);
}
*ppLower = (PDETOUR_TRAMPOLINE)lo;
*ppUpper = (PDETOUR_TRAMPOLINE)hi;
}
bool DetourDoesCodeEndFunction(PUCHAR pCode) {
if (pCode[0] == 0xeb || // jmp +imm8
pCode[0] == 0xe9 || // jmp +imm32
pCode[0] == 0xe0 || // jmp eax
pCode[0] == 0xc2 || // ret +imm8
pCode[0] == 0xc3 || // ret
pCode[0] == 0xcc) { // brk
return TRUE;
}
else if (pCode[0] == 0xf3 && pCode[1] == 0xc3) { // rep ret
return TRUE;
}
else if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
return TRUE;
}
else if ((pCode[0] == 0x26 || // jmp es:
pCode[0] == 0x2e || // jmp cs:
pCode[0] == 0x36 || // jmp ss:
pCode[0] == 0x3e || // jmp ds:
pCode[0] == 0x64 || // jmp fs:
pCode[0] == 0x65) && // jmp gs:
pCode[1] == 0xff && // jmp [+imm32]
pCode[2] == 0x25) {
return TRUE;
}
return FALSE;
}
ULONG DetourIsCodeFiller(PUCHAR pCode) {
// 1-byte through 11-byte NOPs.
if (pCode[0] == 0x90) {
return 1;
}
if (pCode[0] == 0x66 && pCode[1] == 0x90) {
return 2;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x00) {
return 3;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x40 &&
pCode[3] == 0x00) {
return 4;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x44 &&
pCode[3] == 0x00 && pCode[4] == 0x00) {
return 5;
}
if (pCode[0] == 0x66 && pCode[1] == 0x0F && pCode[2] == 0x1F &&
pCode[3] == 0x44 && pCode[4] == 0x00 && pCode[5] == 0x00) {
return 6;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x80 &&
pCode[3] == 0x00 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00) {
return 7;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x84 &&
pCode[3] == 0x00 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00) {
return 8;
}
if (pCode[0] == 0x66 && pCode[1] == 0x0F && pCode[2] == 0x1F &&
pCode[3] == 0x84 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00) {
return 9;
}
if (pCode[0] == 0x66 && pCode[1] == 0x66 && pCode[2] == 0x0F &&
pCode[3] == 0x1F && pCode[4] == 0x84 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00 &&
pCode[9] == 0x00) {
return 10;
}
if (pCode[0] == 0x66 && pCode[1] == 0x66 && pCode[2] == 0x66 &&
pCode[3] == 0x0F && pCode[4] == 0x1F && pCode[5] == 0x84 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00 &&
pCode[9] == 0x00 && pCode[10] == 0x00) {
return 11;
}
// int 3.
if (pCode[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.
UCHAR rbCode[0x30]; // target code + jmp to pRemain
UCHAR cbCode; // size of moved target code
UCHAR cbCodeBreak; // padding to make debugging easier
UCHAR rbRestore[0x30]; // original target code.
UCHAR cbRestore; // size of original code.
UCHAR cbRestoreBreak; // padding to make debugging easier
_DETOUR_ALIGN rAlign[8];// instruction alignment array.
PUCHAR pRemain; // first instruction after moved code. [free list]
PUCHAR pDetour; // first instruction of detour function.
UCHAR rbCodeIn[8]; // jmp [pDetour]
};
C_ASSERT(sizeof(_DETOUR_TRAMPOLINE) == 136);
enum {
SIZE_OF_JUMP_CODE = 14,
};
PUCHAR DetourGenJmpAddress(PUCHAR pCode, PUCHAR pJmpVal) {
JmpCode code;
ULONG_PTR addr = (ULONG_PTR)pJmpVal;
code.lowAddr = addr & 0xFFFFFFFF;
code.highAddr = (addr >> 32) & 0xFFFFFFFF;
MapLockedCopyMemory(pCode, &code, sizeof(code));
pCode += sizeof(code);
return pCode;
}
PUCHAR DetourGenJmpIndirect(PUCHAR pCode, PUCHAR* ppJmpVal) {
PUCHAR pJmpSrc = pCode + 6;
JmpIndirect code;
code.imm32 = (INT32)((PUCHAR)ppJmpVal - pJmpSrc);
MapLockedCopyMemory(pCode, &code, sizeof(code));
pCode++;
pCode++;
((INT32*&)pCode)++;
return pCode;
}
PUCHAR DetourGenBrk(PUCHAR pCode, PUCHAR pLimit) {
ULONG count = pLimit - pCode;
Brk* pBrk = (Brk*)ExAllocatePoolWithTag(NonPagedPool, sizeof(Brk) + count,'oted');
if (pBrk != nullptr) {
MapLockedCopyMemory(pCode, pBrk->int3, count);
ExFreePool(pBrk);
while (pCode < pLimit) {
pCode++;
}
}
return pCode;
}
PUCHAR DetourSkipJmp(PUCHAR pCode, PVOID* ppGlobals) {
if (pCode == nullptr) {
return nullptr;
}
if (ppGlobals != nullptr) {
*ppGlobals = nullptr;
}
// First, skip over the import vector if there is one
if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
// Looks like an import alias jump, then get the code it points to.
PUCHAR pTarget = pCode + 6 + *(UNALIGNED INT32*) & pCode[2];
if (DetourIsImported(pCode, pTarget)) {
PUCHAR pNew = *(UNALIGNED PUCHAR*)pTarget;
LogDebug("%p->%p: skipped over import table.", pCode, pNew);
pCode = pNew;
}
}
// Then, skip over a patch jump
if (pCode[0] == 0xeb) { // jmp +imm8
PUCHAR pNew = pCode + 2 + *(CHAR*)&pCode[1];
LogDebug("%p->%p: skipped over short jump.\n", pCode, pNew);
pCode = pNew;
// First, skip over the import vector if there is one.
if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
// Looks like an import alias jump, then get the code it points to.
PUCHAR pTarget = pCode + 6 + *(UNALIGNED INT32*) & pCode[2];
if (DetourIsImported(pCode, pTarget)) {
pNew = *(UNALIGNED PUCHAR*)pTarget;
LogDebug("%p->%p: skipped over import table.\n", pCode, pNew);
pCode = pNew;
}
}
// Finally, skip over a long jump if it is the target of the patch jump.
else if (pCode[0] == 0xe9) {
pNew = pCode + 5 + *(UNALIGNED INT32*) & pCode[1];
LogDebug("%p->%p: skipped over long jump.\n", pCode, pNew);
pCode = pNew;
}
}
return pCode;
}
bool DetourDoesCodeEndFunction(PUCHAR pCode) {
if (pCode[0] == 0xeb || // jmp +imm8
pCode[0] == 0xe9 || // jmp +imm32
pCode[0] == 0xe0 || // jmp eax
pCode[0] == 0xc2 || // ret +imm8
pCode[0] == 0xc3 || // ret
pCode[0] == 0xcc) { // brk
return TRUE;
}
else if (pCode[0] == 0xf3 && pCode[1] == 0xc3) { // rep ret
return TRUE;
}
else if (pCode[0] == 0xff && pCode[1] == 0x25) { // jmp [+imm32]
return TRUE;
}
else if ((pCode[0] == 0x26 || // jmp es:
pCode[0] == 0x2e || // jmp cs:
pCode[0] == 0x36 || // jmp ss:
pCode[0] == 0x3e || // jmp ds:
pCode[0] == 0x64 || // jmp fs:
pCode[0] == 0x65) && // jmp gs:
pCode[1] == 0xff && // jmp [+imm32]
pCode[2] == 0x25) {
return TRUE;
}
return FALSE;
}
ULONG DetourIsCodeFiller(PUCHAR pCode) {
// 1-byte through 11-byte NOPs.
if (pCode[0] == 0x90) {
return 1;
}
if (pCode[0] == 0x66 && pCode[1] == 0x90) {
return 2;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x00) {
return 3;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x40 &&
pCode[3] == 0x00) {
return 4;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x44 &&
pCode[3] == 0x00 && pCode[4] == 0x00) {
return 5;
}
if (pCode[0] == 0x66 && pCode[1] == 0x0F && pCode[2] == 0x1F &&
pCode[3] == 0x44 && pCode[4] == 0x00 && pCode[5] == 0x00) {
return 6;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x80 &&
pCode[3] == 0x00 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00) {
return 7;
}
if (pCode[0] == 0x0F && pCode[1] == 0x1F && pCode[2] == 0x84 &&
pCode[3] == 0x00 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00) {
return 8;
}
if (pCode[0] == 0x66 && pCode[1] == 0x0F && pCode[2] == 0x1F &&
pCode[3] == 0x84 && pCode[4] == 0x00 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00) {
return 9;
}
if (pCode[0] == 0x66 && pCode[1] == 0x66 && pCode[2] == 0x0F &&
pCode[3] == 0x1F && pCode[4] == 0x84 && pCode[5] == 0x00 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00 &&
pCode[9] == 0x00) {
return 10;
}
if (pCode[0] == 0x66 && pCode[1] == 0x66 && pCode[2] == 0x66 &&
pCode[3] == 0x0F && pCode[4] == 0x1F && pCode[5] == 0x84 &&
pCode[6] == 0x00 && pCode[7] == 0x00 && pCode[8] == 0x00 &&
pCode[9] == 0x00 && pCode[10] == 0x00) {
return 11;
}
// int 3.
if (pCode[0] == 0xcc) {
return 1;
}
return 0;
}
#endif // DETOURS_X64
//////////////////////////////////////////////// Trampoline Memory Management.
//
struct DETOUR_REGION {
ULONG Signature;
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 = nullptr; // List of all regions.
static PDETOUR_REGION s_pRegion = nullptr; // Default region
PZwProtectVirtualMemory g_pZwProtectVirtualMemory = nullptr;
static PVOID DetourAllocTrampolineAllocateNew() {
PVOID pTry = nullptr;
pTry = ExAllocatePoolWithTag(NonPagedPool, DETOUR_REGION_SIZE, 'oted');
return pTry;
}
static PDETOUR_TRAMPOLINE DetourAllocTrampoline(PUCHAR pTarget) {
PDETOUR_TRAMPOLINE pTrampoline = nullptr;
// Insure that there is a default region.
if (s_pRegion == nullptr && s_pRegions != nullptr) {
s_pRegion = s_pRegions;
}
// First check the default region for an valid free block.
if (s_pRegion != nullptr && s_pRegion->pFree != nullptr) {
found_region:
pTrampoline = s_pRegion->pFree;
s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pTrampoline->pRemain;
memset(pTrampoline, 0xcc, sizeof(*pTrampoline));
return pTrampoline;
}
// Then check the existing regions for a valid free block.
for (s_pRegion = s_pRegions; s_pRegion != nullptr; s_pRegion = s_pRegion->pNext) {
if (s_pRegion != nullptr && s_pRegion->pFree != nullptr) {
goto found_region;
}
}
// We need to allocate a new region.
// Round pTarget down to 64KB block.
// /RTCc RuntimeChecks breaks PtrToUlong.
pTarget = pTarget - (ULONG)((ULONG_PTR)pTarget & 0xffff);
PVOID pNewlyAllocated = DetourAllocTrampolineAllocateNew();
if (pNewlyAllocated != nullptr) {
s_pRegion = (DETOUR_REGION*)pNewlyAllocated;
s_pRegion->Signature = DETOUR_REGION_SIGNATURE;
s_pRegion->pFree = NULL;
s_pRegion->pNext = s_pRegions;
s_pRegions = s_pRegion;
LogDebug(" Allocated region %p..%p\n\n",
s_pRegion, ((PUCHAR)s_pRegion) + DETOUR_REGION_SIZE - 1);
// Put everything but the first trampoline on the free list.
PUCHAR pFree = NULL;
pTrampoline = ((PDETOUR_TRAMPOLINE)s_pRegion) + 1;
for (int i = DETOUR_TRAMPOLINES_PER_REGION - 1; i > 1; i--) {
pTrampoline[i].pRemain = pFree;
pFree = (PUCHAR)&pTrampoline[i];
}
s_pRegion->pFree = (PDETOUR_TRAMPOLINE)pFree;
goto found_region;
}
LogError("Couldn't find available memory region!\n");
return nullptr;
}
static bool DetourIsRegionEmpty(PDETOUR_REGION pRegion) {
// Stop if the region isn't a region (this would be bad).
if (pRegion->Signature != DETOUR_REGION_SIGNATURE) {
return FALSE;
}
PUCHAR pRegionBeg = (PUCHAR)pRegion;
PUCHAR pRegionLim = pRegionBeg + 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].pRemain != NULL &&
(pTrampoline[i].pRemain < pRegionBeg ||
pTrampoline[i].pRemain >= pRegionLim)) {
return FALSE;
}
}
// OK, the region is empty.
return TRUE;
}
static void DetourFreeUnusedTrampolineRegions() {
PDETOUR_REGION* ppRegionBase = &s_pRegions;
PDETOUR_REGION pRegion = s_pRegions;
while (pRegion != NULL) {
if (DetourIsRegionEmpty(pRegion)) {
*ppRegionBase = pRegion->pNext;
ExFreePoolWithTag(pRegion, 'oted');
s_pRegion = NULL;
}
else {
ppRegionBase = &pRegion->pNext;
}
pRegion = *ppRegionBase;
}
}
PVOID NTAPI DetourCodeFromPointer(_In_ PVOID pPointer,
_Out_opt_ PVOID* ppGlobals) {
return DetourSkipJmp((PUCHAR)pPointer, ppGlobals);
}
NTSTATUS NTAPI DetourTransactionCommit() {
return DetourTransactionCommitEx();
}
NTSTATUS NTAPI DetourTransactionCommitEx() {
// Common variables
DetourOperation* o = nullptr;
bool freed = false;
// Insert or remove each of the detours
for (o = s_pPendingOperations; o != nullptr; o = o->pNext) {
if (o->fIsRemove) {
MapLockedCopyMemory(o->pTarget, o->pTrampoline->rbRestore,
o->pTrampoline->cbRestore);
#ifdef DETOURS_IA64
* o->ppbPointer = (PBYTE)o->pTrampoline->ppldTarget;
#endif // DETOURS_IA64
#ifdef DETOURS_X86
* o->ppPointer = o->pTarget;
#endif // DETOURS_X86
#ifdef DETOURS_X64
* o->ppPointer = o->pTarget;
#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 {
LogDebug("detours: pbTramp =%p, pbRemain=%p, pbDetour=%p, cbRestore=%u\n",
o->pTrampoline,
o->pTrampoline->pRemain,
o->pTrampoline->pDetour,
o->pTrampoline->cbRestore);
LogDebug("detours: pbTarget=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [before]\n",
o->pTarget,
o->pTarget[0], o->pTarget[1], o->pTarget[2], o->pTarget[3],
o->pTarget[4], o->pTarget[5], o->pTarget[6], o->pTarget[7],
o->pTarget[8], o->pTarget[9], o->pTarget[10], o->pTarget[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
DetourGenJmpIndirect(o->pTrampoline->rbCodeIn, &o->pTrampoline->pDetour);
PUCHAR pCode = DetourGenJmpAddress(o->pTarget, o->pTrampoline->rbCodeIn);
pCode = DetourGenBrk(pCode, o->pTrampoline->pRemain);
*o->ppPointer = o->pTrampoline->rbCode;
UNREFERENCED_PARAMETER(pCode);
#endif // DETOURS_X64
#ifdef DETOURS_X86
PUCHAR pCode = DetourGenJmpImmediate(o->pTarget, o->pTrampoline->pDetour);
pCode = DetourGenBrk(pCode, o->pTrampoline->pRemain);
*o->ppPointer = o->pTrampoline->rbCode;
UNREFERENCED_PARAMETER(pCode);
#endif // DETOURS_X86
#ifdef DETOURS_ARM
#endif // DETOURS_ARM
#ifdef DETOURS_ARM64
#endif // DETOURS_ARM64
LogDebug("detours: pbTarget=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x [after]\n",
o->pTarget,
o->pTarget[0], o->pTarget[1], o->pTarget[2], o->pTarget[3],
o->pTarget[4], o->pTarget[5], o->pTarget[6], o->pTarget[7],
o->pTarget[8], o->pTarget[9], o->pTarget[10], o->pTarget[11]);
LogDebug("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
#endif // DETOURS_IA64
}
}
// Restore all of the page permissions and flush the icache.
for (DetourOperation* pOper = s_pPendingOperations; pOper != nullptr;) {
// We don't care if this fails, because the code is still accessible
if (pOper->fIsRemove && pOper->pTrampoline) {
pOper->pTrampoline = nullptr;
freed = true;
}
DetourOperation* n = pOper->pNext;
delete pOper;
pOper = n;
}
s_pPendingOperations = nullptr;
// Free any trampoline regions that are now unused.
if (freed && !s_fRetainRegions) {
DetourFreeUnusedTrampolineRegions();
}
return STATUS_SUCCESS;
}
NTSTATUS NTAPI DetourAttach(_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour) {
return DetourAttachEx(ppPointer, pDetour, nullptr, nullptr, nullptr);
}
NTSTATUS NTAPI DetourAttachEx(_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour,
_Out_opt_ PDETOUR_TRAMPOLINE* ppRealTrampoline,
_Out_opt_ PVOID* ppRealTarget,
_Out_opt_ PVOID* ppRealDetour) {
NTSTATUS status = STATUS_SUCCESS;
if (ppRealTrampoline != nullptr) {
*ppRealTrampoline = nullptr;
}
if (ppRealTarget != nullptr) {
*ppRealTarget = nullptr;
}
if (ppRealDetour != nullptr) {
*ppRealDetour = nullptr;
}
if (pDetour == nullptr) {
LogDebug("empty detour\n");
return STATUS_INVALID_PARAMETER;
}
if (ppPointer == nullptr) {
LogDebug("ppPointer is null\n");
return STATUS_INVALID_HANDLE;
}
if (*ppPointer == nullptr) {
status = STATUS_INVALID_HANDLE;
LogDebug("*ppPointer is null (ppPointer=%p)\n", ppPointer);
DbgBreakPoint();
return status;
}
PUCHAR pTarget = (PUCHAR)*ppPointer;
PDETOUR_TRAMPOLINE pTrampoline = nullptr;
DetourOperation* o = nullptr;
#ifdef DETOURS_IA64
#else // DETOURS_IA64
pTarget = (PUCHAR)DetourCodeFromPointer(pTarget, 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)pTarget) {
if (s_fIgnoreTooSmall) {
goto stop;
}
else {
DbgBreakPoint();
goto fail;
}
}
if (ppRealTarget != nullptr) {
*ppRealTarget = pTarget;
}
if (ppRealDetour != nullptr) {
*ppRealDetour = pDetour;
}
o = new (NonPagedPool) DetourOperation;
if (o == nullptr) {
status = STATUS_NO_MEMORY;
fail:
DbgBreakPoint();
stop:
if (pTrampoline != nullptr) {
pTrampoline = nullptr;
if (ppRealTrampoline != nullptr) {
*ppRealTrampoline = nullptr;
}
}
if (o != nullptr) {
delete o;
o = nullptr;
}
if (ppRealDetour != nullptr) {
*ppRealDetour = nullptr;
}
if (ppRealTarget != nullptr) {
*ppRealTarget = nullptr;
}
return status;
}
pTrampoline = DetourAllocTrampoline(pTarget);
if (pTrampoline == nullptr) {
status = STATUS_NO_MEMORY;
DbgBreakPoint();
goto fail;
}
if (ppRealTrampoline != nullptr) {
*ppRealTrampoline = pTrampoline;
}
LogDebug("detours: pTramp=%p, pDetour=%p\n", pTrampoline, pDetour);
memset(pTrampoline->rAlign, 0, sizeof(pTrampoline->rAlign));
// Detour the number of movable target instructions.
PUCHAR pSrc = pTarget;
PUCHAR prbCode = pTrampoline->rbCode;
#ifdef DETOURS_IA64
#else
PUCHAR pPool = prbCode + sizeof(pTrampoline->rbCode);
#endif
ULONG target = 0;
ULONG jump = SIZE_OF_JUMP_CODE;
ULONG align = 0;
#ifdef DETOURS_ARM
#endif
while (target < jump) {
PUCHAR pOp = pSrc;
LONG extra = 0;
LogDebug("DetourCopyInstruction(%p,%p)\n",
prbCode, pSrc);
pSrc = (PUCHAR)DetourCopyInstruction(prbCode, (PVOID*)&pPool, pSrc, nullptr, &extra);
LogDebug("DetourCopyInstruction() = %p (%d bytes)\n", pSrc, (int)(pSrc - pOp));
prbCode += (pSrc - pOp) + extra;
target = (LONG)(pSrc - pTarget);
pTrampoline->rAlign[align].obTarget = target;
pTrampoline->rAlign[align].obTrampoline = prbCode - pTrampoline->rbCode;
align++;
if (align >= ARRAYSIZE(pTrampoline->rAlign)) {
break;
}
if (DetourDoesCodeEndFunction(pOp)) {
break;
}
}
// Consume, but don't duplicate padding if it is needed and available
while (target < jump) {
LONG filler = DetourIsCodeFiller(pSrc);
if (filler == 0)
break;
pSrc += filler;
target = (LONG)(pSrc - pTarget);
}
#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((" %u/%u",
pTrampoline->rAlign[n].obTarget,
pTrampoline->rAlign[n].obTrampoline
));
}
DETOUR_TRACE((" ]\n"));
}
#endif
if (target<jump || align>ARRAYSIZE(pTrampoline->rAlign)) {
// Too few instruction
status = STATUS_INVALID_BLOCK_LENGTH;
if (s_fIgnoreTooSmall) {
goto stop;
}
else {
DbgBreakPoint();
goto fail;
}
}
if (prbCode > pPool) {
DbgBreakPoint();
}
pTrampoline->cbCode = (UCHAR)(prbCode - pTrampoline->rbCode);
pTrampoline->cbRestore = (UCHAR)target;
RtlCopyMemory(pTrampoline->rbRestore, pTarget, target);
#if !defined(DETOURS_IA64)
if (target > sizeof(pTrampoline->rbCode) - jump) {
// Too many instructions.
status = STATUS_INVALID_HANDLE;
DbgBreakPoint();
goto fail;
}
#endif // !DETOURS_IA64
pTrampoline->pRemain = pTarget + target;
pTrampoline->pDetour = (PUCHAR)pDetour;
#ifdef DETOURS_IA64
#endif // DETOURS_IA64
prbCode = pTrampoline->rbCode + pTrampoline->cbCode;
#ifdef DETOURS_X64
prbCode = DetourGenJmpIndirect(prbCode, &pTrampoline->pRemain);
prbCode = DetourGenBrk(prbCode, pPool);
#endif
#ifdef DETOURS_X86
prbCode = DetourGenJmpImmediate(prbCode, pTrampoline->pRemain);
prbCode = DetourGenBrk(prbCode, pPool);
#endif // DETOURS_X86
#ifdef DETOURS_ARM
#endif // DETOURS_ARM
#ifdef DETOURS_ARM64
#endif // DETOURS_ARM64
LogDebug("detours: pbTarget=%p: "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x "
"%02x %02x %02x %02x\n",
pTarget,
pTarget[0], pTarget[1], pTarget[2], pTarget[3],
pTarget[4], pTarget[5], pTarget[6], pTarget[7],
pTarget[8], pTarget[9], pTarget[10], pTarget[11]);
LogDebug("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->ppPointer = (PUCHAR*)ppPointer;
o->pTrampoline = pTrampoline;
o->pTarget = pTarget;
o->pNext = s_pPendingOperations;
s_pPendingOperations = o;
return STATUS_SUCCESS;
}
NTSTATUS NTAPI DetourDetach(_Inout_ PVOID* ppPointer,
_In_ PVOID pDetour) {
NTSTATUS error = STATUS_SUCCESS;
if (pDetour == nullptr) {
return STATUS_INVALID_HANDLE;
}
if (ppPointer == NULL) {
return STATUS_INVALID_HANDLE;
}
if (*ppPointer == NULL) {
error = STATUS_INVALID_HANDLE;
DbgBreakPoint();
return error;
}
DetourOperation* o = new (NonPagedPool) DetourOperation;
if (o == NULL) {
error = STATUS_NO_MEMORY;
fail:
DbgBreakPoint();
stop:
if (o != NULL) {
delete o;
o = NULL;
}
return error;
}
#ifdef DETOURS_IA64
#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 target = pTrampoline->cbRestore;
PUCHAR pTarget = pTrampoline->pRemain - target;
if (target == 0 || target > sizeof(pTrampoline->rbCode)) {
error = STATUS_INVALID_BLOCK_LENGTH;
if (s_fIgnoreTooSmall) {
goto stop;
}
else {
DbgBreakPoint();
goto fail;
}
}
if (pTrampoline->pDetour != pDetour) {
error = STATUS_INVALID_BLOCK_LENGTH;
if (s_fIgnoreTooSmall) {
goto stop;
}
else {
DbgBreakPoint();
goto fail;
}
}
o->fIsRemove = TRUE;
o->ppPointer = (PUCHAR*)ppPointer;
o->pTrampoline = pTrampoline;
o->pTarget = pTarget;
o->pNext = s_pPendingOperations;
s_pPendingOperations = o;
return STATUS_SUCCESS;
}