sm-ext-dhooks2/DynamicHooks/hook.cpp
Peace-Maker 70eee8482a Fix compilation against SourceMod 1.10
MacroAssembler base is now in the |sp| namespace.
2019-05-06 18:29:40 +02:00

490 lines
19 KiB
C++

/**
* =============================================================================
* DynamicHooks
* Copyright (C) 2015 Robin Gohmert. All rights reserved.
* =============================================================================
*
* This software is provided 'as-is', without any express or implied warranty.
* In no event will the authors be held liable for any damages arising from
* the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
* asm.h/cpp from devmaster.net (thanks cybermind) edited by pRED* to handle gcc
* -fPIC thunks correctly
*
* Idea and trampoline code taken from DynDetours (thanks your-name-here).
*/
// ============================================================================
// >> INCLUDES
// ============================================================================
#include "hook.h"
#include "utilities.h"
#include "asm.h"
#include <macro-assembler-x86.h>
#include "extension.h"
using namespace sp;
// ============================================================================
// >> DEFINITIONS
// ============================================================================
#define JMP_SIZE 6
// ============================================================================
// >> CHook
// ============================================================================
CHook::CHook(void* pFunc, ICallingConvention* pConvention)
{
m_pFunc = pFunc;
m_pRegisters = new CRegisters(pConvention->GetRegisters());
m_pCallingConvention = pConvention;
m_LastPreReturnAction = ReturnAction_Ignored;
if (!m_hookHandler.init())
return;
if (!m_RetAddr.init())
return;
unsigned char* pTarget = (unsigned char *) pFunc;
// Determine the number of bytes we need to copy
int iBytesToCopy = copy_bytes(pTarget, NULL, JMP_SIZE);
// Create an array for the bytes to copy + a jump to the rest of the
// function.
unsigned char* pCopiedBytes = new unsigned char[iBytesToCopy + JMP_SIZE];
// Fill the array with NOP instructions
memset(pCopiedBytes, 0x90, iBytesToCopy + JMP_SIZE);
// Copy the required bytes to our array
SetMemPatchable(pCopiedBytes, iBytesToCopy + JMP_SIZE);
copy_bytes(pTarget, pCopiedBytes, JMP_SIZE);
// Write a jump after the copied bytes to the function/bridge + number of bytes to copy
WriteJMP(pCopiedBytes + iBytesToCopy, pTarget + iBytesToCopy);
// Save the trampoline
m_pTrampoline = (void *) pCopiedBytes;
// Create the bridge function
m_pBridge = CreateBridge();
// Write a jump to the bridge
WriteJMP((unsigned char *) pFunc, m_pBridge);
}
CHook::~CHook()
{
// Copy back the previously copied bytes
copy_bytes((unsigned char *) m_pTrampoline, (unsigned char *) m_pFunc, JMP_SIZE);
// Free the trampoline array
unsigned char* pTrampoline = (unsigned char *)m_pTrampoline;
delete [] pTrampoline;
// Free the asm bridge and new return address
smutils->GetScriptingEngine()->FreePageMemory(m_pBridge);
smutils->GetScriptingEngine()->FreePageMemory(m_pNewRetAddr);
delete m_pRegisters;
delete m_pCallingConvention;
}
void CHook::AddCallback(HookType_t eHookType, HookHandlerFn* pCallback)
{
if (!pCallback)
return;
HookTypeMap::Insert i = m_hookHandler.findForAdd(eHookType);
if (!i.found()) {
HookHandlerSet set;
set.init();
m_hookHandler.add(i, eHookType, ke::Move(set));
}
i->value.add(pCallback);
}
void CHook::RemoveCallback(HookType_t eHookType, HookHandlerFn* pCallback)
{
HookTypeMap::Result r = m_hookHandler.find(eHookType);
if (!r.found())
return;
r->value.removeIfExists(pCallback);
}
bool CHook::IsCallbackRegistered(HookType_t eHookType, HookHandlerFn* pCallback)
{
HookTypeMap::Result r = m_hookHandler.find(eHookType);
if (!r.found())
return false;
return r->value.has(pCallback);
}
bool CHook::AreCallbacksRegistered()
{
HookTypeMap::Result r = m_hookHandler.find(HOOKTYPE_PRE);
if (r.found() && r->value.elements() > 0)
return true;
r = m_hookHandler.find(HOOKTYPE_POST);
if (r.found() && r->value.elements() > 0)
return true;
return false;
}
ReturnAction_t CHook::HookHandler(HookType_t eHookType)
{
if (eHookType == HOOKTYPE_POST)
{
if (m_LastPreReturnAction == ReturnAction_Override)
m_pCallingConvention->RestoreReturnValue(m_pRegisters);
if (m_LastPreReturnAction < ReturnAction_Supercede)
m_pCallingConvention->RestorePostCallRegisters(m_pRegisters);
}
ReturnAction_t returnAction = ReturnAction_Ignored;
HookTypeMap::Result r = m_hookHandler.find(eHookType);
if (!r.found())
return returnAction;
HookHandlerSet &callbacks = r->value;
for(HookHandlerSet::iterator it=callbacks.iter(); !it.empty(); it.next())
{
ReturnAction_t result = ((HookHandlerFn) *it)(eHookType, this);
if (result > returnAction)
returnAction = result;
}
if (eHookType == HOOKTYPE_PRE)
{
m_LastPreReturnAction = returnAction;
if (returnAction == ReturnAction_Override)
m_pCallingConvention->SaveReturnValue(m_pRegisters);
if (returnAction < ReturnAction_Supercede)
m_pCallingConvention->SavePostCallRegisters(m_pRegisters);
}
return returnAction;
}
void* __cdecl CHook::GetReturnAddress(void* pESP)
{
ReturnAddressMap::Result r = m_RetAddr.find(pESP);
assert(r.found());
if (!r.found())
{
smutils->LogError(myself, "FATAL: Failed to find return address of original function. Check the arguments and return type of your detour setup.");
return NULL;
}
void *pRetAddr = r->value;
// Clear the stack address from the cache now that we ran the post hook code.
m_RetAddr.remove(r);
return pRetAddr;
}
void __cdecl CHook::SetReturnAddress(void* pRetAddr, void* pESP)
{
ReturnAddressMap::Insert i = m_RetAddr.findForAdd(pESP);
if (i.found())
i->value = pRetAddr;
else
m_RetAddr.add(i, pESP, pRetAddr);
}
void* CHook::CreateBridge()
{
sp::MacroAssembler masm;
Label label_supercede;
// Write a redirect to the post-hook code
Write_ModifyReturnAddress(masm);
// Call the pre-hook handler and jump to label_supercede if ReturnAction_Supercede was returned
Write_CallHandler(masm, HOOKTYPE_PRE);
masm.cmpb(r8_al, ReturnAction_Supercede);
// Restore the previously saved registers, so any changes will be applied
Write_RestoreRegisters(masm, HOOKTYPE_PRE);
masm.j(equal, &label_supercede);
// Jump to the trampoline
masm.jmp(ExternalAddress(m_pTrampoline));
// This code will be executed if a pre-hook returns ReturnAction_Supercede
masm.bind(&label_supercede);
// Finally, return to the caller
// This will still call post hooks, but will skip the original function.
masm.ret(m_pCallingConvention->GetPopSize());
void *base = smutils->GetScriptingEngine()->AllocatePageMemory(masm.length());
masm.emitToExecutableMemory(base);
return base;
}
void CHook::Write_ModifyReturnAddress(sp::MacroAssembler& masm)
{
// Save scratch registers that are used by SetReturnAddress
static void* pEAX = NULL;
static void* pECX = NULL;
static void* pEDX = NULL;
masm.movl(Operand(ExternalAddress(&pEAX)), eax);
masm.movl(Operand(ExternalAddress(&pECX)), ecx);
masm.movl(Operand(ExternalAddress(&pEDX)), edx);
// Store the return address in eax
masm.movl(eax, Operand(esp, 0));
// Save the original return address by using the current esp as the key.
// This should be unique until we have returned to the original caller.
void (__cdecl CHook::*SetReturnAddress)(void*, void*) = &CHook::SetReturnAddress;
masm.push(esp);
masm.push(eax);
masm.push(intptr_t(this));
masm.call(ExternalAddress((void *&)SetReturnAddress));
masm.addl(esp, 12);
// Restore scratch registers
masm.movl(eax, Operand(ExternalAddress(&pEAX)));
masm.movl(ecx, Operand(ExternalAddress(&pECX)));
masm.movl(edx, Operand(ExternalAddress(&pEDX)));
// Override the return address. This is a redirect to our post-hook code
m_pNewRetAddr = CreatePostCallback();
masm.movl(Operand(esp, 0), intptr_t(m_pNewRetAddr));
}
void* CHook::CreatePostCallback()
{
sp::MacroAssembler masm;
int iPopSize = m_pCallingConvention->GetPopSize();
// Subtract the previously added bytes (stack size + return address), so
// that we can access the arguments again
masm.subl(esp, iPopSize+4);
// Call the post-hook handler
Write_CallHandler(masm, HOOKTYPE_POST);
// Restore the previously saved registers, so any changes will be applied
Write_RestoreRegisters(masm, HOOKTYPE_POST);
// Save scratch registers that are used by GetReturnAddress
static void* pEAX = NULL;
static void* pECX = NULL;
static void* pEDX = NULL;
masm.movl(Operand(ExternalAddress(&pEAX)), eax);
masm.movl(Operand(ExternalAddress(&pECX)), ecx);
masm.movl(Operand(ExternalAddress(&pEDX)), edx);
// Get the original return address
void* (__cdecl CHook::*GetReturnAddress)(void*) = &CHook::GetReturnAddress;
masm.push(esp);
masm.push(intptr_t(this));
masm.call(ExternalAddress((void *&)GetReturnAddress));
masm.addl(esp, 8);
// Save the original return address
static void* pRetAddr = NULL;
masm.movl(Operand(ExternalAddress(&pRetAddr)), eax);
// Restore scratch registers
masm.movl(eax, Operand(ExternalAddress(&pEAX)));
masm.movl(ecx, Operand(ExternalAddress(&pECX)));
masm.movl(edx, Operand(ExternalAddress(&pEDX)));
// Add the bytes again to the stack (stack size + return address), so we
// don't corrupt the stack.
masm.addl(esp, iPopSize+4);
// Jump to the original return address
masm.jmp(Operand(ExternalAddress(&pRetAddr)));
// Generate the code
void *base = smutils->GetScriptingEngine()->AllocatePageMemory(masm.length());
masm.emitToExecutableMemory(base);
return base;
}
void CHook::Write_CallHandler(sp::MacroAssembler& masm, HookType_t type)
{
ReturnAction_t (__cdecl CHook::*HookHandler)(HookType_t) = &CHook::HookHandler;
// Save the registers so that we can access them in our handlers
Write_SaveRegisters(masm, type);
// Align the stack to 16 bytes.
masm.subl(esp, 8);
// Call the global hook handler
masm.push(type);
masm.push(intptr_t(this));
masm.call(ExternalAddress((void *&)HookHandler));
masm.addl(esp, 16);
}
void CHook::Write_SaveRegisters(sp::MacroAssembler& masm, HookType_t type)
{
ke::Vector<Register_t> vecRegistersToSave = m_pCallingConvention->GetRegisters();
for(size_t i = 0; i < vecRegistersToSave.length(); i++)
{
switch(vecRegistersToSave[i])
{
// ========================================================================
// >> 8-bit General purpose registers
// ========================================================================
case AL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_al->m_pAddress)), r8_al); break;
case CL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_cl->m_pAddress)), r8_cl); break;
case DL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_dl->m_pAddress)), r8_dl); break;
case BL: masm.movl(Operand(ExternalAddress(m_pRegisters->m_bl->m_pAddress)), r8_bl); break;
case AH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ah->m_pAddress)), r8_ah); break;
case CH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ch->m_pAddress)), r8_ch); break;
case DH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_dh->m_pAddress)), r8_dh); break;
case BH: masm.movl(Operand(ExternalAddress(m_pRegisters->m_bh->m_pAddress)), r8_bh); break;
// ========================================================================
// >> 32-bit General purpose registers
// ========================================================================
case EAX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_eax->m_pAddress)), eax); break;
case ECX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ecx->m_pAddress)), ecx); break;
case EDX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_edx->m_pAddress)), edx); break;
case EBX: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ebx->m_pAddress)), ebx); break;
case ESP: masm.movl(Operand(ExternalAddress(m_pRegisters->m_esp->m_pAddress)), esp); break;
case EBP: masm.movl(Operand(ExternalAddress(m_pRegisters->m_ebp->m_pAddress)), ebp); break;
case ESI: masm.movl(Operand(ExternalAddress(m_pRegisters->m_esi->m_pAddress)), esi); break;
case EDI: masm.movl(Operand(ExternalAddress(m_pRegisters->m_edi->m_pAddress)), edi); break;
// ========================================================================
// >> 128-bit XMM registers
// ========================================================================
// TODO: Also provide movups?
case XMM0: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm0->m_pAddress)), xmm0); break;
case XMM1: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm1->m_pAddress)), xmm1); break;
case XMM2: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm2->m_pAddress)), xmm2); break;
case XMM3: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm3->m_pAddress)), xmm3); break;
case XMM4: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm4->m_pAddress)), xmm4); break;
case XMM5: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm5->m_pAddress)), xmm5); break;
case XMM6: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm6->m_pAddress)), xmm6); break;
case XMM7: masm.movaps(Operand(ExternalAddress(m_pRegisters->m_xmm7->m_pAddress)), xmm7); break;
// ========================================================================
// >> 80-bit FPU registers
// ========================================================================
case ST0:
// Don't mess with the FPU stack in a pre-hook. The float return is returned in st0,
// so only load it in a post hook to avoid writing back NaN.
if (type == HOOKTYPE_POST)
masm.fst32(Operand(ExternalAddress(m_pRegisters->m_st0->m_pAddress)));
break;
//case ST1: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st1->m_pAddress)), st1); break;
//case ST2: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st2->m_pAddress)), st2); break;
//case ST3: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st3->m_pAddress)), st3); break;
//case ST4: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st4->m_pAddress)), st4); break;
//case ST5: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st5->m_pAddress)), st5); break;
//case ST6: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st6->m_pAddress)), st6); break;
//case ST7: masm.movl(tword_ptr_abs(Ptr(m_pRegisters->m_st7->m_pAddress)), st7); break;
default: puts("Unsupported register.");
}
}
}
void CHook::Write_RestoreRegisters(sp::MacroAssembler& masm, HookType_t type)
{
ke::Vector<Register_t> vecRegistersToSave = m_pCallingConvention->GetRegisters();
for (size_t i = 0; i < vecRegistersToSave.length(); i++)
{
switch (vecRegistersToSave[i])
{
// ========================================================================
// >> 8-bit General purpose registers
// ========================================================================
case AL: masm.movl(r8_al, Operand(ExternalAddress(m_pRegisters->m_al->m_pAddress))); break;
case CL: masm.movl(r8_cl, Operand(ExternalAddress(m_pRegisters->m_cl->m_pAddress))); break;
case DL: masm.movl(r8_dl, Operand(ExternalAddress(m_pRegisters->m_dl->m_pAddress))); break;
case BL: masm.movl(r8_bl, Operand(ExternalAddress(m_pRegisters->m_bl->m_pAddress))); break;
case AH: masm.movl(r8_ah, Operand(ExternalAddress(m_pRegisters->m_ah->m_pAddress))); break;
case CH: masm.movl(r8_ch, Operand(ExternalAddress(m_pRegisters->m_ch->m_pAddress))); break;
case DH: masm.movl(r8_dh, Operand(ExternalAddress(m_pRegisters->m_dh->m_pAddress))); break;
case BH: masm.movl(r8_bh, Operand(ExternalAddress(m_pRegisters->m_bh->m_pAddress))); break;
// ========================================================================
// >> 32-bit General purpose registers
// ========================================================================
case EAX: masm.movl(eax, Operand(ExternalAddress(m_pRegisters->m_eax->m_pAddress))); break;
case ECX: masm.movl(ecx, Operand(ExternalAddress(m_pRegisters->m_ecx->m_pAddress))); break;
case EDX: masm.movl(edx, Operand(ExternalAddress(m_pRegisters->m_edx->m_pAddress))); break;
case EBX: masm.movl(ebx, Operand(ExternalAddress(m_pRegisters->m_ebx->m_pAddress))); break;
case ESP: masm.movl(esp, Operand(ExternalAddress(m_pRegisters->m_esp->m_pAddress))); break;
case EBP: masm.movl(ebp, Operand(ExternalAddress(m_pRegisters->m_ebp->m_pAddress))); break;
case ESI: masm.movl(esi, Operand(ExternalAddress(m_pRegisters->m_esi->m_pAddress))); break;
case EDI: masm.movl(edi, Operand(ExternalAddress(m_pRegisters->m_edi->m_pAddress))); break;
// ========================================================================
// >> 128-bit XMM registers
// ========================================================================
// TODO: Also provide movups?
case XMM0: masm.movaps(xmm0, Operand(ExternalAddress(m_pRegisters->m_xmm0->m_pAddress))); break;
case XMM1: masm.movaps(xmm1, Operand(ExternalAddress(m_pRegisters->m_xmm1->m_pAddress))); break;
case XMM2: masm.movaps(xmm2, Operand(ExternalAddress(m_pRegisters->m_xmm2->m_pAddress))); break;
case XMM3: masm.movaps(xmm3, Operand(ExternalAddress(m_pRegisters->m_xmm3->m_pAddress))); break;
case XMM4: masm.movaps(xmm4, Operand(ExternalAddress(m_pRegisters->m_xmm4->m_pAddress))); break;
case XMM5: masm.movaps(xmm5, Operand(ExternalAddress(m_pRegisters->m_xmm5->m_pAddress))); break;
case XMM6: masm.movaps(xmm6, Operand(ExternalAddress(m_pRegisters->m_xmm6->m_pAddress))); break;
case XMM7: masm.movaps(xmm7, Operand(ExternalAddress(m_pRegisters->m_xmm7->m_pAddress))); break;
// ========================================================================
// >> 80-bit FPU registers
// ========================================================================
case ST0:
if (type == HOOKTYPE_POST) {
// Replace the top of the FPU stack.
// Copy st0 to st0 and pop -> just pop the FPU stack.
masm.fstp(st0);
// Push a value to the FPU stack.
// TODO: Only write back when changed? Save full 80bits for that case.
// Avoid truncation of the data if it's unchanged.
masm.fld32(Operand(ExternalAddress(m_pRegisters->m_st0->m_pAddress)));
}
break;
//case ST1: masm.movl(st1, tword_ptr_abs(Ptr(m_pRegisters->m_st1->m_pAddress))); break;
//case ST2: masm.movl(st2, tword_ptr_abs(Ptr(m_pRegisters->m_st2->m_pAddress))); break;
//case ST3: masm.movl(st3, tword_ptr_abs(Ptr(m_pRegisters->m_st3->m_pAddress))); break;
//case ST4: masm.movl(st4, tword_ptr_abs(Ptr(m_pRegisters->m_st4->m_pAddress))); break;
//case ST5: masm.movl(st5, tword_ptr_abs(Ptr(m_pRegisters->m_st5->m_pAddress))); break;
//case ST6: masm.movl(st6, tword_ptr_abs(Ptr(m_pRegisters->m_st6->m_pAddress))); break;
//case ST7: masm.movl(st7, tword_ptr_abs(Ptr(m_pRegisters->m_st7->m_pAddress))); break;
default: puts("Unsupported register.");
}
}
}