From 79607447e5b137bef892a3154517ecc570f48808 Mon Sep 17 00:00:00 2001 From: momo5502 Date: Thu, 5 Sep 2024 11:13:54 +0200 Subject: [PATCH] Support exception hooks --- src/emulator/byte_buffer.hpp | 191 ++++++++++++++++++ src/emulator/emulator.hpp | 3 + src/emulator/memory_manager.hpp | 14 ++ src/emulator/serializable.hpp | 9 + src/emulator/x64_emulator.hpp | 1 + src/unicorn_emulator/unicorn_x64_emulator.cpp | 43 +++- src/windows_emulator/main.cpp | 97 +++++---- 7 files changed, 299 insertions(+), 59 deletions(-) create mode 100644 src/emulator/byte_buffer.hpp create mode 100644 src/emulator/serializable.hpp diff --git a/src/emulator/byte_buffer.hpp b/src/emulator/byte_buffer.hpp new file mode 100644 index 00000000..b06a376b --- /dev/null +++ b/src/emulator/byte_buffer.hpp @@ -0,0 +1,191 @@ +#pragma once + +#include +#include +#include +#include + +namespace utils +{ + class buffer_deserializer + { + public: + template + buffer_deserializer(const std::basic_string_view& buffer) + : buffer_(reinterpret_cast(buffer.data()), buffer.size() * sizeof(T)) + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + } + + template + buffer_deserializer(const std::basic_string& buffer) + : buffer_deserializer(std::basic_string_view(buffer.data(), buffer.size())) + { + } + + template + buffer_deserializer(const std::vector& buffer) + : buffer_deserializer(std::basic_string_view(buffer.data(), buffer.size())) + { + } + + void read(void* data, const size_t length) + { + if (this->offset_ + length > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + memcpy(data, this->buffer_.data() + this->offset_, length); + this->offset_ += length; + } + + std::string read_data(const size_t length) + { + std::string result{}; + result.resize(length); + + this->read(result.data(), result.size()); + + return result; + } + + template + T read() + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + + T object{}; + this->read(&object, sizeof(object)); + return object; + } + + template + std::vector read_vector() + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + + std::vector result{}; + const auto size = this->read(); + const auto totalSize = size * sizeof(T); + + if (this->offset_ + totalSize > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + result.resize(size); + this->read(result.data(), totalSize); + + return result; + } + + std::string read_string() + { + std::string result{}; + const auto size = this->read(); + + if (this->offset_ + size > this->buffer_.size()) + { + throw std::runtime_error("Out of bounds read from byte buffer"); + } + + result.resize(size); + this->read(result.data(), size); + + return result; + } + + size_t get_remaining_size() const + { + return this->buffer_.size() - offset_; + } + + std::string get_remaining_data() + { + return this->read_data(this->get_remaining_size()); + } + + size_t get_offset() const + { + return this->offset_; + } + + private: + size_t offset_{0}; + std::basic_string_view buffer_{}; + }; + + class buffer_serializer + { + public: + buffer_serializer() = default; + + void write(const void* buffer, const size_t length) + { + this->buffer_.append(static_cast(buffer), length); + } + + void write(const char* text) + { + this->write(text, strlen(text)); + } + + void write_string(const char* str, const size_t length) + { + this->write(static_cast(length)); + this->write(str, length); + } + + void write_string(const std::string& str) + { + this->write_string(str.data(), str.size()); + } + + void write_string(const char* str) + { + this->write_string(str, strlen(str)); + } + + void write(const buffer_serializer& object) + { + const auto& buffer = object.get_buffer(); + this->write(buffer.data(), buffer.size()); + } + + template + void write(const T& object) + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + this->write(&object, sizeof(object)); + } + + template + void write(const std::vector& vec) + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + this->write(vec.data(), vec.size() * sizeof(T)); + } + + template + void write_vector(const std::vector& vec) + { + static_assert(std::is_trivially_copyable_v, "Type must be trivially copyable"); + this->write(static_cast(vec.size())); + this->write(vec); + } + + const std::string& get_buffer() const + { + return this->buffer_; + } + + std::string move_buffer() + { + return std::move(this->buffer_); + } + + private: + std::string buffer_{}; + }; +} diff --git a/src/emulator/emulator.hpp b/src/emulator/emulator.hpp index 9fcd85ae..63dcabf8 100644 --- a/src/emulator/emulator.hpp +++ b/src/emulator/emulator.hpp @@ -17,6 +17,7 @@ enum class hook_continuation : bool using hook_callback = std::function; +using interrupt_hook_callback = std::function; using simple_memory_hook_callback = std::function; using complex_memory_hook_callback = std::function; @@ -41,6 +42,8 @@ public: complex_memory_hook_callback callback) = 0; virtual emulator_hook* hook_instruction(int instruction_type, hook_callback callback) = 0; + virtual emulator_hook* hook_interrupt(interrupt_hook_callback callback) = 0; + virtual void delete_hook(emulator_hook* hook) = 0; emulator_hook* hook_memory_read(const uint64_t address, const size_t size, simple_memory_hook_callback callback) diff --git a/src/emulator/memory_manager.hpp b/src/emulator/memory_manager.hpp index c4a95f64..f946cc72 100644 --- a/src/emulator/memory_manager.hpp +++ b/src/emulator/memory_manager.hpp @@ -30,6 +30,20 @@ public: virtual ~memory_manager() = default; + template + T read_memory(const uint64_t address) + { + T value{}; + this->read_memory(address, &value, sizeof(value)); + return value; + } + + template + void write_memory(const uint64_t address, const T& value) + { + this->write_memory(address, &value, sizeof(value)); + } + virtual void read_memory(uint64_t address, void* data, size_t size) = 0; virtual bool try_read_memory(uint64_t address, void* data, size_t size) = 0; virtual void write_memory(uint64_t address, const void* data, size_t size) = 0; diff --git a/src/emulator/serializable.hpp b/src/emulator/serializable.hpp new file mode 100644 index 00000000..3d4cb32f --- /dev/null +++ b/src/emulator/serializable.hpp @@ -0,0 +1,9 @@ +#pragma once +#include "byte_buffer.hpp" + +struct serializable +{ + virtual ~serializable() = default; + virtual void serialize(utils::buffer_serializer& buffer) = 0; + virtual void deserialize(utils::buffer_deserializer& buffer) = 0; +}; diff --git a/src/emulator/x64_emulator.hpp b/src/emulator/x64_emulator.hpp index c0593a8f..6514c1d0 100644 --- a/src/emulator/x64_emulator.hpp +++ b/src/emulator/x64_emulator.hpp @@ -4,6 +4,7 @@ enum class x64_hookable_instructions { + invalid, syscall, cpuid, rdtsc, diff --git a/src/unicorn_emulator/unicorn_x64_emulator.cpp b/src/unicorn_emulator/unicorn_x64_emulator.cpp index 2cf8f69c..47a33c05 100644 --- a/src/unicorn_emulator/unicorn_x64_emulator.cpp +++ b/src/unicorn_emulator/unicorn_x64_emulator.cpp @@ -252,9 +252,6 @@ namespace unicorn emulator_hook* hook_instruction(int instruction_type, hook_callback callback) { - const auto uc_instruction = map_hookable_instruction( - static_cast(instruction_type)); - function_wrapper wrapper([c = std::move(callback)](uc_engine*) { return (c() == hook_continuation::skip_instruction) @@ -263,11 +260,23 @@ namespace unicorn }); unicorn_hook hook{*this}; - - uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN, wrapper.get_function(), - wrapper.get_user_data(), 0, std::numeric_limits::max(), uc_instruction)); - auto container = std::make_unique(); + + const auto inst_type = static_cast(instruction_type); + + if (inst_type == x64_hookable_instructions::invalid) + { + uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN_INVALID, wrapper.get_function(), + wrapper.get_user_data(), 0, std::numeric_limits::max())); + } + else + { + const auto uc_instruction = map_hookable_instruction(inst_type); + uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INSN, wrapper.get_function(), + wrapper.get_user_data(), 0, std::numeric_limits::max(), + uc_instruction)); + } + container->add(std::move(wrapper), std::move(hook)); auto* result = container->as_opaque_hook(); @@ -277,6 +286,26 @@ namespace unicorn return result; } + emulator_hook* hook_interrupt(interrupt_hook_callback callback) override + { + function_wrapper wrapper([c = std::move(callback)](uc_engine*, const int interrupt_type) + { + c(interrupt_type); + }); + + unicorn_hook hook{*this}; + auto container = std::make_unique(); + + uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INTR, wrapper.get_function(), + wrapper.get_user_data(), 0, std::numeric_limits::max())); + + container->add(std::move(wrapper), std::move(hook)); + + auto* result = container->as_opaque_hook(); + this->hooks_.push_back(std::move(container)); + return result; + } + emulator_hook* hook_memory_access(const uint64_t address, const size_t size, const memory_operation filter, complex_memory_hook_callback callback) override { diff --git a/src/windows_emulator/main.cpp b/src/windows_emulator/main.cpp index a48dcff4..b74ae4d5 100644 --- a/src/windows_emulator/main.cpp +++ b/src/windows_emulator/main.cpp @@ -329,50 +329,25 @@ namespace return context; } - enum class gdb_registers - { - rax = 0, - rbx, - rcx, - rdx, - rsi, - rdi, - rbp, - rsp, - r8, - r9, - r10, - r11, - r12, - r13, - r14, - r15, - - rip, - eflags, - - end, - }; - - std::unordered_map register_map{ - {gdb_registers::rax, x64_register::rax}, - {gdb_registers::rbx, x64_register::rbx}, - {gdb_registers::rcx, x64_register::rcx}, - {gdb_registers::rdx, x64_register::rdx}, - {gdb_registers::rsi, x64_register::rsi}, - {gdb_registers::rdi, x64_register::rdi}, - {gdb_registers::rbp, x64_register::rbp}, - {gdb_registers::rsp, x64_register::rsp}, - {gdb_registers::r8, x64_register::r8}, - {gdb_registers::r9, x64_register::r9}, - {gdb_registers::r10, x64_register::r10}, - {gdb_registers::r11, x64_register::r11}, - {gdb_registers::r12, x64_register::r12}, - {gdb_registers::r13, x64_register::r13}, - {gdb_registers::r14, x64_register::r14}, - {gdb_registers::r15, x64_register::r15}, - {gdb_registers::rip, x64_register::rip}, - {gdb_registers::eflags, x64_register::rflags}, + std::vector gdb_registers{ + x64_register::rax, + x64_register::rbx, + x64_register::rcx, + x64_register::rdx, + x64_register::rsi, + x64_register::rdi, + x64_register::rbp, + x64_register::rsp, + x64_register::r8, + x64_register::r9, + x64_register::r10, + x64_register::r11, + x64_register::r12, + x64_register::r13, + x64_register::r14, + x64_register::r15, + x64_register::rip, + x64_register::rflags, }; memory_operation map_breakpoint_type(const breakpoint_type type) @@ -488,13 +463,12 @@ namespace try { - const auto entry = register_map.find(static_cast(regno)); - if (entry == register_map.end()) + if (regno < 0 || regno >= gdb_registers.size()) { return true; } - this->emu_->read_register(entry->second, value, sizeof(*value)); + this->emu_->read_register(gdb_registers[regno], value, sizeof(*value)); return true; } catch (...) @@ -507,13 +481,12 @@ namespace { try { - const auto entry = register_map.find(static_cast(regno)); - if (entry == register_map.end()) + if (regno < 0 || regno >= gdb_registers.size()) { - return false; + return true; } - this->emu_->write_register(entry->second, &value, sizeof(value)); + this->emu_->write_register(gdb_registers[regno], &value, sizeof(value)); return true; } catch (...) @@ -634,6 +607,26 @@ namespace return hook_continuation::skip_instruction; }); + emu->hook_instruction(x64_hookable_instructions::invalid, [&] + { + const auto ip = emu->read_instruction_pointer(); + printf("Invalid instruction at: %llX\n", ip); + return hook_continuation::skip_instruction; + }); + + emu->hook_interrupt([&](int interrupt) + { + printf("Interrupt: %i\n", interrupt); + if (interrupt == 13) + { + const auto sp = emu->reg(x64_register::rsp); + const auto new_ip = emu->read_memory(sp); + + emu->reg(x64_register::rsp, sp + 8); + emu->reg(x64_register::rip, new_ip); + } + }); + watch_object(*emu, context.teb); watch_object(*emu, context.peb); watch_object(*emu, context.process_params); @@ -673,7 +666,7 @@ namespace puts("Launching gdb stub..."); x64_gdb_stub_handler handler{*emu}; - run_gdb_stub(handler, "i386:x86-64", static_cast(gdb_registers::end), "0.0.0.0:28960"); + run_gdb_stub(handler, "i386:x86-64", gdb_registers.size(), "0.0.0.0:28960"); } else {