/** * ============================================================================= * 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 #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 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 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."); } } }