Compare commits

...

4 Commits

Author SHA1 Message Date
robert-yates
2b76880cf1 partial process_context abstraction 2025-04-26 18:00:02 +02:00
robert-yates
60931da92a reduce process_context scope 2025-04-26 00:21:26 +02:00
robert-yates
4b5d82079c support mapping 32bit files 2025-04-25 22:49:34 +02:00
robert-yates
9c2a0d946e add 32bit unicorn backend 2025-04-25 21:30:17 +02:00
22 changed files with 945 additions and 95 deletions

View File

@@ -0,0 +1,715 @@
#define UNICORN_EMULATOR_IMPL
#include "unicorn_x86_32_emulator.hpp"
#include <array>
#include "unicorn_memory_regions.hpp"
#include "unicorn_hook.hpp"
#include "function_wrapper.hpp"
#include <ranges>
namespace unicorn
{
namespace
{
static_assert(static_cast<uint32_t>(memory_permission::none) == UC_PROT_NONE);
static_assert(static_cast<uint32_t>(memory_permission::read) == UC_PROT_READ);
static_assert(static_cast<uint32_t>(memory_permission::exec) == UC_PROT_EXEC);
static_assert(static_cast<uint32_t>(memory_permission::all) == UC_PROT_ALL);
static_assert(static_cast<uint32_t>(x86_register::end) == UC_X86_REG_ENDING);
uc_x86_insn map_hookable_instruction(const x86_hookable_instructions instruction)
{
switch (instruction)
{
case x86_hookable_instructions::syscall:
return UC_X86_INS_SYSCALL;
case x86_hookable_instructions::cpuid:
return UC_X86_INS_CPUID;
case x86_hookable_instructions::rdtsc:
return UC_X86_INS_RDTSC;
case x86_hookable_instructions::rdtscp:
return UC_X86_INS_RDTSCP;
default:
throw std::runtime_error("Bad instruction for mapping");
}
}
memory_violation_type map_memory_violation_type(const uc_mem_type mem_type)
{
switch (mem_type)
{
case UC_MEM_READ_PROT:
case UC_MEM_WRITE_PROT:
case UC_MEM_FETCH_PROT:
return memory_violation_type::protection;
case UC_MEM_READ_UNMAPPED:
case UC_MEM_WRITE_UNMAPPED:
case UC_MEM_FETCH_UNMAPPED:
return memory_violation_type::unmapped;
default:
throw std::runtime_error("Memory type does not constitute a violation");
}
}
memory_operation map_memory_operation(const uc_mem_type mem_type)
{
switch (mem_type)
{
case UC_MEM_READ:
case UC_MEM_READ_PROT:
case UC_MEM_READ_AFTER:
case UC_MEM_READ_UNMAPPED:
return memory_operation::read;
case UC_MEM_WRITE:
case UC_MEM_WRITE_PROT:
case UC_MEM_WRITE_UNMAPPED:
return memory_operation::write;
case UC_MEM_FETCH:
case UC_MEM_FETCH_PROT:
case UC_MEM_FETCH_UNMAPPED:
return memory_operation::exec;
default:
return memory_operation::none;
}
}
struct hook_object : utils::object
{
emulator_hook* as_opaque_hook()
{
return reinterpret_cast<emulator_hook*>(this);
}
};
class hook_container : public hook_object
{
public:
template <typename T>
requires(std::is_base_of_v<utils::object, T> && std::is_move_constructible_v<T>)
void add(T data, unicorn_hook hook)
{
hook_entry entry{};
entry.data = std::make_unique<T>(std::move(data));
entry.hook = std::move(hook);
this->hooks_.emplace_back(std::move(entry));
}
private:
struct hook_entry
{
std::unique_ptr<utils::object> data{};
unicorn_hook hook{};
};
std::vector<hook_entry> hooks_;
};
struct mmio_callbacks
{
using read_wrapper = function_wrapper<uint64_t, uc_engine*, uint64_t, unsigned>;
using write_wrapper = function_wrapper<void, uc_engine*, uint64_t, unsigned, uint64_t>;
read_wrapper read{};
write_wrapper write{};
};
class uc_context_serializer
{
public:
uc_context_serializer(uc_engine* uc, const bool in_place)
: uc_(uc)
{
if (in_place)
{
// Unicorn stores pointers in the struct. The serialization here is broken
throw std::runtime_error("Memory saving not supported atm");
}
#ifndef OS_WINDOWS
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif
uc_ctl_context_mode(uc, UC_CTL_CONTEXT_CPU | (in_place ? UC_CTL_CONTEXT_MEMORY : 0));
#ifndef OS_WINDOWS
#pragma GCC diagnostic pop
#endif
this->size_ = uc_context_size(uc);
uce(uc_context_alloc(uc, &this->context_));
}
~uc_context_serializer()
{
if (this->context_)
{
(void)uc_context_free(this->context_);
}
}
void serialize(utils::buffer_serializer& buffer) const
{
uce(uc_context_save(this->uc_, this->context_));
buffer.write(this->context_, this->size_);
}
void deserialize(utils::buffer_deserializer& buffer) const
{
buffer.read(this->context_, this->size_);
uce(uc_context_restore(this->uc_, this->context_));
}
uc_context_serializer(uc_context_serializer&&) = delete;
uc_context_serializer(const uc_context_serializer&) = delete;
uc_context_serializer& operator=(uc_context_serializer&&) = delete;
uc_context_serializer& operator=(const uc_context_serializer&) = delete;
private:
uc_engine* uc_{};
uc_context* context_{};
size_t size_{};
};
basic_block map_block(const uc_tb& translation_block)
{
basic_block block{};
block.address = translation_block.pc;
block.instruction_count = translation_block.icount;
block.size = translation_block.size;
return block;
}
void assert_32bit_limit(const size_t size)
{
if (size > sizeof(uint32_t))
{
throw std::runtime_error("Exceeded uint32_t size limit");
}
}
class unicorn_x86_32_emulator : public x86_32_emulator
{
public:
unicorn_x86_32_emulator()
{
uce(uc_open(UC_ARCH_X86, UC_MODE_32, &this->uc_));
uce(uc_ctl_set_cpu_model(this->uc_, UC_CPU_X86_EPYC_ROME));
#ifndef OS_WINDOWS
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif
constexpr auto is_64_bit = sizeof(void*) >= 8;
uce(uc_ctl_set_tcg_buffer_size(this->uc_, (is_64_bit ? 2 : 1) << 30 /* 2 gb */));
#ifndef OS_WINDOWS
#pragma GCC diagnostic pop
#endif
}
~unicorn_x86_32_emulator() override
{
this->hooks_.clear();
uc_close(this->uc_);
}
void start(const size_t count) override
{
this->has_violation_ = false;
const auto start = this->read_instruction_pointer();
constexpr auto end = std::numeric_limits<uint64_t>::max();
const auto res = uc_emu_start(*this, start, end, 0, count);
if (res == UC_ERR_OK)
{
return;
}
const auto is_violation = //
res == UC_ERR_READ_UNMAPPED || //
res == UC_ERR_WRITE_UNMAPPED || //
res == UC_ERR_FETCH_UNMAPPED || //
res == UC_ERR_READ_PROT || //
res == UC_ERR_WRITE_PROT || //
res == UC_ERR_FETCH_PROT;
if (!is_violation || !this->has_violation_)
{
uce(res);
}
}
void stop() override
{
uce(uc_emu_stop(*this));
}
void load_gdt(const pointer_type address, const uint32_t limit) override
{
const std::array<uint32_t, 2> gdtr = {limit, address};
this->write_register(x86_register::gdtr, gdtr.data(), gdtr.size() * sizeof(uint64_t));
}
void set_segment_base(const x86_register base, const pointer_type value) override
{
constexpr auto IA32_FS_BASE_MSR = 0xC0000100;
constexpr auto IA32_GS_BASE_MSR = 0xC0000101;
struct msr_value
{
uint64_t id{};
uint64_t value{};
};
msr_value msr_val{
.id = 0,
.value = value,
};
switch (base)
{
case x86_register::fs:
case x86_register::fs_base:
msr_val.id = IA32_FS_BASE_MSR;
break;
case x86_register::gs:
case x86_register::gs_base:
msr_val.id = IA32_GS_BASE_MSR;
break;
default:
return;
}
this->write_register(x86_register::msr, &msr_val, sizeof(msr_val));
}
size_t write_raw_register(const int reg, const void* value, const size_t size) override
{
auto result_size = size;
uce(uc_reg_write2(*this, reg, value, &result_size));
if (size < result_size)
{
throw std::runtime_error("Register size mismatch: " + std::to_string(size) +
" != " + std::to_string(result_size));
}
return result_size;
}
size_t read_raw_register(const int reg, void* value, const size_t size) override
{
size_t result_size = size;
memset(value, 0, size);
uce(uc_reg_read2(*this, reg, value, &result_size));
if (size < result_size)
{
throw std::runtime_error("Register size mismatch: " + std::to_string(size) +
" != " + std::to_string(result_size));
}
return result_size;
}
void map_mmio(const uint64_t address, const size_t size, mmio_read_callback read_cb,
mmio_write_callback write_cb) override
{
auto read_wrapper = [c = std::move(read_cb)](uc_engine*, const uint64_t addr, const uint32_t s) {
assert_32bit_limit(s);
uint64_t value{};
c(addr, &value, s);
return value;
};
auto write_wrapper = [c = std::move(write_cb)](uc_engine*, const uint64_t addr, const uint32_t s,
const uint64_t value) {
assert_32bit_limit(s);
c(addr, &value, s);
};
mmio_callbacks cb{
.read = mmio_callbacks::read_wrapper(std::move(read_wrapper)),
.write = mmio_callbacks::write_wrapper(std::move(write_wrapper)),
};
uce(uc_mmio_map(*this, address, size, cb.read.get_c_function(), cb.read.get_user_data(),
cb.write.get_c_function(), cb.write.get_user_data()));
this->mmio_[address] = std::move(cb);
}
void map_memory(const uint64_t address, const size_t size, memory_permission permissions) override
{
uce(uc_mem_map(*this, address, size, static_cast<uint32_t>(permissions)));
}
void unmap_memory(const uint64_t address, const size_t size) override
{
uce(uc_mem_unmap(*this, address, size));
const auto mmio_entry = this->mmio_.find(address);
if (mmio_entry != this->mmio_.end())
{
this->mmio_.erase(mmio_entry);
}
}
bool try_read_memory(const uint64_t address, void* data, const size_t size) const override
{
return uc_mem_read(*this, address, data, size) == UC_ERR_OK;
}
void read_memory(const uint64_t address, void* data, const size_t size) const override
{
uce(uc_mem_read(*this, address, data, size));
}
void write_memory(const uint64_t address, const void* data, const size_t size) override
{
uce(uc_mem_write(*this, address, data, size));
}
void apply_memory_protection(const uint64_t address, const size_t size,
memory_permission permissions) override
{
uce(uc_mem_protect(*this, address, size, static_cast<uint32_t>(permissions)));
}
emulator_hook* hook_instruction(const int instruction_type, instruction_hook_callback callback) override
{
unicorn_hook hook{*this};
auto container = std::make_unique<hook_container>();
const auto inst_type = static_cast<x86_hookable_instructions>(instruction_type);
if (inst_type == x86_hookable_instructions::invalid)
{
function_wrapper<int, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) {
return (c() == instruction_hook_continuation::skip_instruction) ? 1 : 0;
});
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()));
container->add(std::move(wrapper), std::move(hook));
}
else if (inst_type == x86_hookable_instructions::syscall)
{
function_wrapper<void, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) { c(); });
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));
}
else
{
function_wrapper<int, uc_engine*> wrapper([c = std::move(callback)](uc_engine*) {
return (c() == instruction_hook_continuation::skip_instruction) ? 1 : 0;
});
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();
this->hooks_.push_back(std::move(container));
return result;
}
emulator_hook* hook_basic_block(basic_block_hook_callback callback) override
{
function_wrapper<void, uc_engine*, uint64_t, size_t> wrapper(
[c = std::move(callback)](uc_engine*, const uint64_t address, const size_t size) {
basic_block block{};
block.address = address;
block.size = size;
c(block);
});
unicorn_hook hook{*this};
auto container = std::make_unique<hook_container>();
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_BLOCK, 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_edge_generation(edge_generation_hook_callback callback) override
{
function_wrapper<void, uc_engine*, uc_tb*, uc_tb*> wrapper(
[c = std::move(callback)](uc_engine*, const uc_tb* cur_tb, const uc_tb* prev_tb) {
const auto current_block = map_block(*cur_tb);
const auto previous_block = map_block(*prev_tb);
c(current_block, previous_block);
});
unicorn_hook hook{*this};
auto container = std::make_unique<hook_container>();
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_EDGE_GENERATED, 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_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_violation(memory_violation_hook_callback callback) override
{
function_wrapper<bool, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
[c = std::move(callback), this](uc_engine*, const uc_mem_type type, const uint64_t address,
const int size, const int64_t) {
const auto ip = this->read_instruction_pointer();
assert(size >= 0);
const auto operation = map_memory_operation(type);
const auto violation = map_memory_violation_type(type);
const auto resume = c(address, static_cast<uint64_t>(size), operation, violation) ==
memory_violation_continuation::resume;
const auto has_ip_changed = ip != this->read_instruction_pointer();
if (!resume)
{
return false;
}
this->has_violation_ = resume && has_ip_changed;
if (has_ip_changed)
{
return false;
}
return true;
});
unicorn_hook hook{*this};
auto container = std::make_unique<hook_container>();
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_INVALID, wrapper.get_function(),
wrapper.get_user_data(), 0, std::numeric_limits<uint64_t>::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_execution(const uint64_t address, const uint64_t size,
memory_execution_hook_callback callback)
{
auto exec_wrapper = [c = std::move(callback)](uc_engine*, const uint64_t address,
const uint32_t /*size*/) {
c(address); //
};
function_wrapper<void, uc_engine*, uint64_t, uint32_t> wrapper(std::move(exec_wrapper));
unicorn_hook hook{*this};
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_CODE, wrapper.get_function(),
wrapper.get_user_data(), address, address + size));
auto* container = this->create_hook_container();
container->add(std::move(wrapper), std::move(hook));
return container->as_opaque_hook();
}
emulator_hook* hook_memory_execution(memory_execution_hook_callback callback) override
{
return this->hook_memory_execution(0, std::numeric_limits<uint64_t>::max(), std::move(callback));
}
emulator_hook* hook_memory_execution(const uint64_t address,
memory_execution_hook_callback callback) override
{
return this->hook_memory_execution(address, 1, std::move(callback));
}
emulator_hook* hook_memory_read(const uint64_t address, const uint64_t size,
memory_access_hook_callback callback) override
{
auto read_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type,
const uint64_t address, const int length,
const uint64_t value) {
const auto operation = map_memory_operation(type);
if (operation == memory_operation::read && length > 0)
{
c(address, &value, std::min(static_cast<size_t>(length), sizeof(value)));
}
};
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
std::move(read_wrapper));
unicorn_hook hook{*this};
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_READ_AFTER, wrapper.get_function(),
wrapper.get_user_data(), address, address + size));
auto* container = this->create_hook_container();
container->add(std::move(wrapper), std::move(hook));
return container->as_opaque_hook();
}
emulator_hook* hook_memory_write(const uint64_t address, const uint64_t size,
memory_access_hook_callback callback) override
{
auto write_wrapper = [c = std::move(callback)](uc_engine*, const uc_mem_type type, const uint64_t addr,
const int length, const uint64_t value) {
const auto operation = map_memory_operation(type);
if (operation == memory_operation::write && length > 0)
{
c(addr, &value, std::min(static_cast<size_t>(length), sizeof(value)));
}
};
function_wrapper<void, uc_engine*, uc_mem_type, uint64_t, int, int64_t> wrapper(
std::move(write_wrapper));
unicorn_hook hook{*this};
uce(uc_hook_add(*this, hook.make_reference(), UC_HOOK_MEM_WRITE, wrapper.get_function(),
wrapper.get_user_data(), address, address + size));
auto* container = this->create_hook_container();
container->add(std::move(wrapper), std::move(hook));
return container->as_opaque_hook();
}
hook_container* create_hook_container()
{
auto container = std::make_unique<hook_container>();
auto* ptr = container.get();
this->hooks_.push_back(std::move(container));
return ptr;
}
void delete_hook(emulator_hook* hook) override
{
const auto entry =
std::ranges::find_if(this->hooks_, [&](const std::unique_ptr<hook_object>& hook_ptr) {
return hook_ptr->as_opaque_hook() == hook;
});
if (entry != this->hooks_.end())
{
this->hooks_.erase(entry);
}
}
operator uc_engine*() const
{
return this->uc_;
}
void serialize_state(utils::buffer_serializer& buffer, const bool is_snapshot) const override
{
if (this->has_snapshots_ && !is_snapshot)
{
// TODO: Investigate if this is really necessary
throw std::runtime_error("Unable to serialize after snapshot was taken!");
}
this->has_snapshots_ |= is_snapshot;
const uc_context_serializer serializer(this->uc_, is_snapshot);
serializer.serialize(buffer);
}
void deserialize_state(utils::buffer_deserializer& buffer, const bool is_snapshot) override
{
if (this->has_snapshots_ && !is_snapshot)
{
// TODO: Investigate if this is really necessary
throw std::runtime_error("Unable to deserialize after snapshot was taken!");
}
const uc_context_serializer serializer(this->uc_, is_snapshot);
serializer.deserialize(buffer);
}
std::vector<std::byte> save_registers() const override
{
utils::buffer_serializer buffer{};
const uc_context_serializer serializer(this->uc_, false);
serializer.serialize(buffer);
return buffer.move_buffer();
}
void restore_registers(const std::vector<std::byte>& register_data) override
{
utils::buffer_deserializer buffer{register_data};
const uc_context_serializer serializer(this->uc_, false);
serializer.deserialize(buffer);
}
bool has_violation() const override
{
return this->has_violation_;
}
std::string get_name() const override
{
return "Unicorn Engine (32bit)";
}
private:
mutable bool has_snapshots_{false};
uc_engine* uc_{};
bool has_violation_{false};
std::vector<std::unique_ptr<hook_object>> hooks_{};
std::unordered_map<uint64_t, mmio_callbacks> mmio_{};
};
}
std::unique_ptr<x86_32_emulator> create_x86_32_emulator()
{
return std::make_unique<unicorn_x86_32_emulator>();
}
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <memory>
#include <arch_emulator.hpp>
#include "platform/platform.hpp"
#ifdef UNICORN_EMULATOR_IMPL
#define UNICORN_EMULATOR_DLL_STORAGE EXPORT_SYMBOL
#else
#define UNICORN_EMULATOR_DLL_STORAGE IMPORT_SYMBOL
#endif
namespace unicorn
{
#if !MOMO_BUILD_AS_LIBRARY
UNICORN_EMULATOR_DLL_STORAGE
#endif
std::unique_ptr<x86_32_emulator> create_x86_32_emulator();
}

