Isolate memory manager (#137)

This commit is contained in:
Maurice Heumann
2025-02-09 18:33:18 +01:00
committed by GitHub
19 changed files with 983 additions and 924 deletions

View File

@@ -18,5 +18,6 @@ struct cpu_interface
virtual std::vector<std::byte> save_registers() = 0;
virtual void restore_registers(const std::vector<std::byte>& register_data) = 0;
// TODO: Remove this
virtual bool has_violation() const = 0;
};

0
src/emulator/empty.cpp Normal file
View File

View File

@@ -2,9 +2,11 @@
#include "cpu_interface.hpp"
#include "hook_interface.hpp"
#include "memory_manager.hpp"
#include "memory_interface.hpp"
class emulator : public cpu_interface, public memory_manager, public hook_interface
#include "serialization.hpp"
class emulator : public cpu_interface, public memory_interface, public hook_interface
{
public:
emulator() = default;
@@ -16,54 +18,6 @@ class emulator : public cpu_interface, public memory_manager, public hook_interf
emulator(emulator&&) = delete;
emulator& operator=(emulator&&) = delete;
void serialize(utils::buffer_serializer& buffer) const
{
this->perform_serialization(buffer, false);
}
void deserialize(utils::buffer_deserializer& buffer)
{
this->perform_deserialization(buffer, false);
}
void save_snapshot()
{
utils::buffer_serializer serializer{};
this->perform_serialization(serializer, true);
this->last_snapshot_data_ = serializer.move_buffer();
}
void restore_snapshot()
{
if (this->last_snapshot_data_.empty())
{
return;
}
utils::buffer_deserializer deserializer{this->last_snapshot_data_};
this->perform_deserialization(deserializer, true);
}
private:
std::vector<std::byte> last_snapshot_data_{};
void perform_serialization(utils::buffer_serializer& buffer, const bool is_snapshot) const
{
this->serialize_state(buffer, is_snapshot);
this->serialize_memory_state(buffer, is_snapshot);
}
void perform_deserialization(utils::buffer_deserializer& buffer, const bool is_snapshot)
{
if (!is_snapshot)
{
this->unmap_all_memory();
}
this->deserialize_state(buffer, is_snapshot);
this->deserialize_memory_state(buffer, is_snapshot);
}
virtual void serialize_state(utils::buffer_serializer& buffer, bool is_snapshot) const = 0;
virtual void deserialize_state(utils::buffer_deserializer& buffer, bool is_snapshot) = 0;
};

View File

@@ -0,0 +1,76 @@
#pragma once
#include <vector>
#include <functional>
#include "memory_permission.hpp"
using mmio_read_callback = std::function<uint64_t(uint64_t addr, size_t size)>;
using mmio_write_callback = std::function<void(uint64_t addr, size_t size, uint64_t data)>;
class memory_manager;
class memory_interface
{
public:
friend memory_manager;
virtual ~memory_interface() = default;
virtual void read_memory(uint64_t address, void* data, size_t size) const = 0;
virtual bool try_read_memory(uint64_t address, void* data, size_t size) const = 0;
virtual void write_memory(uint64_t address, const void* data, size_t size) = 0;
private:
virtual void map_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) = 0;
virtual void map_memory(uint64_t address, size_t size, memory_permission permissions) = 0;
virtual void unmap_memory(uint64_t address, size_t size) = 0;
virtual void apply_memory_protection(uint64_t address, size_t size, memory_permission permissions) = 0;
public:
template <typename T>
T read_memory(const uint64_t address) const
{
T value{};
this->read_memory(address, &value, sizeof(value));
return value;
}
template <typename T>
T read_memory(const void* address) const
{
return this->read_memory<T>(reinterpret_cast<uint64_t>(address));
}
std::vector<std::byte> read_memory(const uint64_t address, const size_t size) const
{
std::vector<std::byte> data{};
data.resize(size);
this->read_memory(address, data.data(), data.size());
return data;
}
std::vector<std::byte> read_memory(const void* address, const size_t size) const
{
return this->read_memory(reinterpret_cast<uint64_t>(address), size);
}
template <typename T>
void write_memory(const uint64_t address, const T& value)
{
this->write_memory(address, &value, sizeof(value));
}
template <typename T>
void write_memory(void* address, const T& value)
{
this->write_memory(reinterpret_cast<uint64_t>(address), &value, sizeof(value));
}
void write_memory(void* address, const void* data, const size_t size)
{
this->write_memory(reinterpret_cast<uint64_t>(address), data, size);
}
};

View File

