Skip to content

Commit 0569b9c

Browse files
committed
hooksystem: manually map trampoline addresses
better patching of rip calls as we are close enough to just change them up
1 parent cba9c5f commit 0569b9c

File tree

2 files changed

+154
-15
lines changed

2 files changed

+154
-15
lines changed

src/plugins/HookSystem.cpp

Lines changed: 140 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <sys/mman.h>
99
#include <unistd.h>
1010
#include <cstring>
11+
#include <fstream>
1112

1213
CFunctionHook::CFunctionHook(HANDLE owner, void* source, void* destination) {
1314
m_pSource = source;
@@ -64,10 +65,14 @@ CFunctionHook::SInstructionProbe CFunctionHook::probeMinimumJumpSize(void* start
6465
}
6566

6667
CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstructionProbe& probe) {
68+
SAssembly returns;
69+
6770
// analyze the code and fix what we know how to.
6871
uint64_t currentAddress = (uint64_t)m_pSource;
6972
// actually newline + 1
70-
size_t lastAsmNewline = 0;
73+
size_t lastAsmNewline = 0;
74+
// needle for destination binary
75+
size_t currentDestinationOffset = 0;
7176
std::string assemblyBuilder;
7277
for (auto& len : probe.insSizes) {
7378

@@ -82,17 +87,27 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
8287
return {};
8388
const uint64_t DESTINATION = currentAddress + OFFSET + len;
8489

85-
if (code.starts_with("mov")) {
86-
// mov +0xdeadbeef(%rip), %rax
87-
assemblyBuilder += std::format("movabs $0x{:x}, {}\n", DESTINATION, tokens[2]);
88-
} else if (code.starts_with("call")) {
90+
if (code.starts_with("call")) {
8991
// call +0xdeadbeef(%rip)
9092
assemblyBuilder += std::format("pushq %rax\nmovabs $0x{:x}, %rax\ncallq *%rax\npopq %rax\n", DESTINATION);
93+
currentDestinationOffset += 14;
9194
} else if (code.starts_with("lea")) {
9295
// lea 0xdeadbeef(%rip), %rax
9396
assemblyBuilder += std::format("movabs $0x{:x}, {}\n", DESTINATION, tokens[2]);
97+
currentDestinationOffset += 10;
9498
} else {
95-
return {};
99+
auto ADDREND = code.find("(%rip)");
100+
auto ADDRSTART = (code.substr(0, ADDREND).find_last_of(' '));
101+
102+
if (ADDREND == std::string::npos || ADDRSTART == std::string::npos)
103+
return {};
104+
105+
const uint64_t PREDICTEDRIP = (uint64_t)m_pTrampolineAddr + currentDestinationOffset + len;
106+
const bool POSITIVE = DESTINATION > PREDICTEDRIP;
107+
const uint64_t NEWRIPOFFSET = POSITIVE ? DESTINATION - PREDICTEDRIP : PREDICTEDRIP - DESTINATION;
108+
109+
assemblyBuilder += std::format("{} {}0x{:x}{}\n", code.substr(0, ADDRSTART), POSITIVE ? '+' : '-', NEWRIPOFFSET, code.substr(ADDREND));
110+
currentDestinationOffset += len;
96111
}
97112
} else if (code.contains("invalid")) {
98113
std::vector<uint8_t> bytes;
@@ -101,6 +116,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
101116
if (len == 4 && bytes[0] == 0xF3 && bytes[1] == 0x0F && bytes[2] == 0x1E && bytes[3] == 0xFA) {
102117
// F3 0F 1E FA = endbr64, udis doesn't understand that one
103118
assemblyBuilder += "endbr64\n";
119+
currentDestinationOffset += 4;
104120
} else {
105121
// raise error, unknown op
106122
std::string strBytes;
@@ -112,6 +128,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
112128
}
113129
} else {
114130
assemblyBuilder += code + "\n";
131+
currentDestinationOffset += len;
115132
}
116133

117134
lastAsmNewline = probe.assembly.find("\n", lastAsmNewline) + 1;
@@ -131,7 +148,7 @@ CFunctionHook::SAssembly CFunctionHook::fixInstructionProbeRIPCalls(const SInstr
131148
}
132149

133150
std::ifstream ifs("/tmp/hypr/.hookbinary2.o", std::ios::binary);
134-
SAssembly returns = {std::vector<char>(std::istreambuf_iterator<char>(ifs), {})};
151+
returns = {std::vector<char>(std::istreambuf_iterator<char>(ifs), {})};
135152
ifs.close();
136153
std::filesystem::remove("/tmp/hypr/.hookcode.asm");
137154
std::filesystem::remove("/tmp/hypr/.hookbinary.o");
@@ -158,6 +175,10 @@ bool CFunctionHook::hook() {
158175
// nop
159176
static constexpr uint8_t NOP = 0x90;
160177

178+
// alloc trampoline
179+
const auto MAX_TRAMPOLINE_SIZE = HOOK_TRAMPOLINE_MAX_SIZE; // we will never need more.
180+
m_pTrampolineAddr = (void*)g_pFunctionHookSystem->getAddressForTrampo();
181+
161182
// probe instructions to be trampolin'd
162183
SInstructionProbe probe;
163184
try {
@@ -174,9 +195,12 @@ bool CFunctionHook::hook() {
174195
const size_t HOOKSIZE = PROBEFIXEDASM.bytes.size();
175196
const size_t ORIGSIZE = probe.len;
176197

177-
// alloc trampoline
178-
const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX);
179-
m_pTrampolineAddr = mmap(NULL, TRAMPOLINE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
198+
const auto TRAMPOLINE_SIZE = sizeof(ABSOLUTE_JMP_ADDRESS) + HOOKSIZE + sizeof(PUSH_RAX);
199+
200+
if (TRAMPOLINE_SIZE > MAX_TRAMPOLINE_SIZE) {
201+
Debug::log(ERR, "[functionhook] failed, not enough space in trampo to alloc:\n{}", probe.assembly);
202+
return false;
203+
}
180204

181205
m_pOriginalBytes = malloc(ORIGSIZE);
182206
memcpy(m_pOriginalBytes, m_pSource, ORIGSIZE);
@@ -236,14 +260,11 @@ bool CFunctionHook::unhook() {
236260
// revert mprot
237261
mprotect((uint8_t*)m_pSource - ((uint64_t)m_pSource) % sysconf(_SC_PAGE_SIZE), sysconf(_SC_PAGE_SIZE), PROT_READ | PROT_EXEC);
238262

239-
// unmap
240-
munmap(m_pTrampolineAddr, m_iTrampoLen);
241-
242263
// reset vars
243264
m_bActive = false;
244265
m_iHookLen = 0;
245266
m_iTrampoLen = 0;
246-
m_pTrampolineAddr = nullptr;
267+
m_pTrampolineAddr = nullptr; // no unmapping, it's managed by the HookSystem
247268
m_pOriginalBytes = nullptr;
248269

249270
free(m_pOriginalBytes);
@@ -263,3 +284,108 @@ bool CHookSystem::removeHook(CFunctionHook* hook) {
263284
void CHookSystem::removeAllHooksFrom(HANDLE handle) {
264285
std::erase_if(m_vHooks, [&](const auto& other) { return other->m_pOwner == handle; });
265286
}
287+
288+
static uintptr_t seekNewPageAddr() {
289+
const uint64_t PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);
290+
auto MAPS = std::ifstream("/proc/self/maps");
291+
292+
uint64_t lastStart = 0, lastEnd = 0;
293+
294+
std::string line;
295+
while (std::getline(MAPS, line)) {
296+
CVarList props{line, 0, 's', true};
297+
298+
uint64_t start = 0, end = 0;
299+
if (props[0].empty()) {
300+
Debug::log(WARN, "seekNewPageAddr: unexpected line in self maps");
301+
continue;
302+
}
303+
304+
CVarList startEnd{props[0], 0, '-', true};
305+
306+
try {
307+
start = std::stoull(startEnd[0], nullptr, 16);
308+
end = std::stoull(startEnd[1], nullptr, 16);
309+
} catch (std::exception& e) {
310+
Debug::log(WARN, "seekNewPageAddr: unexpected line in self maps: {}", line);
311+
continue;
312+
}
313+
314+
Debug::log(LOG, "seekNewPageAddr: page 0x{:x} - 0x{:x}", start, end);
315+
316+
if (lastStart == 0) {
317+
lastStart = start;
318+
lastEnd = end;
319+
continue;
320+
}
321+
322+
if (start - lastEnd > PAGESIZE_VAR * 2) {
323+
Debug::log(LOG, "seekNewPageAddr: found gap: 0x{:x}-0x{:x} ({} bytes)", lastEnd, start, start - lastEnd);
324+
MAPS.close();
325+
return lastEnd;
326+
}
327+
328+
lastStart = start;
329+
lastEnd = end;
330+
}
331+
332+
MAPS.close();
333+
return 0;
334+
}
335+
336+
uint64_t CHookSystem::getAddressForTrampo() {
337+
// yes, technically this creates a memory leak of 64B every hook creation. But I don't care.
338+
// tracking all the users of the memory would be painful.
339+
// Nobody will hook 100k times, and even if, that's only 640kB. Nothing.
340+
341+
SAllocatedPage* page = nullptr;
342+
for (auto& p : pages) {
343+
if (p.used + HOOK_TRAMPOLINE_MAX_SIZE > p.len)
344+
continue;
345+
346+
page = &p;
347+
break;
348+
}
349+
350+
if (!page)
351+
page = &pages.emplace_back();
352+
353+
if (!page->addr) {
354+
// allocate it
355+
Debug::log(LOG, "getAddressForTrampo: Allocating new page for hooks");
356+
const uint64_t PAGESIZE_VAR = sysconf(_SC_PAGE_SIZE);
357+
const auto BASEPAGEADDR = seekNewPageAddr();
358+
for (int attempt = 0; attempt < 2; ++attempt) {
359+
for (int i = 2; i >= 0; --i) {
360+
const auto PAGEADDR = BASEPAGEADDR + i * PAGESIZE_VAR;
361+
362+
page->addr = (uint64_t)mmap((void*)PAGEADDR, PAGESIZE_VAR, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
363+
page->len = PAGESIZE_VAR;
364+
page->used = 0;
365+
366+
Debug::log(LOG, "Attempted to allocate 0x{:x}, got 0x{:x}", PAGEADDR, page->addr);
367+
368+
if (page->addr == (uint64_t)MAP_FAILED)
369+
continue;
370+
if (page->addr != PAGEADDR && attempt == 0) {
371+
munmap((void*)page->addr, PAGESIZE_VAR);
372+
page->addr = 0;
373+
page->len = 0;
374+
continue;
375+
}
376+
377+
break;
378+
}
379+
if (page->addr)
380+
break;
381+
}
382+
}
383+
384+
const auto ADDRFORCONSUMER = page->addr + page->used;
385+
386+
page->used += HOOK_TRAMPOLINE_MAX_SIZE;
387+
388+
Debug::log(LOG, "getAddressForTrampo: Returning addr 0x{:x} for page at 0x{:x}", ADDRFORCONSUMER, page->addr);
389+
390+
return ADDRFORCONSUMER;
391+
}

src/plugins/HookSystem.hpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
#include <vector>
55
#include <memory>
66

7-
#define HANDLE void*
7+
#define HANDLE void*
8+
#define HOOK_TRAMPOLINE_MAX_SIZE 64
89

910
class CFunctionHook {
1011
public:
@@ -60,6 +61,18 @@ class CHookSystem {
6061

6162
private:
6263
std::vector<std::unique_ptr<CFunctionHook>> m_vHooks;
64+
65+
uint64_t getAddressForTrampo();
66+
67+
struct SAllocatedPage {
68+
uint64_t addr = 0;
69+
uint64_t len = 0;
70+
uint64_t used = 0;
71+
};
72+
73+
std::vector<SAllocatedPage> pages;
74+
75+
friend class CFunctionHook;
6376
};
6477

6578
inline std::unique_ptr<CHookSystem> g_pFunctionHookSystem;

0 commit comments

Comments
 (0)