View File

@@ -1,6 +1,10 @@
#pragma once
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <system_error>
#include <variant>
// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)
@@ -342,4 +346,63 @@ struct SECTION_IMAGE_INFORMATION
ULONG CheckSum;
};
namespace winpe
{
enum class pe_arch
{
pe32,
pe64
};
inline std::variant<pe_arch, std::error_code> get_pe_arch(const std::filesystem::path& file)
{
std::ifstream f(file, std::ios::binary);
if (!f)
{
return std::make_error_code(std::errc::no_such_file_or_directory);
}
PEDosHeader_t dos{};
f.read(reinterpret_cast<char*>(&dos), sizeof(dos));
if (!f || dos.e_magic != PEDosHeader_t::k_Magic)
{
return std::make_error_code(std::errc::executable_format_error);
}
f.seekg(dos.e_lfanew, std::ios::beg);
uint32_t nt_signature = 0;
f.read(reinterpret_cast<char*>(&nt_signature), sizeof(nt_signature));
if (!f || nt_signature != PENTHeaders_t<std::uint32_t>::k_Signature)
{
return std::make_error_code(std::errc::executable_format_error);
}
PEFileHeader_t file_header{};
f.read(reinterpret_cast<char*>(&file_header), sizeof(file_header));
if (!f)
{
return std::make_error_code(std::errc::executable_format_error);
}
uint16_t magic = 0;
f.read(reinterpret_cast<char*>(&magic), sizeof(magic));
if (!f)
{
return std::make_error_code(std::errc::executable_format_error);
}
if (magic == PEOptionalHeader_t<std::uint32_t>::k_Magic)
{
return pe_arch::pe32;
}
if (magic == PEOptionalHeader_t<std::uint64_t>::k_Magic)
{
return pe_arch::pe64;
}
return std::make_error_code(std::errc::executable_format_error);
}
}
// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays)