@@ -1,156 +0,0 @@
#pragma once
#include <map>
#include <atomic>
#include <cstdint>
#include "memory_region.hpp"
#include "address_utils.hpp"
#include "serialization.hpp"
struct region_info : basic_memory_region
{
uint64_t allocation_base{};
size_t allocation_length{};
bool is_reserved{};
bool is_committed{};
};
using mmio_read_callback = std::function<uint64_t(uint64_t addr, size_t size)>;
using mmio_write_callback = std::function<void(uint64_t addr, size_t size, uint64_t data)>;
class memory_manager
{
public:
struct committed_region
{
size_t length{};
memory_permission permissions{};
};
using committed_region_map = std::map<uint64_t, committed_region>;
struct reserved_region
{
size_t length{};
committed_region_map committed_regions{};
bool is_mmio{false};
};
using reserved_region_map = std::map<uint64_t, reserved_region>;
virtual ~memory_manager() = default;
template <typename T>
T read_memory(const uint64_t address) const
{
T value{};
this->read_memory(address, &value, sizeof(value));
return value;
}
template <typename T>
T read_memory(const void* address) const
{
return this->read_memory<T>(reinterpret_cast<uint64_t>(address));
}
std::vector<std::byte> read_memory(const uint64_t address, const size_t size) const
{
std::vector<std::byte> data{};
data.resize(size);
this->read_memory(address, data.data(), data.size());
return data;
}
std::vector<std::byte> read_memory(const void* address, const size_t size) const
{
return this->read_memory(reinterpret_cast<uint64_t>(address), size);
}
template <typename T>
void write_memory(const uint64_t address, const T& value)
{
this->write_memory(address, &value, sizeof(value));
}
template <typename T>
void write_memory(void* address, const T& value)
{
this->write_memory(reinterpret_cast<uint64_t>(address), &value, sizeof(value));
}
void write_memory(void* address, const void* data, const size_t size)
{
this->write_memory(reinterpret_cast<uint64_t>(address), data, size);
}
virtual void read_memory(uint64_t address, void* data, size_t size) const = 0;
virtual bool try_read_memory(uint64_t address, void* data, size_t size) const = 0;
virtual void write_memory(uint64_t address, const void* data, size_t size) = 0;
bool protect_memory(uint64_t address, size_t size, memory_permission permissions,
memory_permission* old_permissions = nullptr);
bool allocate_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb);
bool allocate_memory(uint64_t address, size_t size, memory_permission permissions, bool reserve_only = false);
bool commit_memory(uint64_t address, size_t size, memory_permission permissions);
bool decommit_memory(uint64_t address, size_t size);
bool release_memory(uint64_t address, size_t size);
void unmap_all_memory();
uint64_t allocate_memory(const size_t size, const memory_permission permissions, const bool reserve_only = false)
{
const auto allocation_base = this->find_free_allocation_base(size);
if (!allocate_memory(allocation_base, size, permissions, reserve_only))
{
return 0;
}
return allocation_base;
}
uint64_t find_free_allocation_base(size_t size, uint64_t start = 0) const;
region_info get_region_info(uint64_t address);
reserved_region_map::iterator find_reserved_region(uint64_t address);
bool overlaps_reserved_region(uint64_t address, size_t size) const;
const reserved_region_map& get_reserved_regions() const
{
return reserved_regions_;
}
std::uint64_t get_memory_layout_state_ver() const
{
return memory_layout_state_version_.load(std::memory_order_relaxed);
}
private:
reserved_region_map reserved_regions_{};
virtual void map_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) = 0;
virtual void map_memory(uint64_t address, size_t size, memory_permission permissions) = 0;
virtual void unmap_memory(uint64_t address, size_t size) = 0;
virtual void apply_memory_protection(uint64_t address, size_t size, memory_permission permissions) = 0;
protected:
std::atomic<std::uint64_t> memory_layout_state_version_{0};
void invalidate_memory_layout_state_version()
{
#if MOMO_REFLECTION_LEVEL > 0
memory_layout_state_version_.fetch_add(1, std::memory_order_relaxed);
#endif
}
void serialize_memory_state(utils::buffer_serializer& buffer, bool is_snapshot) const;
void deserialize_memory_state(utils::buffer_deserializer& buffer, bool is_snapshot);
};

View File

@@ -83,8 +83,8 @@ namespace
restore_emulator();
const auto memory = emu.emu().allocate_memory(page_align_up(std::max(data.size(), size_t(1))),
memory_permission::read_write);
const auto memory = emu.memory().allocate_memory(
page_align_up(std::max(data.size(), static_cast<size_t>(1))), memory_permission::read_write);
emu.emu().write_memory(memory, data.data(), data.size());
emu.emu().reg(x64_register::rcx, memory);

View File

@@ -1,7 +1,11 @@
#pragma once
#include "memory_utils.hpp"
#include <x64_emulator.hpp>
#include "memory_manager.hpp"
#include "memory_utils.hpp"
#include "address_utils.hpp"
// TODO: Replace with pointer handling structure for future 32 bit support
using emulator_pointer = uint64_t;
@@ -39,6 +43,7 @@ class windows_emulator;
struct process_context;
using x64_emulator_wrapper = object_wrapper<x64_emulator>;
using memory_manager_wrapper = object_wrapper<memory_manager>;
using process_context_wrapper = object_wrapper<process_context>;
using windows_emulator_wrapper = object_wrapper<windows_emulator>;
@@ -53,8 +58,8 @@ class emulator_object
{
}
emulator_object(emulator& emu, const uint64_t address = 0)
: emu_(&emu),
emulator_object(memory_interface& memory, const uint64_t address = 0)
: memory_(&memory),
address_(address)
{
}
@@ -92,13 +97,13 @@ class emulator_object
T read(const size_t index = 0) const
{
T obj{};
this->emu_->read_memory(this->address_ + index * this->size(), &obj, sizeof(obj));
this->memory_->read_memory(this->address_ + index * this->size(), &obj, sizeof(obj));
return obj;
}
void write(const T& value, const size_t index = 0) const
{
this->emu_->write_memory(this->address_ + index * this->size(), &value, sizeof(value));
this->memory_->write_memory(this->address_ + index * this->size(), &value, sizeof(value));
}
void write_if_valid(const T& value, const size_t index = 0) const
@@ -113,7 +118,7 @@ class emulator_object
void access(const F& accessor, const size_t index = 0) const
{
T obj{};
this->emu_->read_memory(this->address_ + index * this->size(), &obj, sizeof(obj));
this->memory_->read_memory(this->address_ + index * this->size(), &obj, sizeof(obj));
accessor(obj);
@@ -136,7 +141,7 @@ class emulator_object
}
private:
emulator* emu_{};
memory_interface* memory_{};
uint64_t address_{};
};
@@ -144,13 +149,13 @@ class emulator_object
class emulator_allocator
{
public:
emulator_allocator(emulator& emu)
: emu_(&emu)
emulator_allocator(memory_interface& memory)
: memory_(&memory)
{
}
emulator_allocator(emulator& emu, const uint64_t address, const uint64_t size)
: emu_(&emu),
emulator_allocator(memory_interface& memory, const uint64_t address, const uint64_t size)
: memory_(&memory),
address_(address),
size_(size),
active_address_(address)
@@ -177,7 +182,7 @@ class emulator_allocator
emulator_object<T> reserve(const size_t count = 1)
{
const auto potential_start = this->reserve(sizeof(T) * count, alignof(T));
return emulator_object<T>(*this->emu_, potential_start);
return emulator_object<T>(*this->memory_, potential_start);
}
char16_t* copy_string(const std::u16string_view str)
@@ -199,10 +204,10 @@ class emulator_allocator
const auto string_buffer = this->reserve(max_length, required_alignment);
this->emu_->write_memory(string_buffer, str.data(), total_length);
this->memory_->write_memory(string_buffer, str.data(), total_length);
constexpr std::array<char, element_size> nullbyte{};
this->emu_->write_memory(string_buffer + total_length, nullbyte.data(), nullbyte.size());
this->memory_->write_memory(string_buffer + total_length, nullbyte.data(), nullbyte.size());
result.Buffer = string_buffer;
result.Length = static_cast<USHORT>(total_length);
@@ -236,9 +241,9 @@ class emulator_allocator
return this->active_address_;
}
emulator& get_emulator() const
memory_interface& get_memory() const
{
return *this->emu_;
return *this->memory_;
}
void serialize(utils::buffer_serializer& buffer) const
@@ -255,18 +260,18 @@ class emulator_allocator
buffer.read(this->active_address_);
}
void release()
void release(memory_manager& manager)
{
if (this->emu_ && this->address_ && this->size_)
if (this->address_ && this->size_)
{
this->emu_->release_memory(this->address_, this->size_);
manager.release_memory(this->address_, this->size_);
this->address_ = 0;
this->size_ = 0;
}
}
private:
emulator* emu_{};
memory_interface* memory_{};
uint64_t address_{};
uint64_t size_{};
uint64_t active_address_{0};

