Support exception hooks

This commit is contained in:
momo5502
2024-09-05 11:13:54 +02:00
parent 0c963439a9
commit 79607447e5
7 changed files with 299 additions and 59 deletions

View File

@@ -0,0 +1,191 @@
#pragma once
#include <string>
#include <vector>
#include <stdexcept>
#include <cstring>
namespace utils
{
class buffer_deserializer
{
public:
template <typename T>
buffer_deserializer(const std::basic_string_view<T>& buffer)
: buffer_(reinterpret_cast<const std::byte*>(buffer.data()), buffer.size() * sizeof(T))
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
}
template <typename T>
buffer_deserializer(const std::basic_string<T>& buffer)
: buffer_deserializer(std::basic_string_view<T>(buffer.data(), buffer.size()))
{
}
template <typename T>
buffer_deserializer(const std::vector<T>& buffer)
: buffer_deserializer(std::basic_string_view<T>(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 <typename T>
T read()
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
T object{};
this->read(&object, sizeof(object));
return object;
}
template <typename T>
std::vector<T> read_vector()
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
std::vector<T> result{};
const auto size = this->read<uint32_t>();
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<uint32_t>();
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<std::byte> buffer_{};
};
class buffer_serializer
{
public:
buffer_serializer() = default;
void write(const void* buffer, const size_t length)
{
this->buffer_.append(static_cast<const char*>(buffer), length);
}
void write(const char* text)
{
this->write(text, strlen(text));
}
void write_string(const char* str, const size_t length)
{
this->write<uint32_t>(static_cast<uint32_t>(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 <typename T>
void write(const T& object)
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
this->write(&object, sizeof(object));
}
template <typename T>
void write(const std::vector<T>& vec)
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
this->write(vec.data(), vec.size() * sizeof(T));
}
template <typename T>
void write_vector(const std::vector<T>& vec)
{
static_assert(std::is_trivially_copyable_v<T>, "Type must be trivially copyable");
this->write(static_cast<uint32_t>(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_{};
};
}

View File

@@ -17,6 +17,7 @@ enum class hook_continuation : bool
using hook_callback = std::function<hook_continuation()>;
using interrupt_hook_callback = std::function<void(int interrupt)>;
using simple_memory_hook_callback = std::function<void(uint64_t address, size_t size)>;
using complex_memory_hook_callback = std::function<void(uint64_t address, size_t size, memory_operation operation)>;
@@ -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)

View File

@@ -30,6 +30,20 @@ public:
virtual ~memory_manager() = default;
template <typename T>
T read_memory(const uint64_t address)
{
T value{};
this->read_memory(address, &value, sizeof(value));
return value;
}
template <typename T>
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;

View File

@@ -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;
};

View File

@@ -4,6 +4,7 @@
enum class x64_hookable_instructions
{
invalid,
syscall,
cpuid,
rdtsc,

View File

@@ -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<x64_hookable_instructions>(instruction_type));
function_wrapper<int, uc_engine*> 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<pointer_type>::max(), uc_instruction));
auto container = std::make_unique<hook_container>();
const auto inst_type = static_cast<x64_hookable_instructions>(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<pointer_type>::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<pointer_type>::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<void, uc_engine*, int> wrapper([c = std::move(callback)](uc_engine*, const int interrupt_type)
{
c(interrupt_type);
});
unicorn_hook hook{*this};
auto container = std::make_unique<hook_container>();
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_INTR, wrapper.get_function(),
wrapper.get_user_data(), 0, std::numeric_limits<pointer_type>::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
{

View File

@@ -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<gdb_registers, x64_register> 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<gdb_registers>(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<gdb_registers>(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<uint64_t>(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<size_t>(gdb_registers::end), "0.0.0.0:28960");
run_gdb_stub(handler, "i386:x86-64", gdb_registers.size(), "0.0.0.0:28960");
}
else
{