From f0315d629e62d539bdf055b9aea7f993819a3f4a Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 02:17:47 +0200 Subject: [PATCH 01/22] fix(instruction): remove implicit conversion operator --- include/lime/instruction.hpp | 1 - src/hooks.hook.cpp | 22 +++++++++++----------- src/instruction.cpp | 21 +++++++++++---------- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/include/lime/instruction.hpp b/include/lime/instruction.hpp index d0a8e8d..6b5e5d8 100644 --- a/include/lime/instruction.hpp +++ b/include/lime/instruction.hpp @@ -79,7 +79,6 @@ namespace lime [[nodiscard]] std::optional operator+(std::size_t) const; public: - [[nodiscard]] operator std::uintptr_t() const; [[nodiscard]] std::strong_ordering operator<=>(const instruction &) const; public: diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index d98f9c6..4f7c342 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -81,10 +81,10 @@ namespace lime if (auto follow = start.follow(); follow) { start = std::move(follow.value()); - page.emplace(page::unsafe(start)); + page.emplace(page::unsafe(start.addr())); } - rtn->m_impl->source = std::make_unique
(address::unsafe(std::move(start))); + rtn->m_impl->source = std::make_unique
(address::unsafe(start.addr())); rtn->m_impl->source_page = std::make_unique(std::move(page.value())); rtn->m_impl->target = target; @@ -98,7 +98,7 @@ namespace lime # The springboard is used to jump to the target function. # The idea here is just that our module is probably too far away from our target, so we just use an absolute jmp - # for simplicity sake. As we wan't to override as little instructions as possible in the source function we use + # for simplicity sake. As we want to override as little instructions as possible in the source function we use # the springboard. */ @@ -234,7 +234,7 @@ namespace lime const auto disp = current.displacement(); - if (disp.size > 0 && !try_offset(disp, current, difference)) + if (disp.size > 0 && !try_offset(disp, current.addr(), difference)) { return false; } @@ -248,7 +248,7 @@ namespace lime continue; } - if (try_offset(imm, current, difference)) + if (try_offset(imm, current.addr(), difference)) { continue; } @@ -260,7 +260,7 @@ namespace lime /* # We failed to offset, thus we have to fall back to a jump-table at the end of the trampoline - */ + */ const auto imm_value = std::visit([](auto amount) { return static_cast(amount); }, imm.amount); @@ -269,11 +269,11 @@ namespace lime const auto offset = -imm_value + static_cast(rel_jump); const auto destination = original_rip + imm_value; - const auto table_entry = impl::make_jmp(current, destination, near); + const auto table_entry = impl::make_jmp(current.addr(), destination, near); jump_table.insert(jump_table.end(), table_entry.begin(), table_entry.end()); - if (!try_offset(imm, current, -offset)) + if (!try_offset(imm, current.addr(), -offset)) { return false; } @@ -339,11 +339,11 @@ namespace lime bool hook_base::impl::can_relocate_far() { - auto i = 0u; + auto start = reinterpret_cast(prologue.data()); - while (prologue.size() > i) + for (auto i = 0u; prologue.size() > i;) { - const auto current = instruction::unsafe(reinterpret_cast(prologue.data() + i)); + auto current = instruction::unsafe(start + i); i += current.size(); if (!current.relative()) diff --git a/src/instruction.cpp b/src/instruction.cpp index bd1c734..4ce15b4 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -68,18 +68,24 @@ namespace lime auto start = m_impl->address - max_instruction_size; std::optional last; - while (start < m_impl->address) + for (auto current = start; current < m_impl->address; current++) { - auto instruction = at(start); + auto instruction = at(current); if (!instruction) { - start++; continue; } - start += instruction->size(); + auto next = instruction->next(); + + if (!next || next->addr() != m_impl->address) + { + continue; + } + last.emplace(std::move(instruction.value())); + break; } return last; @@ -132,14 +138,9 @@ namespace lime return at(m_impl->address + amount); } - instruction::operator std::uintptr_t() const - { - return addr(); - } - std::strong_ordering instruction::operator<=>(const instruction &other) const { - auto address = static_cast(other); + auto address = other.addr(); if (address > m_impl->address) { From ae2265ad7242c73c1555ce1023fc116b57e49f58 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 02:18:34 +0200 Subject: [PATCH 02/22] refacror(tests/instruction): use shell code [skip ci] --- tests/instruction.test.cpp | 121 ++++++++++++++++++++++--------------- 1 file changed, 73 insertions(+), 48 deletions(-) diff --git a/tests/instruction.test.cpp b/tests/instruction.test.cpp index cf3e191..b7b1f38 100644 --- a/tests/instruction.test.cpp +++ b/tests/instruction.test.cpp @@ -1,75 +1,100 @@ #include + +#include #include using namespace boost::ut; using namespace boost::ut::literals; -int func2(int a, int b) -{ - return a + b; -} +std::vector code = { +#if INTPTR_MAX == INT64_MAX + 0x8b, 0x02, // mov eax, [rdx] + 0x4c, 0x8b, 0xc1, // mov [rcx], eax + 0x48, 0x8B, 0x05, 0x05, 0x00, 0x00, 0x00, // mov rax, [rip+0x5] + 0xff, 0x25, 0x10, 0x00, 0x00, 0x00, // jmp [rip+0x10] + 0xff, 0xe0, // jmp rax +#else + 0x8B, 0x02, // mov eax, [edx] + 0x89, 0x01, // mov [ecx], eax + 0xA1, 0x05, 0x00, 0x00, 0x00, // mov eax, [rip+0x5] + 0xFF, 0x25, 0x10, 0x00, 0x00, 0x00, // jmp [rip+0x10] + 0xFF, 0xE0, // jmp eax +#endif +}; -int func1(int a) +suite<"Instruction"> instruction_suite = [] { - if (a > 10) - { - return 20; - } + static constexpr auto MNEMONIC_MOV = 436; + static constexpr auto MNEMONIC_JMP = 311; - return func2(a, 30); -} + auto *data = code.data(); -suite<"Instruction"> instruction_suite = [] -{ - auto instruction = lime::instruction::at(reinterpret_cast(func1)); + auto instruction = lime::instruction::at(reinterpret_cast(data)); expect(eq(instruction.has_value(), true)); - expect(eq(instruction->addr(), reinterpret_cast(func1))); - static constexpr auto MNEMONIC_JLE = 306; - auto jump = instruction->next(MNEMONIC_JLE); + expect(eq(instruction->size(), 2)); + expect(eq(instruction->addr(), reinterpret_cast(data))); - expect(eq(jump.has_value(), true)); - expect(eq(jump->relative(), true)); - expect(eq(jump->branching(), true)); + expect(eq(instruction->relative(), false)); + expect(eq(instruction->branching(), false)); + expect(eq(instruction->mnemonic(), MNEMONIC_MOV)); - expect(gt(jump->size(), 0)); - expect(eq(jump->mnemonic(), MNEMONIC_JLE)); - expect(gt(jump->addr(), instruction->addr())); + auto mov = instruction->next(); - static constexpr auto MNEMONIC_CALL = 67; - auto call = instruction->next(MNEMONIC_CALL); + expect(eq(mov.has_value(), true)); - expect(eq(call.has_value(), true)); - expect(eq(call->relative(), true)); + expect(eq(mov->relative(), false)); + expect(eq(mov->branching(), false)); + expect(eq(mov->mnemonic(), MNEMONIC_MOV)); - expect(gt(call->size(), 0)); - expect(eq(call->mnemonic(), MNEMONIC_CALL)); - expect(gt(call->addr(), instruction->addr())); + auto prev = mov->prev(); - /* - * We depend on compiler generated assembly here which isn't always what we'd expect it to be. - * This test would probably be better suited on a small shell-code (TODO). - * - * As clang and gcc both compile `func1` and `func2` into roughly what we'd expect while MSVC does not the following - * asserts are only enabled for Windows under clang-cl - */ + expect(eq(prev.has_value(), true)); + expect(eq(prev->mnemonic(), MNEMONIC_MOV)); -#if !defined(_MSC_VER) || defined(__clang__) - auto follow = call->follow(); + expect(eq(prev->size(), 2)); + expect(eq(prev->addr(), reinterpret_cast(data))); - expect(eq(follow.has_value(), true)); - expect(eq(follow->addr(), reinterpret_cast(func2))); + auto rel_move = mov->next(); - auto next = follow->next(); + expect(eq(rel_move.has_value(), true)); - expect(eq(next.has_value(), true)); - expect(gt(next->addr(), follow->addr())); + expect(eq(rel_move->relative(), true)); + expect(eq(rel_move->branching(), false)); + expect(eq(rel_move->mnemonic(), MNEMONIC_MOV)); - auto prev = next->prev(); + auto jmp = rel_move->next(); + auto jmp_until = instruction->next(MNEMONIC_JMP); - expect(eq(prev.has_value(), true)); - expect(eq(prev->addr(), follow->addr())); - expect(eq(prev->mnemonic(), follow->mnemonic())); + expect(eq(jmp.has_value(), true)); + expect(eq(jmp_until.has_value(), true)); + + expect(eq(jmp->addr(), jmp_until->addr())); + + expect(eq(jmp->relative(), true)); + expect(eq(jmp->branching(), true)); + expect(eq(jmp->mnemonic(), MNEMONIC_JMP)); + + auto abs_jmp = jmp->next(); + + expect(eq(abs_jmp.has_value(), true)); + + expect(eq(abs_jmp->relative(), false)); + expect(eq(abs_jmp->branching(), true)); + expect(eq(abs_jmp->mnemonic(), MNEMONIC_JMP)); + +#if INTPTR_MAX == INT64_MAX + expect(eq(next->size(), 3)); + expect(eq(next->addr(), reinterpret_cast(data) + 0x2)); + + expect(eq(rel_move->size(), 7)); + expect(eq(rel_move->addr(), reinterpret_cast(data) + 15)); + + expect(eq(jmp->size(), 6)); + expect(eq(jmp->addr(), rel_move->addr() + rel_move->size())); + + expect(eq(abs_jmp->size(), 2)); + expect(eq(abs_jmp->addr(), jmp->addr() + jmp->size())); #endif }; From 77767a2abf06cfb2fc3c18de94fe883906bd5e09 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 02:19:00 +0200 Subject: [PATCH 03/22] chore(deps): bump zydis [skip ci] --- CMakeLists.txt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4ccafdd..6867506 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,11 +62,8 @@ include("cmake/cpm.cmake") CPMAddPackage( NAME Zydis - VERSION 4.0.0 + VERSION 4.1.0 GIT_REPOSITORY "https://github.com/zyantific/zydis" - OPTIONS "ZYDIS_BUILD_TOOLS OFF" - "ZYDIS_BUILD_DOXYGEN OFF" - "ZYDIS_BUILD_EXAMPLES OFF" ) CPMAddPackage( From ed436720a4d95859fd256e92a894cdb9acb55a47 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 23:01:13 +0200 Subject: [PATCH 04/22] fix(tests/instruction): tests for x86 --- tests/instruction.test.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/instruction.test.cpp b/tests/instruction.test.cpp index b7b1f38..947feb8 100644 --- a/tests/instruction.test.cpp +++ b/tests/instruction.test.cpp @@ -24,6 +24,8 @@ std::vector code = { suite<"Instruction"> instruction_suite = [] { + static constexpr auto is64bit = sizeof(std::uintptr_t) == 8; + static constexpr auto MNEMONIC_MOV = 436; static constexpr auto MNEMONIC_JMP = 311; @@ -60,7 +62,7 @@ suite<"Instruction"> instruction_suite = [] expect(eq(rel_move.has_value(), true)); - expect(eq(rel_move->relative(), true)); + expect(eq(rel_move->relative(), is64bit)); expect(eq(rel_move->branching(), false)); expect(eq(rel_move->mnemonic(), MNEMONIC_MOV)); @@ -72,7 +74,7 @@ suite<"Instruction"> instruction_suite = [] expect(eq(jmp->addr(), jmp_until->addr())); - expect(eq(jmp->relative(), true)); + expect(eq(jmp->relative(), is64bit)); expect(eq(jmp->branching(), true)); expect(eq(jmp->mnemonic(), MNEMONIC_JMP)); @@ -84,17 +86,15 @@ suite<"Instruction"> instruction_suite = [] expect(eq(abs_jmp->branching(), true)); expect(eq(abs_jmp->mnemonic(), MNEMONIC_JMP)); -#if INTPTR_MAX == INT64_MAX - expect(eq(next->size(), 3)); - expect(eq(next->addr(), reinterpret_cast(data) + 0x2)); - - expect(eq(rel_move->size(), 7)); - expect(eq(rel_move->addr(), reinterpret_cast(data) + 15)); - - expect(eq(jmp->size(), 6)); + expect(eq(abs_jmp->addr(), jmp->addr() + jmp->size())); + expect(eq(rel_move->addr(), mov->addr() + mov->size())); expect(eq(jmp->addr(), rel_move->addr() + rel_move->size())); + expect(eq(mov->addr(), instruction->addr() + instruction->size())); +#if INTPTR_MAX == INT64_MAX + expect(eq(mov->size(), 3)); + expect(eq(jmp->size(), 6)); expect(eq(abs_jmp->size(), 2)); - expect(eq(abs_jmp->addr(), jmp->addr() + jmp->size())); + expect(eq(rel_move->size(), 7)); #endif }; From 869246bae845e651bb99d10f00168c91dfad51e1 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 23:05:34 +0200 Subject: [PATCH 05/22] fix(hooks): destructor on invalid state --- src/hooks.hook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 4f7c342..5be1633 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -46,7 +46,7 @@ namespace lime hook_base::~hook_base() { - if (!m_impl) + if (!m_impl || !m_impl->source_page) { return; } From f6eee3b4a3fbec158c3b724f4053833b44457689 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 23:17:22 +0200 Subject: [PATCH 06/22] refactor(ci): more x86 tests --- .github/workflows/build.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00c84fa..52d549b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: - name: Compile run: | - cmake -B build -Dlime_tests=ON + cmake -B build -Dlime_tests=ON cmake --build build --config Debug - name: Run Tests @@ -57,6 +57,7 @@ jobs: fail-fast: false matrix: language: ["cpp"] + arch: ["Win32", "x64"] steps: - name: Checkout @@ -64,7 +65,7 @@ jobs: - name: Compile run: | - cmake -B build -Dlime_tests=ON -T ClangCL -A x64 + cmake -B build -Dlime_tests=ON -T ClangCL -A ${{ matrix.arch }} cmake --build build --config Debug - name: Run Tests From aeec3f60e286bcecd12661ef22ac810638531b22 Mon Sep 17 00:00:00 2001 From: Curve Date: Thu, 9 May 2024 23:51:04 +0200 Subject: [PATCH 07/22] fix(hooks): require page to be at-least readable --- include/lime/hooks/hook.hpp | 1 + src/hooks.hook.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/include/lime/hooks/hook.hpp b/include/lime/hooks/hook.hpp index a1fa861..9147f37 100644 --- a/include/lime/hooks/hook.hpp +++ b/include/lime/hooks/hook.hpp @@ -46,6 +46,7 @@ namespace lime protect, relocate, bad_page, + bad_prot, }; class hook_base diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 5be1633..99e3442 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -76,6 +76,11 @@ namespace lime return tl::make_unexpected(hook_error::bad_page); } + if (!(page->prot() & lime::protection::read)) + { + return tl::make_unexpected(hook_error::bad_prot); + } + auto start = instruction::unsafe(source); if (auto follow = start.follow(); follow) From 50c01a6818a98e1c861894823440295d58c5be60 Mon Sep 17 00:00:00 2001 From: Curve Date: Fri, 10 May 2024 00:19:46 +0200 Subject: [PATCH 08/22] fix(page): nearby alloc on x86 --- src/page.linux.cpp | 6 ++++++ src/page.win.cpp | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/src/page.linux.cpp b/src/page.linux.cpp index 2815435..27bfdcf 100644 --- a/src/page.linux.cpp +++ b/src/page.linux.cpp @@ -1,4 +1,5 @@ #include "page.hpp" +#include "constants.hpp" #include #include @@ -208,6 +209,11 @@ namespace lime std::shared_ptr page::allocate(std::uintptr_t where, std::size_t size, protection protection) { + if constexpr (lime::arch == lime::architecture::x86) + { + return allocate(size, protection); + } + auto aligned = where & (getpagesize() - 1) ? (where + getpagesize()) & ~(getpagesize() - 1) : where; auto *alloc = MAP_FAILED; diff --git a/src/page.win.cpp b/src/page.win.cpp index ef0c4eb..3621869 100644 --- a/src/page.win.cpp +++ b/src/page.win.cpp @@ -1,4 +1,5 @@ #include "page.hpp" +#include "constants.hpp" #include #include @@ -220,6 +221,12 @@ namespace lime #ifdef LIME_DISABLE_ALLOC2 return nullptr; #else + + if constexpr (lime::arch == lime::architecture::x86) + { + return allocate(size, protection); + } + MEM_EXTENDED_PARAMETER param{}; MEM_ADDRESS_REQUIREMENTS requirements{}; From dfd61342a5b4ea17513555db1c66fffac00f1ba7 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 12 May 2024 23:40:02 +0200 Subject: [PATCH 09/22] feat(instruction): allow to set `rip` on `absolute` --- include/lime/instruction.hpp | 4 ++++ private/disasm.hpp | 2 +- src/disasm.cpp | 4 ++-- src/instruction.cpp | 34 ++++++++++++++++++++++------------ 4 files changed, 29 insertions(+), 15 deletions(-) diff --git a/include/lime/instruction.hpp b/include/lime/instruction.hpp index 6b5e5d8..4e45a1f 100644 --- a/include/lime/instruction.hpp +++ b/include/lime/instruction.hpp @@ -74,6 +74,10 @@ namespace lime [[nodiscard]] std::optional follow() const; [[nodiscard]] std::optional next(std::size_t mnemonic) const; + public: + [[nodiscard]] std::optional absolute() const; + [[nodiscard]] std::optional absolute(std::uintptr_t rip) const; + public: [[nodiscard]] std::optional operator-(std::size_t) const; [[nodiscard]] std::optional operator+(std::size_t) const; diff --git a/private/disasm.hpp b/private/disasm.hpp index f63f9d4..4b94a64 100644 --- a/private/disasm.hpp +++ b/private/disasm.hpp @@ -21,5 +21,5 @@ namespace lime::disasm disp displacement(std::uintptr_t); std::vector immediates(std::uintptr_t); - std::optional follow(std::uintptr_t); + std::optional follow(std::uintptr_t address, std::optional = std::nullopt); } // namespace lime::disasm diff --git a/src/disasm.cpp b/src/disasm.cpp index 43c86cf..f010470 100644 --- a/src/disasm.cpp +++ b/src/disasm.cpp @@ -153,7 +153,7 @@ namespace lime return rtn; } - std::optional disasm::follow(std::uintptr_t address) + std::optional disasm::follow(std::uintptr_t address, std::optional rip) { const auto decoder = get_decoder(); const auto *buffer = reinterpret_cast(address); @@ -167,7 +167,7 @@ namespace lime } ZyanU64 result = 0; - ZydisCalcAbsoluteAddress(&inst, operands, address, &result); + ZydisCalcAbsoluteAddress(&inst, operands, rip.value_or(address), &result); return result; } diff --git a/src/instruction.cpp b/src/instruction.cpp index 4ce15b4..167efe4 100644 --- a/src/instruction.cpp +++ b/src/instruction.cpp @@ -96,18 +96,6 @@ namespace lime return *this + size(); } - std::optional instruction::follow() const - { - auto new_address = disasm::follow(m_impl->address); - - if (!new_address) - { - return std::nullopt; - } - - return at(new_address.value()); - } - std::optional instruction::next(std::size_t mnemonic) const { auto current = next(); @@ -128,6 +116,28 @@ namespace lime return current; } + std::optional instruction::follow() const + { + auto new_address = disasm::follow(m_impl->address); + + if (!new_address) + { + return std::nullopt; + } + + return at(new_address.value()); + } + + std::optional instruction::absolute() const + { + return disasm::follow(m_impl->address); + } + + std::optional instruction::absolute(std::uintptr_t rip) const + { + return disasm::follow(m_impl->address, rip); + } + std::optional instruction::operator-(std::size_t amount) const { return at(m_impl->address - amount); From fa5c90dbaa2bacd94c2d0773c7050b12fce60392 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 12 May 2024 23:41:08 +0200 Subject: [PATCH 10/22] refactor(hook): cleanup, major refactor --- include/lime/hooks/hook.hpp | 1 + src/hooks.hook.cpp | 416 ++++++++++++++++++------------------ 2 files changed, 207 insertions(+), 210 deletions(-) diff --git a/include/lime/hooks/hook.hpp b/include/lime/hooks/hook.hpp index 9147f37..cb1788c 100644 --- a/include/lime/hooks/hook.hpp +++ b/include/lime/hooks/hook.hpp @@ -47,6 +47,7 @@ namespace lime relocate, bad_page, bad_prot, + bad_func, }; class hook_base diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 99e3442..8f425aa 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -4,42 +4,57 @@ #include "address.hpp" #include "constants.hpp" #include "instruction.hpp" +#include "tl/expected.hpp" +#include #include +#include namespace lime { + using offset_ptr = std::variant; + + static constexpr auto rwx = protection::read | protection::write | protection::execute; + struct hook_base::impl { std::uintptr_t target; public: std::unique_ptr
source; - std::unique_ptr source_page; - - public: + std::shared_ptr source_page; std::vector prologue; public: + std::shared_ptr trampoline; std::shared_ptr spring_board; - [[nodiscard]] std::size_t create_springboard(); public: - std::shared_ptr trampoline; - [[nodiscard]] bool build_trampoline(); + [[nodiscard]] std::size_t required_prologue_size(bool near) const; + [[nodiscard]] std::size_t estimate_trampoline_size(bool near) const; + + public: + bool create_trampoline(bool near); + bool create_springboard(); public: - template - [[nodiscard]] bool try_offset(std::uintptr_t, std::int64_t); - [[nodiscard]] bool try_offset(imm, std::uintptr_t, std::int64_t); - [[nodiscard]] bool try_offset(disp, std::uintptr_t, std::int64_t); + static std::optional offset_of(const instruction &source); + static std::optional offset_of(std::uintptr_t, const disp &); + static std::optional offset_of(std::uintptr_t, const std::vector &); public: - [[nodiscard]] bool can_relocate_far(); - [[nodiscard]] std::size_t estimate_size(bool); + static bool try_offset(const offset_ptr &source, std::intptr_t offset); + static bool try_redirect(const offset_ptr &source, std::intptr_t target); public: - static std::vector make_jmp(std::uintptr_t, std::uintptr_t, bool = false); + static std::vector make_ptr(std::uintptr_t address); + static std::vector make_jmp(std::uintptr_t source, std::uintptr_t target, bool near = false); }; hook_base::hook_base() : m_impl(std::make_unique()) {} @@ -68,7 +83,6 @@ namespace lime hook_base::rtn_t hook_base::create(std::uintptr_t source, std::uintptr_t target) { - auto rtn = std::unique_ptr(new hook_base); auto page = page::at(source); if (!page) @@ -76,336 +90,318 @@ namespace lime return tl::make_unexpected(hook_error::bad_page); } - if (!(page->prot() & lime::protection::read)) + if (!(page->prot() & protection::read)) { return tl::make_unexpected(hook_error::bad_prot); } - auto start = instruction::unsafe(source); + auto start = instruction::at(source); - if (auto follow = start.follow(); follow) + if (!start) { - start = std::move(follow.value()); - page.emplace(page::unsafe(start.addr())); + return tl::make_unexpected(hook_error::bad_func); } - rtn->m_impl->source = std::make_unique
(address::unsafe(start.addr())); - rtn->m_impl->source_page = std::make_unique(std::move(page.value())); - rtn->m_impl->target = target; - - /* - # We will now try to create a springboard and check how many bytes of the prologue we'll have to overwrite - # ├ Worst-Case - # │ └ We can't allocate a spring-board, size will be size::jmp_far - # │ - # └ Best-Case - # └ We can allocate a spring-board, size will be size::jmp_near - - # The springboard is used to jump to the target function. - # The idea here is just that our module is probably too far away from our target, so we just use an absolute jmp - # for simplicity sake. As we want to override as little instructions as possible in the source function we use - # the springboard. - */ - - const auto prologue_size = rtn->m_impl->create_springboard(); - rtn->m_impl->prologue = rtn->m_impl->source->copy(prologue_size); - - /* - # We will now try to relocate the function prologue to our trampoline - */ - - if (!rtn->m_impl->build_trampoline()) + if (auto follow = start->follow(); follow) { - return tl::make_unexpected(hook_error::relocate); - } - - if (rtn->m_impl->spring_board) - { - auto jmp = impl::make_jmp(rtn->m_impl->spring_board->start(), target); - address::unsafe(rtn->m_impl->spring_board->start()).write(jmp.data(), jmp.size()); + start = std::move(follow.value()); + page.emplace(page::unsafe(start->addr())); } - /* - # Now we'll jump to the springboard/target - */ + auto rtn = std::unique_ptr(new hook_base); - const auto destination = rtn->m_impl->spring_board ? rtn->m_impl->spring_board->start() : target; - const auto near = static_cast(rtn->m_impl->spring_board); + rtn->m_impl->target = target; + rtn->m_impl->source_page = std::make_unique(std::move(page.value())); + rtn->m_impl->source = std::make_unique
(address::unsafe(start->addr())); - auto jmp = impl::make_jmp(*rtn->m_impl->source, destination, near); + const auto near = rtn->m_impl->create_trampoline(true); - if (auto remaining = static_cast(prologue_size - jmp.size()); remaining > 0) + if (!near && !rtn->m_impl->create_trampoline(false)) { - std::vector nop(remaining, 0x90); - jmp.insert(jmp.end(), nop.begin(), nop.end()); + return tl::make_unexpected(hook_error::relocate); } - static constexpr auto prot = protection::read | protection::write | protection::execute; + const auto spring_board = near ? false : rtn->m_impl->create_springboard(); + const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - if (!rtn->m_impl->source_page->protect(prot)) + const auto jump = impl::make_jmp(source, destination, near || spring_board); + + if (!rtn->m_impl->source_page->protect(rwx)) { return tl::make_unexpected(hook_error::protect); } - rtn->m_impl->source->write(jmp.data(), jmp.size()); + rtn->m_impl->source->write(jump.data(), jump.size()); rtn->m_impl->source_page->restore(); return rtn; } - std::size_t hook_base::impl::create_springboard() + std::size_t hook_base::impl::required_prologue_size(bool near) const { - static constexpr auto prot = protection::read | protection::write | protection::execute; - spring_board = page::allocate(*source, size::jmp_far, prot); + auto required = near ? size::jmp_near : size::jmp_far; + auto rtn = 0u; + + auto inst = instruction::unsafe(source->addr()); + + while (rtn < required) + { + rtn += inst.size(); + inst = std::move(inst.next().value()); + } - const auto required_size = spring_board ? size::jmp_near : size::jmp_far; - auto rtn = 0u; + return rtn; + } - auto current = instruction::unsafe(*source); + std::size_t hook_base::impl::estimate_trampoline_size(bool near) const + { + auto rtn = prologue.size() + (near ? size::jmp_near : size::jmp_far); + const auto addr = reinterpret_cast(prologue.data()); - while (required_size > rtn) + for (auto i = 0u; prologue.size() > i;) { - rtn += current.size(); + auto current = instruction::unsafe(addr + i); + i += current.size(); - auto next = current.next(); - assert(next && "Failed to decode prologue instruction"); + if (!current.relative()) + { + continue; + } - current = std::move(next.value()); + if (!current.branching()) + { + rtn += sizeof(std::uintptr_t); + continue; + } + + rtn += (near ? size::jmp_near : size::jmp_far); } return rtn; } - bool hook_base::impl::build_trampoline() + bool hook_base::impl::create_trampoline(bool near) { - static constexpr auto prot = protection::read | protection::write | protection::execute; + prologue = source->copy(required_prologue_size(near)); - const auto near = !can_relocate_far(); - const auto size = estimate_size(near); + const auto size = estimate_trampoline_size(near); + trampoline.reset(); if (near) { - trampoline = page::allocate(*source, size, prot); + trampoline = page::allocate(source->addr(), size, rwx); } else { - trampoline = page::allocate(size, prot); + trampoline = page::allocate(size, rwx); } - if (!trampoline) - { - return false; - } - - /* - # First, we write the prologue to our trampoline - */ - - const auto skip = spring_board ? size::jmp_near : size::jmp_far; - const auto jump_back = impl::make_jmp(trampoline->start() + prologue.size(), source->addr() + skip, near); - - auto body = prologue; - body.insert(body.end(), jump_back.begin(), jump_back.end()); + auto content = prologue; + content.reserve(size); - address::unsafe(trampoline->start()).write(body.data(), body.size()); + const auto original = source->addr() + prologue.size(); + std::ranges::move(make_jmp(trampoline->start() + prologue.size(), original, near), std::back_inserter(content)); - /* - # Next, we fix-up the relocated instructions - */ + const auto start = reinterpret_cast(content.data()); - const auto prologue_end = address::unsafe(trampoline->start() + prologue.size() + size::jmp_far); - std::vector jump_table; - - auto i = 0u; - - while (prologue.size() > i) + for (auto i = 0u, size = 0u; prologue.size() > i; i += size) { - auto current = instruction::unsafe(trampoline->start() + i); - i += current.size(); + auto inst = instruction::unsafe(start + i); + size = inst.size(); - if (!current.relative()) + if (!inst.relative()) { continue; } - /* - # We now calculate the difference between the old instructions location and its new location - # Using this, we can try to correct relative instructions, by subtracting the difference - # from their relative-amount. - */ + const auto original_rip = source->addr() + i; + const auto original_target = inst.absolute(original_rip).value(); - const auto original_rip = static_cast(*source) + i; - const auto difference = static_cast(current.addr() + current.size()) - original_rip; + const auto rip = trampoline->start() + i; + const auto new_offset = trampoline->start() + content.size() - rip - size; - const auto disp = current.displacement(); + auto offset = offset_of(inst); - if (disp.size > 0 && !try_offset(disp, current.addr(), difference)) + if (!offset) { return false; } - const auto immediates = current.immediates(); - - for (const auto &imm : immediates) + if (try_offset(offset.value(), static_cast(rip - original_rip))) { - if (imm.size <= 0 || !imm.relative) - { - continue; - } - - if (try_offset(imm, current.addr(), difference)) - { - continue; - } - - if (!current.branching()) - { - return false; - } - - /* - # We failed to offset, thus we have to fall back to a jump-table at the end of the trampoline - */ - - const auto imm_value = - std::visit([](auto amount) { return static_cast(amount); }, imm.amount); - - const auto rel_jump = (prologue_end.addr() - current.addr() - current.size()) + jump_table.size(); - const auto offset = -imm_value + static_cast(rel_jump); + continue; + } + else if (!inst.branching()) + { + std::ranges::move(make_ptr(original_target), std::back_inserter(content)); + } + else + { + std::ranges::move(make_jmp(0, original_target, false), std::back_inserter(content)); + } - const auto destination = original_rip + imm_value; - const auto table_entry = impl::make_jmp(current.addr(), destination, near); + if (try_redirect(offset.value(), static_cast(new_offset))) + { + continue; + } - jump_table.insert(jump_table.end(), table_entry.begin(), table_entry.end()); + return false; + } - if (!try_offset(imm, current.addr(), -offset)) - { - return false; - } - } + if (content.size() > size) + { + return false; } - address::unsafe(prologue_end).write(jump_table.data(), jump_table.size()); + address::unsafe(trampoline->start()).write(content.data(), content.size()); return true; } - template - bool hook_base::impl::try_offset(std::uintptr_t address, std::int64_t amount) + bool hook_base::impl::create_springboard() { - auto &original = *reinterpret_cast(address); - - const auto first = original - static_cast(amount); - const auto second = static_cast(original) - amount; + spring_board = page::allocate(target, size::jmp_far, rwx); - if (first != second) + if (!spring_board) { return false; } - original -= amount; + auto content = make_jmp(0, target, false); + address::unsafe(spring_board->start()).write(content.data(), content.size()); + return true; } - bool hook_base::impl::try_offset(imm value, std::uintptr_t address, std::int64_t amount) + std::optional hook_base::impl::offset_of(const instruction &source) { - const auto is_signed = std::holds_alternative(value.amount); - const auto target = address + value.offset; + const auto disp = source.displacement(); - switch (value.size) + if (auto offset = offset_of(source.addr(), disp); offset) { - case 8: - return is_signed ? try_offset(target, amount) : try_offset(target, amount); - case 16: - return is_signed ? try_offset(target, amount) : try_offset(target, amount); - case 32: - return is_signed ? try_offset(target, amount) : try_offset(target, amount); + return offset; + } + + if (auto offset = offset_of(source.addr(), source.immediates()); offset) + { + return offset; } - return false; + return std::nullopt; } - bool hook_base::impl::try_offset(disp value, std::uintptr_t address, std::int64_t amount) + std::optional hook_base::impl::offset_of(std::uintptr_t source, const disp &disp) { - const auto target = address + value.offset; + const auto addr = source + disp.offset; - switch (value.size) + switch (disp.size) { case 8: - return try_offset(target, amount); + return reinterpret_cast(addr); case 16: - return try_offset(target, amount); + return reinterpret_cast(addr); case 32: - return try_offset(target, amount); + return reinterpret_cast(addr); } - return false; + return std::nullopt; } - bool hook_base::impl::can_relocate_far() + std::optional hook_base::impl::offset_of(std::uintptr_t source, const std::vector &immediates) { - auto start = reinterpret_cast(prologue.data()); - - for (auto i = 0u; prologue.size() > i;) + for (const auto &imm : immediates) { - auto current = instruction::unsafe(start + i); - i += current.size(); - - if (!current.relative()) + if (imm.size <= 0 || !imm.relative) { continue; } - if (current.branching()) + const auto is_signed = std::holds_alternative(imm.amount); + const auto addr = source + imm.offset; + + switch (imm.size) { - continue; + case 8: + return is_signed ? offset_ptr{reinterpret_cast(addr)} + : offset_ptr{reinterpret_cast(addr)}; + case 16: + return is_signed ? offset_ptr{reinterpret_cast(addr)} + : offset_ptr{reinterpret_cast(addr)}; + case 32: + return is_signed ? offset_ptr{reinterpret_cast(addr)} + : offset_ptr{reinterpret_cast(addr)}; } - - return false; } - return true; + return std::nullopt; } - std::size_t hook_base::impl::estimate_size(bool near) + bool hook_base::impl::try_offset(const offset_ptr &source, std::intptr_t offset) { - const auto size = size::jmp_far + prologue.size(); + auto visitor = [&offset](T *source) + { + std::intptr_t desired = *source - offset; + auto actual = *source - static_cast(offset); + + if (static_cast(actual) != desired) + { + return false; + } - auto i = 0u; + *source = actual; - while (prologue.size() > i) + return true; + }; + + return std::visit(visitor, source); + } + + bool hook_base::impl::try_redirect(const offset_ptr &source, std::intptr_t target) + { + auto visitor = [&target](T *source) { - const auto current = instruction::unsafe(reinterpret_cast(prologue.data() + i)); - i += current.size(); + if (std::numeric_limits::max() < target) + { + return false; + } - if (!current.relative()) + if (std::numeric_limits::min() > target) { - continue; + return false; } - i += near ? size::jmp_near : size::jmp_far; - } + *source = static_cast(target); + + return true; + }; + + return std::visit(visitor, source); + } + + std::vector hook_base::impl::make_ptr(std::uintptr_t address) + { + std::vector rtn(sizeof(std::uintptr_t)); + *reinterpret_cast(rtn.data()) = address; - return size; + return rtn; } - std::vector hook_base::impl::make_jmp(std::uintptr_t from, std::uintptr_t to, bool near) + std::vector hook_base::impl::make_jmp(std::uintptr_t source, std::uintptr_t target, bool near) { std::vector rtn; if (near || arch == architecture::x86) { rtn.insert(rtn.end(), {0xE9}); - rtn.resize(rtn.size() + sizeof(std::int32_t)); - *reinterpret_cast(rtn.data() + 1) = static_cast(to - from - size::jmp_near); + + auto relative = static_cast(target - source - size::jmp_near); + *reinterpret_cast(rtn.data() + 1) = relative; } else { - rtn.insert(rtn.end(), {0xFF, 0x25, 0x00, 0x00, 0x00, 0x00}); - - rtn.resize(rtn.size() + sizeof(std::uintptr_t)); - *reinterpret_cast(rtn.data() + 6) = to; + rtn = {0xFF, 0x25, 0x00, 0x00, 0x00, 0x00}; + std::ranges::move(make_ptr(target), std::back_inserter(rtn)); } return rtn; From 518f64e351ff8f7f45aebed60136ef684f7ffe38 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 12 May 2024 23:52:56 +0200 Subject: [PATCH 11/22] fix(hooks): dangerous shadow, `try_offset` calculation --- src/hooks.hook.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 8f425aa..1548d84 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -204,10 +204,10 @@ namespace lime const auto start = reinterpret_cast(content.data()); - for (auto i = 0u, size = 0u; prologue.size() > i; i += size) + for (auto i = 0u, inst_size = 0u; prologue.size() > i; i += inst_size) { auto inst = instruction::unsafe(start + i); - size = inst.size(); + inst_size = inst.size(); if (!inst.relative()) { @@ -218,7 +218,7 @@ namespace lime const auto original_target = inst.absolute(original_rip).value(); const auto rip = trampoline->start() + i; - const auto new_offset = trampoline->start() + content.size() - rip - size; + const auto new_offset = trampoline->start() + content.size() - rip - inst_size; auto offset = offset_of(inst); @@ -340,8 +340,8 @@ namespace lime { auto visitor = [&offset](T *source) { - std::intptr_t desired = *source - offset; - auto actual = *source - static_cast(offset); + auto desired = static_cast(*source) - offset; + auto actual = *source - static_cast(offset); if (static_cast(actual) != desired) { From 197e23dd608412e242ed1156bab0692e37b2e916 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 12 May 2024 23:54:10 +0200 Subject: [PATCH 12/22] refactor(tests): update hook test --- tests/hook.test.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/hook.test.cpp b/tests/hook.test.cpp index 9debb4e..7daad95 100644 --- a/tests/hook.test.cpp +++ b/tests/hook.test.cpp @@ -6,6 +6,11 @@ using namespace boost::ut::literals; int test_fn(int param) { + if (param == 1337) + { + return 0; + } + return param; } @@ -19,9 +24,11 @@ int test_hook(int param) suite<"Hooks"> hook_suite = [] { expect(eq(test_fn(10), 10)); + expect(eq(test_fn(1337), 0)); original_test = std::move(lime::hook::create(test_fn, test_hook).value()); expect(eq(test_fn(10), 15)); + expect(eq(test_fn(1337), 1342)); original_test.reset(); expect(eq(test_fn(10), 10)); From 36a67b240fe5ec8762f19233febf9259a5aa3ce4 Mon Sep 17 00:00:00 2001 From: Curve Date: Sun, 12 May 2024 23:55:12 +0200 Subject: [PATCH 13/22] refactor(tests): update instruction test --- tests/instruction.test.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/tests/instruction.test.cpp b/tests/instruction.test.cpp index 947feb8..8c814a2 100644 --- a/tests/instruction.test.cpp +++ b/tests/instruction.test.cpp @@ -10,15 +10,16 @@ std::vector code = { #if INTPTR_MAX == INT64_MAX 0x8b, 0x02, // mov eax, [rdx] 0x4c, 0x8b, 0xc1, // mov [rcx], eax - 0x48, 0x8B, 0x05, 0x05, 0x00, 0x00, 0x00, // mov rax, [rip+0x5] + 0x48, 0x8b, 0x05, 0x05, 0x00, 0x00, 0x00, // mov rax, [rip+0x5] 0xff, 0x25, 0x10, 0x00, 0x00, 0x00, // jmp [rip+0x10] 0xff, 0xe0, // jmp rax + 0xe8, 0x00, 0x00, 0x00, 0x00, // call 0x0 #else - 0x8B, 0x02, // mov eax, [edx] + 0x8b, 0x02, // mov eax, [edx] 0x89, 0x01, // mov [ecx], eax - 0xA1, 0x05, 0x00, 0x00, 0x00, // mov eax, [rip+0x5] - 0xFF, 0x25, 0x10, 0x00, 0x00, 0x00, // jmp [rip+0x10] - 0xFF, 0xE0, // jmp eax + 0xa1, 0x05, 0x00, 0x00, 0x00, // mov eax, [rip+0x5] + 0xff, 0x25, 0x10, 0x00, 0x00, 0x00, // jmp [rip+0x10] + 0xff, 0xe0, // jmp eax #endif }; @@ -92,9 +93,17 @@ suite<"Instruction"> instruction_suite = [] expect(eq(mov->addr(), instruction->addr() + instruction->size())); #if INTPTR_MAX == INT64_MAX + auto call = abs_jmp->next(); + + expect(eq(call.has_value(), true)); + + expect(eq(call->relative(), true)); + expect(eq(call->branching(), true)); + expect(eq(mov->size(), 3)); + expect(eq(rel_move->size(), 7)); + expect(eq(jmp->size(), 6)); expect(eq(abs_jmp->size(), 2)); - expect(eq(rel_move->size(), 7)); #endif }; From 982d0ac529595ca5f1670cf898d2ebe3743b947a Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 00:11:23 +0200 Subject: [PATCH 14/22] fix(hooks): include ``, abort if trampoline allocation failed --- src/hooks.hook.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 1548d84..38c03cc 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -8,6 +8,7 @@ #include #include +#include #include namespace lime @@ -196,6 +197,11 @@ namespace lime trampoline = page::allocate(size, rwx); } + if (!trampoline) + { + return false; + } + auto content = prologue; content.reserve(size); From 5347bfb6b8945d03f32e678c0b98122601833b3f Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 01:07:26 +0200 Subject: [PATCH 15/22] refactor(hook): pad jump with nops --- src/hooks.hook.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 38c03cc..fd54fd7 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -125,7 +125,15 @@ namespace lime const auto spring_board = near ? false : rtn->m_impl->create_springboard(); const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - const auto jump = impl::make_jmp(source, destination, near || spring_board); + auto jump = impl::make_jmp(source, destination, near || spring_board); + const auto prologue = rtn->m_impl->prologue.size(); + + if (jump.size() < prologue) + { + const auto remaining = prologue - jump.size(); + std::vector padding(remaining, 0x90); + std::ranges::move(padding, std::back_inserter(jump)); + } if (!rtn->m_impl->source_page->protect(rwx)) { From dc52b98ee9087549ba3ce9c85253efc093cfc43a Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 01:53:49 +0200 Subject: [PATCH 16/22] fix(hooks): source for spring board --- src/hooks.hook.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index fd54fd7..570a176 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -274,7 +274,7 @@ namespace lime bool hook_base::impl::create_springboard() { - spring_board = page::allocate(target, size::jmp_far, rwx); + spring_board = page::allocate(source->addr(), size::jmp_far, rwx); if (!spring_board) { From b1278f32b2fbd1baf193593f64888c4cc41486b1 Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 01:54:23 +0200 Subject: [PATCH 17/22] refactor(hook): remove padding --- src/hooks.hook.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 570a176..a8a7825 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -125,14 +125,11 @@ namespace lime const auto spring_board = near ? false : rtn->m_impl->create_springboard(); const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - auto jump = impl::make_jmp(source, destination, near || spring_board); - const auto prologue = rtn->m_impl->prologue.size(); + const auto jump = impl::make_jmp(start->addr(), destination, near || spring_board); - if (jump.size() < prologue) + if (!rtn->m_impl->source_page->protect(rwx)) { - const auto remaining = prologue - jump.size(); - std::vector padding(remaining, 0x90); - std::ranges::move(padding, std::back_inserter(jump)); + return tl::make_unexpected(hook_error::protect); } if (!rtn->m_impl->source_page->protect(rwx)) From 7aefd93a0c1e284732525ab6acd7bc9a954056e8 Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 02:21:22 +0200 Subject: [PATCH 18/22] fix(hook): near jump should only be used with trampoline --- src/hooks.hook.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index a8a7825..b0d0a54 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -122,10 +122,10 @@ namespace lime return tl::make_unexpected(hook_error::relocate); } - const auto spring_board = near ? false : rtn->m_impl->create_springboard(); + const auto spring_board = rtn->m_impl->create_springboard(); const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - const auto jump = impl::make_jmp(start->addr(), destination, near || spring_board); + const auto jump = impl::make_jmp(start->addr(), destination, spring_board); if (!rtn->m_impl->source_page->protect(rwx)) { @@ -403,7 +403,7 @@ namespace lime if (near || arch == architecture::x86) { - rtn.insert(rtn.end(), {0xE9}); + rtn = {0xE9}; rtn.resize(rtn.size() + sizeof(std::int32_t)); auto relative = static_cast(target - source - size::jmp_near); From 5f119e7bba9ceb7098242c8b1ced5a02e7af45e4 Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 02:33:05 +0200 Subject: [PATCH 19/22] fix(hook): jump to spring-board --- src/hooks.hook.cpp | 47 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index b0d0a54..096ef77 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -41,8 +41,8 @@ namespace lime [[nodiscard]] std::size_t estimate_trampoline_size(bool near) const; public: - bool create_trampoline(bool near); bool create_springboard(); + bool create_trampoline(bool near, bool spring_board); public: static std::optional offset_of(const instruction &source); @@ -115,17 +115,16 @@ namespace lime rtn->m_impl->source_page = std::make_unique(std::move(page.value())); rtn->m_impl->source = std::make_unique
(address::unsafe(start->addr())); - const auto near = rtn->m_impl->create_trampoline(true); + const auto spring_board = rtn->m_impl->create_springboard(); + const auto near = rtn->m_impl->create_trampoline(true, spring_board); - if (!near && !rtn->m_impl->create_trampoline(false)) + if (!near && !rtn->m_impl->create_trampoline(false, spring_board)) { return tl::make_unexpected(hook_error::relocate); } - const auto spring_board = rtn->m_impl->create_springboard(); - const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - - const auto jump = impl::make_jmp(start->addr(), destination, spring_board); + const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; + const auto jump = impl::make_jmp(start->addr(), destination, spring_board); if (!rtn->m_impl->source_page->protect(rwx)) { @@ -186,9 +185,24 @@ namespace lime return rtn; } - bool hook_base::impl::create_trampoline(bool near) + bool hook_base::impl::create_springboard() { - prologue = source->copy(required_prologue_size(near)); + spring_board = page::allocate(source->addr(), size::jmp_far, rwx); + + if (!spring_board) + { + return false; + } + + auto content = make_jmp(0, target, false); + address::unsafe(spring_board->start()).write(content.data(), content.size()); + + return true; + } + + bool hook_base::impl::create_trampoline(bool near, bool spring_board) + { + prologue = source->copy(required_prologue_size(spring_board)); const auto size = estimate_trampoline_size(near); trampoline.reset(); @@ -269,21 +283,6 @@ namespace lime return true; } - bool hook_base::impl::create_springboard() - { - spring_board = page::allocate(source->addr(), size::jmp_far, rwx); - - if (!spring_board) - { - return false; - } - - auto content = make_jmp(0, target, false); - address::unsafe(spring_board->start()).write(content.data(), content.size()); - - return true; - } - std::optional hook_base::impl::offset_of(const instruction &source) { const auto disp = source.displacement(); From 102da666adb932b66b06754aef7a47121533d795 Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 10:48:19 +0200 Subject: [PATCH 20/22] fix(hook): jumps for x86 --- src/hooks.hook.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 096ef77..6124366 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -131,11 +131,6 @@ namespace lime return tl::make_unexpected(hook_error::protect); } - if (!rtn->m_impl->source_page->protect(rwx)) - { - return tl::make_unexpected(hook_error::protect); - } - rtn->m_impl->source->write(jump.data(), jump.size()); rtn->m_impl->source_page->restore(); @@ -194,7 +189,7 @@ namespace lime return false; } - auto content = make_jmp(0, target, false); + auto content = make_jmp(spring_board->start(), target, false); address::unsafe(spring_board->start()).write(content.data(), content.size()); return true; @@ -242,10 +237,11 @@ namespace lime const auto original_rip = source->addr() + i; const auto original_target = inst.absolute(original_rip).value(); - const auto rip = trampoline->start() + i; - const auto new_offset = trampoline->start() + content.size() - rip - inst_size; + const auto rip = trampoline->start() + i; + const auto reloc_rip = trampoline->start() + content.size(); - auto offset = offset_of(inst); + const auto new_offset = reloc_rip - rip - inst_size; + auto offset = offset_of(inst); if (!offset) { @@ -262,7 +258,7 @@ namespace lime } else { - std::ranges::move(make_jmp(0, original_target, false), std::back_inserter(content)); + std::ranges::move(make_jmp(reloc_rip, original_target, false), std::back_inserter(content)); } if (try_redirect(offset.value(), static_cast(new_offset))) From cf2ba3c1e6776805482ad0e0f95b96366fff12ab Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 11:20:04 +0200 Subject: [PATCH 21/22] refactor(hook): cleanup includes, tweak naming --- src/hooks.hook.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index 6124366..b1b50bc 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -4,7 +4,6 @@ #include "address.hpp" #include "constants.hpp" #include "instruction.hpp" -#include "tl/expected.hpp" #include #include @@ -237,10 +236,10 @@ namespace lime const auto original_rip = source->addr() + i; const auto original_target = inst.absolute(original_rip).value(); - const auto rip = trampoline->start() + i; - const auto reloc_rip = trampoline->start() + content.size(); + const auto rip = trampoline->start() + i; + const auto reloc_address = trampoline->start() + content.size(); - const auto new_offset = reloc_rip - rip - inst_size; + const auto new_offset = reloc_address - rip - inst_size; auto offset = offset_of(inst); if (!offset) @@ -258,7 +257,7 @@ namespace lime } else { - std::ranges::move(make_jmp(reloc_rip, original_target, false), std::back_inserter(content)); + std::ranges::move(make_jmp(reloc_address, original_target, false), std::back_inserter(content)); } if (try_redirect(offset.value(), static_cast(new_offset))) From 0cf1c1afe1b96eb80a4092d93f4afc15ffe934bf Mon Sep 17 00:00:00 2001 From: Curve Date: Mon, 13 May 2024 12:11:46 +0200 Subject: [PATCH 22/22] refactor(hook): add padding --- src/hooks.hook.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/hooks.hook.cpp b/src/hooks.hook.cpp index b1b50bc..576cced 100644 --- a/src/hooks.hook.cpp +++ b/src/hooks.hook.cpp @@ -123,7 +123,15 @@ namespace lime } const auto destination = spring_board ? rtn->m_impl->spring_board->start() : target; - const auto jump = impl::make_jmp(start->addr(), destination, spring_board); + + auto jump = impl::make_jmp(start->addr(), destination, spring_board); + const auto remaining = rtn->m_impl->prologue.size() - jump.size(); + + if (remaining > 0) + { + std::vector padding(remaining, 0x90); + std::ranges::move(padding, std::back_inserter(jump)); + } if (!rtn->m_impl->source_page->protect(rwx)) {