View File

@@ -102,8 +102,8 @@ namespace utils
}
}
kusd_mmio::kusd_mmio(x64_emulator& emu, process_context& process)
: emu_(&emu),
kusd_mmio::kusd_mmio(memory_manager& memory, process_context& process)
: memory_(&memory),
process_(&process)
{
}
@@ -114,7 +114,7 @@ kusd_mmio::~kusd_mmio()
}
kusd_mmio::kusd_mmio(utils::buffer_deserializer& buffer)
: kusd_mmio(buffer.read<x64_emulator_wrapper>(), buffer.read<process_context_wrapper>())
: kusd_mmio(buffer.read<memory_manager_wrapper>(), buffer.read<process_context_wrapper>())
{
}
@@ -205,7 +205,7 @@ void kusd_mmio::register_mmio()
this->registered_ = true;
this->emu_->allocate_mmio(
this->memory_->allocate_mmio(
KUSD_ADDRESS, KUSD_BUFFER_SIZE,
[this](const uint64_t addr, const size_t size) { return this->read(addr, size); },
[](const uint64_t, const size_t, const uint64_t) {
@@ -218,6 +218,6 @@ void kusd_mmio::deregister_mmio()
if (this->registered_)
{
this->registered_ = false;
this->emu_->release_memory(KUSD_ADDRESS, KUSD_BUFFER_SIZE);
this->memory_->release_memory(KUSD_ADDRESS, KUSD_BUFFER_SIZE);
}
}

View File

@@ -11,7 +11,7 @@ class windows_emulator;
class kusd_mmio
{
public:
kusd_mmio(x64_emulator& emu, process_context& process);
kusd_mmio(memory_manager& memory, process_context& process);
~kusd_mmio();
kusd_mmio(utils::buffer_deserializer& buffer);
@@ -39,7 +39,7 @@ class kusd_mmio
void setup(bool use_relative_time);
private:
x64_emulator* emu_{};
memory_manager* memory_{};
process_context* process_{};
bool registered_{};

View File

@@ -0,0 +1,98 @@
#pragma once
#include <map>
#include <atomic>
#include <cstdint>
#include "memory_region.hpp"
#include "serialization.hpp"
#include <memory_interface.hpp>
struct region_info : basic_memory_region
{
uint64_t allocation_base{};
size_t allocation_length{};
bool is_reserved{};
bool is_committed{};
};
using mmio_read_callback = std::function<uint64_t(uint64_t addr, size_t size)>;
using mmio_write_callback = std::function<void(uint64_t addr, size_t size, uint64_t data)>;
class memory_manager : public memory_interface
{
public:
memory_manager(memory_interface& memory)
: memory_(&memory)
{
}
struct committed_region
{
size_t length{};
memory_permission permissions{};
};
using committed_region_map = std::map<uint64_t, committed_region>;
struct reserved_region
{
size_t length{};
committed_region_map committed_regions{};
bool is_mmio{false};
};
using reserved_region_map = std::map<uint64_t, reserved_region>;
void read_memory(uint64_t address, void* data, size_t size) const final;
bool try_read_memory(uint64_t address, void* data, size_t size) const final;
void write_memory(uint64_t address, const void* data, size_t size) final;
bool protect_memory(uint64_t address, size_t size, memory_permission permissions,
memory_permission* old_permissions = nullptr);
bool allocate_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb);
bool allocate_memory(uint64_t address, size_t size, memory_permission permissions, bool reserve_only = false);
bool commit_memory(uint64_t address, size_t size, memory_permission permissions);
bool decommit_memory(uint64_t address, size_t size);
bool release_memory(uint64_t address, size_t size);
void unmap_all_memory();
uint64_t allocate_memory(size_t size, memory_permission permissions, bool reserve_only = false);
uint64_t find_free_allocation_base(size_t size, uint64_t start = 0) const;
region_info get_region_info(uint64_t address);
reserved_region_map::iterator find_reserved_region(uint64_t address);
bool overlaps_reserved_region(uint64_t address, size_t size) const;
const reserved_region_map& get_reserved_regions() const
{
return this->reserved_regions_;
}
std::uint64_t get_layout_version() const
{
return this->layout_version_.load(std::memory_order_relaxed);
}
void serialize_memory_state(utils::buffer_serializer& buffer, bool is_snapshot) const;
void deserialize_memory_state(utils::buffer_deserializer& buffer, bool is_snapshot);
private:
memory_interface* memory_{};
reserved_region_map reserved_regions_{};
std::atomic<std::uint64_t> layout_version_{0};
void map_mmio(uint64_t address, size_t size, mmio_read_callback read_cb, mmio_write_callback write_cb) final;
void map_memory(uint64_t address, size_t size, memory_permission permissions) final;
void unmap_memory(uint64_t address, size_t size) final;
void apply_memory_protection(uint64_t address, size_t size, memory_permission permissions) final;
void update_layout_version();
};

View File

@@ -54,8 +54,8 @@ namespace utils
}
}
module_manager::module_manager(emulator& emu, file_system& file_sys)
: emu_(&emu),
module_manager::module_manager(memory_manager& memory, file_system& file_sys)
: memory_(&memory),
file_sys_(&file_sys)
{
}
@@ -80,7 +80,7 @@ mapped_module* module_manager::map_local_module(const std::filesystem::path& fil
try
{
auto mod = map_module_from_file(*this->emu_, std::move(local_file));
auto mod = map_module_from_file(*this->memory_, std::move(local_file));
mod.is_static = is_static;
logger.log("Mapped %s at 0x%" PRIx64 "\n", mod.path.generic_string().c_str(), mod.image_base);
@@ -126,7 +126,7 @@ bool module_manager::unmap(const uint64_t address, const logger& logger)
logger.log("Unmapping %s (0x%" PRIx64 ")\n", mod->second.path.generic_string().c_str(), mod->second.image_base);
unmap_module(*this->emu_, mod->second);
unmap_module(*this->memory_, mod->second);
this->modules_.erase(mod);
return true;

View File

@@ -10,7 +10,7 @@ class module_manager
{
public:
using module_map = std::map<uint64_t, mapped_module>;
module_manager(emulator& emu, file_system& file_sys);
module_manager(memory_manager& memory, file_system& file_sys);
mapped_module* map_module(const windows_path& file, const logger& logger, bool is_static = false);
mapped_module* map_local_module(const std::filesystem::path& file, const logger& logger, bool is_static = false);
@@ -47,7 +47,7 @@ class module_manager
}
private:
emulator* emu_{};
memory_manager* memory_{};
file_system* file_sys_{};
module_map modules_{};

View File

@@ -20,13 +20,13 @@ namespace
return nt_headers_offset + (first_section_absolute - absolute_base);
}
std::vector<uint8_t> read_mapped_memory(const emulator& emu, const mapped_module& binary)
std::vector<uint8_t> read_mapped_memory(const memory_manager& memory, const mapped_module& binary)
{
std::vector<uint8_t> memory{};
memory.resize(binary.size_of_image);
emu.read_memory(binary.image_base, memory.data(), memory.size());
std::vector<uint8_t> mem{};
mem.resize(binary.size_of_image);
memory.read_memory(binary.image_base, mem.data(), mem.size());
return memory;
return mem;
}
void collect_exports(mapped_module& binary, const utils::safe_buffer_accessor<const uint8_t> buffer,
@@ -141,7 +141,8 @@ namespace
}
}
void map_sections(emulator& emu, mapped_module& binary, const utils::safe_buffer_accessor<const uint8_t> buffer,
void map_sections(memory_manager& memory, mapped_module& binary,
const utils::safe_buffer_accessor<const uint8_t> buffer,
const PENTHeaders_t<std::uint64_t>& nt_headers, const uint64_t nt_headers_offset)
{
const auto first_section_offset = get_first_section_offset(nt_headers, nt_headers_offset);
@@ -156,7 +157,7 @@ namespace
{
const auto size_of_data = std::min(section.SizeOfRawData, section.Misc.VirtualSize);
const auto* source_ptr = buffer.get_pointer_for_range(section.PointerToRawData, size_of_data);
emu.write_memory(target_ptr, source_ptr, size_of_data);
memory.write_memory(target_ptr, source_ptr, size_of_data);
}
auto permissions = memory_permission::none;
@@ -178,7 +179,7 @@ namespace
const auto size_of_section = page_align_up(std::max(section.SizeOfRawData, section.Misc.VirtualSize));
emu.protect_memory(target_ptr, size_of_section, permissions, nullptr);
memory.protect_memory(target_ptr, size_of_section, permissions, nullptr);
mapped_section section_info{};
section_info.region.start = target_ptr;
@@ -195,7 +196,8 @@ namespace
}
}
mapped_module map_module_from_data(emulator& emu, const std::span<const uint8_t> data, std::filesystem::path file)
mapped_module map_module_from_data(memory_manager& memory, const std::span<const uint8_t> data,
std::filesystem::path file)
{
mapped_module binary{};
binary.path = std::move(file);
@@ -217,14 +219,15 @@ mapped_module map_module_from_data(emulator& emu, const std::span<const uint8_t>
binary.image_base = optional_header.ImageBase;
binary.size_of_image = page_align_up(optional_header.SizeOfImage); // TODO: Sanitize
if (!emu.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read))
if (!memory.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read))
{
binary.image_base = emu.find_free_allocation_base(binary.size_of_image);
binary.image_base = memory.find_free_allocation_base(binary.size_of_image);
const auto is_dll = nt_headers.FileHeader.Characteristics & IMAGE_FILE_DLL;
const auto has_dynamic_base = optional_header.DllCharacteristics & IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
const auto is_relocatable = is_dll || has_dynamic_base;
if (!is_relocatable || !emu.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read))
if (!is_relocatable ||
!memory.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read))
{
throw std::runtime_error("Memory range not allocatable");
}
@@ -233,22 +236,22 @@ mapped_module map_module_from_data(emulator& emu, const std::span<const uint8_t>
binary.entry_point = binary.image_base + optional_header.AddressOfEntryPoint;
const auto* header_buffer = buffer.get_pointer_for_range(0, optional_header.SizeOfHeaders);
emu.write_memory(binary.image_base, header_buffer, optional_header.SizeOfHeaders);
memory.write_memory(binary.image_base, header_buffer, optional_header.SizeOfHeaders);
map_sections(emu, binary, buffer, nt_headers, nt_headers_offset);
map_sections(memory, binary, buffer, nt_headers, nt_headers_offset);
auto mapped_memory = read_mapped_memory(emu, binary);
auto mapped_memory = read_mapped_memory(memory, binary);
utils::safe_buffer_accessor<uint8_t> mapped_buffer{mapped_memory};
apply_relocations(binary, mapped_buffer, optional_header);
collect_exports(binary, mapped_buffer, optional_header);
emu.write_memory(binary.image_base, mapped_memory.data(), mapped_memory.size());
memory.write_memory(binary.image_base, mapped_memory.data(), mapped_memory.size());
return binary;
}
mapped_module map_module_from_file(emulator& emu, std::filesystem::path file)
mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path file)
{
const auto data = utils::io::read_file(file);
if (data.empty())
@@ -256,10 +259,10 @@ mapped_module map_module_from_file(emulator& emu, std::filesystem::path file)
throw std::runtime_error("Bad file data");
}
return map_module_from_data(emu, data, std::move(file));
return map_module_from_data(memory, data, std::move(file));
}
bool unmap_module(emulator& emu, const mapped_module& mod)
bool unmap_module(memory_manager& memory, const mapped_module& mod)
{
return emu.release_memory(mod.image_base, mod.size_of_image);
return memory.release_memory(mod.image_base, mod.size_of_image);
}