View File

@@ -64,3 +64,16 @@ struct x86_64_traits
};
using x86_64_emulator = x86_emulator<x86_64_traits>;
// --[x86_32]-------------------------------------------------------------------------
struct x86_32_traits
{
using pointer_type = uint32_t;
using register_type = x86_register;
static constexpr register_type instruction_pointer = x86_register::eip;
static constexpr register_type stack_pointer = x86_register::esp;
using hookable_instructions = x86_hookable_instructions;
};
using x86_32_emulator = x86_emulator<x86_32_traits>;

View File

@@ -20,7 +20,7 @@ namespace
win_emu.log.disable_output(true);
win_emu.start();
if (win_emu.process.exception_rip.has_value())
if (win_emu.process.exception_ip.has_value())
{
throw std::runtime_error("Exception!");
}

View File

@@ -29,7 +29,7 @@ namespace
emu.reg(x86_register::rsp, stack_end);
}
bool is_object_signaled(process_context& c, const handle h, const uint32_t current_thread_id)
bool is_object_signaled(process_context64& c, const handle h, const uint32_t current_thread_id)
{
const auto type = h.value.type;
@@ -83,7 +83,7 @@ namespace
}
}
emulator_thread::emulator_thread(memory_manager& memory, const process_context& context, const uint64_t start_address,
emulator_thread::emulator_thread(memory_manager& memory, const process_context64& context, const uint64_t start_address,
const uint64_t argument, const uint64_t stack_size, const bool suspended,
const uint32_t id)
: memory_ptr(&memory),
@@ -139,7 +139,7 @@ bool emulator_thread::is_terminated() const
return this->exit_status.has_value();
}
bool emulator_thread::is_thread_ready(process_context& process, utils::clock& clock)
bool emulator_thread::is_thread_ready(process_context64& process, utils::clock& clock)
{
if (this->is_terminated() || this->suspended > 0)
{
@@ -208,7 +208,7 @@ bool emulator_thread::is_thread_ready(process_context& process, utils::clock& cl
return true;
}
void emulator_thread::setup_registers(x86_64_emulator& emu, const process_context& context) const
void emulator_thread::setup_registers(x86_64_emulator& emu, const process_context64& context) const
{
if (!this->gs_segment)
{

View File

@@ -6,7 +6,7 @@
#include <utils/moved_marker.hpp>
struct process_context;
struct process_context64;
struct pending_apc
{
@@ -48,7 +48,7 @@ class emulator_thread : public ref_counted_object
{
}
emulator_thread(memory_manager& memory, const process_context& context, uint64_t start_address, uint64_t argument,
emulator_thread(memory_manager& memory, const process_context64& context, uint64_t start_address, uint64_t argument,
uint64_t stack_size, bool suspended, uint32_t id);
emulator_thread(const emulator_thread&) = delete;
@@ -103,7 +103,7 @@ class emulator_thread : public ref_counted_object
bool is_terminated() const;
bool is_thread_ready(process_context& process, utils::clock& clock);
bool is_thread_ready(process_context64& process, utils::clock& clock);
void save(x86_64_emulator& emu)
{
@@ -115,7 +115,7 @@ class emulator_thread : public ref_counted_object
emu.restore_registers(this->last_registers);
}
void setup_if_necessary(x86_64_emulator& emu, const process_context& context)
void setup_if_necessary(x86_64_emulator& emu, const process_context64& context)
{
if (!this->executed_instructions)
{
@@ -211,7 +211,7 @@ class emulator_thread : public ref_counted_object
}
private:
void setup_registers(x86_64_emulator& emu, const process_context& context) const;
void setup_registers(x86_64_emulator& emu, const process_context64& context) const;
void release()
{

View File

@@ -48,13 +48,11 @@ class object_wrapper
class windows_emulator;
class module_manager;
struct process_context;
using clock_wrapper = object_wrapper<utils::clock>;
using x64_emulator_wrapper = object_wrapper<x86_64_emulator>;
using memory_manager_wrapper = object_wrapper<memory_manager>;
using module_manager_wrapper = object_wrapper<module_manager>;
using process_context_wrapper = object_wrapper<process_context>;
using windows_emulator_wrapper = object_wrapper<windows_emulator>;
using socket_factory_wrapper = object_wrapper<network::socket_factory>;

View File

@@ -140,7 +140,7 @@ namespace
}
}
void dispatch_exception(x86_64_emulator& emu, const process_context& proc, const DWORD status,
void dispatch_exception(x86_64_emulator& emu, const process_context64& proc, const DWORD status,
const std::vector<EmulatorTraits<Emu64>::ULONG_PTR>& parameters)
{
CONTEXT64 ctx{};
@@ -172,7 +172,7 @@ void dispatch_exception(x86_64_emulator& emu, const process_context& proc, const
dispatch_exception_pointers(emu, proc.ki_user_exception_dispatcher, pointers);
}
void dispatch_access_violation(x86_64_emulator& emu, const process_context& proc, const uint64_t address,
void dispatch_access_violation(x86_64_emulator& emu, const process_context64& proc, const uint64_t address,
const memory_operation operation)
{
dispatch_exception(emu, proc, STATUS_ACCESS_VIOLATION,
@@ -182,22 +182,22 @@ void dispatch_access_violation(x86_64_emulator& emu, const process_context& proc
});
}
void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context& proc)
void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context64& proc)
{
dispatch_exception(emu, proc, STATUS_ILLEGAL_INSTRUCTION, {});
}
void dispatch_integer_division_by_zero(x86_64_emulator& emu, const process_context& proc)
void dispatch_integer_division_by_zero(x86_64_emulator& emu, const process_context64& proc)
{
dispatch_exception(emu, proc, STATUS_INTEGER_DIVIDE_BY_ZERO, {});
}
void dispatch_single_step(x86_64_emulator& emu, const process_context& proc)
void dispatch_single_step(x86_64_emulator& emu, const process_context64& proc)
{
dispatch_exception(emu, proc, STATUS_SINGLE_STEP, {});
}
void dispatch_breakpoint(x86_64_emulator& emu, const process_context& proc)
void dispatch_breakpoint(x86_64_emulator& emu, const process_context64& proc)
{
dispatch_exception(emu, proc, STATUS_BREAKPOINT, {});
}

View File

@@ -5,21 +5,21 @@
#include <platform/traits.hpp>
#include <platform/primitives.hpp>
struct process_context;
struct process_context64;
void dispatch_exception(x86_64_emulator& emu, const process_context& proc, DWORD status,
void dispatch_exception(x86_64_emulator& emu, const process_context64& proc, DWORD status,
const std::vector<EmulatorTraits<Emu64>::ULONG_PTR>& parameters);
template <typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, DWORD>)
void dispatch_exception(x86_64_emulator& emu, const process_context& proc, const T status,
void dispatch_exception(x86_64_emulator& emu, const process_context64& proc, const T status,
const std::vector<EmulatorTraits<Emu64>::ULONG_PTR>& parameters)
{
dispatch_exception(emu, proc, static_cast<DWORD>(status), parameters);
}
void dispatch_access_violation(x86_64_emulator& emu, const process_context& proc, uint64_t address,
void dispatch_access_violation(x86_64_emulator& emu, const process_context64& proc, uint64_t address,
memory_operation operation);
void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context& proc);
void dispatch_integer_division_by_zero(x86_64_emulator& emu, const process_context& proc);
void dispatch_single_step(x86_64_emulator& emu, const process_context& proc);
void dispatch_breakpoint(x86_64_emulator& emu, const process_context& proc);
void dispatch_illegal_instruction_violation(x86_64_emulator& emu, const process_context64& proc);
void dispatch_integer_division_by_zero(x86_64_emulator& emu, const process_context64& proc);
void dispatch_single_step(x86_64_emulator& emu, const process_context64& proc);
void dispatch_breakpoint(x86_64_emulator& emu, const process_context64& proc);

View File

@@ -8,7 +8,6 @@
#include "handles.hpp"
class windows_emulator;
struct process_context;
struct io_device_context
{

View File

@@ -7,7 +7,6 @@
#include <utils/time.hpp>
struct process_context;
class windows_emulator;
class kusd_mmio

View File

@@ -1,6 +1,7 @@
#include "../std_include.hpp"
#include "module_manager.hpp"
#include "module_mapping.hpp"
#include "platform/win_pefile.hpp"
#include "windows-emulator/logger.hpp"
#include <serialization_helper.hpp>
@@ -119,7 +120,28 @@ mapped_module* module_manager::map_local_module(const std::filesystem::path& fil
try
{
auto mod = map_module_from_file(*this->memory_, std::move(local_file));
mapped_module mod;
auto petype_result = winpe::get_pe_arch(file);
if (std::holds_alternative<winpe::pe_arch>(petype_result))
{
if (std::get<winpe::pe_arch>(petype_result) == winpe::pe_arch::pe32)
{
mod = map_module_from_file<std::uint32_t>(*this->memory_, std::move(local_file));
}
else if (std::get<winpe::pe_arch>(petype_result) == winpe::pe_arch::pe64)
{
mod = map_module_from_file<std::uint64_t>(*this->memory_, std::move(local_file));
}
else
{
throw std::runtime_error("Unknown PE architecture");
}
}
else
{
throw std::runtime_error("Unknown PE architecture");
}
mod.is_static = is_static;
logger.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);

View File

@@ -7,7 +7,8 @@
namespace
{
uint64_t get_first_section_offset(const PENTHeaders_t<std::uint64_t>& nt_headers, const uint64_t nt_headers_offset)
template <typename T>
uint64_t get_first_section_offset(const PENTHeaders_t<T>& nt_headers, const uint64_t nt_headers_offset)
{
const auto* nt_headers_addr = reinterpret_cast<const uint8_t*>(&nt_headers);
const size_t optional_header_offset =
@@ -29,8 +30,9 @@ namespace
return mem;
}
template <typename T>
void collect_exports(mapped_module& binary, const utils::safe_buffer_accessor<const std::byte> buffer,
const PEOptionalHeader_t<std::uint64_t>& optional_header)
const PEOptionalHeader_t<T>& optional_header)
{
const auto& export_directory_entry = optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (export_directory_entry.VirtualAddress == 0 || export_directory_entry.Size == 0)
@@ -79,8 +81,9 @@ namespace
obj.set(new_value);
}
template <typename T>
void apply_relocations(const mapped_module& binary, const utils::safe_buffer_accessor<std::byte> buffer,
const PEOptionalHeader_t<std::uint64_t>& optional_header)
const PEOptionalHeader_t<T>& optional_header)
{
const auto delta = binary.image_base - optional_header.ImageBase;
if (delta == 0)
@@ -141,9 +144,10 @@ namespace
}
}
template <typename T>
void map_sections(memory_manager& memory, mapped_module& binary,
const utils::safe_buffer_accessor<const std::byte> buffer,
const PENTHeaders_t<std::uint64_t>& nt_headers, const uint64_t nt_headers_offset)
const utils::safe_buffer_accessor<const std::byte> buffer, const PENTHeaders_t<T>& nt_headers,
const uint64_t nt_headers_offset)
{
const auto first_section_offset = get_first_section_offset(nt_headers, nt_headers_offset);
const auto sections = buffer.as<IMAGE_SECTION_HEADER>(static_cast<size_t>(first_section_offset));
@@ -196,6 +200,7 @@ namespace
}
}
template <typename T>
mapped_module map_module_from_data(memory_manager& memory, const std::span<const std::byte> data,
std::filesystem::path file)
{
@@ -208,10 +213,10 @@ mapped_module map_module_from_data(memory_manager& memory, const std::span<const
const auto dos_header = buffer.as<PEDosHeader_t>(0).get();
const auto nt_headers_offset = dos_header.e_lfanew;
const auto nt_headers = buffer.as<PENTHeaders_t<std::uint64_t>>(nt_headers_offset).get();
const auto nt_headers = buffer.as<PENTHeaders_t<T>>(nt_headers_offset).get();
const auto& optional_header = nt_headers.OptionalHeader;
if (nt_headers.FileHeader.Machine != PEMachineType::AMD64)
if (nt_headers.FileHeader.Machine != PEMachineType::I386 && nt_headers.FileHeader.Machine != PEMachineType::AMD64)
{
throw std::runtime_error("Unsupported architecture!");
}
@@ -254,6 +259,7 @@ mapped_module map_module_from_data(memory_manager& memory, const std::span<const
return binary;
}
template <typename T>
mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path file)
{
const auto data = utils::io::read_file(file);
@@ -262,10 +268,19 @@ mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path
throw std::runtime_error("Bad file data: " + file.string());
}
return map_module_from_data(memory, data, std::move(file));
return map_module_from_data<T>(memory, data, std::move(file));
}
bool unmap_module(memory_manager& memory, const mapped_module& mod)
{
return memory.release_memory(mod.image_base, static_cast<size_t>(mod.size_of_image));
}
template mapped_module map_module_from_data<std::uint32_t>(memory_manager& memory,
const std::span<const std::byte> data,
std::filesystem::path file);
template mapped_module map_module_from_data<std::uint64_t>(memory_manager& memory,
const std::span<const std::byte> data,
std::filesystem::path file);
template mapped_module map_module_from_file<std::uint32_t>(memory_manager& memory, std::filesystem::path file);
template mapped_module map_module_from_file<std::uint64_t>(memory_manager& memory, std::filesystem::path file);

View File

@@ -4,6 +4,6 @@
#include "../memory_manager.hpp"
mapped_module map_module_from_data(memory_manager& memory, std::span<const uint8_t> data, std::filesystem::path file);
template <typename T>
mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path file);
bool unmap_module(memory_manager& memory, const mapped_module& mod);

View File

@@ -27,9 +27,9 @@ namespace
}
}
void process_context::setup(x86_64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container)
void process_context64::setup(x86_64_emulator& emu, memory_manager& memory, const application_settings& app_settings,
const mapped_module& executable, const mapped_module& ntdll,
const apiset::container& apiset_container)
{
setup_gdt(emu, memory);
@@ -125,7 +125,7 @@ void process_context::setup(x86_64_emulator& emu, memory_manager& memory, const
this->default_register_set = emu.save_registers();
}
void process_context::serialize(utils::buffer_serializer& buffer) const
void process_context64::serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->current_ip);
buffer.write(this->previous_ip);
@@ -133,7 +133,7 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->shared_section_size);
buffer.write(this->dbwin_buffer);
buffer.write(this->dbwin_buffer_size);
buffer.write_optional(this->exception_rip);
buffer.write_optional(this->exception_ip);
buffer.write_optional(this->exit_status);
buffer.write(this->base_allocator);
buffer.write(this->peb);
@@ -163,7 +163,7 @@ void process_context::serialize(utils::buffer_serializer& buffer) const
buffer.write(this->threads.find_handle(this->active_thread).bits);
}
void process_context::deserialize(utils::buffer_deserializer& buffer)
void process_context64::deserialize(utils::buffer_deserializer& buffer)
{
buffer.read(this->current_ip);
buffer.read(this->previous_ip);
@@ -171,7 +171,7 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->shared_section_size);
buffer.read(this->dbwin_buffer);
buffer.read(this->dbwin_buffer_size);
buffer.read_optional(this->exception_rip);
buffer.read_optional(this->exception_ip);
buffer.read_optional(this->exit_status);
buffer.read(this->base_allocator);
buffer.read(this->peb);
@@ -207,7 +207,7 @@ void process_context::deserialize(utils::buffer_deserializer& buffer)
this->active_thread = this->threads.get(buffer.read<uint64_t>());
}
generic_handle_store* process_context::get_handle_store(const handle handle)
generic_handle_store* process_context64::get_handle_store(const handle handle)
{
switch (handle.value.type)
{
@@ -234,8 +234,8 @@ generic_handle_store* process_context::get_handle_store(const handle handle)
}
}
handle process_context::create_thread(memory_manager& memory, const uint64_t start_address, const uint64_t argument,
const uint64_t stack_size, const bool suspended)
handle process_context64::create_thread(memory_manager& memory, const uint64_t start_address, const uint64_t argument,
const uint64_t stack_size, const bool suspended)
{
emulator_thread t{memory, *this, start_address, argument, stack_size, suspended, ++this->spawned_thread_count};
auto [h, thr] = this->threads.store_and_get(std::move(t));
@@ -243,7 +243,9 @@ handle process_context::create_thread(memory_manager& memory, const uint64_t sta
return h;
}
uint16_t process_context::add_or_find_atom(std::u16string name)
// --[ process_context_common ]-----------------------------------------------------------------------------------------
uint16_t process_context_common::add_or_find_atom(std::u16string name)
{
uint16_t index = 0;
if (!atoms.empty())
@@ -286,7 +288,7 @@ uint16_t process_context::add_or_find_atom(std::u16string name)
return index;
}
bool process_context::delete_atom(const std::u16string& name)
bool process_context_common::delete_atom(const std::u16string& name)
{
for (auto it = atoms.begin(); it != atoms.end(); ++it)
{
@@ -303,7 +305,7 @@ bool process_context::delete_atom(const std::u16string& name)
return false;
}
bool process_context::delete_atom(uint16_t atom_id)
bool process_context_common::delete_atom(uint16_t atom_id)
{
const auto it = atoms.find(atom_id);
if (it == atoms.end())
@@ -319,7 +321,7 @@ bool process_context::delete_atom(uint16_t atom_id)
return true;
}
const std::u16string* process_context::get_atom_name(uint16_t atom_id) const
const std::u16string* process_context_common::get_atom_name(uint16_t atom_id) const
{
const auto it = atoms.find(atom_id);
if (it == atoms.end())

View File

@@ -28,7 +28,7 @@
struct emulator_settings;
struct application_settings;
struct process_context
struct process_context_common
{
struct callbacks
{
@@ -50,7 +50,44 @@ struct process_context
atom_entry() = default;
};
process_context(x86_64_emulator& emu, memory_manager& memory, utils::clock& clock, callbacks& cb)
std::map<uint16_t, atom_entry> atoms{};
uint16_t add_or_find_atom(std::u16string name);
bool delete_atom(const std::u16string& name);
bool delete_atom(uint16_t atom_id);
const std::u16string* get_atom_name(uint16_t atom_id) const;
uint64_t current_ip{0};
uint64_t previous_ip{0};
uint64_t shared_section_address{0};
uint64_t shared_section_size{0};
uint64_t dbwin_buffer{0};
uint64_t dbwin_buffer_size{0};
std::optional<NTSTATUS> exit_status{};
uint64_t ntdll_image_base{};
uint64_t ldr_initialize_thunk{};
uint64_t rtl_user_thread_start{};
uint64_t ki_user_apc_dispatcher{};
uint64_t ki_user_exception_dispatcher{};
std::optional<uint64_t> exception_ip{};
handle_store<handle_types::event, event> events{};
handle_store<handle_types::file, file> files{};
handle_store<handle_types::section, section> sections{};
handle_store<handle_types::semaphore, semaphore> semaphores{};
handle_store<handle_types::port, port> ports{};
handle_store<handle_types::mutant, mutant> mutants{};
handle_store<handle_types::registry, registry_key, 2> registry_keys{};
std::vector<std::byte> default_register_set{};
};
struct process_context64 final : process_context_common
{
process_context64(x86_64_emulator& emu, memory_manager& memory, utils::clock& clock, callbacks& cb)
: callbacks_(&cb),
base_allocator(emu),
peb(emu),
@@ -65,11 +102,6 @@ struct process_context
handle create_thread(memory_manager& memory, uint64_t start_address, uint64_t argument, uint64_t stack_size,
bool suspended);
uint16_t add_or_find_atom(std::u16string name);
bool delete_atom(const std::u16string& name);
bool delete_atom(uint16_t atom_id);
const std::u16string* get_atom_name(uint16_t atom_id) const;
void serialize(utils::buffer_serializer& buffer) const;
void deserialize(utils::buffer_deserializer& buffer);
@@ -77,40 +109,13 @@ struct process_context
callbacks* callbacks_{};
uint64_t current_ip{0};
uint64_t previous_ip{0};
uint64_t shared_section_address{0};
uint64_t shared_section_size{0};
uint64_t dbwin_buffer{0};
uint64_t dbwin_buffer_size{0};
std::optional<uint64_t> exception_rip{};
std::optional<NTSTATUS> exit_status{};
emulator_allocator base_allocator;
emulator_object<PEB64> peb;
emulator_object<RTL_USER_PROCESS_PARAMETERS64> process_params;
kusd_mmio kusd;
uint64_t ntdll_image_base{};
uint64_t ldr_initialize_thunk{};
uint64_t rtl_user_thread_start{};
uint64_t ki_user_apc_dispatcher{};
uint64_t ki_user_exception_dispatcher{};
handle_store<handle_types::event, event> events{};
handle_store<handle_types::file, file> files{};
handle_store<handle_types::section, section> sections{};
handle_store<handle_types::device, io_device_container> devices{};
handle_store<handle_types::semaphore, semaphore> semaphores{};
handle_store<handle_types::port, port> ports{};
handle_store<handle_types::mutant, mutant> mutants{};
handle_store<handle_types::registry, registry_key, 2> registry_keys{};
std::map<uint16_t, atom_entry> atoms{};
std::vector<std::byte> default_register_set{};
uint32_t spawned_thread_count{0};
handle_store<handle_types::thread, emulator_thread> threads{};

View File

@@ -1,6 +1,6 @@
#pragma once
#include "process_context.hpp"
#include "module/module_manager.hpp"
struct syscall_context;
using syscall_handler = void (*)(const syscall_context& c);

View File

@@ -8,7 +8,7 @@ struct syscall_context
{
windows_emulator& win_emu;
x86_64_emulator& emu;
process_context& proc;
process_context64& proc;
mutable bool write_status{true};
mutable bool retrigger_syscall{false};
};

View File

@@ -18,7 +18,7 @@ namespace syscalls
}
c.proc.exit_status = error_status;
c.proc.exception_rip = c.emu.read_instruction_pointer();
c.proc.exception_ip = c.emu.read_instruction_pointer();
c.emu.stop();
return STATUS_SUCCESS;
@@ -36,7 +36,7 @@ namespace syscalls
return STATUS_NOT_SUPPORTED;
}
c.proc.exception_rip = thread_context.read().Rip;
c.proc.exception_ip = thread_context.read().Rip;
c.emu.stop();
return STATUS_SUCCESS;

View File

@@ -68,7 +68,7 @@ namespace
}
}
emulator_thread* get_thread_by_id(process_context& process, const uint32_t id)
emulator_thread* get_thread_by_id(process_context64& process, const uint32_t id)
{
for (auto& t : process.threads | std::views::values)
{
@@ -540,7 +540,7 @@ void windows_emulator::setup_hooks()
if (this->fuzzing || true) // TODO: Fix
{
this->process.exception_rip = rip;
this->process.exception_ip = rip;
this->emu().stop();
}
});
@@ -564,7 +564,7 @@ void windows_emulator::setup_hooks()
if (this->fuzzing)
{
this->process.exception_rip = ip;
this->process.exception_ip = ip;
this->emu().stop();
return memory_violation_continuation::stop;
}

View File

@@ -15,7 +15,7 @@
std::unique_ptr<x86_64_emulator> create_default_x86_64_emulator();
struct emulator_callbacks : module_manager::callbacks, process_context::callbacks
struct emulator_callbacks : module_manager::callbacks, process_context64::callbacks
{
utils::optional_function<instruction_hook_continuation(uint32_t syscall_id, x86_64_emulator::pointer_type address,
std::string_view mod_name, std::string_view syscall_name)>
@@ -69,7 +69,7 @@ class windows_emulator
memory_manager memory;
registry_manager registry{};
module_manager mod_manager;
process_context process;
process_context64 process;
syscall_dispatcher dispatcher;
windows_emulator(const emulator_settings& settings = {}, emulator_callbacks callbacks = {},