用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

807 lines
31 KiB

//Copyright (c) 2007-2008, Marton Anka
//
//Permission is hereby granted, free of charge, to any person obtaining a
//copy of this software and associated documentation files (the "Software"),
//to deal in the Software without restriction, including without limitation
//the rights to use, copy, modify, merge, publish, distribute, sublicense,
//and/or sell copies of the Software, and to permit persons to whom the
//Software is furnished to do so, subject to the following conditions:
//
//The above copyright notice and this permission notice shall be included
//in all copies or substantial portions of the Software.
//
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
//OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
//IN THE SOFTWARE.
#include <windows.h>
#include <tlhelp32.h>
#include <stdio.h>
#include <tchar.h>
#include "mhook.h"
#include "../disasm-lib/disasm.h"
//=========================================================================
#ifndef cntof
#define cntof(a) (sizeof(a)/sizeof(a[0]))
#endif
//=========================================================================
#ifndef GOOD_HANDLE
#define GOOD_HANDLE(a) ((a!=INVALID_HANDLE_VALUE)&&(a!=NULL))
#endif
//=========================================================================
#ifndef gle
#define gle GetLastError
#endif
//=========================================================================
#ifndef DISABLE_ODPRINTF
#ifdef _DEBUG
#define ODPRINTF(a) odprintf a
#else
#define ODPRINTF(a)
#endif
inline void __cdecl odprintf(PCSTR format, ...) {
va_list args;
va_start(args, format);
int len = _vscprintf(format, args);
if (len > 0) {
len += (1 + 2);
PSTR buf = (PSTR) malloc(len);
if (buf) {
len = vsprintf_s(buf, len, format, args);
if (len > 0) {
while (len && isspace(buf[len-1])) len--;
buf[len++] = '\r';
buf[len++] = '\n';
buf[len] = 0;
OutputDebugStringA(buf);
}
free(buf);
}
va_end(args);
}
}
inline void __cdecl odprintf(PCWSTR format, ...) {
va_list args;
va_start(args, format);
int len = _vscwprintf(format, args);
if (len > 0) {
len += (1 + 2);
PWSTR buf = (PWSTR) malloc(sizeof(WCHAR)*len);
if (buf) {
len = vswprintf_s(buf, len, format, args);
if (len > 0) {
while (len && iswspace(buf[len-1])) len--;
buf[len++] = L'\r';
buf[len++] = L'\n';
buf[len] = 0;
OutputDebugStringW(buf);
}
free(buf);
}
va_end(args);
}
}
#else
#define ODPRINTF(a)
#endif //#ifndef DISABLE_ODPRINTF
//=========================================================================
#define MHOOKS_MAX_CODE_BYTES 32
#define MHOOKS_MAX_RIPS 4
//=========================================================================
// The trampoline structure - stores every bit of info about a hook
struct MHOOKS_TRAMPOLINE {
PBYTE pSystemFunction; // the original system function
DWORD cbOverwrittenCode; // number of bytes overwritten by the jump
PBYTE pHookFunction; // the hook function that we provide
BYTE codeJumpToHookFunction[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that jumps to the hook function
BYTE codeTrampoline[MHOOKS_MAX_CODE_BYTES]; // placeholder for code that holds the first few
// bytes from the system function and a jump to the remainder
// in the original location
BYTE codeUntouched[MHOOKS_MAX_CODE_BYTES]; // placeholder for unmodified original code
// (we patch IP-relative addressing)
};
//=========================================================================
// The patch data structures - store info about rip-relative instructions
// during hook placement
struct MHOOKS_RIPINFO
{
DWORD dwOffset;
S64 nDisplacement;
};
struct MHOOKS_PATCHDATA
{
S64 nLimitUp;
S64 nLimitDown;
DWORD nRipCnt;
MHOOKS_RIPINFO rips[MHOOKS_MAX_RIPS];
};
//=========================================================================
// Global vars
static BOOL g_bVarsInitialized = FALSE;
static CRITICAL_SECTION g_cs;
static MHOOKS_TRAMPOLINE* g_pHooks[MHOOKS_MAX_SUPPORTED_HOOKS];
static DWORD g_nHooksInUse = 0;
static HANDLE* g_hThreadHandles = NULL;
static DWORD g_nThreadHandles = 0;
#define MHOOK_JMPSIZE 5
//=========================================================================
// Toolhelp defintions so the functions can be dynamically bound to
typedef HANDLE (WINAPI * _CreateToolhelp32Snapshot)(
DWORD dwFlags,
DWORD th32ProcessID
);
typedef BOOL (WINAPI * _Thread32First)(
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);
typedef BOOL (WINAPI * _Thread32Next)(
HANDLE hSnapshot,
LPTHREADENTRY32 lpte
);
//=========================================================================
// Bring in the toolhelp functions from kernel32
_CreateToolhelp32Snapshot fnCreateToolhelp32Snapshot = (_CreateToolhelp32Snapshot) GetProcAddress(GetModuleHandle(_T("kernel32")), "CreateToolhelp32Snapshot");
_Thread32First fnThread32First = (_Thread32First) GetProcAddress(GetModuleHandle(_T("kernel32")), "Thread32First");
_Thread32Next fnThread32Next = (_Thread32Next) GetProcAddress(GetModuleHandle(_T("kernel32")), "Thread32Next");
//=========================================================================
static VOID EnterCritSec() {
if (!g_bVarsInitialized) {
InitializeCriticalSection(&g_cs);
ZeroMemory(g_pHooks, sizeof(g_pHooks));
g_bVarsInitialized = TRUE;
}
EnterCriticalSection(&g_cs);
}
//=========================================================================
static VOID LeaveCritSec() {
LeaveCriticalSection(&g_cs);
}
//=========================================================================
// Internal function:
//
// Skip over jumps that lead to the real function. Gets around import
// jump tables, etc.
//=========================================================================
static PBYTE SkipJumps(PBYTE pbCode) {
#ifdef _M_IX86_X64
if (pbCode[0] == 0xff && pbCode[1] == 0x25) {
#ifdef _M_IX86
// on x86 we have an absolute pointer...
PBYTE pbTarget = *(PBYTE *)&pbCode[2];
// ... that shows us an absolute pointer.
return SkipJumps(*(PBYTE *)pbTarget);
#elif defined _M_X64
// on x64 we have a 32-bit offset...
INT32 lOffset = *(INT32 *)&pbCode[2];
// ... that shows us an absolute pointer
return SkipJumps(*(PBYTE*)(pbCode + 6 + lOffset));
#endif
} else if (pbCode[0] == 0xe9) {
// here the behavior is identical, we have...
// ...a 32-bit offset to the destination.
return SkipJumps(pbCode + 5 + *(INT32 *)&pbCode[1]);
} else if (pbCode[0] == 0xeb) {
// and finally an 8-bit offset to the destination
return SkipJumps(pbCode + 2 + *(CHAR *)&pbCode[1]);
}
#else
#error unsupported platform
#endif
return pbCode;
}
//=========================================================================
// Internal function:
//
// Writes code at pbCode that jumps to pbJumpTo. Will attempt to do this
// in as few bytes as possible. Important on x64 where the long jump
// (0xff 0x25 ....) can take up 14 bytes.
//=========================================================================
static PBYTE EmitJump(PBYTE pbCode, PBYTE pbJumpTo) {
#ifdef _M_IX86_X64
PBYTE pbJumpFrom = pbCode + 5;
SIZE_T cbDiff = pbJumpFrom > pbJumpTo ? pbJumpFrom - pbJumpTo : pbJumpTo - pbJumpFrom;
ODPRINTF((L"mhooks: EmitJump: Jumping from %p to %p, diff is %p", pbJumpFrom, pbJumpTo, cbDiff));
if (cbDiff <= 0x7fff0000) {
pbCode[0] = 0xe9;
pbCode += 1;
*((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbJumpTo - pbJumpFrom);
pbCode += sizeof(DWORD);
} else {
pbCode[0] = 0xff;
pbCode[1] = 0x25;
pbCode += 2;
#ifdef _M_IX86
// on x86 we write an absolute address (just behind the instruction)
*((PDWORD)pbCode) = (DWORD)(DWORD_PTR)(pbCode + sizeof(DWORD));
#elif defined _M_X64
// on x64 we write the relative address of the same location
*((PDWORD)pbCode) = (DWORD)0;
#endif
pbCode += sizeof(DWORD);
*((PDWORD_PTR)pbCode) = (DWORD_PTR)(pbJumpTo);
pbCode += sizeof(DWORD_PTR);
}
#else
#error unsupported platform
#endif
return pbCode;
}
//=========================================================================
// Internal function:
//
// Will try to allocate the trampoline structure within 2 gigabytes of
// the target function.
//=========================================================================
static MHOOKS_TRAMPOLINE* TrampolineAlloc(PBYTE pSystemFunction, S64 nLimitUp, S64 nLimitDown) {
MHOOKS_TRAMPOLINE* pTrampoline = NULL;
// do we have room to store this guy?
if (g_nHooksInUse < MHOOKS_MAX_SUPPORTED_HOOKS) {
// determine lower and upper bounds for the allocation locations.
// in the basic scenario this is +/- 2GB but IP-relative instructions
// found in the original code may require a smaller window.
PBYTE pLower = pSystemFunction + nLimitUp;
pLower = pLower < (PBYTE)(DWORD_PTR)0x0000000080000000 ?
(PBYTE)(0x1) : (PBYTE)(pLower - (PBYTE)0x7fff0000);
PBYTE pUpper = pSystemFunction + nLimitDown;
pUpper = pUpper < (PBYTE)(DWORD_PTR)0xffffffff80000000 ?
(PBYTE)(pUpper + (DWORD_PTR)0x7ff80000) : (PBYTE)(DWORD_PTR)0xfffffffffff80000;
ODPRINTF((L"mhooks: TrampolineAlloc: Allocating for %p between %p and %p", pSystemFunction, pLower, pUpper));
SYSTEM_INFO sSysInfo = {0};
::GetSystemInfo(&sSysInfo);
// go through the available memory blocks and try to allocate a chunk for us
for (PBYTE pbAlloc = pLower; pbAlloc < pUpper;) {
// determine current state
MEMORY_BASIC_INFORMATION mbi;
ODPRINTF((L"mhooks: TrampolineAlloc: Looking at address %p", pbAlloc));
if (!VirtualQuery(pbAlloc, &mbi, sizeof(mbi)))
break;
// free & large enough?
if (mbi.State == MEM_FREE && mbi.RegionSize >= sizeof(MHOOKS_TRAMPOLINE) && mbi.RegionSize >= sSysInfo.dwAllocationGranularity) {
// yes, align the pointer to the 64K boundary first
pbAlloc = (PBYTE)(ULONG_PTR((ULONG_PTR(pbAlloc) + (sSysInfo.dwAllocationGranularity-1)) / sSysInfo.dwAllocationGranularity) * sSysInfo.dwAllocationGranularity);
// and then try to allocate it
pTrampoline = (MHOOKS_TRAMPOLINE*)VirtualAlloc(pbAlloc, sizeof(MHOOKS_TRAMPOLINE), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READ);
if (pTrampoline) {
ODPRINTF((L"mhooks: TrampolineAlloc: Allocated block at %p as the trampoline", pTrampoline));
break;
}
}
// continue the search
pbAlloc = (PBYTE)mbi.BaseAddress + mbi.RegionSize;
}
// found and allocated a trampoline?
if (pTrampoline) {
// put it into our list so we know we'll have to free it
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i] == NULL) {
g_pHooks[i] = pTrampoline;
g_nHooksInUse++;
break;
}
}
}
}
return pTrampoline;
}
//=========================================================================
// Internal function:
//
// Return the internal trampoline structure that belongs to a hooked function.
//=========================================================================
static MHOOKS_TRAMPOLINE* TrampolineGet(PBYTE pHookedFunction) {
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i]) {
if (g_pHooks[i]->codeTrampoline == pHookedFunction)
return g_pHooks[i];
}
}
return NULL;
}
//=========================================================================
// Internal function:
//
// Free a trampoline structure.
//=========================================================================
static VOID TrampolineFree(MHOOKS_TRAMPOLINE* pTrampoline, BOOL bNeverUsed) {
for (DWORD i=0; i<MHOOKS_MAX_SUPPORTED_HOOKS; i++) {
if (g_pHooks[i] == pTrampoline) {
g_pHooks[i] = NULL;
// It might be OK to call VirtualFree, but quite possibly it isn't:
// If a thread has some of our trampoline code on its stack
// and we yank the region from underneath it then it will
// surely crash upon returning. So instead of freeing the
// memory we just let it leak. Ugly, but safe.
if (bNeverUsed)
VirtualFree(pTrampoline, 0, MEM_RELEASE);
g_nHooksInUse--;
break;
}
}
}
//=========================================================================
// Internal function:
//
// Suspend a given thread and try to make sure that its instruction
// pointer is not in the given range.
//=========================================================================
static HANDLE SuspendOneThread(DWORD dwThreadId, PBYTE pbCode, DWORD cbBytes) {
// open the thread
HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwThreadId);
if (GOOD_HANDLE(hThread)) {
// attempt suspension
DWORD dwSuspendCount = SuspendThread(hThread);
if (dwSuspendCount != -1) {
// see where the IP is
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_CONTROL;
int nTries = 0;
while (GetThreadContext(hThread, &ctx)) {
#ifdef _M_IX86
PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Eip;
#elif defined _M_X64
PBYTE pIp = (PBYTE)(DWORD_PTR)ctx.Rip;
#endif
if (pIp >= pbCode && pIp < (pbCode + cbBytes)) {
if (nTries < 3) {
// oops - we should try to get the instruction pointer out of here.
ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE", dwThreadId, pIp));
ResumeThread(hThread);
Sleep(100);
SuspendThread(hThread);
nTries++;
} else {
// we gave it all we could. (this will probably never
// happen - unless the thread has already been suspended
// to begin with)
ODPRINTF((L"mhooks: SuspendOneThread: suspended thread %d - IP is at %p - IS COLLIDING WITH CODE - CAN'T FIX", dwThreadId, pIp));
ResumeThread(hThread);
CloseHandle(hThread);
hThread = NULL;
break;
}
} else {
// success, the IP is not conflicting
ODPRINTF((L"mhooks: SuspendOneThread: Successfully suspended thread %d - IP is at %p", dwThreadId, pIp));
break;
}
}
} else {
// couldn't suspend
CloseHandle(hThread);
hThread = NULL;
}
}
return hThread;
}
//=========================================================================
// Internal function:
//
// Resumes all previously suspended threads in the current process.
//=========================================================================
static VOID ResumeOtherThreads() {
// make sure things go as fast as possible
INT nOriginalPriority = GetThreadPriority(GetCurrentThread());
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
// go through our list
for (DWORD i=0; i<g_nThreadHandles; i++) {
// and resume & close thread handles
ResumeThread(g_hThreadHandles[i]);
CloseHandle(g_hThreadHandles[i]);
}
// clean up
free(g_hThreadHandles);
g_hThreadHandles = NULL;
g_nThreadHandles = 0;
SetThreadPriority(GetCurrentThread(), nOriginalPriority);
}
//=========================================================================
// Internal function:
//
// Suspend all threads in this process while trying to make sure that their
// instruction pointer is not in the given range.
//=========================================================================
static BOOL SuspendOtherThreads(PBYTE pbCode, DWORD cbBytes) {
BOOL bRet = FALSE;
// make sure we're the most important thread in the process
INT nOriginalPriority = GetThreadPriority(GetCurrentThread());
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
// get a view of the threads in the system
HANDLE hSnap = fnCreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, GetCurrentProcessId());
if (GOOD_HANDLE(hSnap)) {
THREADENTRY32 te;
te.dwSize = sizeof(te);
// count threads in this process (except for ourselves)
DWORD nThreadsInProcess = 0;
if (fnThread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID == GetCurrentProcessId()) {
if (te.th32ThreadID != GetCurrentThreadId()) {
nThreadsInProcess++;
}
}
te.dwSize = sizeof(te);
} while(fnThread32Next(hSnap, &te));
}
ODPRINTF((L"mhooks: SuspendOtherThreads: counted %d other threads", nThreadsInProcess));
if (nThreadsInProcess) {
// alloc buffer for the handles we really suspended
g_hThreadHandles = (HANDLE*)malloc(nThreadsInProcess*sizeof(HANDLE));
if (g_hThreadHandles) {
ZeroMemory(g_hThreadHandles, nThreadsInProcess*sizeof(HANDLE));
DWORD nCurrentThread = 0;
BOOL bFailed = FALSE;
te.dwSize = sizeof(te);
// go through every thread
if (fnThread32First(hSnap, &te)) {
do {
if (te.th32OwnerProcessID == GetCurrentProcessId()) {
if (te.th32ThreadID != GetCurrentThreadId()) {
// attempt to suspend it
g_hThreadHandles[nCurrentThread] = SuspendOneThread(te.th32ThreadID, pbCode, cbBytes);
if (GOOD_HANDLE(g_hThreadHandles[nCurrentThread])) {
ODPRINTF((L"mhooks: SuspendOtherThreads: successfully suspended %d", te.th32ThreadID));
nCurrentThread++;
} else {
ODPRINTF((L"mhooks: SuspendOtherThreads: error while suspending thread %d: %d", te.th32ThreadID, gle()));
// TODO: this might not be the wisest choice
// but we can choose to ignore failures on
// thread suspension. It's pretty unlikely that
// we'll fail - and even if we do, the chances
// of a thread's IP being in the wrong place
// is pretty small.
// bFailed = TRUE;
}
}
}
te.dwSize = sizeof(te);
} while(fnThread32Next(hSnap, &te) && !bFailed);
}
g_nThreadHandles = nCurrentThread;
bRet = !bFailed;
}
}
CloseHandle(hSnap);
//TODO: we might want to have another pass to make sure all threads
// in the current process (including those that might have been
// created since we took the original snapshot) have been
// suspended.
} else {
ODPRINTF((L"mhooks: SuspendOtherThreads: can't CreateToolhelp32Snapshot: %d", gle()));
}
SetThreadPriority(GetCurrentThread(), nOriginalPriority);
if (!bRet) {
ODPRINTF((L"mhooks: SuspendOtherThreads: Had a problem (or not running multithreaded), resuming all threads."));
ResumeOtherThreads();
}
return bRet;
}
//=========================================================================
// if IP-relative addressing has been detected, fix up the code so the
// offset points to the original location
static void FixupIPRelativeAddressing(PBYTE pbNew, PBYTE pbOriginal, MHOOKS_PATCHDATA* pdata)
{
#if defined _M_X64
S64 diff = pbNew - pbOriginal;
for (DWORD i = 0; i < pdata->nRipCnt; i++) {
DWORD dwNewDisplacement = (DWORD)(pdata->rips[i].nDisplacement - diff);
ODPRINTF((L"mhooks: fixing up RIP instruction operand for code at 0x%p: "
L"old displacement: 0x%8.8x, new displacement: 0x%8.8x",
pbNew + pdata->rips[i].dwOffset,
(DWORD)pdata->rips[i].nDisplacement,
dwNewDisplacement));
*(PDWORD)(pbNew + pdata->rips[i].dwOffset) = dwNewDisplacement;
}
#endif
}
//=========================================================================
// Examine the machine code at the target function's entry point, and
// skip bytes in a way that we'll always end on an instruction boundary.
// We also detect branches and subroutine calls (as well as returns)
// at which point disassembly must stop.
// Finally, detect and collect information on IP-relative instructions
// that we can patch.
static DWORD DisassembleAndSkip(PVOID pFunction, DWORD dwMinLen, MHOOKS_PATCHDATA* pdata) {
DWORD dwRet = 0;
pdata->nLimitDown = 0;
pdata->nLimitUp = 0;
pdata->nRipCnt = 0;
#ifdef _M_IX86
ARCHITECTURE_TYPE arch = ARCH_X86;
#elif defined _M_X64
ARCHITECTURE_TYPE arch = ARCH_X64;
#else
#error unsupported platform
#endif
DISASSEMBLER dis;
if (InitDisassembler(&dis, arch)) {
INSTRUCTION* pins = NULL;
U8* pLoc = (U8*)pFunction;
DWORD dwFlags = DISASM_DECODE | DISASM_DISASSEMBLE | DISASM_ALIGNOUTPUT;
ODPRINTF((L"mhooks: DisassembleAndSkip: Disassembling %p", pLoc));
while ( (dwRet < dwMinLen) && (pins = GetInstruction(&dis, (ULONG_PTR)pLoc, pLoc, dwFlags)) ) {
ODPRINTF(("mhooks: DisassembleAndSkip: %p: %s", pLoc, pins->String));
if (pins->Type == ITYPE_RET ) break;
if (pins->Type == ITYPE_BRANCH ) break;
if (pins->Type == ITYPE_BRANCHCC) break;
if (pins->Type == ITYPE_CALL ) break;
if (pins->Type == ITYPE_CALLCC ) break;
#if defined _M_X64
BOOL bProcessRip = FALSE;
// mov or lea to register from rip+imm32
if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) &&
(pins->X86.OperandSize == 8) && (pins->OperandCount == 2) &&
(pins->Operands[1].Flags & OP_IPREL) && (pins->Operands[1].Register == AMD64_REG_RIP))
{
// rip-addressing "mov reg, [rip+imm32]"
ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 1, pins->X86.Displacement, *(PDWORD)(pLoc+3)));
bProcessRip = TRUE;
}
// mov or lea to rip+imm32 from register
else if ((pins->Type == ITYPE_MOV || pins->Type == ITYPE_LEA) && (pins->X86.Relative) &&
(pins->X86.OperandSize == 8) && (pins->OperandCount == 2) &&
(pins->Operands[0].Flags & OP_IPREL) && (pins->Operands[0].Register == AMD64_REG_RIP))
{
// rip-addressing "mov [rip+imm32], reg"
ODPRINTF((L"mhooks: DisassembleAndSkip: found OP_IPREL on operand %d with displacement 0x%x (in memory: 0x%x)", 0, pins->X86.Displacement, *(PDWORD)(pLoc+3)));
bProcessRip = TRUE;
}
else if ( (pins->OperandCount >= 1) && (pins->Operands[0].Flags & OP_IPREL) )
{
// unsupported rip-addressing
ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 0));
// dump instruction bytes to the debug output
for (DWORD i=0; i<pins->Length; i++) {
ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i]));
}
break;
}
else if ( (pins->OperandCount >= 2) && (pins->Operands[1].Flags & OP_IPREL) )
{
// unsupported rip-addressing
ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 1));
// dump instruction bytes to the debug output
for (DWORD i=0; i<pins->Length; i++) {
ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i]));
}
break;
}
else if ( (pins->OperandCount >= 3) && (pins->Operands[2].Flags & OP_IPREL) )
{
// unsupported rip-addressing
ODPRINTF((L"mhooks: DisassembleAndSkip: found unsupported OP_IPREL on operand %d", 2));
// dump instruction bytes to the debug output
for (DWORD i=0; i<pins->Length; i++) {
ODPRINTF((L"mhooks: DisassembleAndSkip: instr byte %2.2d: 0x%2.2x", i, pLoc[i]));
}
break;
}
// follow through with RIP-processing if needed
if (bProcessRip) {
// calculate displacement relative to function start
S64 nAdjustedDisplacement = pins->X86.Displacement + (pLoc - (U8*)pFunction);
// store displacement values furthest from zero (both positive and negative)
if (nAdjustedDisplacement < pdata->nLimitDown)
pdata->nLimitDown = nAdjustedDisplacement;
if (nAdjustedDisplacement > pdata->nLimitUp)
pdata->nLimitUp = nAdjustedDisplacement;
// store patch info
if (pdata->nRipCnt < MHOOKS_MAX_RIPS) {
pdata->rips[pdata->nRipCnt].dwOffset = dwRet + 3;
pdata->rips[pdata->nRipCnt].nDisplacement = pins->X86.Displacement;
pdata->nRipCnt++;
} else {
// no room for patch info, stop disassembly
break;
}
}
#endif
dwRet += pins->Length;
pLoc += pins->Length;
}
CloseDisassembler(&dis);
}
return dwRet;
}
//=========================================================================
BOOL Mhook_SetHook(PVOID *ppSystemFunction, PVOID pHookFunction) {
MHOOKS_TRAMPOLINE* pTrampoline = NULL;
PVOID pSystemFunction = *ppSystemFunction;
// ensure thread-safety
EnterCritSec();
ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction));
// find the real functions (jump over jump tables, if any)
pSystemFunction = SkipJumps((PBYTE)pSystemFunction);
pHookFunction = SkipJumps((PBYTE)pHookFunction);
ODPRINTF((L"mhooks: Mhook_SetHook: Started on the job: %p / %p", pSystemFunction, pHookFunction));
// figure out the length of the overwrite zone
MHOOKS_PATCHDATA patchdata = {0};
DWORD dwInstructionLength = DisassembleAndSkip(pSystemFunction, MHOOK_JMPSIZE, &patchdata);
if (dwInstructionLength >= MHOOK_JMPSIZE) {
ODPRINTF((L"mhooks: Mhook_SetHook: disassembly signals %d bytes", dwInstructionLength));
// suspend every other thread in this process, and make sure their IP
// is not in the code we're about to overwrite.
SuspendOtherThreads((PBYTE)pSystemFunction, dwInstructionLength);
// allocate a trampoline structure (TODO: it is pretty wasteful to get
// VirtualAlloc to grab chunks of memory smaller than 100 bytes)
pTrampoline = TrampolineAlloc((PBYTE)pSystemFunction, patchdata.nLimitUp, patchdata.nLimitDown);
if (pTrampoline) {
ODPRINTF((L"mhooks: Mhook_SetHook: allocated structure at %p", pTrampoline));
// open ourselves so we can VirtualProtectEx
HANDLE hProc = GetCurrentProcess();
DWORD dwOldProtectSystemFunction = 0;
DWORD dwOldProtectTrampolineFunction = 0;
// set the system function to PAGE_EXECUTE_READWRITE
if (VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) {
ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on system function"));
// mark our trampoline buffer to PAGE_EXECUTE_READWRITE
if (VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), PAGE_EXECUTE_READWRITE, &dwOldProtectTrampolineFunction)) {
ODPRINTF((L"mhooks: Mhook_SetHook: readwrite set on trampoline structure"));
// create our trampoline function
PBYTE pbCode = pTrampoline->codeTrampoline;
// save original code..
for (DWORD i = 0; i<dwInstructionLength; i++) {
pTrampoline->codeUntouched[i] = pbCode[i] = ((PBYTE)pSystemFunction)[i];
}
pbCode += dwInstructionLength;
// plus a jump to the continuation in the original location
pbCode = EmitJump(pbCode, ((PBYTE)pSystemFunction) + dwInstructionLength);
ODPRINTF((L"mhooks: Mhook_SetHook: updated the trampoline"));
// fix up any IP-relative addressing in the code
FixupIPRelativeAddressing(pTrampoline->codeTrampoline, (PBYTE)pSystemFunction, &patchdata);
DWORD_PTR dwDistance = (PBYTE)pHookFunction < (PBYTE)pSystemFunction ?
(PBYTE)pSystemFunction - (PBYTE)pHookFunction : (PBYTE)pHookFunction - (PBYTE)pSystemFunction;
if (dwDistance > 0x7fff0000) {
// create a stub that jumps to the replacement function.
// we need this because jumping from the API to the hook directly
// will be a long jump, which is 14 bytes on x64, and we want to
// avoid that - the API may or may not have room for such stuff.
// (remember, we only have 5 bytes guaranteed in the API.)
// on the other hand we do have room, and the trampoline will always be
// within +/- 2GB of the API, so we do the long jump in there.
// the API will jump to the "reverse trampoline" which
// will jump to the user's hook code.
pbCode = pTrampoline->codeJumpToHookFunction;
pbCode = EmitJump(pbCode, (PBYTE)pHookFunction);
ODPRINTF((L"mhooks: Mhook_SetHook: created reverse trampoline"));
FlushInstructionCache(hProc, pTrampoline->codeJumpToHookFunction,
pbCode - pTrampoline->codeJumpToHookFunction);
// update the API itself
pbCode = (PBYTE)pSystemFunction;
pbCode = EmitJump(pbCode, pTrampoline->codeJumpToHookFunction);
} else {
// the jump will be at most 5 bytes so we can do it directly
// update the API itself
pbCode = (PBYTE)pSystemFunction;
pbCode = EmitJump(pbCode, (PBYTE)pHookFunction);
}
// update data members
pTrampoline->cbOverwrittenCode = dwInstructionLength;
pTrampoline->pSystemFunction = (PBYTE)pSystemFunction;
pTrampoline->pHookFunction = (PBYTE)pHookFunction;
// flush instruction cache and restore original protection
FlushInstructionCache(hProc, pTrampoline->codeTrampoline, dwInstructionLength);
VirtualProtectEx(hProc, pTrampoline, sizeof(MHOOKS_TRAMPOLINE), dwOldProtectTrampolineFunction, &dwOldProtectTrampolineFunction);
} else {
ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 2: %d", gle()));
}
// flush instruction cache and restore original protection
FlushInstructionCache(hProc, pSystemFunction, dwInstructionLength);
VirtualProtectEx(hProc, pSystemFunction, dwInstructionLength, dwOldProtectSystemFunction, &dwOldProtectSystemFunction);
} else {
ODPRINTF((L"mhooks: Mhook_SetHook: failed VirtualProtectEx 1: %d", gle()));
}
if (pTrampoline->pSystemFunction) {
// this is what the application will use as the entry point
// to the "original" unhooked function.
*ppSystemFunction = pTrampoline->codeTrampoline;
ODPRINTF((L"mhooks: Mhook_SetHook: Hooked the function!"));
} else {
// if we failed discard the trampoline (forcing VirtualFree)
TrampolineFree(pTrampoline, TRUE);
pTrampoline = NULL;
}
}
// resume everybody else
ResumeOtherThreads();
} else {
ODPRINTF((L"mhooks: disassembly signals %d bytes (unacceptable)", dwInstructionLength));
}
LeaveCritSec();
return (pTrampoline != NULL);
}
//=========================================================================
BOOL Mhook_Unhook(PVOID *ppHookedFunction) {
ODPRINTF((L"mhooks: Mhook_Unhook: %p", *ppHookedFunction));
BOOL bRet = FALSE;
EnterCritSec();
// get the trampoline structure that corresponds to our function
MHOOKS_TRAMPOLINE* pTrampoline = TrampolineGet((PBYTE)*ppHookedFunction);
if (pTrampoline) {
// make sure nobody's executing code where we're about to overwrite a few bytes
SuspendOtherThreads(pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode);
ODPRINTF((L"mhooks: Mhook_Unhook: found struct at %p", pTrampoline));
// open ourselves so we can VirtualProtectEx
HANDLE hProc = GetCurrentProcess();
DWORD dwOldProtectSystemFunction = 0;
// make memory writable
if (VirtualProtectEx(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, PAGE_EXECUTE_READWRITE, &dwOldProtectSystemFunction)) {
ODPRINTF((L"mhooks: Mhook_Unhook: readwrite set on system function"));
PBYTE pbCode = (PBYTE)pTrampoline->pSystemFunction;
for (DWORD i = 0; i<pTrampoline->cbOverwrittenCode; i++) {
pbCode[i] = pTrampoline->codeUntouched[i];
}
// flush instruction cache and make memory unwritable
FlushInstructionCache(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode);
VirtualProtectEx(hProc, pTrampoline->pSystemFunction, pTrampoline->cbOverwrittenCode, dwOldProtectSystemFunction, &dwOldProtectSystemFunction);
// return the original function pointer
*ppHookedFunction = pTrampoline->pSystemFunction;
bRet = TRUE;
ODPRINTF((L"mhooks: Mhook_Unhook: sysfunc: %p", *ppHookedFunction));
// free the trampoline while not really discarding it from memory
TrampolineFree(pTrampoline, FALSE);
ODPRINTF((L"mhooks: Mhook_Unhook: unhook successful"));
} else {
ODPRINTF((L"mhooks: Mhook_Unhook: failed VirtualProtectEx 1: %d", gle()));
}
// make the other guys runnable
ResumeOtherThreads();
}
LeaveCritSec();
return bRet;
}
//=========================================================================