View File

@@ -1,9 +1,9 @@
#pragma once
#include <x64_emulator.hpp>
#include "mapped_module.hpp"
#include "../memory_manager.hpp"
mapped_module map_module_from_data(emulator& emu, std::span<const uint8_t> data, std::filesystem::path file);
mapped_module map_module_from_file(emulator& emu, std::filesystem::path file);
mapped_module map_module_from_data(memory_manager& memory, std::span<const uint8_t> data, std::filesystem::path file);
mapped_module map_module_from_file(memory_manager& memory, std::filesystem::path file);
bool unmap_module(emulator& emu, const mapped_module& mod);
bool unmap_module(memory_manager& memory, const mapped_module& mod);

View File

@@ -318,17 +318,17 @@ class moved_marker
class emulator_thread : public ref_counted_object
{
public:
emulator_thread(x64_emulator& emu)
: emu_ptr(&emu)
emulator_thread(memory_manager& memory)
: memory_ptr(&memory)
{
}
emulator_thread(utils::buffer_deserializer& buffer)
: emulator_thread(buffer.read<x64_emulator_wrapper>().get())
: emulator_thread(buffer.read<memory_manager_wrapper>().get())
{
}
emulator_thread(x64_emulator& emu, const process_context& context, uint64_t start_address, uint64_t argument,
emulator_thread(memory_manager& memory, const process_context& context, uint64_t start_address, uint64_t argument,
uint64_t stack_size, uint32_t id);
emulator_thread(const emulator_thread&) = delete;
@@ -337,14 +337,14 @@ class emulator_thread : public ref_counted_object
emulator_thread(emulator_thread&& obj) noexcept = default;
emulator_thread& operator=(emulator_thread&& obj) noexcept = default;
~emulator_thread()
~emulator_thread() override
{
this->release();
}
moved_marker marker{};
x64_emulator* emu_ptr{};
memory_manager* memory_ptr{};
uint64_t stack_base{};
uint64_t stack_size{};
@@ -407,7 +407,7 @@ class emulator_thread : public ref_counted_object
}
}
void serialize_object(utils::buffer_serializer& buffer) const
void serialize_object(utils::buffer_serializer& buffer) const override
{
if (this->marker.was_moved())
{
@@ -438,7 +438,7 @@ class emulator_thread : public ref_counted_object
buffer.write_vector(this->last_registers);
}
void deserialize_object(utils::buffer_deserializer& buffer)
void deserialize_object(utils::buffer_deserializer& buffer) override
{
if (this->marker.was_moved())
{
@@ -465,8 +465,8 @@ class emulator_thread : public ref_counted_object
buffer.read_optional(this->await_time);
buffer.read_optional(this->pending_status);
buffer.read_optional(this->gs_segment, [this] { return emulator_allocator(*this->emu_ptr); });
buffer.read_optional(this->teb, [this] { return emulator_object<TEB64>(*this->emu_ptr); });
buffer.read_optional(this->gs_segment, [this] { return emulator_allocator(*this->memory_ptr); });
buffer.read_optional(this->teb, [this] { return emulator_object<TEB64>(*this->memory_ptr); });
buffer.read_vector(this->last_registers);
}
@@ -488,18 +488,18 @@ class emulator_thread : public ref_counted_object
if (this->stack_base)
{
if (!this->emu_ptr)
if (!this->memory_ptr)
{
throw std::runtime_error("Emulator was never assigned!");
}
this->emu_ptr->release_memory(this->stack_base, this->stack_size);
this->memory_ptr->release_memory(this->stack_base, this->stack_size);
this->stack_base = 0;
}
if (this->gs_segment)
{
this->gs_segment->release();
this->gs_segment->release(*this->memory_ptr);
this->gs_segment = {};
}
}
@@ -507,12 +507,12 @@ class emulator_thread : public ref_counted_object
struct process_context
{
process_context(x64_emulator& emu, file_system& file_sys)
process_context(x64_emulator& emu, memory_manager& memory, file_system& file_sys)
: base_allocator(emu),
peb(emu),
process_params(emu),
kusd(emu, *this),
mod_manager(emu, file_sys)
kusd(memory, *this),
mod_manager(memory, file_sys)
{
}
@@ -645,10 +645,10 @@ struct process_context
this->active_thread = this->threads.get(buffer.read<uint64_t>());
}
handle create_thread(x64_emulator& emu, const uint64_t start_address, const uint64_t argument,
handle create_thread(memory_manager& memory, const uint64_t start_address, const uint64_t argument,
const uint64_t stack_size)
{
emulator_thread t{emu, *this, start_address, argument, stack_size, ++this->spawned_thread_count};
emulator_thread t{memory, *this, start_address, argument, stack_size, ++this->spawned_thread_count};
return this->threads.store(std::move(t));
}
};

View File

@@ -704,8 +704,8 @@ namespace
{
constexpr auto shared_section_size = 0x10000;
const auto address = c.emu.find_free_allocation_base(shared_section_size);
c.emu.allocate_memory(address, shared_section_size, memory_permission::read_write);
const auto address = c.win_emu.memory().find_free_allocation_base(shared_section_size);
c.win_emu.memory().allocate_memory(address, shared_section_size, memory_permission::read_write);
const std::u16string_view windows_dir = c.proc.kusd.get().NtSystemRoot.arr;
const auto windows_dir_size = windows_dir.size() * 2;
@@ -789,7 +789,7 @@ namespace
}
const auto protection = map_nt_to_emulator_protection(section_entry->section_page_protection);
const auto address = c.emu.allocate_memory(size, protection);
const auto address = c.win_emu.memory().allocate_memory(size, protection);
if (!file_data.empty())
{
@@ -850,7 +850,7 @@ namespace
const emulator_object<EMU_MEMORY_BASIC_INFORMATION64> info{c.emu, memory_information};
info.access([&](EMU_MEMORY_BASIC_INFORMATION64& image_info) {
const auto region_info = c.emu.get_region_info(base_address);
const auto region_info = c.win_emu.memory().get_region_info(base_address);
assert(!region_info.is_committed || region_info.is_reserved);
@@ -910,7 +910,7 @@ namespace
return STATUS_BUFFER_OVERFLOW;
}
const auto region_info = c.emu.get_region_info(base_address);
const auto region_info = c.win_emu.memory().get_region_info(base_address);
if (!region_info.is_reserved)
{
return STATUS_INVALID_ADDRESS;
@@ -2015,7 +2015,8 @@ namespace
try
{
c.emu.protect_memory(aligned_start, aligned_length, requested_protection, &old_protection_value);
c.win_emu.memory().protect_memory(aligned_start, aligned_length, requested_protection,
&old_protection_value);
}
catch (...)
{
@@ -2117,7 +2118,7 @@ namespace
auto potential_base = base_address.read();
if (!potential_base)
{
potential_base = c.emu.find_free_allocation_base(allocation_bytes);
potential_base = c.win_emu.memory().find_free_allocation_base(allocation_bytes);
}
if (!potential_base)
@@ -2137,7 +2138,7 @@ namespace
throw std::runtime_error("Unsupported allocation type!");
}
if (commit && !reserve && c.emu.commit_memory(potential_base, allocation_bytes, protection))
if (commit && !reserve && c.win_emu.memory().commit_memory(potential_base, allocation_bytes, protection))
{
c.win_emu.log.print(color::dark_gray, "--> Committed 0x%" PRIx64 " - 0x%" PRIx64 "\n", potential_base,
potential_base + allocation_bytes);
@@ -2148,7 +2149,7 @@ namespace
c.win_emu.log.print(color::dark_gray, "--> Allocated 0x%" PRIx64 " - 0x%" PRIx64 "\n", potential_base,
potential_base + allocation_bytes);
return c.emu.allocate_memory(potential_base, allocation_bytes, protection, !commit)
return c.win_emu.memory().allocate_memory(potential_base, allocation_bytes, protection, !commit)
? STATUS_SUCCESS
: STATUS_MEMORY_NOT_ALLOCATED;
}
@@ -2176,14 +2177,14 @@ namespace
if (free_type & MEM_RELEASE)
{
return c.emu.release_memory(allocation_base, allocation_size) ? STATUS_SUCCESS
: STATUS_MEMORY_NOT_ALLOCATED;
return c.win_emu.memory().release_memory(allocation_base, allocation_size) ? STATUS_SUCCESS
: STATUS_MEMORY_NOT_ALLOCATED;
}
if (free_type & MEM_DECOMMIT)
{
return c.emu.decommit_memory(allocation_base, allocation_size) ? STATUS_SUCCESS
: STATUS_MEMORY_NOT_ALLOCATED;
return c.win_emu.memory().decommit_memory(allocation_base, allocation_size) ? STATUS_SUCCESS
: STATUS_MEMORY_NOT_ALLOCATED;
}
throw std::runtime_error("Bad free type");
@@ -2260,7 +2261,7 @@ namespace
}
client_shared_memory.access([&](PORT_VIEW64& view) {
p.view_base = c.emu.allocate_memory(view.ViewSize, memory_permission::read_write);
p.view_base = c.win_emu.memory().allocate_memory(view.ViewSize, memory_permission::read_write);
view.ViewBase = p.view_base;
view.ViewRemoteBase = view.ViewBase;
});
@@ -2752,7 +2753,7 @@ namespace
}
const auto size = page_align_up(locale_file.size());
const auto base = c.emu.allocate_memory(size, memory_permission::read);
const auto base = c.win_emu.memory().allocate_memory(size, memory_permission::read);
c.emu.write_memory(base, locale_file.data(), locale_file.size());
base_address.write(base);
@@ -3445,7 +3446,7 @@ namespace
return STATUS_NOT_SUPPORTED;
}
const auto h = c.proc.create_thread(c.emu, start_routine, argument, stack_size);
const auto h = c.proc.create_thread(c.win_emu.memory(), start_routine, argument, stack_size);
thread_handle.write(h);
if (!attribute_list)

View File

@@ -190,19 +190,19 @@ namespace
reinterpret_cast<const API_SET_NAMESPACE&>(*obtain_api_set(location, root).data()));
}
emulator_allocator create_allocator(emulator& emu, const size_t size)
emulator_allocator create_allocator(memory_manager& memory, const size_t size)
{
const auto base = emu.find_free_allocation_base(size);
emu.allocate_memory(base, size, memory_permission::read_write);
const auto base = memory.find_free_allocation_base(size);
memory.allocate_memory(base, size, memory_permission::read_write);
return emulator_allocator{emu, base, size};
return emulator_allocator{memory, base, size};
}
void setup_gdt(x64_emulator& emu)
void setup_gdt(x64_emulator& emu, memory_manager& memory)
{
constexpr uint64_t gdtr[4] = {0, GDT_ADDR, GDT_LIMIT, 0};
emu.write_register(x64_register::gdtr, &gdtr, sizeof(gdtr));
emu.allocate_memory(GDT_ADDR, GDT_LIMIT, memory_permission::read);
memory.allocate_memory(GDT_ADDR, GDT_LIMIT, memory_permission::read);
emu.write_memory<uint64_t>(GDT_ADDR + 6 * (sizeof(uint64_t)), 0xEFFE000000FFFF);
emu.reg<uint16_t>(x64_register::cs, 0x33);
@@ -216,8 +216,9 @@ namespace
{
auto& emu = win_emu.emu();
auto& context = win_emu.process();
auto& memory = win_emu.memory();
setup_gdt(emu);
setup_gdt(emu, memory);
context.registry =
registry_manager(win_emu.get_emulation_root().empty() ? settings.registry_directory
@@ -225,7 +226,7 @@ namespace
context.kusd.setup(settings.use_relative_time);
context.base_allocator = create_allocator(emu, PEB_SEGMENT_SIZE);
context.base_allocator = create_allocator(memory, PEB_SEGMENT_SIZE);
auto& allocator = context.base_allocator;
context.peb = allocator.reserve<PEB64>();
@@ -317,23 +318,22 @@ namespace
});
}
using exception_record_map = std::unordered_map<const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*,
emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>>>;
using exception_record = EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>;
using exception_record_map = std::unordered_map<const exception_record*, emulator_object<exception_record>>;
emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>> save_exception_record(
emulator_allocator& allocator, const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>& record,
exception_record_map& record_mapping)
emulator_object<exception_record> save_exception_record(emulator_allocator& allocator,
const exception_record& record,
exception_record_map& record_mapping)
{
const auto record_obj = allocator.reserve<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>>();
const auto record_obj = allocator.reserve<exception_record>();
record_obj.write(record);
if (record.ExceptionRecord)
{
record_mapping.emplace(&record, record_obj);
emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>> nested_record_obj{allocator.get_emulator()};
const auto nested_record = record_mapping.find(
reinterpret_cast<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*>(record.ExceptionRecord));
emulator_object<exception_record> nested_record_obj{allocator.get_memory()};
const auto nested_record = record_mapping.find(reinterpret_cast<exception_record*>(record.ExceptionRecord));
if (nested_record != record_mapping.end())
{
@@ -342,11 +342,10 @@ namespace
else
{
nested_record_obj = save_exception_record(
allocator, *reinterpret_cast<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*>(record.ExceptionRecord),
record_mapping);
allocator, *reinterpret_cast<exception_record*>(record.ExceptionRecord), record_mapping);
}
record_obj.access([&](EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>& r) {
record_obj.access([&](exception_record& r) {
r.ExceptionRecord = reinterpret_cast<EmulatorTraits<Emu64>::PVOID>(nested_record_obj.ptr());
});
}
@@ -354,8 +353,8 @@ namespace
return record_obj;
}
emulator_object<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>> save_exception_record(
emulator_allocator& allocator, const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>& record)
emulator_object<exception_record> save_exception_record(emulator_allocator& allocator,
const exception_record& record)
{
exception_record_map record_mapping{};
return save_exception_record(allocator, record, record_mapping);
@@ -374,12 +373,12 @@ namespace
}
}
size_t calculate_exception_record_size(const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>& record)
size_t calculate_exception_record_size(const exception_record& record)
{
std::unordered_set<const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*> records{};
std::unordered_set<const exception_record*> records{};
size_t total_size = 0;
const EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>* current_record = &record;
const exception_record* current_record = &record;
while (current_record)
{
if (!records.insert(current_record).second)
@@ -388,7 +387,7 @@ namespace
}
total_size += sizeof(*current_record);
current_record = reinterpret_cast<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*>(record.ExceptionRecord);
current_record = reinterpret_cast<exception_record*>(record.ExceptionRecord);
}
return total_size;
@@ -408,8 +407,8 @@ namespace
{
constexpr auto mach_frame_size = 0x40;
constexpr auto context_record_size = 0x4F0;
const auto exception_record_size = calculate_exception_record_size(
*reinterpret_cast<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*>(pointers.ExceptionRecord));
const auto exception_record_size =
calculate_exception_record_size(*reinterpret_cast<exception_record*>(pointers.ExceptionRecord));
const auto combined_size = align_up(exception_record_size + context_record_size, 0x10);
assert(combined_size == 0x590);
@@ -434,8 +433,8 @@ namespace
context_record_obj.write(*reinterpret_cast<CONTEXT64*>(pointers.ContextRecord));
emulator_allocator allocator{emu, new_sp + context_record_size, exception_record_size};
const auto exception_record_obj = save_exception_record(
allocator, *reinterpret_cast<EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>>*>(pointers.ExceptionRecord));
const auto exception_record_obj =
save_exception_record(allocator, *reinterpret_cast<exception_record*>(pointers.ExceptionRecord));
if (exception_record_obj.value() != allocator.get_base())
{
@@ -462,7 +461,7 @@ namespace
ctx.ContextFlags = CONTEXT64_ALL;
context_frame::save(emu, ctx);
EMU_EXCEPTION_RECORD<EmulatorTraits<Emu64>> record{};
exception_record record{};
memset(&record, 0, sizeof(record));
record.ExceptionCode = static_cast<DWORD>(status);
record.ExceptionFlags = 0;
@@ -679,20 +678,20 @@ namespace
}
}
emulator_thread::emulator_thread(x64_emulator& emu, const process_context& context, const uint64_t start_address,
emulator_thread::emulator_thread(memory_manager& memory, const process_context& context, const uint64_t start_address,
const uint64_t argument, const uint64_t stack_size, const uint32_t id)
: emu_ptr(&emu),
: memory_ptr(&memory),
stack_size(page_align_up(std::max(stack_size, static_cast<uint64_t>(STACK_SIZE)))),
start_address(start_address),
argument(argument),
id(id),
last_registers(context.default_register_set)
{
this->stack_base = emu.allocate_memory(this->stack_size, memory_permission::read_write);
this->stack_base = memory.allocate_memory(this->stack_size, memory_permission::read_write);
this->gs_segment = emulator_allocator{
emu,
emu.allocate_memory(GS_SEGMENT_SIZE, memory_permission::read_write),
memory,
memory.allocate_memory(GS_SEGMENT_SIZE, memory_permission::read_write),
GS_SEGMENT_SIZE,
};
@@ -877,7 +876,8 @@ windows_emulator::windows_emulator(const std::filesystem::path& emulation_root,
: emulation_root_{emulation_root.empty() ? emulation_root : absolute(emulation_root)},
file_sys_(emulation_root_.empty() ? emulation_root_ : emulation_root_ / "filesys"),
emu_(std::move(emu)),
process_(*emu_, file_sys_)
memory_manager_(*this->emu_),
process_(*emu_, memory_manager_, file_sys_)
{
#ifndef OS_WINDOWS
if (this->get_emulation_root().empty())
@@ -896,7 +896,7 @@ void windows_emulator::setup_process(const emulator_settings& settings, const wi
auto& emu = this->emu();
auto& context = this->process();
context.mod_manager = module_manager(emu, this->file_sys()); // TODO: Cleanup module manager
context.mod_manager = module_manager(this->memory(), this->file_sys()); // TODO: Cleanup module manager
const auto application = settings.application.is_absolute() //
? settings.application
@@ -924,7 +924,7 @@ void windows_emulator::setup_process(const emulator_settings& settings, const wi
context.default_register_set = emu.save_registers();
const auto main_thread_id = context.create_thread(emu, context.executable->entry_point, 0, 0);
const auto main_thread_id = context.create_thread(this->memory(), context.executable->entry_point, 0, 0);
switch_to_thread(*this, main_thread_id);
}
@@ -1188,13 +1188,18 @@ void windows_emulator::serialize(utils::buffer_serializer& buffer) const
{
buffer.write(this->switch_thread_);
buffer.write(this->use_relative_time_);
this->emu().serialize(buffer);
this->emu().serialize_state(buffer, false);
this->memory().serialize_memory_state(buffer, false);
this->process_.serialize(buffer);
this->dispatcher_.serialize(buffer);
}
void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
{
buffer.register_factory<memory_manager_wrapper>([this] {
return memory_manager_wrapper{this->memory()}; //
});
buffer.register_factory<x64_emulator_wrapper>([this] {
return x64_emulator_wrapper{this->emu()}; //
});
@@ -1206,16 +1211,19 @@ void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
buffer.read(this->switch_thread_);
buffer.read(this->use_relative_time_);
this->emu().deserialize(buffer);
this->memory().unmap_all_memory();
this->emu().deserialize_state(buffer, false);
this->memory().deserialize_memory_state(buffer, false);
this->process_.deserialize(buffer);
this->dispatcher_.deserialize(buffer);
}
void windows_emulator::save_snapshot()
{
this->emu().save_snapshot();
utils::buffer_serializer serializer{};
this->emu().serialize_state(serializer, true);
this->memory().serialize_memory_state(serializer, true);
this->process_.serialize(serializer);
this->process_snapshot_ = serializer.move_buffer();
@@ -1232,9 +1240,9 @@ void windows_emulator::restore_snapshot()
return;
}
this->emu().restore_snapshot();
utils::buffer_deserializer deserializer{this->process_snapshot_};
this->emu().deserialize_state(deserializer, true);
this->memory().deserialize_memory_state(deserializer, true);
this->process_.deserialize(deserializer);
// this->process_ = *this->process_snapshot_;
}

View File

@@ -9,6 +9,7 @@
#include "process_context.hpp"
#include "logger.hpp"
#include "file_system.hpp"
#include "memory_manager.hpp"
std::unique_ptr<x64_emulator> create_default_x64_emulator();
@@ -186,6 +187,16 @@ class windows_emulator
return this->file_sys_;
}
memory_manager& memory()
{
return this->memory_manager_;
}
const memory_manager& memory() const
{
return this->memory_manager_;
}
const std::filesystem::path& get_emulation_root()
{
return this->emulation_root_;
@@ -205,6 +216,8 @@ class windows_emulator
std::vector<instruction_hook_callback> syscall_hooks_{};
std::unordered_map<uint16_t, uint16_t> port_mappings_{};
memory_manager memory_manager_;
std::set<std::string, std::less<>> modules_{};
process_context process_;