diff --git a/cpp/backup_registers.hpp b/cpp/backup_registers.hpp new file mode 100644 index 0000000..69b0c6d --- /dev/null +++ b/cpp/backup_registers.hpp @@ -0,0 +1,241 @@ +#pragma once +#include +namespace sunset { + namespace detail { + namespace x86 { + + /* + pushad + pushfd + sub esp, 16 + movdqu dqword [esp], xmm0 + sub esp, 16 + movdqu dqword [esp], xmm1 + sub esp, 16 + movdqu dqword [esp], xmm2 + sub esp, 16 + movdqu dqword [esp], xmm3 + sub esp, 16 + movdqu dqword [esp], xmm4 + sub esp, 16 + movdqu dqword [esp], xmm5 + sub esp, 16 + movdqu dqword [esp], xmm6 + sub esp, 16 + movdqu dqword [esp], xmm7 + push esp + */ + constexpr std::array BACKUP_REGISTERS = { 0x60, 0x9C, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x54 }; + + /* + pushad + pushfd + push esp + */ + constexpr std::array BACKUP_GENERAL_REGISTERS = { 0x60, 0x9C, 0x54 }; + + /* + pop eax + movdqu xmm7, dqword [esp] + add esp, 16 + movdqu xmm6, dqword [esp] + add esp, 16 + movdqu xmm5, dqword [esp] + add esp, 16 + movdqu xmm4, dqword [esp] + add esp, 16 + movdqu xmm3, dqword [esp] + add esp, 16 + movdqu xmm2, dqword [esp] + add esp, 16 + movdqu xmm1, dqword [esp] + add esp, 16 + movdqu xmm0, dqword [esp] + add esp, 16 + popfd + popad + */ + constexpr std::array RESTORE_REGISTERS = { 0x58, 0xF3, 0x0F, 0x6F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0x9D, 0x61 }; + + /* + pop eax + popfd + popad + */ + constexpr std::array RESTORE_GENERAL_REGISTERS = { 0x58, 0x9D, 0x61 }; + }; + namespace x64 { + + /* + push rax + mov rax, rsp + add rax, 8 + push rcx + push rdx + push rbx + push rbp + push rax + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + pushfq + sub rsp, 16 + movdqu dqword [rsp], xmm0 + sub rsp, 16 + movdqu dqword [rsp], xmm1 + sub rsp, 16 + movdqu dqword [rsp], xmm2 + sub rsp, 16 + movdqu dqword [rsp], xmm3 + sub rsp, 16 + movdqu dqword [rsp], xmm4 + sub rsp, 16 + movdqu dqword [rsp], xmm5 + sub rsp, 16 + movdqu dqword [rsp], xmm6 + sub rsp, 16 + movdqu dqword [rsp], xmm7 + sub rsp, 16 + movdqu dqword [rsp], xmm8 + sub rsp, 16 + movdqu dqword [rsp], xmm9 + sub rsp, 16 + movdqu dqword [rsp], xmm10 + sub rsp, 16 + movdqu dqword [rsp], xmm11 + sub rsp, 16 + movdqu dqword [rsp], xmm12 + sub rsp, 16 + movdqu dqword [rsp], xmm13 + sub rsp, 16 + movdqu dqword [rsp], xmm14 + sub rsp, 16 + movdqu dqword [rsp], xmm15 + mov rcx, rsp + sub rsp, 8 // This extra sub rsp instruction is here since on x64, the stack must be 16-byte aligned before a function call. + */ + + // Includes XMM registers + constexpr std::array BACKUP_REGISTERS = { 0x50, 0x48, 0x89, 0xE0, 0x48, 0x83, 0xC0, 0x08, 0x51, 0x52, 0x53, 0x55, 0x50, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x9C, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xE1, 0x48, 0x83, 0xEC, 0x08 }; + + /* + push rax + mov rax, rsp + add rax, 8 + push rcx + push rdx + push rbx + push rbp + push rax + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + pushfq + mov rcx, rsp + sub rsp, 8 // This extra sub rsp instruction is here since on x64, the stack must be 16-byte aligned before a function call. + */ + + // Omits XMM registers + constexpr std::array BACKUP_GENERAL_REGISTERS = { 0x50, 0x48, 0x89, 0xE0, 0x48, 0x83, 0xC0, 0x08, 0x51, 0x52, 0x53, 0x55, 0x50, 0x56, 0x57, 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, 0x57, 0x9C, 0x48, 0x89, 0xE1, 0x48, 0x83, 0xEC, 0x08 }; + + /* + add rsp, 8 // This extra add rsp instruction is here since the corresponding backup routine has an extra sub rsp instruction; used to ensure stack alignment. + movdqu xmm15, dqword [rsp] + add rsp, 16 + movdqu xmm14, dqword [rsp] + add rsp, 16 + movdqu xmm13, dqword [rsp] + add rsp, 16 + movdqu xmm12, dqword [rsp] + add rsp, 16 + movdqu xmm11, dqword [rsp] + add rsp, 16 + movdqu xmm10, dqword [rsp] + add rsp, 16 + movdqu xmm9, dqword [rsp] + add rsp, 16 + movdqu xmm8, dqword [rsp] + add rsp, 16 + movdqu xmm7, dqword [rsp] + add rsp, 16 + movdqu xmm6, dqword [rsp] + add rsp, 16 + movdqu xmm5, dqword [rsp] + add rsp, 16 + movdqu xmm4, dqword [rsp] + add rsp, 16 + movdqu xmm3, dqword [rsp] + add rsp, 16 + movdqu xmm2, dqword [rsp] + add rsp, 16 + movdqu xmm1, dqword [rsp] + add rsp, 16 + movdqu xmm0, dqword [rsp] + add rsp, 16 + popfq + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + add rsp, 8 + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + */ + + // Includes XMM registers + constexpr std::array RESTORE_REGISTERS = { 0x48, 0x83, 0xC4, 0x08, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x48, 0x83, 0xC4, 0x08, 0x5D, 0x5B, 0x5A, 0x59, 0x58 }; + + /* + add rsp, 8 // This extra add rsp instruction is here since the corresponding backup routine has an extra sub rsp instruction; used to ensure stack alignment. + popfq + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + add rsp, 8 + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + */ + + // Omits XMM registers + constexpr std::array RESTORE_GENERAL_REGISTERS = { 0x48, 0x83, 0xC4, 0x08, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x48, 0x83, 0xC4, 0x08, 0x5D, 0x5B, 0x5A, 0x59, 0x58 }; + }; +#if defined(_M_X64) + namespace arch = x64; +#elif defined(_M_IX86) + namespace arch = x86; +#endif + }; +}; diff --git a/cpp/err.hpp b/cpp/err.hpp index debc807..ed4f8cc 100644 --- a/cpp/err.hpp +++ b/cpp/err.hpp @@ -1,10 +1,31 @@ -#pragma once +/* + MIT License + + Copyright (c) 2020-2022 Basit Ayantunde + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once #include #include -// Use custom Result/Option implementations if STX is not present. -#ifndef STX_VERSION template class Err { private: @@ -48,7 +69,6 @@ class Result { constexpr Result(Err error) : error_value(error.consume()), is_ok(false), inner() {}; constexpr Result(Ok&& object) : inner(std::move(object.consume())), is_ok(true), error_value() {}; - constexpr T unwrap() { if (is_ok) { return std::move(inner); @@ -102,9 +122,4 @@ class Option { return std::move(or_value); } } -}; -#else -template -using Result = stx::Result; -using stx::Option, stx::None, stx::Some; -#endif \ No newline at end of file +}; \ No newline at end of file diff --git a/cpp/relocate_code.hpp b/cpp/relocate_code.hpp index 05f7ed3..31b4430 100644 --- a/cpp/relocate_code.hpp +++ b/cpp/relocate_code.hpp @@ -1,153 +1,176 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "err.hpp" - -#define ZYDIS_STATIC_BUILD -#include -#include - -namespace sunset { - namespace detail { - enum class CodeRelocError { - // Failed to calculate an absolute address. - FailedAbsoluteAddressCalc, - // Failed to convert a ZydisDecodedInstruction to a ZydisEncoderRequest. - FailedDecodedInstrToEncoderRequest, - // Failed to encode an instruction. - FailedEncodeInstr - }; - } -}; - -namespace std -{ - template<> struct formatter : public std::formatter - { - constexpr auto parse(std::format_parse_context& ctx) { - return ctx.begin(); - } - - auto format(const sunset::detail::CodeRelocError& p, std::format_context& fc) const - { - switch (p) - { - case sunset::detail::CodeRelocError::FailedAbsoluteAddressCalc: - return std::format_to(fc.out(), "FailedAbsoluteAddressCalc"); - case sunset::detail::CodeRelocError::FailedDecodedInstrToEncoderRequest: - return std::format_to(fc.out(), "FailedDecodedInstrToEncoderRequest"); - case sunset::detail::CodeRelocError::FailedEncodeInstr: - return std::format_to(fc.out(), "FailedEncodeInstr"); - default: - return std::format_to(fc.out(), ""); - } - } - }; -}; - -namespace sunset { - namespace detail { - - inline Result, CodeRelocError> relocate_code(std::uintptr_t source, std::size_t source_size, std::uintptr_t dest) { - ZydisDecoder decoder{}; - ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); - - ZydisDecodedInstruction instruction{}; - ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; - - ZydisEncoderRequest encoder_request{}; - - std::ptrdiff_t source_offset = 0; - std::ptrdiff_t dest_offset = 0; - std::vector output; - - while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(source + source_offset), source_size - source_offset, &instruction, operands))) { - // Iterate over all operands, checking if each one is EIP-relative and patching the address accordingly. - for (std::size_t index = 0; index < instruction.operand_count; index++) { - auto& elem = operands[index]; - if (elem.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { - if (elem.imm.is_relative) { - ZyanU64 absolute_address = 0; - if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&instruction, &elem, static_cast(source + source_offset), &absolute_address))) { - return Err(CodeRelocError::FailedAbsoluteAddressCalc); - } - elem.imm.value.u = absolute_address; - elem.imm.is_relative = false; - } - } - if (elem.type == ZYDIS_OPERAND_TYPE_MEMORY) { - if (elem.mem.base == ZYDIS_REGISTER_EIP) { - ZyanU64 absolute_address = 0; - if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&instruction, &elem, static_cast(source + source_offset), &absolute_address))) { - return Err(CodeRelocError::FailedAbsoluteAddressCalc); - } - elem.mem.base = ZYDIS_REGISTER_NONE; - elem.mem.disp.value = absolute_address; - } - } - } - - std::size_t old_size = output.size(); - - // Reserve space in the vector for the encoded instruction. - output.resize(old_size + ZYDIS_MAX_INSTRUCTION_LENGTH); - - // Convert the decoded instruction to an encoder request. - if (!ZYAN_SUCCESS(ZydisEncoderDecodedInstructionToEncoderRequest(&instruction, operands, instruction.operand_count_visible, &encoder_request))) { - return Err(CodeRelocError::FailedDecodedInstrToEncoderRequest); - } - - // Encode the instruction into the output vector. - ZyanUSize encoded_size = ZYDIS_MAX_INSTRUCTION_LENGTH; - if (!ZYAN_SUCCESS(ZydisEncoderEncodeInstructionAbsolute(&encoder_request, output.data() + dest_offset, &encoded_size, static_cast(dest + dest_offset)))) { - return Err(CodeRelocError::FailedEncodeInstr); - } - - // Shrink the vector to fit. - output.resize(old_size + encoded_size); - - // Increment the offset. - source_offset += instruction.length; - dest_offset += encoded_size; - } - return Ok(std::move(output)); - } - - inline std::size_t get_instruction_len(std::uintptr_t ptr) { - ZydisDecoder decoder{}; - ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); - - ZydisDecodedInstruction instruction{}; - ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; - if (!ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(ptr), ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction, operands))) { - return 0; - } - return instruction.length; - } - - inline std::pair find_suitable_backup_size(std::uintptr_t base) { - ZydisDecoder decoder{}; - ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); - - ZydisDecodedInstruction instruction{}; - ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; - - std::ptrdiff_t offset = 0; - std::size_t padded = 0; - - while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(base + offset), ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction, operands))) { - if (offset >= 5) { - break; - } - offset += instruction.length; - padded += ZYDIS_MAX_INSTRUCTION_LENGTH; - } - return { offset, padded }; - } - }; -}; +#pragma once + +#include +#include +#include +#include +#include + +#include "err.hpp" + +#define ZYDIS_STATIC_BUILD +#include +#include + +namespace sunset { + namespace detail { + enum class CodeRelocError { + // Failed to calculate an absolute address. + FailedAbsoluteAddressCalc, + // Failed to convert a ZydisDecodedInstruction to a ZydisEncoderRequest. + FailedDecodedInstrToEncoderRequest, + // Failed to encode an instruction. + FailedEncodeInstr + }; + } +}; + +namespace std +{ + template<> struct formatter : public std::formatter + { + constexpr auto parse(std::format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(const sunset::detail::CodeRelocError& p, std::format_context& fc) const + { + switch (p) + { + case sunset::detail::CodeRelocError::FailedAbsoluteAddressCalc: + return std::format_to(fc.out(), "FailedAbsoluteAddressCalc"); + case sunset::detail::CodeRelocError::FailedDecodedInstrToEncoderRequest: + return std::format_to(fc.out(), "FailedDecodedInstrToEncoderRequest"); + case sunset::detail::CodeRelocError::FailedEncodeInstr: + return std::format_to(fc.out(), "FailedEncodeInstr"); + default: + return std::format_to(fc.out(), ""); + } + } + }; +}; + +namespace sunset { + namespace detail { + + constexpr std::size_t MINIMUM_OVERWRITE = 5; + + inline Result, CodeRelocError> relocate_code(std::uintptr_t source, std::size_t source_size, std::uintptr_t dest) { + ZydisDecoder decoder{}; +#ifdef _M_X64 + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); +#else + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); +#endif + ZydisDecodedInstruction instruction{}; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; + + ZydisEncoderRequest encoder_request{}; + + std::ptrdiff_t source_offset = 0; + std::ptrdiff_t dest_offset = 0; + std::vector output; + + while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(source + source_offset), source_size - source_offset, &instruction, operands))) { + // Iterate over all operands, checking if each one is EIP-relative and patching the address accordingly. + for (std::size_t index = 0; index < instruction.operand_count; index++) { + auto& elem = operands[index]; + if (elem.type == ZYDIS_OPERAND_TYPE_IMMEDIATE) { + if (elem.imm.is_relative) { + ZyanU64 absolute_address = 0; + // A hacky solution to get the size of the original instruction. We use this to fix the new IP-relative address. + std::uintptr_t absolute_address_minus_instruction_size = source + source_offset + elem.imm.value.s; + if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&instruction, &elem, static_cast(source + source_offset), &absolute_address))) { + return Err(CodeRelocError::FailedAbsoluteAddressCalc); + } + std::size_t estimated_instruction_size = static_cast(absolute_address) - absolute_address_minus_instruction_size; + // Here, we calculate a new relative address from the absolute address Zydis gives us, and the absolute estimated original instruction size. + std::intptr_t relative_address = static_cast(absolute_address) - (dest + dest_offset) - estimated_instruction_size; + elem.imm.value.s = relative_address; + } + } + if (elem.type == ZYDIS_OPERAND_TYPE_MEMORY) { +#ifdef _M_X64 + if (elem.mem.base == ZYDIS_REGISTER_RIP) { +#else + if (elem.mem.base == ZYDIS_REGISTER_EIP) { +#endif + ZyanU64 absolute_address = 0; + // A hacky solution to get the size of the original instruction. We use this to fix the new IP-relative address. + std::uintptr_t absolute_address_minus_instruction_size = source + source_offset + elem.mem.disp.value; + if (!ZYAN_SUCCESS(ZydisCalcAbsoluteAddress(&instruction, &elem, static_cast(source + source_offset), &absolute_address))) { + return Err(CodeRelocError::FailedAbsoluteAddressCalc); + } + std::size_t estimated_instruction_size = static_cast(absolute_address) - absolute_address_minus_instruction_size; + // Here, we calculate a new relative address from the absolute address Zydis gives us, and the absolute estimated original instruction size. + std::intptr_t relative_address = static_cast(absolute_address) - (dest + dest_offset) - estimated_instruction_size; + elem.mem.disp.value = relative_address; + } + } + } + + std::size_t old_size = output.size(); + + // Reserve space in the vector for the encoded instruction. + output.resize(old_size + ZYDIS_MAX_INSTRUCTION_LENGTH); + + // Convert the decoded instruction to an encoder request. + if (!ZYAN_SUCCESS(ZydisEncoderDecodedInstructionToEncoderRequest(&instruction, operands, instruction.operand_count_visible, &encoder_request))) { + return Err(CodeRelocError::FailedDecodedInstrToEncoderRequest); + } + + // Encode the instruction into the output vector. + ZyanUSize encoded_size = ZYDIS_MAX_INSTRUCTION_LENGTH; + if (!ZYAN_SUCCESS(ZydisEncoderEncodeInstruction(&encoder_request, output.data() + dest_offset, &encoded_size))) { + return Err(CodeRelocError::FailedEncodeInstr); + } + + // Shrink the vector to fit. + output.resize(old_size + encoded_size); + + // Increment the offset. + source_offset += instruction.length; + dest_offset += encoded_size; + } + return Ok(std::move(output)); + } + + inline std::size_t get_instruction_len(std::uintptr_t ptr) { + ZydisDecoder decoder{}; +#ifdef _M_X64 + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); +#else + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); +#endif + ZydisDecodedInstruction instruction{}; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; + if (!ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(ptr), ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction, operands))) { + return 0; + } + return instruction.length; + } + + inline std::pair find_suitable_backup_size(std::uintptr_t base) { + ZydisDecoder decoder{}; +#ifdef _M_X64 + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_STACK_WIDTH_64); +#else + ZydisDecoderInit(&decoder, ZYDIS_MACHINE_MODE_LONG_COMPAT_32, ZYDIS_STACK_WIDTH_32); +#endif + ZydisDecodedInstruction instruction{}; + ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT]{}; + + std::ptrdiff_t offset = 0; + std::size_t padded = 0; + + while (ZYAN_SUCCESS(ZydisDecoderDecodeFull(&decoder, reinterpret_cast(base + offset), ZYDIS_MAX_INSTRUCTION_LENGTH, &instruction, operands))) { + if (offset >= MINIMUM_OVERWRITE) { + break; + } + offset += instruction.length; + padded += ZYDIS_MAX_INSTRUCTION_LENGTH; + } + return { offset, padded }; + } + }; +}; diff --git a/cpp/sunset.hpp b/cpp/sunset.hpp index 77c4b71..19a0dc9 100644 --- a/cpp/sunset.hpp +++ b/cpp/sunset.hpp @@ -1,282 +1,653 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "relocate_code.hpp" - -#define DefineReplacementHook(name) \ -struct name : public sunset::detail::ReplacementHook - -#define DefineInlineHook(name) \ -struct name : public sunset::detail::InlineHook - -namespace sunset { - - namespace utils { - enum class Perm : DWORD { - None = PAGE_NOACCESS, - Read = PAGE_READONLY, - ReadWrite = PAGE_READWRITE, - WriteCopy = PAGE_WRITECOPY, - Execute = PAGE_EXECUTE, - ExecuteRead = PAGE_EXECUTE_READ, - ExecuteReadWrite = PAGE_EXECUTE_READWRITE, - ExecuteWriteCopy = PAGE_EXECUTE_WRITECOPY, - Guard = PAGE_GUARD, - NoCache = PAGE_NOCACHE, - WriteCombine = PAGE_WRITECOMBINE, - }; - // Sets the desired permission on the memory block. - template >* = nullptr> - inline std::pair set_permission(T ptr, size_t size, Perm perm) { - Perm old_perm = Perm::None; - bool success = static_cast(VirtualProtect(reinterpret_cast(ptr), size, static_cast(perm), reinterpret_cast(&old_perm))); - return std::make_pair(old_perm, success); - } - - class JitMemory { - public: - std::uint8_t* data; - std::size_t len; - private: - inline void destroy_impl() { - if (data != nullptr) { - VirtualFree(reinterpret_cast(data), 0, MEM_RELEASE); - data = nullptr; - len = 0; - } - } - public: - inline JitMemory(std::size_t size) { - data = reinterpret_cast(VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); - len = size; - } - JitMemory() = delete; - JitMemory(const JitMemory&) = delete; - JitMemory& operator=(const JitMemory&) = delete; - inline JitMemory(JitMemory&& other) noexcept : data(nullptr), len(0) { - data = other.data; - len = other.len; - other.data = nullptr; - other.len = 0; - } - inline JitMemory& operator=(JitMemory&& other) noexcept { - if (this != &other) { - destroy_impl(); - data = other.data; - len = other.len; - other.data = nullptr; - other.len = 0; - } - return *this; - } - // All these hoops to jump through just for psuedo-destructive moves... - inline ~JitMemory() { - destroy_impl(); - } - }; - }; - - template >* = nullptr, typename T2, typename std::enable_if_t>* = nullptr> - inline void write_jmp(T1 src, T2 dst) { - uintptr_t relativeAddress = (uintptr_t)((std::uint8_t*)dst - (uintptr_t)src) - 5; - - utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite); - *(std::uint8_t*)src = 0xE9; - *(uintptr_t*)((uintptr_t)src + 1) = relativeAddress; - } - - template >* = nullptr, typename T2, typename std::enable_if_t>* = nullptr> - inline void write_call(T1 src, T2 dst) { - uintptr_t relativeAddress = (uintptr_t)((std::uint8_t*)dst - (uintptr_t)src) - 5; - - utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite); - *(std::uint8_t*)src = 0xE8; - *(uintptr_t*)((uintptr_t)src + 1) = relativeAddress; - } - - template >* = nullptr, typename T2, typename std::enable_if_t>* = nullptr> - inline void write_push(T1 src, T2 dst) { - utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite); - *(std::uint8_t*)src = 0x68; - *(uintptr_t*)((uintptr_t)src + 1) = (uintptr_t)dst; - } - - template >* = nullptr> - inline bool write_nop(T addr, std::size_t code_size) { - const auto& [original_protection, success] = utils::set_permission(addr, code_size, utils::Perm::ExecuteReadWrite); - if (success) { - std::memset(reinterpret_cast(addr), 0x90, code_size); - return true; - } - return false; - } - - namespace legacy { - template >* = nullptr, typename T2, typename std::enable_if_t>* = nullptr> - inline void inline_replace(T1 src, T2 dst, std::size_t size) { - write_nop(src, size); - write_call(src, dst); - } - template >* = nullptr, typename T2, typename std::enable_if_t>* = nullptr> - inline void inline_replace_jump(T1 src, T2 dst, std::size_t size) { - write_nop(src, size); - write_jmp(src, dst); - } - }; - - union Register { - void* pointer; - std::uint32_t unsigned_integer; - std::int32_t signed_integer; - float floating_point; - }; - - struct InlineCtx { - Register eflags; - Register edi; - Register esi; - Register ebp; - Register esp; - Register ebx; - Register edx; - Register ecx; - Register eax; - - inline std::string to_string() { - return std::format("eax: {:#X}\necx: {:#X}\nedx: {:#X}\nebx: {:#X}\nesp: {:#X}\nebp: {:#X}\nesi: {:#X}\nedi: {:#X}\neflags: {:#X}\n", - eax.unsigned_integer, - ecx.unsigned_integer, - edx.unsigned_integer, - ebx.unsigned_integer, - esp.unsigned_integer, - ebp.unsigned_integer, - esi.unsigned_integer, - edi.unsigned_integer, - eflags.unsigned_integer - ); - } - }; - - static_assert(sizeof(InlineCtx) == 36); - - template - using GenericFuncPtr = R(*)(A...); - - namespace detail { - - extern std::vector JIT_MEMORY; - extern std::mutex JIT_MEMORY_LOCK; - - template - class ReplacementHook { - - template - using CallbackFuncPtr = decltype(&T::callback); - - static inline auto& orig_ref() { - static constinit CallbackFuncPtr s_func_ptr = nullptr; - - return s_func_ptr; - } - - public: - template - static inline decltype(auto) original(Args &&... args) { - return orig_ref()(std::forward(args)...); - } - - template - static inline void install_at_func_ptr(GenericFuncPtr ptr) { - using ArgFuncPtr = decltype(ptr); - - static_assert(std::is_same_v>, "Argument pointer type must match callback type!"); - - orig_ref() = ptr; - - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourAttach(reinterpret_cast(&orig_ref()), Derived::callback); - DetourTransactionCommit(); - } - - static inline void install_at_ptr(uintptr_t ptr) { - - orig_ref() = CallbackFuncPtr<>(ptr); - - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourAttach(reinterpret_cast(&orig_ref()), Derived::callback); - DetourTransactionCommit(); - } - - static inline void uninstall() { - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourDetach(reinterpret_cast(&orig_ref()), Derived::callback); - DetourTransactionCommit(); - } - }; - - template - class InlineHook { - - template - using CallbackFuncPtr = decltype(&T::callback); - - public: - - static inline void install_at_ptr(uintptr_t ptr) { - static_assert(std::is_same_v>, "Callback function must be void and take an InlineCtx!"); - - // Calculate the minimum bytes needed to be backed up, and an upper-bound limit of how many bytes the relocated code could take. (Used for below allocation) - auto [original_code_len, padded_code_len] = find_suitable_backup_size(ptr); - - if (original_code_len < 5) throw std::exception(); - - // Allocate code for inline handler. - //auto jit_memory = reinterpret_cast(VirtualAlloc(NULL, 11 + padded_code_len + 5, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); - auto jit_area = utils::JitMemory(11 + padded_code_len + 5); - auto jit_area_ptr = jit_area.data; - - // Build inline handler. - *jit_area_ptr = 0x60; - jit_area_ptr++; // pushad - *jit_area_ptr = 0x9C; - jit_area_ptr++; // pushfd - *jit_area_ptr = 0x54; - jit_area_ptr++; // push esp - write_call(jit_area_ptr, Derived::callback); - jit_area_ptr += 5; // call Derived::callback - *jit_area_ptr = 0x58; - jit_area_ptr++; // pop eax - *jit_area_ptr = 0x9D; - jit_area_ptr++; // popfd - *jit_area_ptr = 0x61; - jit_area_ptr++; // popad - - // Attempt to build/relocate the code, and if successful, copy into the trampoline. - auto relocated = relocate_code(ptr, original_code_len, reinterpret_cast(jit_area_ptr)).unwrap(); - std::memcpy(jit_area_ptr, relocated.data(), relocated.size()); - jit_area_ptr += relocated.size(); - write_jmp(jit_area_ptr, reinterpret_cast(ptr + 5)); jit_area_ptr += 5; // jmp ptr - // Insert jmp from the source to the inline handler. - const auto& [old_perm, success] = utils::set_permission(reinterpret_cast(ptr), original_code_len, utils::Perm::ExecuteReadWrite); - write_nop(reinterpret_cast(ptr), original_code_len); - // Ensure original function has the trampoline area nop'd out. - write_jmp(reinterpret_cast(ptr), jit_area_ptr - (11 + relocated.size() + 5)); - utils::set_permission(reinterpret_cast(ptr), original_code_len, old_perm); - - std::lock_guard guard(JIT_MEMORY_LOCK); - JIT_MEMORY.push_back(std::move(jit_area)); - } - }; - }; -}; +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include + +#include "relocate_code.hpp" +#include "backup_registers.hpp" + +#define DefineReplacementHook(name) \ +struct name : public sunset::detail::ReplacementHook + +#define DefineInlineHook(name) \ +struct name : public sunset::detail::InlineHook + +#define DefineExtendedInlineHook(name) \ +struct name : public sunset::detail::ExInlineHook + +namespace sunset { + namespace detail { + enum class InlineHookError { + // The user-set address does not have enough free space (>= 5 Bytes) for a jmp instruction. + NotEnoughSpaceToInsertJump, + // The callback function is too far away from the inline handler to use a 6-Byte call-indirect instruction. + CallbackTooFarFromInlineHandler, + // The original function is too far away from the inline handler to use 5-Byte jmp instructions. + OriginalCodeTooFarFromInlineHandler + }; + } +}; + +namespace std +{ + template<> struct formatter : public std::formatter + { + constexpr auto parse(std::format_parse_context& ctx) { + return ctx.begin(); + } + + auto format(const sunset::detail::InlineHookError& p, std::format_context& fc) const + { + switch (p) + { + case sunset::detail::InlineHookError::NotEnoughSpaceToInsertJump: + return std::format_to(fc.out(), "NotEnoughSpaceToInsertJump"); + case sunset::detail::InlineHookError::CallbackTooFarFromInlineHandler: + return std::format_to(fc.out(), "CallbackTooFarFromInlineHandler"); + case sunset::detail::InlineHookError::OriginalCodeTooFarFromInlineHandler: + return std::format_to(fc.out(), "OriginalCodeTooFarFromInlineHandler"); + default: + return std::format_to(fc.out(), ""); + } + } + }; +}; + +namespace sunset { + + namespace utils { + + template >* = nullptr> constexpr bool is_pow_two(T n) { + return n != 0 && (n & (n - 1)) == 0; + } + + template >* = nullptr> constexpr T align_up(T num, T align) { + return (num + align - 1) & ~(align - 1); + } + + template >* = nullptr> constexpr T align_down(T num, T align) { + return num & ~(align - 1); + } + + enum class Perm : DWORD { + None = PAGE_NOACCESS, + Read = PAGE_READONLY, + ReadWrite = PAGE_READWRITE, + WriteCopy = PAGE_WRITECOPY, + Execute = PAGE_EXECUTE, + ExecuteRead = PAGE_EXECUTE_READ, + ExecuteReadWrite = PAGE_EXECUTE_READWRITE, + ExecuteWriteCopy = PAGE_EXECUTE_WRITECOPY, + Guard = PAGE_GUARD, + NoCache = PAGE_NOCACHE, + WriteCombine = PAGE_WRITECOMBINE, + }; + // Sets the desired permission on the memory block. + template >* = nullptr> + inline Result set_permission(T ptr, size_t size, Perm perm) { + Perm old_perm = Perm::None; + bool success = static_cast(VirtualProtect(reinterpret_cast(ptr), size, static_cast(perm), reinterpret_cast(&old_perm))); + if (success) { + return Ok(std::move(old_perm)); + } + else { + return Err(GetLastError()); + } + } + + inline std::uintptr_t get_main_load_address() { + return reinterpret_cast(GetModuleHandleA(nullptr)); + } + + class JitMemory { + public: + std::uint8_t* data; + std::size_t len; + private: + inline void destroy_impl() { + if (data != nullptr) { + VirtualFree(reinterpret_cast(data), 0, MEM_RELEASE); + data = nullptr; + len = 0; + } + } + inline JitMemory(void* _data, std::size_t size) { + data = reinterpret_cast(_data); + len = size; + } + public: + inline JitMemory(std::size_t size) { + data = reinterpret_cast(VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE)); + len = size; + } + inline JitMemory() { + data = nullptr; + len = 0; + } + JitMemory(const JitMemory&) = delete; + JitMemory& operator=(const JitMemory&) = delete; + inline JitMemory(JitMemory&& other) noexcept : data(nullptr), len(0) { + data = other.data; + len = other.len; + other.data = nullptr; + other.len = 0; + } + inline JitMemory& operator=(JitMemory&& other) noexcept { + if (this != &other) { + destroy_impl(); + data = other.data; + len = other.len; + other.data = nullptr; + other.len = 0; + } + return *this; + } + // All these hoops to jump through just for psuedo-destructive moves... + inline ~JitMemory() { + destroy_impl(); + } + static inline JitMemory from_raw_parts(void* data, std::size_t len) { + return std::move(JitMemory(data, len)); + } + }; + + inline Option allocate_near(std::uintptr_t address, std::size_t len, std::size_t max_distance = (std::numeric_limits::max)()) { + SYSTEM_INFO info{}; + GetSystemInfo(&info); + + MEMORY_BASIC_INFORMATION mbi{}; + std::uintptr_t aligned_address = align_down(address, static_cast(info.dwAllocationGranularity)); + + // Iterate backwards towards the start of the available address space from the given `address` argument. + std::uintptr_t current_address = aligned_address; + while (current_address > reinterpret_cast(info.lpMinimumApplicationAddress) && static_cast(std::llabs(current_address - address)) < max_distance) { + VirtualQueryEx(GetCurrentProcess(), reinterpret_cast(current_address), &mbi, sizeof(MEMORY_BASIC_INFORMATION)); + if (mbi.State == MEM_FREE) { + void* memory = VirtualAlloc(reinterpret_cast(current_address), len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (memory != nullptr) { + return Some(JitMemory::from_raw_parts(memory, len)); + } + } + current_address -= info.dwAllocationGranularity; + } + + // Iterate forwards towards the end of the available address space from the given `address` argument. + current_address = aligned_address; + while (current_address < reinterpret_cast(info.lpMaximumApplicationAddress) && static_cast(std::llabs(current_address - address)) < max_distance) { + VirtualQueryEx(GetCurrentProcess(), reinterpret_cast(current_address), &mbi, sizeof(MEMORY_BASIC_INFORMATION)); + if (mbi.State == MEM_FREE) { + void* memory = VirtualAlloc(reinterpret_cast(current_address), len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + if (memory != nullptr) { + return Some(JitMemory::from_raw_parts(memory, len)); + } + } + current_address += info.dwAllocationGranularity; + } + + return None; + } + }; + + namespace inst { + + inline bool jmp(void* src, void* dst) { + std::uintptr_t destination_address = reinterpret_cast(dst); + std::uintptr_t source_address = reinterpret_cast(src); + std::intptr_t relative_address = destination_address - source_address - 5; + if (relative_address < (std::numeric_limits::min)() || relative_address > (std::numeric_limits::max)()) { + return false; + } + auto restore = utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(source_address) = 0xE9; + *reinterpret_cast(source_address + 1) = static_cast(relative_address); + utils::set_permission(src, 5, restore); + return true; + } + + inline bool call(void* src, void* dst) { + std::uintptr_t destination_address = reinterpret_cast(dst); + std::uintptr_t source_address = reinterpret_cast(src); + std::intptr_t relative_address = destination_address - source_address - 5; + if (relative_address < (std::numeric_limits::min)() || relative_address > (std::numeric_limits::max)()) { + return false; + } + auto restore = utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(source_address) = 0xE8; + *reinterpret_cast(source_address + 1) = static_cast(relative_address); + utils::set_permission(src, 5, restore); + return true; + } + +#if defined(_M_X64) + inline bool call_indirect(void* src, void* dst) { + std::uintptr_t destination_address = reinterpret_cast(dst); + std::uintptr_t source_address = reinterpret_cast(src); + std::intptr_t relative_address = destination_address - source_address - 6; + if (relative_address < (std::numeric_limits::min)() || relative_address >(std::numeric_limits::max)()) { + return false; + } + auto restore = utils::set_permission(src, 6, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(source_address) = 0xFF; + *reinterpret_cast(source_address + 1) = 0x15; + *reinterpret_cast(source_address + 2) = static_cast(relative_address); + utils::set_permission(src, 6, restore); + return true; + } +#elif defined(_M_IX86) + inline bool call_indirect(void* src, void* dst) { + std::uintptr_t destination_address = reinterpret_cast(dst); + std::uintptr_t source_address = reinterpret_cast(src); + auto restore = utils::set_permission(src, 6, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(source_address) = 0xFF; + *reinterpret_cast(source_address + 1) = 0x15; + *reinterpret_cast(source_address + 2) = static_cast(destination_address); + utils::set_permission(src, 6, restore); + return true; + } +#endif + +#if defined(_M_X64) + /* + enum class RegisterIndex : std::uint8_t { + Rax = 0, + Rcx = 1, + Rdx = 2, + Rbx = 3, + Rsp = 4, + Rbp = 5, + Rsi = 6, + Rdi = 7, + R8 = 8, + R9 = 9, + R10 = 10, + R11 = 11, + R12 = 12, + R13 = 13, + R14 = 14, + R15 = 15 + }; + */ +#elif defined(_M_IX86) + enum class RegisterIndex : std::uint8_t { + Eax = 0, + Ecx = 1, + Edx = 2, + Ebx = 3, + Esp = 4, + Ebp = 5, + Esi = 6, + Edi = 7, + }; + + inline void mov_u32(void* src, RegisterIndex reg, std::uint32_t imm32) { + auto restore = utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(src) = 0xB8 + std::to_underlying(reg); + *reinterpret_cast(reinterpret_cast(src) + 1) = imm32; + utils::set_permission(src, 5, restore).unwrap(); + } +#endif + inline void push_u32(void* src, std::uint32_t dst) { + auto restore = utils::set_permission(src, 5, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(src) = 0x68; + *reinterpret_cast(reinterpret_cast(src) + 1) = dst; + utils::set_permission(src, 5, restore).unwrap(); + } + + inline void push_u8(void* src, std::uint8_t dst) { + auto restore = utils::set_permission(src, 2, utils::Perm::ExecuteReadWrite).unwrap(); + *reinterpret_cast(src) = 0x6A; + *reinterpret_cast(reinterpret_cast(src) + 1) = dst; + utils::set_permission(src, 2, restore).unwrap(); + } + + inline void nop(void* addr, std::size_t code_size) { + auto original_protection = utils::set_permission(addr, code_size, utils::Perm::ExecuteReadWrite).unwrap(); + std::memset(reinterpret_cast(addr), 0x90, code_size); + utils::set_permission(addr, code_size, original_protection).unwrap(); + } + }; + + union XMMRegister { + float f32[4]; + double f64[2]; + int8_t i8[16]; + uint8_t u8[16]; + int16_t i16[8]; + uint16_t u16[8]; + int32_t i32[4]; + uint32_t u32[4]; + int64_t i64[2]; + uint64_t u64[2]; + }; + + static_assert(sizeof(XMMRegister) == 16); + +#ifdef _M_X64 + union Register { + void* pointer; + std::uint64_t unsigned_integer; + std::int64_t signed_integer; + double floating_point; + }; + + static_assert(sizeof(Register) == 8); + + struct InlineCtx { + Register rflags; + Register r15; + Register r14; + Register r13; + Register r12; + Register r11; + Register r10; + Register r9; + Register r8; + Register rdi; + Register rsi; + Register rsp; + Register rbp; + Register rbx; + Register rdx; + Register rcx; + Register rax; + }; + + struct InlineCtxEx { + XMMRegister xmm15; + XMMRegister xmm14; + XMMRegister xmm13; + XMMRegister xmm12; + XMMRegister xmm11; + XMMRegister xmm10; + XMMRegister xmm9; + XMMRegister xmm8; + XMMRegister xmm7; + XMMRegister xmm6; + XMMRegister xmm5; + XMMRegister xmm4; + XMMRegister xmm3; + XMMRegister xmm2; + XMMRegister xmm1; + XMMRegister xmm0; + Register rflags; + Register r15; + Register r14; + Register r13; + Register r12; + Register r11; + Register r10; + Register r9; + Register r8; + Register rdi; + Register rsi; + Register rsp; + Register rbp; + Register rbx; + Register rdx; + Register rcx; + Register rax; + }; + + static_assert(sizeof(InlineCtx) == 136); + static_assert(sizeof(InlineCtxEx) == 392); +#endif + +#ifdef _M_IX86 + union Register { + void* pointer; + std::uint32_t unsigned_integer; + std::int32_t signed_integer; + float floating_point; + }; + static_assert(sizeof(Register) == 4); + + struct InlineCtx { + Register eflags; + Register edi; + Register esi; + Register ebp; + Register esp; + Register ebx; + Register edx; + Register ecx; + Register eax; + }; + + struct InlineCtxEx { + XMMRegister xmm7; + XMMRegister xmm6; + XMMRegister xmm5; + XMMRegister xmm4; + XMMRegister xmm3; + XMMRegister xmm2; + XMMRegister xmm1; + XMMRegister xmm0; + Register eflags; + Register edi; + Register esi; + Register ebp; + Register esp; + Register ebx; + Register edx; + Register ecx; + Register eax; + }; + + static_assert(sizeof(InlineCtx) == 36); + static_assert(sizeof(InlineCtxEx) == 168); +#endif + + template + using GenericFuncPtr = R(*)(A...); + + namespace detail { + + extern std::vector JIT_MEMORY; + extern std::mutex JIT_MEMORY_LOCK; + + template + class ReplacementHook { + + template + using CallbackFuncPtr = decltype(&T::callback); + + static inline auto& orig_ref() { + static constinit CallbackFuncPtr s_func_ptr = nullptr; + + return s_func_ptr; + } + + public: + template + static inline decltype(auto) original(Args &&... args) { + return orig_ref()(std::forward(args)...); + } + + template + static inline void install_at_func_ptr(GenericFuncPtr ptr) { + using ArgFuncPtr = decltype(ptr); + + static_assert(std::is_same_v>, "Argument pointer type must match callback type!"); + + orig_ref() = ptr; + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(reinterpret_cast(&orig_ref()), Derived::callback); + DetourTransactionCommit(); + } + + static inline void install_at_ptr(std::uintptr_t ptr) { + + orig_ref() = CallbackFuncPtr<>(ptr); + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(reinterpret_cast(&orig_ref()), Derived::callback); + DetourTransactionCommit(); + } + + static inline void uninstall() { + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourDetach(reinterpret_cast(&orig_ref()), Derived::callback); + DetourTransactionCommit(); + } + }; + + template + class InlineHook { + + template + using CallbackFuncPtr = decltype(&T::callback); + + + public: + + static inline Result install_at_ptr(std::uintptr_t ptr) { + static_assert(std::is_same_v>, "Callback function must be void and take an InlineCtx!"); + + // Calculate the minimum bytes needed to be backed up, and an upper-bound limit of how many bytes the relocated code could take. (Used for below allocation) + auto [original_code_len, padded_code_len] = find_suitable_backup_size(ptr); + + if (original_code_len < MINIMUM_OVERWRITE) { + return Err(InlineHookError::NotEnoughSpaceToInsertJump); + } + + // Allocate code for inline handler. + // The size of the JIT memory block should be able to account for: + // - A pointer to Derived::callback (sizeof(void*)) + // - The register backup routine + // - A 6-byte indirect call instruction to call Derived::callback (6) + // - The register restore routine + // - A 5-byte jump instruction to return to the original code (5) + const std::size_t jit_area_len = arch::BACKUP_GENERAL_REGISTERS.size() + arch::RESTORE_GENERAL_REGISTERS.size() + 6 + padded_code_len + 5 + sizeof(void*); +#ifdef _M_X64 + // On x64, if we can't find any free space nearby, we have to panic. + utils::JitMemory jit_area = utils::allocate_near(ptr, jit_area_len).unwrap(); +#else + // But on x86, we can reliably fall back to a plain ol' VirtualAlloc. + utils::JitMemory jit_area = utils::allocate_near(ptr, jit_area_len).unwrap_or(utils::JitMemory(jit_area_len)); +#endif + // Write the callback address at the start of the inline handler, so we can use a call-indirect instruction to get there. + std::uint8_t* jit_area_ptr = jit_area.data; + *reinterpret_cast(jit_area_ptr) = reinterpret_cast(Derived::callback); + jit_area_ptr += sizeof(void*); + + // Build inline handler. + std::memcpy(jit_area_ptr, arch::BACKUP_GENERAL_REGISTERS.data(), arch::BACKUP_GENERAL_REGISTERS.size()); + jit_area_ptr += arch::BACKUP_GENERAL_REGISTERS.size(); + // The address of the callback is held at the start of the JIT memory, so we'll use jit_area.data here. + if (!inst::call_indirect(jit_area_ptr, jit_area.data)) { + // This should be infallible since it will literally only be a few hundred bytes away tops... + return Err(InlineHookError::CallbackTooFarFromInlineHandler); + } + jit_area_ptr += 6; + std::memcpy(jit_area_ptr, arch::RESTORE_GENERAL_REGISTERS.data(), arch::RESTORE_GENERAL_REGISTERS.size()); + jit_area_ptr += arch::RESTORE_GENERAL_REGISTERS.size(); + + // Attempt to build/relocate the code, and if successful, copy into the trampoline. + std::vector relocated = relocate_code(ptr, original_code_len, reinterpret_cast(jit_area_ptr)).unwrap(); + std::memcpy(jit_area_ptr, relocated.data(), relocated.size()); + jit_area_ptr += relocated.size(); + + // Write the jmp from the inline handler back to the original function. + if (!inst::jmp(jit_area_ptr, reinterpret_cast(ptr + original_code_len))) { + return Err(InlineHookError::OriginalCodeTooFarFromInlineHandler); + } + jit_area_ptr += 5; + + // Ensure original function has the trampoline area nop'd out before inserting the jmp from the source to the inline handler (jmp ptr). + inst::nop(reinterpret_cast(ptr), original_code_len); + if (!inst::jmp(reinterpret_cast(ptr), jit_area.data + sizeof(void*))) { + return Err(InlineHookError::OriginalCodeTooFarFromInlineHandler); + } + + std::lock_guard guard(JIT_MEMORY_LOCK); + JIT_MEMORY.push_back(std::move(jit_area)); + + return Ok(true); + } + }; + + // FIXME: Add support for extended inline hooks on x86. + template + class ExInlineHook { + + template + using CallbackFuncPtr = decltype(&T::callback); + + public: + + static inline Result install_at_ptr(std::uintptr_t ptr) { + static_assert(std::is_same_v>, "Callback function must be void and take an InlineCtxEx!"); + + // Calculate the minimum bytes needed to be backed up, and an upper-bound limit of how many bytes the relocated code could take. (Used for below allocation) + auto [original_code_len, padded_code_len] = find_suitable_backup_size(ptr); + + if (original_code_len < MINIMUM_OVERWRITE) { + return Err(InlineHookError::NotEnoughSpaceToInsertJump); + } + + // Allocate code for inline handler. + // The size of the JIT memory block should be able to account for: + // - A pointer to Derived::callback (sizeof(void*)) + // - The register backup routine + // - A 6-byte indirect call instruction to call Derived::callback (6) + // - The register restore routine + // - A 5-byte jump instruction to return to the original code (5) + const std::size_t jit_area_len = arch::BACKUP_REGISTERS.size() + arch::RESTORE_REGISTERS.size() + 6 + padded_code_len + 5 + sizeof(void*); +#ifdef _M_X64 + // On x64, if we can't find any free space nearby, we have to panic. + utils::JitMemory jit_area = utils::allocate_near(ptr, jit_area_len).unwrap(); +#else + // But on x86, we can reliably fall back to a plain ol' VirtualAlloc. + utils::JitMemory jit_area = utils::allocate_near(ptr, jit_area_len).unwrap_or(utils::JitMemory(jit_area_len)); +#endif + // Write the callback address at the start of the inline handler, so we can use a call-indirect instruction to get there. + std::uint8_t* jit_area_ptr = jit_area.data; + *reinterpret_cast(jit_area_ptr) = reinterpret_cast(Derived::callback); + jit_area_ptr += sizeof(void*); + + // Build inline handler. + std::memcpy(jit_area_ptr, arch::BACKUP_REGISTERS.data(), arch::BACKUP_REGISTERS.size()); + jit_area_ptr += arch::BACKUP_REGISTERS.size(); + // The address of the callback is held at the start of the JIT memory, so we'll use jit_area.data here. + if (!inst::call_indirect(jit_area_ptr, jit_area.data)) { + // This should be infallible since it will literally only be a few hundred bytes away tops... + return Err(InlineHookError::CallbackTooFarFromInlineHandler); + } + jit_area_ptr += 6; + std::memcpy(jit_area_ptr, arch::RESTORE_REGISTERS.data(), arch::RESTORE_REGISTERS.size()); + jit_area_ptr += arch::RESTORE_REGISTERS.size(); + + // Attempt to build/relocate the code, and if successful, copy into the trampoline. + std::vector relocated = relocate_code(ptr, original_code_len, reinterpret_cast(jit_area_ptr)).unwrap(); + std::memcpy(jit_area_ptr, relocated.data(), relocated.size()); + jit_area_ptr += relocated.size(); + + // Write the jmp from the inline handler back to the original function. + if (!inst::jmp(jit_area_ptr, reinterpret_cast(ptr + original_code_len))) { + return Err(InlineHookError::OriginalCodeTooFarFromInlineHandler); + } + jit_area_ptr += 5; + + // Ensure original function has the trampoline area nop'd out before inserting the jmp from the source to the inline handler (jmp ptr). + inst::nop(reinterpret_cast(ptr), original_code_len); + if (!inst::jmp(reinterpret_cast(ptr), jit_area.data + sizeof(void*))) { + return Err(InlineHookError::OriginalCodeTooFarFromInlineHandler); + } + + std::lock_guard guard(JIT_MEMORY_LOCK); + JIT_MEMORY.push_back(std::move(jit_area)); + + return Ok(true); + } + }; + }; +}; diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml index 5d00e78..bb102b1 100644 --- a/rust/.cargo/config.toml +++ b/rust/.cargo/config.toml @@ -1,2 +1,2 @@ [build] -target = ["i686-pc-windows-msvc"] +target = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc"] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index e17323f..731f6f6 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "sunset" -version = "0.1.0" +name = "sunset-rs" +version = "1.0.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,5 +9,5 @@ edition = "2021" sunset-macro = { path = "./sunset-macro" } detours-sys = { git = "https://github.com/svark/detours.git", rev = "040bf24", features = ["buildtime_bindgen"] } zydis = { version = "4.0.0-beta.2", features = ["encoder"] } -windows-sys = {version = "0.48.0", features = ["Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader","Win32_System_Diagnostics_Debug","Win32_System_Memory", "Win32_System_Threading", "Win32_Foundation", "Win32_Security"]} +windows-sys = {version = "0.48.0", features = ["Win32_UI_WindowsAndMessaging", "Win32_System_LibraryLoader","Win32_System_Diagnostics_Debug","Win32_System_Memory", "Win32_System_Threading", "Win32_System_SystemInformation", "Win32_Foundation", "Win32_Security"]} once_cell = "1.18.0" diff --git a/rust/src/detail/backup_registers.rs b/rust/src/detail/backup_registers.rs new file mode 100644 index 0000000..b4ee165 --- /dev/null +++ b/rust/src/detail/backup_registers.rs @@ -0,0 +1,289 @@ +pub mod x86 { + /* + pushad + pushfd + sub esp, 16 + movdqu dqword [esp], xmm0 + sub esp, 16 + movdqu dqword [esp], xmm1 + sub esp, 16 + movdqu dqword [esp], xmm2 + sub esp, 16 + movdqu dqword [esp], xmm3 + sub esp, 16 + movdqu dqword [esp], xmm4 + sub esp, 16 + movdqu dqword [esp], xmm5 + sub esp, 16 + movdqu dqword [esp], xmm6 + sub esp, 16 + movdqu dqword [esp], xmm7 + push esp + */ + pub const BACKUP_REGISTERS: [u8; 99] = [ + 0x60, 0x9C, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, + 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, + 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x9C, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xA4, 0x24, 0x00, 0x00, + 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, + 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xEC, 0x10, 0xF3, + 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x54, + ]; + + /* + pushad + pushfd + push esp + */ + pub const BACKUP_GENERAL_REGISTERS: [u8; 3] = [0x60, 0x9C, 0x54]; + + /* + pop eax + movdqu xmm7, dqword [esp] + add esp, 16 + movdqu xmm6, dqword [esp] + add esp, 16 + movdqu xmm5, dqword [esp] + add esp, 16 + movdqu xmm4, dqword [esp] + add esp, 16 + movdqu xmm3, dqword [esp] + add esp, 16 + movdqu xmm2, dqword [esp] + add esp, 16 + movdqu xmm1, dqword [esp] + add esp, 16 + movdqu xmm0, dqword [esp] + add esp, 16 + popfd + popad + */ + pub const RESTORE_REGISTERS: [u8; 99] = [ + 0x58, 0xF3, 0x0F, 0x6F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, + 0x6F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xAC, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, + 0x10, 0xF3, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, + 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x84, 0x24, + 0x00, 0x00, 0x00, 0x00, 0x83, 0xC4, 0x10, 0x9D, 0x61, + ]; + + /* + pop eax + popfd + popad + */ + pub const RESTORE_GENERAL_REGISTERS: [u8; 3] = [0x58, 0x9D, 0x61]; +} + +pub mod x64 { + /* + push rax + mov rax, rsp + add rax, 8 + push rcx + push rdx + push rbx + push rbp + push rax + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + pushfq + sub rsp, 16 + movdqu dqword [rsp], xmm0 + sub rsp, 16 + movdqu dqword [rsp], xmm1 + sub rsp, 16 + movdqu dqword [rsp], xmm2 + sub rsp, 16 + movdqu dqword [rsp], xmm3 + sub rsp, 16 + movdqu dqword [rsp], xmm4 + sub rsp, 16 + movdqu dqword [rsp], xmm5 + sub rsp, 16 + movdqu dqword [rsp], xmm6 + sub rsp, 16 + movdqu dqword [rsp], xmm7 + sub rsp, 16 + movdqu dqword [rsp], xmm8 + sub rsp, 16 + movdqu dqword [rsp], xmm9 + sub rsp, 16 + movdqu dqword [rsp], xmm10 + sub rsp, 16 + movdqu dqword [rsp], xmm11 + sub rsp, 16 + movdqu dqword [rsp], xmm12 + sub rsp, 16 + movdqu dqword [rsp], xmm13 + sub rsp, 16 + movdqu dqword [rsp], xmm14 + sub rsp, 16 + movdqu dqword [rsp], xmm15 + mov rcx, rsp + sub rsp, 8 // This extra sub rsp instruction is here since on x64, the stack must be 16-byte aligned before a function call. + */ + + // Includes XMM registers + pub const BACKUP_REGISTERS: [u8; 255] = [ + 0x50, 0x48, 0x89, 0xE0, 0x48, 0x83, 0xC0, 0x08, 0x51, 0x52, 0x53, 0x55, 0x50, 0x56, 0x57, + 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, + 0x57, 0x9C, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, + 0xF3, 0x0F, 0x7F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, + 0x7F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xAC, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xB4, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x83, 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xEC, 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, + 0x10, 0xF3, 0x44, 0x0F, 0x7F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, + 0xF3, 0x44, 0x0F, 0x7F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, + 0x44, 0x0F, 0x7F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xEC, 0x10, 0xF3, 0x44, + 0x0F, 0x7F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x89, 0xE1, 0x48, 0x83, 0xEC, 0x08, + ]; + + /* + push rax + mov rax, rsp + add rax, 8 + push rcx + push rdx + push rbx + push rbp + push rax + push rsi + push rdi + push r8 + push r9 + push r10 + push r11 + push r12 + push r13 + push r14 + push r15 + pushfq + mov rcx, rsp + sub rsp, 8 // This extra sub rsp instruction is here since on x64, the stack must be 16-byte aligned before a function call. + */ + + // Omits XMM registers + pub const BACKUP_GENERAL_REGISTERS: [u8; 39] = [ + 0x50, 0x48, 0x89, 0xE0, 0x48, 0x83, 0xC0, 0x08, 0x51, 0x52, 0x53, 0x55, 0x50, 0x56, 0x57, + 0x41, 0x50, 0x41, 0x51, 0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56, 0x41, + 0x57, 0x9C, 0x48, 0x89, 0xE1, 0x48, 0x83, 0xEC, 0x08, + ]; + + /* + add rsp, 8 // This extra add rsp instruction is here since the corresponding backup routine has an extra sub rsp instruction; used to ensure stack alignment. + movdqu xmm15, dqword [rsp] + add rsp, 16 + movdqu xmm14, dqword [rsp] + add rsp, 16 + movdqu xmm13, dqword [rsp] + add rsp, 16 + movdqu xmm12, dqword [rsp] + add rsp, 16 + movdqu xmm11, dqword [rsp] + add rsp, 16 + movdqu xmm10, dqword [rsp] + add rsp, 16 + movdqu xmm9, dqword [rsp] + add rsp, 16 + movdqu xmm8, dqword [rsp] + add rsp, 16 + movdqu xmm7, dqword [rsp] + add rsp, 16 + movdqu xmm6, dqword [rsp] + add rsp, 16 + movdqu xmm5, dqword [rsp] + add rsp, 16 + movdqu xmm4, dqword [rsp] + add rsp, 16 + movdqu xmm3, dqword [rsp] + add rsp, 16 + movdqu xmm2, dqword [rsp] + add rsp, 16 + movdqu xmm1, dqword [rsp] + add rsp, 16 + movdqu xmm0, dqword [rsp] + add rsp, 16 + popfq + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + add rsp, 8 + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + */ + + // Includes XMM registers + pub const RESTORE_REGISTERS: [u8; 248] = [ + 0x48, 0x83, 0xC4, 0x08, 0xF3, 0x44, 0x0F, 0x6F, 0xBC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, + 0xC4, 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xAC, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, + 0x10, 0xF3, 0x44, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, + 0xF3, 0x44, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, + 0x44, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, + 0x0F, 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x44, 0x0F, + 0x6F, 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xBC, + 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xB4, 0x24, 0x00, + 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xAC, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0xA4, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, + 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, 0x9C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, + 0x10, 0xF3, 0x0F, 0x6F, 0x94, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, + 0x0F, 0x6F, 0x8C, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0xF3, 0x0F, 0x6F, + 0x84, 0x24, 0x00, 0x00, 0x00, 0x00, 0x48, 0x83, 0xC4, 0x10, 0x9D, 0x41, 0x5F, 0x41, 0x5E, + 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x48, + 0x83, 0xC4, 0x08, 0x5D, 0x5B, 0x5A, 0x59, 0x58, + ]; + + /* + add rsp, 8 // This extra add rsp instruction is here since the corresponding backup routine has an extra sub rsp instruction; used to ensure stack alignment. + popfq + pop r15 + pop r14 + pop r13 + pop r12 + pop r11 + pop r10 + pop r9 + pop r8 + pop rdi + pop rsi + add rsp, 8 + pop rbp + pop rbx + pop rdx + pop rcx + pop rax + */ + + // Omits XMM registers + pub const RESTORE_GENERAL_REGISTERS: [u8; 32] = [ + 0x48, 0x83, 0xC4, 0x08, 0x9D, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D, 0x41, 0x5C, 0x41, 0x5B, + 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58, 0x5F, 0x5E, 0x48, 0x83, 0xC4, 0x08, 0x5D, 0x5B, 0x5A, + 0x59, 0x58, + ]; +} diff --git a/rust/src/detail.rs b/rust/src/detail/mod.rs similarity index 67% rename from rust/src/detail.rs rename to rust/src/detail/mod.rs index fd93ae9..1b7e364 100644 --- a/rust/src/detail.rs +++ b/rust/src/detail/mod.rs @@ -1,3 +1,6 @@ +mod backup_registers; +pub use backup_registers::*; + use std::{ffi::c_void, mem::MaybeUninit}; use zydis::{ @@ -18,12 +21,24 @@ pub enum CodeRelocError { FailedEncodeInstr, } +#[cfg(target_arch = "x86_64")] +const INSTRUCTION_POINTER: Register = Register::RIP; + +#[cfg(target_arch = "x86")] +const INSTRUCTION_POINTER: Register = Register::EIP; + +pub const MINIMUM_OVERWRITE: usize = 5; + pub fn relocate_code( source: usize, source_size: usize, dest: usize, ) -> std::result::Result, CodeRelocError> { + #[cfg(target_arch = "x86_64")] + let decoder = Decoder::new(MachineMode::LONG_64, StackWidth::_64).unwrap(); + #[cfg(target_arch = "x86")] let decoder = Decoder::new(MachineMode::LONG_COMPAT_32, StackWidth::_32).unwrap(); + let source_slice = unsafe { std::slice::from_raw_parts(source as *const u8, source_size) }; let mut source_offset = 0; @@ -40,22 +55,37 @@ pub fn relocate_code( let original_operand = operand.clone(); if let DecodedOperandKind::Imm(ref mut info) = operand.kind { if info.is_relative { + // A hacky solution to get the size of the original instruction. We use this to fix the new IP-relative address. + let absolute_address_minus_instruction_size = + source + source_offset + (info.value as usize); if let Ok(absolute_address) = instr .calc_absolute_address((source + source_offset) as u64, &original_operand) { - info.is_relative = false; - info.value = absolute_address; + let estimated_instruction_size = (absolute_address as isize) + - absolute_address_minus_instruction_size as isize; + // Here, we calculate a new relative address from the absolute address Zydis gives us, and the absolute estimated original instruction size. + let relative_address = absolute_address as isize + - (dest + dest_offset) as isize + - estimated_instruction_size; + info.value = relative_address as u64; } else { return Err(CodeRelocError::FailedAbsoluteAddressCalc); } } } else if let DecodedOperandKind::Mem(ref mut info) = operand.kind { - if info.base == Register::EIP { + if info.base == INSTRUCTION_POINTER { + let absolute_address_minus_instruction_size = + source + source_offset + (info.disp.displacement as usize); if let Ok(absolute_address) = instr .calc_absolute_address((source + source_offset) as u64, &original_operand) { - info.base = Register::NONE; - info.disp.displacement = absolute_address as i64; + let estimated_instruction_size = (absolute_address as isize) + - absolute_address_minus_instruction_size as isize; + // Here, we calculate a new relative address from the absolute address Zydis gives us, and the absolute estimated original instruction size. + let relative_address = absolute_address as isize + - (dest + dest_offset) as isize + - estimated_instruction_size; // FIXME + info.disp.displacement = relative_address as i64; } else { return Err(CodeRelocError::FailedAbsoluteAddressCalc); } @@ -108,7 +138,11 @@ pub fn relocate_code( } pub fn get_instruction_len(ptr: *const u8) -> usize { + #[cfg(target_arch = "x86_64")] + let decoder = Decoder::new(MachineMode::LONG_64, StackWidth::_64).unwrap(); + #[cfg(target_arch = "x86")] let decoder = Decoder::new(MachineMode::LONG_COMPAT_32, StackWidth::_32).unwrap(); + let source_slice = unsafe { std::slice::from_raw_parts(ptr, MAX_INSTRUCTION_LENGTH) }; if let Ok(Some(instr)) = decoder.decode_first::(source_slice) { @@ -119,7 +153,11 @@ pub fn get_instruction_len(ptr: *const u8) -> usize { } pub fn find_suitable_backup_size(base: *const u8) -> (usize, usize) { + #[cfg(target_arch = "x86_64")] + let decoder = Decoder::new(MachineMode::LONG_64, StackWidth::_64).unwrap(); + #[cfg(target_arch = "x86")] let decoder = Decoder::new(MachineMode::LONG_COMPAT_32, StackWidth::_32).unwrap(); + let source_slice = unsafe { std::slice::from_raw_parts(base, MAX_INSTRUCTION_LENGTH) }; let mut offset = 0; @@ -127,7 +165,7 @@ pub fn find_suitable_backup_size(base: *const u8) -> (usize, usize) { for instr_info in decoder.decode_all::(source_slice, base as u64) { let (_ip, _raw_bytes, instr) = instr_info.unwrap(); - if offset >= 5 { + if offset >= MINIMUM_OVERWRITE { break; } offset += instr.length as usize; diff --git a/rust/src/inst.rs b/rust/src/inst.rs new file mode 100644 index 0000000..5e24567 --- /dev/null +++ b/rust/src/inst.rs @@ -0,0 +1,103 @@ +use crate::utils; +use std::slice; + +pub unsafe fn jmp(src: *mut (), dst: *mut ()) -> bool { + let destination_address = dst.expose_provenance(); + let source_address = src.expose_provenance(); + let relative_address = (destination_address - source_address - 5) as isize; + + if relative_address < i32::MIN as isize || relative_address > i32::MAX as isize { + return false; + } + + let restore = utils::set_permission(src, 5, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0xE9]); + utils::write_data(target.add(1), &(relative_address as i32).to_ne_bytes()); + + utils::set_permission(source_address as *mut (), 5, restore); + true +} + +pub unsafe fn call(src: *mut (), dst: *mut ()) -> bool { + let destination_address = dst.expose_provenance(); + let source_address = src.expose_provenance(); + let relative_address = (destination_address - source_address - 5) as isize; + + if relative_address < i32::MIN as isize || relative_address > i32::MAX as isize { + return false; + } + + let restore = utils::set_permission(src, 5, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0xE8]); + utils::write_data(target.add(1), &(relative_address as i32).to_ne_bytes()); + + utils::set_permission(source_address as *mut (), 5, restore); + true +} + +#[cfg(target_arch = "x86_64")] +pub unsafe fn call_indirect(src: *mut (), dst: *mut ()) -> bool { + let destination_address = dst.expose_provenance(); + let source_address = src.expose_provenance(); + let relative_address = (destination_address - source_address - 6) as isize; + + if relative_address < i32::MIN as isize || relative_address > i32::MAX as isize { + return false; + } + + let restore = utils::set_permission(src, 6, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0xFF, 0x15]); + utils::write_data(target.add(2), &(relative_address as i32).to_ne_bytes()); + + utils::set_permission(source_address as *mut (), 6, restore); + true +} + +#[cfg(target_arch = "x86")] +pub unsafe fn call_indirect(src: *mut (), dst: *mut ()) -> bool { + let destination_address = dst as u32; + let source_address = src.expose_provenance(); + + let restore = utils::set_permission(src, 6, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0xFF, 0x15]); + utils::write_data(target.add(2), &destination_address.to_ne_bytes()); + + utils::set_permission(source_address as *mut (), 6, restore); + true +} + +pub unsafe fn push_u32(src: *mut (), val: u32) { + let source_address = src.expose_provenance(); + let restore = utils::set_permission(src, 5, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0x68]); + utils::write_data(target.add(1), &val.to_ne_bytes()); + + utils::set_permission(source_address as *mut (), 5, restore); +} + +pub unsafe fn push_u8(src: *mut (), val: u8) { + let source_address = src.expose_provenance(); + let restore = utils::set_permission(src, 2, utils::Access::ExecuteReadWrite).unwrap(); + + let target = source_address as *mut u8; + utils::write_data(target, &[0x68]); + utils::write_data(target.add(1), &[val]); + + utils::set_permission(source_address as *mut (), 2, restore); +} + +pub unsafe fn nop(addr: *mut (), code_size: usize) { + let restore = utils::set_permission(addr, code_size, utils::Access::ExecuteReadWrite).unwrap(); + slice::from_raw_parts_mut(addr as *mut u8, code_size).fill(0x90); + utils::set_permission(addr, code_size, restore).unwrap(); +} diff --git a/rust/src/legacy.rs b/rust/src/legacy.rs deleted file mode 100644 index c7aa644..0000000 --- a/rust/src/legacy.rs +++ /dev/null @@ -1,11 +0,0 @@ -use super::*; - -pub unsafe fn inline_replace(src: *mut u8, dst: *mut u8, size: usize) { - write_nop(src, size); - write_call(src, dst); -} - -pub unsafe fn inline_replace_jump(src: *mut u8, dst: *mut u8, size: usize) { - write_nop(src, size); - write_jmp(src, dst); -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index e58f3ae..04b5ef3 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,15 +1,19 @@ -use std::sync::Mutex; +use std::{ptr::with_exposed_provenance, sync::Mutex}; use once_cell::sync::Lazy; use utils::JitMemory; use windows_sys::Win32::System::Threading::GetCurrentThread; -use crate::detail::*; - pub use sunset_macro::*; +use crate::detail::relocate_code; +#[cfg(target_arch = "x86_64")] +use crate::detail::x64 as arch; +#[cfg(target_arch = "x86")] +use crate::detail::x86 as arch; + pub mod detail; -pub mod legacy; +pub mod inst; pub mod utils; #[macro_export] @@ -28,47 +32,123 @@ macro_rules! install_hooks { }; } -pub unsafe fn write_jmp(src: *mut u8, dst: *mut u8) -> Option<()> { - let relative_address = (dst as u32) - (src as u32) - 5; - - crate::utils::set_permission(src, 5, crate::utils::Perm::ExecuteReadWrite)?; - *src = 0xE9; - *(src.add(1) as *mut u32) = relative_address; - Some(()) +#[cfg(target_arch = "x86_64")] +#[repr(C, packed)] +pub union Register { + pub pointer: *mut (), + pub unsigned_integer: u64, + pub signed_integer: i64, + pub floating_point: f64, } -pub unsafe fn write_call(src: *mut u8, dst: *mut u8) -> Option<()> { - let relative_address = (dst as u32) - (src as u32) - 5; +#[cfg(target_arch = "x86")] +#[repr(C, packed)] +pub union Register { + pub pointer: *mut (), + pub unsigned_integer: u32, + pub signed_integer: i32, + pub floating_point: f32, +} - crate::utils::set_permission(src, 5, crate::utils::Perm::ExecuteReadWrite)?; - *src = 0xE8; - *(src.add(1) as *mut u32) = relative_address; - Some(()) +#[repr(C, packed)] +pub union XMMRegister { + f32: [f32; 4], + f64: [f64; 2], + i8: [i8; 16], + u8: [i8; 16], + i16: [i16; 8], + u16: [i16; 8], + i32: [i32; 4], + u32: [i32; 4], + i64: [i64; 2], + u64: [u64; 2], } -pub unsafe fn write_push(src: *mut u8, dst: u32) -> Option<()> { - crate::utils::set_permission(src, 5, crate::utils::Perm::ExecuteReadWrite)?; - *src = 0x68; - *(src.add(1) as *mut u32) = dst; - Some(()) +#[cfg(target_arch = "x86_64")] +#[repr(C, packed)] +pub struct InlineCtx { + pub rflags: Register, + pub r15: Register, + pub r14: Register, + pub r13: Register, + pub r12: Register, + pub r11: Register, + pub r10: Register, + pub r9: Register, + pub r8: Register, + pub rdi: Register, + pub rsi: Register, + pub rsp: Register, + pub rbp: Register, + pub rbx: Register, + pub rdx: Register, + pub rcx: Register, + pub rax: Register, } -pub unsafe fn write_nop(addr: *mut u8, code_size: usize) -> Option<()> { - crate::utils::set_permission(addr, code_size, crate::utils::Perm::ExecuteReadWrite)?; - std::ptr::write_bytes(addr, 0x90, code_size); - Some(()) +#[cfg(target_arch = "x86")] +#[repr(C, packed)] +pub struct InlineCtx { + pub eflags: Register, + pub edi: Register, + pub esi: Register, + pub ebp: Register, + pub esp: Register, + pub ebx: Register, + pub edx: Register, + pub ecx: Register, + pub eax: Register, } +#[cfg(target_arch = "x86_64")] #[repr(C, packed)] -pub union Register { - pub pointer: *mut (), - pub unsigned_integer: u32, - pub signed_integer: i32, - pub floating_point: f32, +pub struct InlineCtxEx { + pub xmm15: XMMRegister, + pub xmm14: XMMRegister, + pub xmm13: XMMRegister, + pub xmm12: XMMRegister, + pub xmm11: XMMRegister, + pub xmm10: XMMRegister, + pub xmm9: XMMRegister, + pub xmm8: XMMRegister, + pub xmm7: XMMRegister, + pub xmm6: XMMRegister, + pub xmm5: XMMRegister, + pub xmm4: XMMRegister, + pub xmm3: XMMRegister, + pub xmm2: XMMRegister, + pub xmm1: XMMRegister, + pub xmm0: XMMRegister, + pub rflags: Register, + pub r15: Register, + pub r14: Register, + pub r13: Register, + pub r12: Register, + pub r11: Register, + pub r10: Register, + pub r9: Register, + pub r8: Register, + pub rdi: Register, + pub rsi: Register, + pub rsp: Register, + pub rbp: Register, + pub rbx: Register, + pub rdx: Register, + pub rcx: Register, + pub rax: Register, } +#[cfg(target_arch = "x86")] #[repr(C, packed)] -pub struct InlineCtx { +pub struct InlineCtxEx { + pub xmm7: XMMRegister, + pub xmm6: XMMRegister, + pub xmm5: XMMRegister, + pub xmm4: XMMRegister, + pub xmm3: XMMRegister, + pub xmm2: XMMRegister, + pub xmm1: XMMRegister, + pub xmm0: XMMRegister, pub eflags: Register, pub edi: Register, pub esi: Register, @@ -81,78 +161,242 @@ pub struct InlineCtx { } type CallbackFuncPtr = extern "cdecl" fn(&mut InlineCtx); +type ExCallbackFuncPtr = extern "cdecl" fn(&mut InlineCtxEx); #[derive(Debug)] pub enum InlineHookErr { - // The size of the code at the desired address cannot be made large enough to fit a 5-byte JMP. - InvalidCodeSize, - - // Failed to relocate code from the source to a trampoline. - FailedToRelocateCode, + // The user-set address does not have enough free space (>= 5 Bytes) for a jmp instruction. + NotEnoughSpaceToInsertJump, + // The callback function is too far away from the inline handler to use a 6-Byte call-indirect instruction. + CallbackTooFarFromInlineHandler, + // The original function is too far away from the inline handler to use 5-Byte jmp instructions. + OriginalCodeTooFarFromInlineHandler, } static mut JIT_MEMORY: Lazy>> = Lazy::new(|| Mutex::new(vec![])); +const ADDRESS_SIZE: usize = std::mem::size_of::(); + pub unsafe fn inline_hook(ptr: usize, callback: CallbackFuncPtr) -> Result<(), InlineHookErr> { // Calculate the minimum bytes needed to be backed up, and an upper-bound limit of how many bytes the relocated code could take. (Used for below allocation) - let (original_code_len, padded_code_len) = find_suitable_backup_size(ptr as *const u8); - - if original_code_len < 5 { - Err(InlineHookErr::InvalidCodeSize) - } else { - let jit_area = JitMemory::new(11 + padded_code_len + 5); - // Build inline handler. - jit_area.data[0] = 0x60; // pushad - jit_area.data[1] = 0x9C; // pushfd - jit_area.data[2] = 0x54; // push esp - write_call(jit_area.data.as_mut_ptr().offset(3), callback as *mut u8).unwrap(); // call callback - jit_area.data[8] = 0x58; // pop eax (We don't actually need to use EAX later, its just that a ``pop eax`` takes fewer bytes than a ``add esp, 4``.) - jit_area.data[9] = 0x9D; // popfd - jit_area.data[10] = 0x61; // popad - - // Attempt to build/relocate the code, and if successful, copy into the trampoline. - match relocate_code( - ptr as usize, - original_code_len, - jit_area.data.as_ptr().offset(11) as usize, - ) { - Ok(relocated) => { - jit_area.data[11..11 + relocated.len()].copy_from_slice(&relocated); - - let old_perm = crate::utils::set_permission( - ptr as *mut u8, - original_code_len, - crate::utils::Perm::ExecuteReadWrite, - ) - .unwrap(); - - // Insert jmp from the inline handler back to the original function. - write_jmp( - jit_area.data.as_mut_ptr().offset((11 + relocated.len()) as isize), - (ptr + 5) as *mut u8, - ) - .unwrap(); - - // Ensure original function has the trampoline area nop'd out. - write_nop(ptr as *mut u8, original_code_len); - - // Insert jmp from the source to the inline handler. - write_jmp(ptr as *mut u8, jit_area.data.as_mut_ptr()); - - // Reset the permission at the source. - crate::utils::set_permission(ptr as *mut u8, original_code_len, old_perm).unwrap(); - - let mut vec = JIT_MEMORY.lock().unwrap(); - vec.push(jit_area); - - Ok(()) - } - Err(err) => { - dbg!(err); - Err(InlineHookErr::FailedToRelocateCode) - } - } + let (original_code_len, padded_code_len) = detail::find_suitable_backup_size(ptr as *const u8); + + if original_code_len < detail::MINIMUM_OVERWRITE { + return Err(InlineHookErr::NotEnoughSpaceToInsertJump); + } + + // Allocate code for inline handler. + // The size of the JIT memory block should be able to account for: + // - A pointer to Derived::callback (sizeof(void*)) + // - The register backup routine + // - A 6-byte indirect call instruction to call Derived::callback (6) + // - The register restore routine + // - A 5-byte jump instruction to return to the original code (5) + let jit_area_len = arch::BACKUP_GENERAL_REGISTERS.len() + + arch::RESTORE_GENERAL_REGISTERS.len() + + 6 + + padded_code_len + + 5 + + ADDRESS_SIZE; + + #[cfg(target_arch = "x86_64")] + // On x64, if we can't find any free space nearby, we have to panic. + let jit_area = utils::allocate_near(ptr, jit_area_len, i32::MAX as usize).unwrap(); + #[cfg(target_arch = "x86")] + // But on x86, we can reliably fall back to a plain ol' VirtualAlloc. + let jit_area = utils::allocate_near(ptr, jit_area_len, i32::MAX as usize) + .unwrap_or(utils::JitMemory::new(jit_area_len)); + + // Write the callback address at the start of the inline handler, so we can use a call-indirect instruction to get there. + jit_area.data[0..ADDRESS_SIZE].copy_from_slice(&usize::to_ne_bytes( + (callback as *const ()).expose_provenance(), + )); + + // Build inline handler. + jit_area.data[ADDRESS_SIZE..ADDRESS_SIZE + arch::BACKUP_GENERAL_REGISTERS.len()] + .copy_from_slice(&arch::BACKUP_GENERAL_REGISTERS); + // The address of the callback is held at the start of the JIT memory, so we'll use jit_area.data.as_ptr() here. + if !inst::call_indirect( + jit_area + .data + .as_mut_ptr() + .add(ADDRESS_SIZE + arch::BACKUP_GENERAL_REGISTERS.len()) as *mut (), + jit_area.data.as_mut_ptr() as *mut (), + ) { + // This should be infallible since it will literally only be a few hundred bytes away tops... + return Err(InlineHookErr::CallbackTooFarFromInlineHandler); + } + jit_area.data[ADDRESS_SIZE + arch::BACKUP_GENERAL_REGISTERS.len() + 6 + ..ADDRESS_SIZE + + arch::BACKUP_GENERAL_REGISTERS.len() + + 6 + + arch::RESTORE_GENERAL_REGISTERS.len()] + .copy_from_slice(&arch::RESTORE_GENERAL_REGISTERS); + + // Attempt to build/relocate the code, and if successful, copy into the trampoline. + let relocated = relocate_code( + ptr, + original_code_len, + jit_area + .data + .as_ptr() + .add( + ADDRESS_SIZE + + arch::BACKUP_GENERAL_REGISTERS.len() + + 6 + + arch::RESTORE_GENERAL_REGISTERS.len(), + ) + .expose_provenance(), + ) + .unwrap(); + jit_area.data[ADDRESS_SIZE + + arch::BACKUP_GENERAL_REGISTERS.len() + + 6 + + arch::RESTORE_GENERAL_REGISTERS.len() + ..ADDRESS_SIZE + + arch::BACKUP_GENERAL_REGISTERS.len() + + 6 + + arch::RESTORE_GENERAL_REGISTERS.len() + + relocated.len()] + .copy_from_slice(&relocated); + + // Write the jmp from the inline handler back to the original function. + if !inst::jmp( + jit_area.data.as_mut_ptr().add( + ADDRESS_SIZE + + arch::BACKUP_GENERAL_REGISTERS.len() + + 6 + + arch::RESTORE_GENERAL_REGISTERS.len() + + relocated.len(), + ) as *mut (), + with_exposed_provenance::<()>(ptr + original_code_len) as *mut (), + ) { + return Err(InlineHookErr::OriginalCodeTooFarFromInlineHandler); + } + + // Ensure original function has the trampoline area nop'd out before inserting the jmp from the source to the inline handler (jmp ptr). + inst::nop( + with_exposed_provenance::<()>(ptr) as *mut (), + original_code_len, + ); + if !inst::jmp( + with_exposed_provenance::<()>(ptr) as *mut (), + jit_area.data.as_mut_ptr().add(ADDRESS_SIZE) as *mut (), + ) { + return Err(InlineHookErr::OriginalCodeTooFarFromInlineHandler); + } + + let mut vec = JIT_MEMORY.lock().unwrap(); + vec.push(jit_area); + + Ok(()) +} + +pub unsafe fn extended_inline_hook( + ptr: usize, + callback: ExCallbackFuncPtr, +) -> Result<(), InlineHookErr> { + // Calculate the minimum bytes needed to be backed up, and an upper-bound limit of how many bytes the relocated code could take. (Used for below allocation) + let (original_code_len, padded_code_len) = detail::find_suitable_backup_size(ptr as *const u8); + + if original_code_len < detail::MINIMUM_OVERWRITE { + return Err(InlineHookErr::NotEnoughSpaceToInsertJump); + } + + // Allocate code for inline handler. + // The size of the JIT memory block should be able to account for: + // - A pointer to Derived::callback (sizeof(void*)) + // - The register backup routine + // - A 6-byte indirect call instruction to call Derived::callback (6) + // - The register restore routine + // - A 5-byte jump instruction to return to the original code (5) + let jit_area_len = arch::BACKUP_REGISTERS.len() + + arch::RESTORE_REGISTERS.len() + + 6 + + padded_code_len + + 5 + + std::mem::size_of::<*const ()>(); + + #[cfg(target_arch = "x86_64")] + // On x64, if we can't find any free space nearby, we have to panic. + let jit_area = utils::allocate_near(ptr, jit_area_len, i32::MAX as usize).unwrap(); + #[cfg(target_arch = "x86")] + // But on x86, we can reliably fall back to a plain ol' VirtualAlloc. + let jit_area = utils::allocate_near(ptr, jit_area_len, i32::MAX as usize) + .unwrap_or(utils::JitMemory::new(jit_area_len)); + + // Write the callback address at the start of the inline handler, so we can use a call-indirect instruction to get there. + jit_area.data[0..ADDRESS_SIZE].copy_from_slice(&usize::to_ne_bytes( + (callback as *const ()).expose_provenance(), + )); + + // Build inline handler. + jit_area.data[ADDRESS_SIZE..ADDRESS_SIZE + arch::BACKUP_REGISTERS.len()] + .copy_from_slice(&arch::BACKUP_REGISTERS); + // The address of the callback is held at the start of the JIT memory, so we'll use jit_area.data.as_ptr() here. + if !inst::call_indirect( + jit_area + .data + .as_mut_ptr() + .add(ADDRESS_SIZE + arch::BACKUP_REGISTERS.len()) as *mut (), + jit_area.data.as_mut_ptr() as *mut (), + ) { + // This should be infallible since it will literally only be a few hundred bytes away tops... + return Err(InlineHookErr::CallbackTooFarFromInlineHandler); + } + jit_area.data[ADDRESS_SIZE + arch::BACKUP_REGISTERS.len() + 6 + ..ADDRESS_SIZE + arch::BACKUP_REGISTERS.len() + 6 + arch::RESTORE_REGISTERS.len()] + .copy_from_slice(&arch::RESTORE_REGISTERS); + + // Attempt to build/relocate the code, and if successful, copy into the trampoline. + let relocated = relocate_code( + ptr, + original_code_len, + jit_area + .data + .as_ptr() + .add(ADDRESS_SIZE + arch::BACKUP_REGISTERS.len() + 6 + arch::RESTORE_REGISTERS.len()) + .expose_provenance(), + ) + .unwrap(); + jit_area.data[ADDRESS_SIZE + arch::BACKUP_REGISTERS.len() + 6 + arch::RESTORE_REGISTERS.len() + ..ADDRESS_SIZE + + arch::BACKUP_REGISTERS.len() + + 6 + + arch::RESTORE_REGISTERS.len() + + relocated.len()] + .copy_from_slice(&relocated); + + // Write the jmp from the inline handler back to the original function. + if !inst::jmp( + jit_area.data.as_mut_ptr().add( + ADDRESS_SIZE + + arch::BACKUP_REGISTERS.len() + + 6 + + arch::RESTORE_REGISTERS.len() + + relocated.len(), + ) as *mut (), + with_exposed_provenance::<()>(ptr + original_code_len) as *mut (), + ) { + return Err(InlineHookErr::OriginalCodeTooFarFromInlineHandler); } + + // Ensure original function has the trampoline area nop'd out before inserting the jmp from the source to the inline handler (jmp ptr). + inst::nop( + with_exposed_provenance::<()>(ptr) as *mut (), + original_code_len, + ); + if !inst::jmp( + with_exposed_provenance::<()>(ptr) as *mut (), + jit_area.data.as_mut_ptr().add(ADDRESS_SIZE) as *mut (), + ) { + return Err(InlineHookErr::OriginalCodeTooFarFromInlineHandler); + } + + let mut vec = JIT_MEMORY.lock().unwrap(); + vec.push(jit_area); + + Ok(()) } pub unsafe fn replace_hook(ptr: &mut F, callback: *const ()) { diff --git a/rust/src/utils.rs b/rust/src/utils.rs index 7fe5b38..acc990c 100644 --- a/rust/src/utils.rs +++ b/rust/src/utils.rs @@ -1,7 +1,14 @@ -use windows_sys::Win32::System::Memory::*; +use std::{mem::MaybeUninit, os::raw::c_void}; + +use windows_sys::Win32::System::{ + LibraryLoader::GetModuleHandleA, + Memory::*, + SystemInformation::{GetSystemInfo, SYSTEM_INFO}, + Threading::GetCurrentProcess, +}; #[repr(u32)] -pub enum Perm { +pub enum Access { None = PAGE_NOACCESS, Read = PAGE_READONLY, ReadWrite = PAGE_READWRITE, @@ -16,48 +23,128 @@ pub enum Perm { } // Sets the desired permission on the memory block. -pub unsafe fn set_permission(ptr: *mut u8, size: usize, perm: Perm) -> Option { - let mut old_perm: Perm = Perm::None; +pub unsafe fn set_permission(ptr: *mut (), size: usize, access: Access) -> Option { + let mut old_access: Access = Access::None; let success = unsafe { VirtualProtect( std::mem::transmute(ptr), size, - perm as PAGE_PROTECTION_FLAGS, - std::mem::transmute(&mut old_perm), + access as PAGE_PROTECTION_FLAGS, + std::mem::transmute(&mut old_access), ) }; if success != 0 { - Some(old_perm) + Some(old_access) } else { None } } +pub fn get_main_load_address() -> usize { + unsafe { GetModuleHandleA(std::ptr::null()) as usize } +} + pub struct JitMemory<'a> { - pub data: &'a mut [u8], + pub data: &'a mut [u8], } impl<'a> JitMemory<'a> { - pub fn new(size: usize) -> Self { - let data = unsafe { - let ptr = VirtualAlloc( - std::ptr::null_mut(), - size, - MEM_COMMIT | MEM_RESERVE, - PAGE_EXECUTE_READWRITE, - ) as *mut u8; - std::slice::from_raw_parts_mut(ptr, size) - }; - Self { - data, - } - } + pub fn new(size: usize) -> Self { + let data = unsafe { + let ptr = VirtualAlloc( + std::ptr::null(), + size, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE, + ) as *mut u8; + std::slice::from_raw_parts_mut(ptr, size) + }; + Self { data } + } + pub unsafe fn new_placement(address: usize, size: usize) -> Option { + let ptr = VirtualAlloc( + address as *const c_void, + size, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE, + ) as *mut u8; + if ptr.is_null() { + return None; + } + Some(Self { + data: std::slice::from_raw_parts_mut(ptr, size), + }) + } } impl<'a> std::ops::Drop for JitMemory<'a> { - fn drop(&mut self) { - unsafe { - VirtualFree(self.data.as_mut_ptr() as *mut core::ffi::c_void, 0, MEM_RELEASE) - }; - } + fn drop(&mut self) { + unsafe { + VirtualFree( + self.data.as_mut_ptr() as *mut core::ffi::c_void, + 0, + MEM_RELEASE, + ) + }; + } +} + +pub unsafe fn allocate_near( + address: usize, + len: usize, + max_distance: usize, +) -> Option> { + let mut info_uninit: MaybeUninit = MaybeUninit::zeroed(); + GetSystemInfo(info_uninit.as_mut_ptr()); + let info = info_uninit.assume_init(); + + let aligned_address = address & !(info.dwAllocationGranularity as usize - 1); + + // Iterate backwards towards the start of the available address space from the given `address` argument. + let mut current_address = aligned_address; + while current_address > info.lpMinimumApplicationAddress as usize + && current_address.abs_diff(address) < max_distance + { + let mut mbi_uninit: MaybeUninit = MaybeUninit::zeroed(); + VirtualQueryEx( + GetCurrentProcess(), + current_address as *const c_void, + mbi_uninit.as_mut_ptr(), + std::mem::size_of::(), + ); + let mbi = mbi_uninit.assume_init(); + if mbi.State == MEM_FREE { + if let Some(block) = JitMemory::new_placement(current_address, len) { + return Some(block); + } + } + current_address -= info.dwAllocationGranularity as usize; + } + + // Iterate forwards towards the end of the available address space from the given `address` argument. + current_address = aligned_address; + while current_address < info.lpMaximumApplicationAddress as usize + && current_address.abs_diff(address) < max_distance + { + let mut mbi_uninit: MaybeUninit = MaybeUninit::zeroed(); + VirtualQueryEx( + GetCurrentProcess(), + current_address as *const c_void, + mbi_uninit.as_mut_ptr(), + std::mem::size_of::(), + ); + let mbi = mbi_uninit.assume_init(); + if mbi.State == MEM_FREE { + if let Some(block) = JitMemory::new_placement(current_address, len) { + return Some(block); + } + } + current_address += info.dwAllocationGranularity as usize; + } + + return None; +} + +pub unsafe fn write_data(dst: *mut u8, bytes: &[u8]) { + std::slice::from_raw_parts_mut(dst, bytes.len()).copy_from_slice(bytes); } diff --git a/rust/sunset-macro/src/attributes/mod.rs b/rust/sunset-macro/src/attributes/mod.rs index 4c50f89..93d6092 100644 --- a/rust/sunset-macro/src/attributes/mod.rs +++ b/rust/sunset-macro/src/attributes/mod.rs @@ -1,7 +1,8 @@ -use syn::parse::{ParseStream, Parse}; -use syn::{Token, parenthesized, token}; +use syn::parse::{Parse, ParseStream}; +use syn::{parenthesized, token, Token}; mod kw { syn::custom_keyword!(inline); + syn::custom_keyword!(extended_inline); syn::custom_keyword!(offset); } @@ -9,18 +10,27 @@ mod kw { pub struct HookAttrs { pub offset: Option, pub inline: bool, + pub extended_inline: bool, } fn merge(attr1: HookAttrs, attr2: HookAttrs) -> HookAttrs { let ( - HookAttrs { offset: o1, inline: i1 }, - HookAttrs { offset: o2, inline: i2 }, + HookAttrs { + offset: o1, + inline: i1, + extended_inline: ei1, + }, + HookAttrs { + offset: o2, + inline: i2, + extended_inline: ei2, + }, ) = (attr1, attr2); - HookAttrs { offset: o1.or(o2), - inline: i1 || i2 + inline: i1 || i2, + extended_inline: ei1 || ei2, } } @@ -29,7 +39,7 @@ impl Parse for HookAttrs { let look = input.lookahead1(); let attr = if look.peek(kw::offset) { let MetaItem:: { item: offset, .. } = input.parse()?; - + let mut a = HookAttrs::default(); a.offset = Some(offset); a @@ -38,6 +48,11 @@ impl Parse for HookAttrs { let mut a = HookAttrs::default(); a.inline = true; a + } else if look.peek(kw::extended_inline) { + let _: kw::extended_inline = input.parse()?; + let mut a = HookAttrs::default(); + a.extended_inline = true; + a } else { return Err(look.error()); }; @@ -57,6 +72,7 @@ impl Parse for HookAttrs { #[derive(Debug, Clone)] pub struct MetaItem { + #[allow(dead_code)] pub ident: Keyword, pub item: Item, } @@ -73,9 +89,6 @@ impl Parse for MetaItem { input.parse()? }; - Ok(Self { - ident, - item - }) + Ok(Self { ident, item }) } } diff --git a/rust/sunset-macro/src/install_fn.rs b/rust/sunset-macro/src/install_fn.rs index 7b2a49b..cba1073 100644 --- a/rust/sunset-macro/src/install_fn.rs +++ b/rust/sunset-macro/src/install_fn.rs @@ -1,27 +1,38 @@ -use quote::{quote, quote_spanned, ToTokens}; use super::attributes::HookAttrs; use proc_macro2::Span; +use quote::{quote, quote_spanned, ToTokens}; pub fn generate(name: &syn::Ident, orig: &syn::Ident, attrs: &HookAttrs) -> impl ToTokens { let _install_fn = quote::format_ident!("{}_sunset_internal_install_hook", name); let replace = ({ - attrs.offset.as_ref().map(|offset|{ - quote! { - #offset - } - }) - }) - .unwrap_or_else(||{ - quote_spanned!(Span::call_site() => - compile_error!("Missing 'offset' item in hook macro"); - ) - }); + attrs.offset.as_ref().map(|offset| { + quote! { + #offset + } + }) + }) + .unwrap_or_else(|| { + quote_spanned!(Span::call_site() => + compile_error!("Missing 'offset' item in hook macro"); + ) + }); - if attrs.inline { - quote!{ + if attrs.extended_inline { + quote! { + pub fn #_install_fn() { + unsafe { + ::sunset_rs::extended_inline_hook( + (#replace as usize), + (#name), + ).unwrap() + } + } + } + } else if attrs.inline { + quote! { pub fn #_install_fn() { unsafe { - ::sunset::inline_hook( + ::sunset_rs::inline_hook( (#replace as usize), (#name), ).unwrap() @@ -29,18 +40,18 @@ pub fn generate(name: &syn::Ident, orig: &syn::Ident, attrs: &HookAttrs) -> impl } } } else { - quote!{ + quote! { #[allow(non_upper_case_globals)] pub static mut #orig: usize = #replace; pub fn #_install_fn() { unsafe { - ::sunset::replace_hook( - &mut #orig, + ::sunset_rs::replace_hook( + &mut #orig, #name as *const (), ) } } } } -} \ No newline at end of file +} diff --git a/rust/sunset-macro/src/lib.rs b/rust/sunset-macro/src/lib.rs index 04c808b..666cc78 100644 --- a/rust/sunset-macro/src/lib.rs +++ b/rust/sunset-macro/src/lib.rs @@ -1,5 +1,3 @@ -#![feature(abi_thiscall)] - use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens}; @@ -92,10 +90,12 @@ pub fn hook(attrs: TokenStream, input: TokenStream) -> TokenStream { let mut output = TokenStream2::new(); // force cdecl if inline - if attrs.inline { + if attrs.inline || attrs.extended_inline { mod_fn.sig.abi = Some(syn::Abi { - extern_token: syn::token::Extern { span: Span::call_site() }, - name: Some(syn::LitStr::new("cdecl", Span::call_site())) + extern_token: syn::token::Extern { + span: Span::call_site(), + }, + name: Some(syn::LitStr::new("cdecl", Span::call_site())), }); } @@ -104,13 +104,10 @@ pub fn hook(attrs: TokenStream, input: TokenStream) -> TokenStream { let args_tokens = mod_fn.sig.inputs.iter().map(remove_mut); let return_tokens = mod_fn.sig.output.to_token_stream(); - let _orig_fn = quote::format_ident!( - "{}_sunset_internal_original_fn", - mod_fn.sig.ident - ); + let _orig_fn = quote::format_ident!("{}_sunset_internal_original_fn", mod_fn.sig.ident); // allow original! - if !attrs.inline { + if !attrs.inline && !attrs.extended_inline { let orig_stmt: Stmt = parse_quote! { #[allow(unused_macros)] macro_rules! original { @@ -123,7 +120,7 @@ pub fn hook(attrs: TokenStream, input: TokenStream) -> TokenStream { }; mod_fn.block.stmts.insert(0, orig_stmt); let orig_stmt: Stmt = parse_quote! { - #[allow(unused_macros)] + #[allow(unused_macros)] macro_rules! call_original { ($($args:expr),* $(,)?) => { original!()($($args),*) @@ -152,5 +149,6 @@ pub fn install_hook(input: TokenStream) -> TokenStream { quote!( #path(); - ).into() + ) + .into() }