hookSystem: use a full trampo setup for hooks

instead of planting a longjmp at the fn head, make a shortjmp to a launch trampo

this helps with shortjmps that can be in the fn and will break when relocated. They are very unlikely to occur within the first 5 bytes (jmp rel32) but can happen in the first 10 or so (longjmp)

fixes csgo-vk-fix on latest main with release building on gcc / clang
This commit is contained in:
Vaxry 2025-10-07 12:37:21 +01:00
parent c3747fab56
commit 5a20862126
Signed by: vaxry
GPG key ID: 665806380871D640
2 changed files with 55 additions and 43 deletions

View file

@ -106,7 +106,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
if (ADDREND == std::string::npos || ADDRSTART == std::string::npos) if (ADDREND == std::string::npos || ADDRSTART == std::string::npos)
return {}; return {};
const uint64_t PREDICTEDRIP = rc<uint64_t>(m_trampolineAddr) + currentDestinationOffset + len; const uint64_t PREDICTEDRIP = rc<uint64_t>(m_landTrampolineAddr) + currentDestinationOffset + len;
const int32_t NEWRIPOFFSET = DESTINATION - PREDICTEDRIP; const int32_t NEWRIPOFFSET = DESTINATION - PREDICTEDRIP;
size_t ripOffset = 0; size_t ripOffset = 0;
@ -144,25 +144,25 @@ bool CFunctionHook::hook() {
return false; return false;
#endif #endif
// movabs $0,%rax | jmpq *%rax // jmp rel32
// offset for addr: 2 // offset for relative addr: 1
static constexpr uint8_t RELATIVE_JMP_ADDRESS[] = {0xE9, 0x00, 0x00, 0x00, 0x00};
static constexpr size_t RELATIVE_JMP_ADDRESS_OFFSET = 1;
// movabs $0,%rax | jmpq *rax
static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0}; static constexpr uint8_t ABSOLUTE_JMP_ADDRESS[] = {0x48, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xE0};
static constexpr size_t ABSOLUTE_JMP_ADDRESS_OFFSET = 2; static constexpr size_t ABSOLUTE_JMP_ADDRESS_OFFSET = 2;
// pushq %rax
static constexpr uint8_t PUSH_RAX[] = {0x50};
// popq %rax
static constexpr uint8_t POP_RAX[] = {0x58};
// nop // nop
static constexpr uint8_t NOP = 0x90; static constexpr uint8_t NOP = 0x90;
// alloc trampoline // alloc trampolines
const auto MAX_TRAMPOLINE_SIZE = HOOK_TRAMPOLINE_MAX_SIZE; // we will never need more. const auto MAX_TRAMPOLINE_SIZE = HOOK_TRAMPOLINE_MAX_SIZE; // we will never need more.
m_trampolineAddr = rc<void*>(g_pFunctionHookSystem->getAddressForTrampo()); m_launchTrampolineAddr = rc<void*>(g_pFunctionHookSystem->getAddressForTrampo());
m_landTrampolineAddr = rc<void*>(g_pFunctionHookSystem->getAddressForTrampo());
// probe instructions to be trampolin'd // probe instructions to be trampolin'd
SInstructionProbe probe; SInstructionProbe probe;
try { try {
probe = probeMinimumJumpSize(m_source, sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(PUSH_RAX) + sizeof(POP_RAX)); probe = probeMinimumJumpSize(m_source, sizeof(RELATIVE_JMP_ADDRESS));
} catch (std::exception& e) { return false; } } catch (std::exception& e) { return false; }
const auto PROBEFIXEDASM = fixInstructionProbeRIPCalls(probe); const auto PROBEFIXEDASM = fixInstructionProbeRIPCalls(probe);
@ -172,10 +172,15 @@ bool CFunctionHook::hook() {
return false; return false;
} }
if (rc<int64_t>(m_source) - rc<int64_t>(m_destination) > 2000000000 /* 2 GB */) {
Debug::log(ERR, "[functionhook] failed, source and dest are over 2GB apart");
return false;
}
const size_t HOOKSIZE = PROBEFIXEDASM.bytes.size(); const size_t HOOKSIZE = PROBEFIXEDASM.bytes.size();
const size_t ORIGSIZE = probe.len; const size_t ORIGSIZE = probe.len;
const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX); const auto TRAMPOLINE_SIZE = sizeof(RELATIVE_JMP_ADDRESS) + HOOKSIZE;
if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) { if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) {
Debug::log(ERR, "[functionhook] failed, not enough space in trampo to alloc:\n{}", probe.assembly); Debug::log(ERR, "[functionhook] failed, not enough space in trampo to alloc:\n{}", probe.assembly);
@ -185,39 +190,46 @@ bool CFunctionHook::hook() {
m_originalBytes.resize(ORIGSIZE); m_originalBytes.resize(ORIGSIZE);
memcpy(m_originalBytes.data(), m_source, ORIGSIZE); memcpy(m_originalBytes.data(), m_source, ORIGSIZE);
// populate trampoline // populate land trampoline
memcpy(m_trampolineAddr, PROBEFIXEDASM.bytes.data(), HOOKSIZE); // first, original but fixed func bytes memcpy(m_landTrampolineAddr, PROBEFIXEDASM.bytes.data(), HOOKSIZE); // first, original but fixed func bytes
memcpy(sc<uint8_t*>(m_trampolineAddr) + HOOKSIZE, PUSH_RAX, sizeof(PUSH_RAX)); // then, pushq %rax memcpy(sc<uint8_t*>(m_landTrampolineAddr) + HOOKSIZE, RELATIVE_JMP_ADDRESS, sizeof(RELATIVE_JMP_ADDRESS)); // then, jump to source
memcpy(sc<uint8_t*>(m_trampolineAddr) + HOOKSIZE + sizeof(PUSH_RAX), ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // then, jump to source
// fixup trampoline addr // populate short jump addr
*rc<uint64_t*>(sc<uint8_t*>(m_trampolineAddr) + TRAMPOLINE_SIZE - sizeof(ABSOLUTE_JMP_ADDRESS) + ABSOLUTE_JMP_ADDRESS_OFFSET) = *rc<int32_t*>(sc<uint8_t*>(m_landTrampolineAddr) + TRAMPOLINE_SIZE - sizeof(RELATIVE_JMP_ADDRESS) + RELATIVE_JMP_ADDRESS_OFFSET) =
rc<uint64_t>(sc<uint8_t*>(m_source) + sizeof(ABSOLUTE_JMP_ADDRESS)); sc<int64_t>((sc<uint8_t*>(m_source) + probe.len) // jump to source + probe len (skip header)
- (sc<uint8_t*>(m_landTrampolineAddr) + TRAMPOLINE_SIZE) // from trampo + size - jmp (not - size because jmp is rel to rip after instr)
);
// make jump to hk // populate launch trampoline
memcpy(m_launchTrampolineAddr, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); // long jump to our hk
// populate long jump addr
*rc<uint64_t*>(sc<uint8_t*>(m_launchTrampolineAddr) + ABSOLUTE_JMP_ADDRESS_OFFSET) = rc<uint64_t>(m_destination); // long jump to hk fn
// make short jump to launch trampoile
const auto PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE); const auto PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);
const uint8_t* PROTSTART = sc<uint8_t*>(m_source) - (rc<uint64_t>(m_source) % PAGESIZE_VAR); const uint8_t* PROTSTART = sc<uint8_t*>(m_source) - (rc<uint64_t>(m_source) % PAGESIZE_VAR);
const size_t PROTLEN = std::ceil(sc<float>(ORIGSIZE + (rc<uint64_t>(m_source) - rc<uint64_t>(PROTSTART))) / sc<float>(PAGESIZE_VAR)) * PAGESIZE_VAR; const size_t PROTLEN = std::ceil(sc<float>(ORIGSIZE + (rc<uint64_t>(m_source) - rc<uint64_t>(PROTSTART))) / sc<float>(PAGESIZE_VAR)) * PAGESIZE_VAR;
mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_WRITE | PROT_EXEC); mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_WRITE | PROT_EXEC);
memcpy(m_source, ABSOLUTE_JMP_ADDRESS, sizeof(ABSOLUTE_JMP_ADDRESS)); memcpy(m_source, RELATIVE_JMP_ADDRESS, sizeof(RELATIVE_JMP_ADDRESS));
// make popq %rax and NOP all remaining size_t currentOp = sizeof(RELATIVE_JMP_ADDRESS);
memcpy(sc<uint8_t*>(m_source) + sizeof(ABSOLUTE_JMP_ADDRESS), POP_RAX, sizeof(POP_RAX));
size_t currentOp = sizeof(ABSOLUTE_JMP_ADDRESS) + sizeof(POP_RAX);
memset(sc<uint8_t*>(m_source) + currentOp, NOP, ORIGSIZE - currentOp); memset(sc<uint8_t*>(m_source) + currentOp, NOP, ORIGSIZE - currentOp);
// fixup jump addr // populate short jump addr
*rc<uint64_t*>(sc<uint8_t*>(m_source) + ABSOLUTE_JMP_ADDRESS_OFFSET) = rc<uint64_t>(m_destination); *rc<int32_t*>(sc<uint8_t*>(m_source) + RELATIVE_JMP_ADDRESS_OFFSET) = sc<int32_t>( //
rc<uint64_t>(m_launchTrampolineAddr) // jump to the launch trampoline which jumps to hk
- (rc<uint64_t>(m_source) + 5) // from source
);
// revert mprot // revert mprot
mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_EXEC); mprotect(const_cast<uint8_t*>(PROTSTART), PROTLEN, PROT_READ | PROT_EXEC);
// set original addr to trampo addr // set original addr to land trampo addr
m_original = m_trampolineAddr; m_original = m_landTrampolineAddr;
m_active = true; m_active = true;
m_hookLen = ORIGSIZE; m_hookLen = ORIGSIZE;
m_trampoLen = TRAMPOLINE_SIZE;
return true; return true;
} }
@ -241,11 +253,11 @@ bool CFunctionHook::unhook() {
mprotect(sc<uint8_t*>(m_source) - rc<uint64_t>(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC); mprotect(sc<uint8_t*>(m_source) - rc<uint64_t>(m_source) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
// reset vars // reset vars
m_active = false; m_active = false;
m_hookLen = 0; m_hookLen = 0;
m_trampoLen = 0; m_landTrampolineAddr = nullptr; // no unmapping, it's managed by the HookSystem
m_trampolineAddr = nullptr; // no unmapping, it's managed by the HookSystem m_launchTrampolineAddr = nullptr; // no unmapping, it's managed by the HookSystem
m_original = nullptr; m_original = nullptr;
m_originalBytes.clear(); m_originalBytes.clear();
return true; return true;

View file

@ -6,7 +6,7 @@
#include "../helpers/memory/Memory.hpp" #include "../helpers/memory/Memory.hpp"
#define HANDLE void* #define HANDLE void*
#define HOOK_TRAMPOLINE_MAX_SIZE 64 #define HOOK_TRAMPOLINE_MAX_SIZE 32
class CFunctionHook { class CFunctionHook {
public: public:
@ -24,13 +24,13 @@ class CFunctionHook {
void* m_original = nullptr; void* m_original = nullptr;
private: private:
void* m_source = nullptr; void* m_source = nullptr;
void* m_trampolineAddr = nullptr; void* m_launchTrampolineAddr = nullptr;
void* m_destination = nullptr; void* m_landTrampolineAddr = nullptr;
size_t m_hookLen = 0; void* m_destination = nullptr;
size_t m_trampoLen = 0; size_t m_hookLen = 0;
HANDLE m_owner = nullptr; HANDLE m_owner = nullptr;
bool m_active = false; bool m_active = false;
std::vector<unsigned char> m_originalBytes; std::vector<unsigned char> m_originalBytes;