/* * MinHook - Minimalistic API Hook Library * Copyright (C) 2009 Tsuda Kageyu. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include "pstdint.h" #if defined _M_X64 #include "hde64/include/hde64.h" #elif defined _M_IX86 #include "hde32/hde32.h" #endif #include "trampoline.h" namespace MinHook { namespace { #if defined _M_X64 typedef hde64s hde_t; inline unsigned int hde_disasm(const void* code, hde_t* hs) { return hde64_disasm(code, hs); } #elif defined _M_IX86 typedef hde32s hde_t; inline unsigned int hde_disasm(const void* code, hde_t* hs) { return hde32_disasm(code, hs); } #endif // Structs for writing x86/x64 instcutions. #pragma pack(push, 1) struct JMP_REL_SHORT { uint8_t opcode; uint8_t operand; }; struct JMP_REL { uint8_t opcode; uint32_t operand; }; typedef JMP_REL CALL_REL; struct JMP_ABS { uint16_t opcode; uint32_t operand; }; typedef JMP_ABS CALL_ABS, JCC_REL; // 32/64bit indirect absolute conditional jump that x86/x64 lacks. struct JCC_ABS { uint8_t opcode; // 7* 02 J** +4 uint8_t dummy0; uint8_t dummy1; // EB 06 JMP +8 uint8_t dummy2; uint16_t dummy3; // FF25 xxxxxxxx JMP [RIP+xxxxxxxx] uint32_t operand; }; #pragma pack(pop) uintptr_t GetRelativeBranchDestination(uint8_t* pInst, const hde_t& hs, bool isShort); inline bool IsInternalJump(void* pTarget, uintptr_t dest); template void AppendTempAddress(uintptr_t address, size_t pos, const T& inst, CREATE_TREMPOLINE_T& ct); #if defined _M_X64 void AppendRipRelativeAddress(uint8_t* pInst, size_t pos, const hde_t& hs, CREATE_TREMPOLINE_T& ct); #endif inline void SetJccOpcode(const hde_t& hs, JCC_REL& inst); inline void SetJccOpcode(const hde_t& hs, JCC_ABS& inst); bool IsCodePadding(uint8_t* pInst, size_t size); bool IsExecutableAddress(void* pAddress); }} namespace MinHook { bool CreateTrampolineFunction(CREATE_TREMPOLINE_T& ct) { assert(("CreateTrampolineFunction", ct.pTarget != NULL)); #if defined _M_X64 CALL_ABS call = { 0x15FF, 0x00000000 }; JMP_ABS jmp = { 0x25FF, 0x00000000 }; JCC_ABS jcc = { 0x70, 0x02, 0xEB, 0x06, 0x25FF, 0x00000000 }; #elif defined _M_IX86 CALL_REL call = { 0xE8, 0x00000000 }; JMP_REL jmp = { 0xE9, 0x00000000 }; JCC_REL jcc = { 0x800F, 0x00000000 }; #endif size_t oldPos = 0; size_t newPos = 0; uintptr_t jmpDest = 0; // Destination address of an internal jump. bool finished = false; // Is the function completed? while (!finished) { uint8_t *pInst = reinterpret_cast(ct.pTarget) + oldPos; hde_t hs; hde_disasm(pInst, &hs); if ((hs.flags & F_ERROR) == F_ERROR) { return false; } void* pCopySrc = pInst; size_t copySize = hs.len; if (pInst - reinterpret_cast(ct.pTarget) >= sizeof(JMP_REL)) { // The trampoline function is long enough. // Complete the function with the jump to the target function. AppendTempAddress(reinterpret_cast(pInst), newPos, jmp, ct); pCopySrc = &jmp; copySize = sizeof(jmp); finished = true; } #if defined _M_X64 else if ((hs.modrm & 0xC7) == 0x05) { // Instructions using RIP relative addressing. (ModR/M = 00???101B) // Modify only RIP relative address. AppendRipRelativeAddress(pInst, newPos, hs, ct); // Complete the function if JMP (FF /4). if (hs.opcode == 0xFF && hs.modrm_reg == 4) { finished = true; } } #endif else if (hs.opcode == 0xE8) { // Direct relative CALL AppendTempAddress(GetRelativeBranchDestination(pInst, hs, false), newPos, call, ct); pCopySrc = &call; copySize = sizeof(call); } else if ((hs.opcode & 0xFD) == 0xE9) { // Direct relative JMP (EB or E9) uintptr_t dest = GetRelativeBranchDestination(pInst, hs, hs.opcode == 0xEB); // Simply copy an internal jump. if (IsInternalJump(ct.pTarget, dest)) { jmpDest = std::max(jmpDest, dest); } else { AppendTempAddress(dest, newPos, jmp, ct); pCopySrc = &jmp; copySize = sizeof(jmp); // 分岐中でなければ関数を終了 finished = (reinterpret_cast(pInst) >= jmpDest); } } else if ((hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0 || (hs.opcode2 & 0xF0) == 0x80) { // Direct relative Jcc uintptr_t dest = GetRelativeBranchDestination(pInst, hs, (hs.opcode & 0xF0) == 0x70 || (hs.opcode & 0xFC) == 0xE0); // Simply copy an internal jump. if (IsInternalJump(ct.pTarget, dest)) { jmpDest = std::max(jmpDest, dest); } else if ((hs.opcode & 0xFC) == 0xE0) // JCXZ/JECXZ to the outside are not supported. { return false; } else { AppendTempAddress(dest, newPos, jcc, ct); SetJccOpcode(hs, jcc); pCopySrc = &jcc; copySize = sizeof(jcc); } } else if ((hs.opcode & 0xFE) == 0xC2) { // RET (C2 or C3) // Complete the function if not in a branch. finished = (reinterpret_cast(pInst) >= jmpDest); } // Can't alter the instruction length in a branch. if (reinterpret_cast(pInst) < jmpDest && copySize != hs.len) { return false; } ct.trampoline.resize(newPos + copySize); memcpy(&ct.trampoline[ newPos ], pCopySrc, copySize); ct.oldIPs.push_back(oldPos); oldPos += hs.len; ct.newIPs.push_back(newPos); newPos += copySize; } // Is there enough place for a long jump? if (oldPos < sizeof(JMP_REL) && !IsCodePadding(reinterpret_cast(ct.pTarget) + oldPos, sizeof(JMP_REL) - oldPos)) { // Is there enough place for a short jump? if (oldPos < sizeof(JMP_REL_SHORT) && !IsCodePadding(reinterpret_cast(ct.pTarget) + oldPos, sizeof(JMP_REL_SHORT) - oldPos)) { return false; } // Can we place the long jump above the function? if (!IsExecutableAddress(reinterpret_cast(ct.pTarget) - sizeof(JMP_REL))) { return false; } if (!IsCodePadding(reinterpret_cast(ct.pTarget) - sizeof(JMP_REL), sizeof(JMP_REL))) { return false; } ct.patchAbove = true; } return true; } bool ResolveTemporaryAddresses(CREATE_TREMPOLINE_T& ct) { assert(("ResolveTemporaryAddresses", ct.pTrampoline != NULL)); #if defined _M_X64 assert(("ResolveTemporaryAddresses", (ct.table.empty() || ct.pTable != NULL))); #endif #if defined _M_X64 uintptr_t* pt = reinterpret_cast(ct.pTable); #endif for (size_t i = 0, count = ct.tempAddr.size(); i < count; ++i) { const TEMP_ADDR& ta = ct.tempAddr[i]; if (ta.position > ct.trampoline.size() - sizeof(uint32_t)) { return false; } uintptr_t addr; #if defined _M_X64 if (ta.address < 0x10000) // Table index when < 0x10000, otherwise RIP relative address. { addr = reinterpret_cast(pt++); } else #endif { addr = ta.address; } *reinterpret_cast(&ct.trampoline[ ta.position ]) = static_cast(addr - (reinterpret_cast(ct.pTrampoline) + ta.pc)); } for (size_t i = 0; i < ct.oldIPs.size(); ++i) { ct.oldIPs[ i ] += reinterpret_cast(ct.pTarget); ct.newIPs[ i ] += reinterpret_cast(ct.pTrampoline); } if (ct.patchAbove) { ct.oldIPs.push_back(reinterpret_cast(ct.pTarget)); ct.newIPs.push_back(reinterpret_cast(ct.pTarget) - sizeof(JMP_REL)); } return true; } } namespace MinHook { namespace { inline uintptr_t GetRelativeBranchDestination(uint8_t* pInst, const hde_t& hs, bool isShort) { int32_t imm = isShort ? static_cast(hs.imm.imm8) : static_cast(hs.imm.imm32); return reinterpret_cast(pInst) + hs.len + imm; } inline bool IsInternalJump(void* pTarget, uintptr_t dest) { uintptr_t pt = reinterpret_cast(pTarget); return (pt <= dest && dest <= pt + sizeof(JMP_REL)); } template void AppendTempAddress(uintptr_t address, size_t pos, const T& inst, CREATE_TREMPOLINE_T& ct) { TEMP_ADDR ta; #if defined _M_X64 ta.address = ct.table.size(); ct.table.push_back(address); #elif defined _M_IX86 ta.address = address; #endif ta.position = pos + (reinterpret_cast(&inst.operand) - reinterpret_cast(&inst)); ta.pc = pos + sizeof(inst); ct.tempAddr.push_back(ta); } #if defined _M_X64 void AppendRipRelativeAddress(uint8_t* pInst, size_t pos, const hde_t& hs, CREATE_TREMPOLINE_T& ct) { TEMP_ADDR ta; ta.address = reinterpret_cast(pInst) + hs.len + static_cast(hs.disp.disp32); ta.position = pos + hs.len - ((hs.flags & 0x3C) >> 2) - 4; // pos + instruction length - immediate value length - 4 ta.pc = pos + hs.len; ct.tempAddr.push_back(ta); } #endif inline void SetJccOpcode(const hde_t& hs, JCC_REL& inst) { uint8_t n = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); inst.opcode = 0x800F | (n << 8); } inline void SetJccOpcode(const hde_t& hs, JCC_ABS& inst) { uint8_t n = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F); inst.opcode = 0x70 | n; } bool IsCodePadding(uint8_t* pInst, size_t size) { uint8_t paddingByte = pInst[0]; switch (paddingByte) { case 0x00: case 0x90: // NOP case 0xCC: // INT3 for (size_t i = 1; i < size; ++i) { if (pInst[i] != paddingByte) { return false; } } return true; default: return false; } } bool IsExecutableAddress(void* pAddress) { static const DWORD PageExecuteMask = (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY); // Is the address is allocated and executable? MEMORY_BASIC_INFORMATION mi = { 0 }; VirtualQuery(pAddress, &mi, sizeof(mi)); return ((mi.Protect & PageExecuteMask) != 0); } }}