mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-28 07:21:02 +00:00
Prepare fuzzing engine
This commit is contained in:
30
src/windows-emulator/CMakeLists.txt
Normal file
30
src/windows-emulator/CMakeLists.txt
Normal file
@@ -0,0 +1,30 @@
|
||||
file(GLOB_RECURSE SRC_FILES CONFIGURE_DEPENDS
|
||||
*.cpp
|
||||
*.hpp
|
||||
*.rc
|
||||
)
|
||||
|
||||
list(SORT SRC_FILES)
|
||||
|
||||
add_library(windows-emulator ${SRC_FILES})
|
||||
|
||||
momo_assign_source_group(${SRC_FILES})
|
||||
|
||||
target_precompile_headers(windows-emulator PRIVATE std_include.hpp)
|
||||
|
||||
target_link_libraries(windows-emulator PRIVATE
|
||||
common
|
||||
unicorn-emulator
|
||||
mini-gdbstub
|
||||
)
|
||||
|
||||
target_link_libraries(windows-emulator PUBLIC
|
||||
emulator
|
||||
phnt::phnt
|
||||
)
|
||||
|
||||
target_include_directories(windows-emulator INTERFACE
|
||||
"${CMAKE_CURRENT_LIST_DIR}"
|
||||
)
|
||||
|
||||
momo_strip_target(windows-emulator)
|
||||
151
src/windows-emulator/context_frame.cpp
Normal file
151
src/windows-emulator/context_frame.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#include "std_include.hpp"
|
||||
#include "context_frame.hpp"
|
||||
|
||||
namespace context_frame
|
||||
{
|
||||
void restore(x64_emulator& emu, const CONTEXT& context)
|
||||
{
|
||||
if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS)
|
||||
{
|
||||
emu.reg(x64_register::dr0, context.Dr0);
|
||||
emu.reg(x64_register::dr1, context.Dr1);
|
||||
emu.reg(x64_register::dr2, context.Dr2);
|
||||
emu.reg(x64_register::dr3, context.Dr3);
|
||||
emu.reg(x64_register::dr6, context.Dr6);
|
||||
emu.reg(x64_register::dr7, context.Dr7);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_CONTROL)
|
||||
{
|
||||
emu.reg<uint16_t>(x64_register::ss, context.SegSs);
|
||||
emu.reg<uint16_t>(x64_register::cs, context.SegCs);
|
||||
|
||||
emu.reg(x64_register::rip, context.Rip);
|
||||
emu.reg(x64_register::rsp, context.Rsp);
|
||||
|
||||
emu.reg<uint32_t>(x64_register::eflags, context.EFlags);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_INTEGER)
|
||||
{
|
||||
emu.reg(x64_register::rax, context.Rax);
|
||||
emu.reg(x64_register::rbx, context.Rbx);
|
||||
emu.reg(x64_register::rcx, context.Rcx);
|
||||
emu.reg(x64_register::rdx, context.Rdx);
|
||||
emu.reg(x64_register::rbp, context.Rbp);
|
||||
emu.reg(x64_register::rsi, context.Rsi);
|
||||
emu.reg(x64_register::rdi, context.Rdi);
|
||||
emu.reg(x64_register::r8, context.R8);
|
||||
emu.reg(x64_register::r9, context.R9);
|
||||
emu.reg(x64_register::r10, context.R10);
|
||||
emu.reg(x64_register::r11, context.R11);
|
||||
emu.reg(x64_register::r12, context.R12);
|
||||
emu.reg(x64_register::r13, context.R13);
|
||||
emu.reg(x64_register::r14, context.R14);
|
||||
emu.reg(x64_register::r15, context.R15);
|
||||
}
|
||||
|
||||
/*if (context.ContextFlags & CONTEXT_SEGMENTS)
|
||||
{
|
||||
emu.reg<uint16_t>(x64_register::ds, context.SegDs);
|
||||
emu.reg<uint16_t>(x64_register::es, context.SegEs);
|
||||
emu.reg<uint16_t>(x64_register::fs, context.SegFs);
|
||||
emu.reg<uint16_t>(x64_register::gs, context.SegGs);
|
||||
}*/
|
||||
|
||||
if (context.ContextFlags & CONTEXT_FLOATING_POINT)
|
||||
{
|
||||
emu.reg<uint16_t>(x64_register::fpcw, context.FltSave.ControlWord);
|
||||
emu.reg<uint16_t>(x64_register::fpsw, context.FltSave.StatusWord);
|
||||
emu.reg<uint16_t>(x64_register::fptag, context.FltSave.TagWord);
|
||||
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const auto reg = static_cast<x64_register>(static_cast<int>(x64_register::st0) + i);
|
||||
emu.reg<M128A>(reg, context.FltSave.FloatRegisters[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_XSTATE)
|
||||
{
|
||||
emu.reg<uint32_t>(x64_register::mxcsr, context.MxCsr);
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
const auto reg = static_cast<x64_register>(static_cast<int>(x64_register::xmm0) + i);
|
||||
emu.reg<M128A>(reg, (&context.Xmm0)[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void save(x64_emulator& emu, CONTEXT& context)
|
||||
{
|
||||
if (context.ContextFlags & CONTEXT_DEBUG_REGISTERS)
|
||||
{
|
||||
context.Dr0 = emu.reg(x64_register::dr0);
|
||||
context.Dr1 = emu.reg(x64_register::dr1);
|
||||
context.Dr2 = emu.reg(x64_register::dr2);
|
||||
context.Dr3 = emu.reg(x64_register::dr3);
|
||||
context.Dr6 = emu.reg(x64_register::dr6);
|
||||
context.Dr7 = emu.reg(x64_register::dr7);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_CONTROL)
|
||||
{
|
||||
context.SegSs = emu.reg<uint16_t>(x64_register::ss);
|
||||
context.SegCs = emu.reg<uint16_t>(x64_register::cs);
|
||||
context.Rip = emu.reg(x64_register::rip);
|
||||
context.Rsp = emu.reg(x64_register::rsp);
|
||||
context.EFlags = emu.reg<uint32_t>(x64_register::eflags);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_INTEGER)
|
||||
{
|
||||
context.Rax = emu.reg(x64_register::rax);
|
||||
context.Rbx = emu.reg(x64_register::rbx);
|
||||
context.Rcx = emu.reg(x64_register::rcx);
|
||||
context.Rdx = emu.reg(x64_register::rdx);
|
||||
context.Rbp = emu.reg(x64_register::rbp);
|
||||
context.Rsi = emu.reg(x64_register::rsi);
|
||||
context.Rdi = emu.reg(x64_register::rdi);
|
||||
context.R8 = emu.reg(x64_register::r8);
|
||||
context.R9 = emu.reg(x64_register::r9);
|
||||
context.R10 = emu.reg(x64_register::r10);
|
||||
context.R11 = emu.reg(x64_register::r11);
|
||||
context.R12 = emu.reg(x64_register::r12);
|
||||
context.R13 = emu.reg(x64_register::r13);
|
||||
context.R14 = emu.reg(x64_register::r14);
|
||||
context.R15 = emu.reg(x64_register::r15);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_SEGMENTS)
|
||||
{
|
||||
context.SegDs = emu.reg<uint16_t>(x64_register::ds);
|
||||
context.SegEs = emu.reg<uint16_t>(x64_register::es);
|
||||
context.SegFs = emu.reg<uint16_t>(x64_register::fs);
|
||||
context.SegGs = emu.reg<uint16_t>(x64_register::gs);
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_FLOATING_POINT)
|
||||
{
|
||||
context.FltSave.ControlWord = emu.reg<uint16_t>(x64_register::fpcw);
|
||||
context.FltSave.StatusWord = emu.reg<uint16_t>(x64_register::fpsw);
|
||||
context.FltSave.TagWord = static_cast<BYTE>(emu.reg<uint16_t>(x64_register::fptag));
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const auto reg = static_cast<x64_register>(static_cast<int>(x64_register::st0) + i);
|
||||
context.FltSave.FloatRegisters[i] = emu.reg<M128A>(reg);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.ContextFlags & CONTEXT_XSTATE)
|
||||
{
|
||||
context.MxCsr = emu.reg<uint32_t>(x64_register::mxcsr);
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
const auto reg = static_cast<x64_register>(static_cast<int>(x64_register::xmm0) + i);
|
||||
(&context.Xmm0)[i] = emu.reg<M128A>(reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
8
src/windows-emulator/context_frame.hpp
Normal file
8
src/windows-emulator/context_frame.hpp
Normal file
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
#include "x64_emulator.hpp"
|
||||
|
||||
namespace context_frame
|
||||
{
|
||||
void save(x64_emulator& emu, CONTEXT& context);
|
||||
void restore(x64_emulator& emu, const CONTEXT& context);
|
||||
}
|
||||
139
src/windows-emulator/debugging/gdb_stub.cpp
Normal file
139
src/windows-emulator/debugging/gdb_stub.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "../std_include.hpp"
|
||||
#include "gdb_stub.hpp"
|
||||
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
extern "C" {
|
||||
#include <gdbstub.h>
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
gdb_action_t map_gdb_action(const gdb_action action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case gdb_action::none:
|
||||
return ACT_NONE;
|
||||
case gdb_action::resume:
|
||||
return ACT_RESUME;
|
||||
case gdb_action::shutdown:
|
||||
return ACT_SHUTDOWN;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Bad action");
|
||||
}
|
||||
|
||||
breakpoint_type map_breakpoint_type(const bp_type_t type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case BP_SOFTWARE:
|
||||
return breakpoint_type::software;
|
||||
case BP_HARDWARE_EXEC:
|
||||
return breakpoint_type::hardware_exec;
|
||||
case BP_HARDWARE_WRITE:
|
||||
return breakpoint_type::hardware_write;
|
||||
case BP_HARDWARE_READ:
|
||||
return breakpoint_type::hardware_read;
|
||||
case BP_HARDWARE_READ_WRITE:
|
||||
return breakpoint_type::hardware_read_write;
|
||||
}
|
||||
|
||||
throw std::runtime_error("Bad breakpoint type");
|
||||
}
|
||||
|
||||
gdb_stub_handler& get_handler(void* args)
|
||||
{
|
||||
return *static_cast<gdb_stub_handler*>(args);
|
||||
}
|
||||
|
||||
gdb_action_t cont(void* args)
|
||||
{
|
||||
return map_gdb_action(get_handler(args).cont());
|
||||
}
|
||||
|
||||
gdb_action_t stepi(void* args)
|
||||
{
|
||||
return map_gdb_action(get_handler(args).stepi());
|
||||
}
|
||||
|
||||
int read_reg(void* args, const int regno, size_t* value)
|
||||
{
|
||||
return get_handler(args).read_reg(regno, value) ? 0 : 1;
|
||||
}
|
||||
|
||||
int write_reg(void* args, const int regno, const size_t value)
|
||||
{
|
||||
return get_handler(args).write_reg(regno, value) ? 0 : 1;
|
||||
}
|
||||
|
||||
int read_mem(void* args, const size_t addr, const size_t len, void* val)
|
||||
{
|
||||
return get_handler(args).read_mem(addr, len, val) ? 0 : 1;
|
||||
}
|
||||
|
||||
int write_mem(void* args, const size_t addr, const size_t len, void* val)
|
||||
{
|
||||
return get_handler(args).write_mem(addr, len, val) ? 0 : 1;
|
||||
}
|
||||
|
||||
bool set_bp(void* args, const size_t addr, const bp_type_t type, const size_t size)
|
||||
{
|
||||
return get_handler(args).set_bp(map_breakpoint_type(type), addr, size);
|
||||
}
|
||||
|
||||
bool del_bp(void* args, const size_t addr, const bp_type_t type, const size_t size)
|
||||
{
|
||||
return get_handler(args).del_bp(map_breakpoint_type(type), addr, size);
|
||||
}
|
||||
|
||||
void on_interrupt(void* args)
|
||||
{
|
||||
get_handler(args).on_interrupt();
|
||||
}
|
||||
|
||||
target_ops get_target_ops()
|
||||
{
|
||||
target_ops ops{};
|
||||
|
||||
ops.cont = cont;
|
||||
ops.stepi = stepi;
|
||||
ops.read_reg = read_reg;
|
||||
ops.write_reg = write_reg;
|
||||
ops.read_mem = read_mem;
|
||||
ops.write_mem = write_mem;
|
||||
ops.set_bp = set_bp;
|
||||
ops.del_bp = del_bp;
|
||||
ops.on_interrupt = on_interrupt;
|
||||
|
||||
return ops;
|
||||
}
|
||||
}
|
||||
|
||||
bool run_gdb_stub(gdb_stub_handler& handler, std::string target_description, const size_t register_count,
|
||||
std::string bind_address)
|
||||
{
|
||||
const arch_info_t info
|
||||
{
|
||||
target_description.data(),
|
||||
static_cast<int>(register_count),
|
||||
sizeof(uint64_t),
|
||||
};
|
||||
|
||||
auto ops = get_target_ops();
|
||||
|
||||
gdbstub_t stub{};
|
||||
|
||||
if (!gdbstub_init(&stub, &ops, info, bind_address.data()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto _ = utils::finally([&]
|
||||
{
|
||||
gdbstub_close(&stub);
|
||||
});
|
||||
|
||||
return gdbstub_run(&stub, &handler);
|
||||
}
|
||||
38
src/windows-emulator/debugging/gdb_stub.hpp
Normal file
38
src/windows-emulator/debugging/gdb_stub.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
enum class gdb_action : uint8_t
|
||||
{
|
||||
none,
|
||||
resume,
|
||||
shutdown,
|
||||
};
|
||||
|
||||
enum class breakpoint_type : uint8_t
|
||||
{
|
||||
software,
|
||||
hardware_exec,
|
||||
hardware_write,
|
||||
hardware_read,
|
||||
hardware_read_write,
|
||||
};
|
||||
|
||||
struct gdb_stub_handler
|
||||
{
|
||||
virtual ~gdb_stub_handler() = default;
|
||||
|
||||
virtual gdb_action cont() = 0;
|
||||
virtual gdb_action stepi() = 0;
|
||||
|
||||
virtual bool read_reg(int regno, size_t* value) = 0;
|
||||
virtual bool write_reg(int regno, size_t value) = 0;
|
||||
|
||||
virtual bool read_mem(size_t addr, size_t len, void* val) = 0;
|
||||
virtual bool write_mem(size_t addr, size_t len, void* val) = 0;
|
||||
|
||||
virtual bool set_bp(breakpoint_type type, size_t addr, size_t size) = 0;
|
||||
virtual bool del_bp(breakpoint_type type, size_t addr, size_t size) = 0;
|
||||
|
||||
virtual void on_interrupt() = 0;
|
||||
};
|
||||
|
||||
bool run_gdb_stub(gdb_stub_handler& handler, std::string target_description, size_t register_count, std::string bind_address);
|
||||
215
src/windows-emulator/debugging/x64_gdb_stub_handler.hpp
Normal file
215
src/windows-emulator/debugging/x64_gdb_stub_handler.hpp
Normal file
@@ -0,0 +1,215 @@
|
||||
#pragma once
|
||||
#include <x64_emulator.hpp>
|
||||
#include "gdb_stub.hpp"
|
||||
#include "scoped_hook.hpp"
|
||||
|
||||
inline 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,
|
||||
/*x64_register::cs,
|
||||
x64_register::ss,
|
||||
x64_register::ds,
|
||||
x64_register::es,
|
||||
x64_register::fs,
|
||||
x64_register::gs,*/
|
||||
};
|
||||
|
||||
inline memory_operation map_breakpoint_type(const breakpoint_type type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case breakpoint_type::software:
|
||||
case breakpoint_type::hardware_exec:
|
||||
return memory_operation::exec;
|
||||
case breakpoint_type::hardware_read:
|
||||
return memory_permission::read;
|
||||
case breakpoint_type::hardware_write:
|
||||
return memory_permission::write;
|
||||
case breakpoint_type::hardware_read_write:
|
||||
return memory_permission::read_write;
|
||||
default:
|
||||
throw std::runtime_error("Bad bp type");
|
||||
}
|
||||
}
|
||||
|
||||
struct breakpoint_key
|
||||
{
|
||||
size_t addr{};
|
||||
size_t size{};
|
||||
breakpoint_type type{};
|
||||
|
||||
bool operator==(const breakpoint_key& other) const
|
||||
{
|
||||
return this->addr == other.addr && this->size == other.size && this->type == other.type;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::hash<breakpoint_key>
|
||||
{
|
||||
std::size_t operator()(const breakpoint_key& k) const noexcept
|
||||
{
|
||||
return ((std::hash<size_t>()(k.addr)
|
||||
^ (std::hash<size_t>()(k.size) << 1)) >> 1)
|
||||
^ (std::hash<size_t>()(static_cast<size_t>(k.type)) << 1);
|
||||
}
|
||||
};
|
||||
|
||||
class x64_gdb_stub_handler : public gdb_stub_handler
|
||||
{
|
||||
public:
|
||||
x64_gdb_stub_handler(x64_emulator& emu)
|
||||
: emu_(&emu)
|
||||
{
|
||||
}
|
||||
|
||||
~x64_gdb_stub_handler() override = default;
|
||||
|
||||
gdb_action cont() override
|
||||
{
|
||||
try
|
||||
{
|
||||
this->emu_->start_from_ip();
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
puts(e.what());
|
||||
}
|
||||
|
||||
return gdb_action::resume;
|
||||
}
|
||||
|
||||
gdb_action stepi() override
|
||||
{
|
||||
try
|
||||
{
|
||||
this->emu_->start_from_ip({}, 1);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
puts(e.what());
|
||||
}
|
||||
|
||||
return gdb_action::resume;
|
||||
}
|
||||
|
||||
bool read_reg(const int regno, size_t* value) override
|
||||
{
|
||||
*value = 0;
|
||||
|
||||
try
|
||||
{
|
||||
if (static_cast<size_t>(regno) >= gdb_registers.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
this->emu_->read_register(gdb_registers[regno], value, sizeof(*value));
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool write_reg(const int regno, const size_t value) override
|
||||
{
|
||||
try
|
||||
{
|
||||
if (static_cast<size_t>(regno) >= gdb_registers.size())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
this->emu_->write_register(gdb_registers[regno], &value, sizeof(value));
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool read_mem(const size_t addr, const size_t len, void* val) override
|
||||
{
|
||||
return this->emu_->try_read_memory(addr, val, len);
|
||||
}
|
||||
|
||||
bool write_mem(const size_t addr, const size_t len, void* val) override
|
||||
{
|
||||
try
|
||||
{
|
||||
this->emu_->write_memory(addr, val, len);
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool set_bp(const breakpoint_type type, const size_t addr, const size_t size) override
|
||||
{
|
||||
try
|
||||
{
|
||||
this->hooks_[{addr, size, type}] = scoped_hook(*this->emu_, this->emu_->hook_memory_access(
|
||||
addr, size, map_breakpoint_type(type),
|
||||
[this](uint64_t, size_t, uint64_t, memory_operation)
|
||||
{
|
||||
this->on_interrupt();
|
||||
}));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool del_bp(const breakpoint_type type, const size_t addr, const size_t size) override
|
||||
{
|
||||
try
|
||||
{
|
||||
const auto entry = this->hooks_.find({addr, size, type});
|
||||
if (entry == this->hooks_.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
this->hooks_.erase(entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void on_interrupt() override
|
||||
{
|
||||
this->emu_->stop();
|
||||
}
|
||||
|
||||
private:
|
||||
x64_emulator* emu_{};
|
||||
std::unordered_map<breakpoint_key, scoped_hook> hooks_{};
|
||||
};
|
||||
216
src/windows-emulator/emulator_utils.hpp
Normal file
216
src/windows-emulator/emulator_utils.hpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
#include "memory_utils.hpp"
|
||||
|
||||
template <typename T>
|
||||
class emulator_object
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
|
||||
emulator_object() = default;
|
||||
|
||||
emulator_object(emulator& emu, const uint64_t address = 0)
|
||||
: emu_(&emu)
|
||||
, address_(address)
|
||||
{
|
||||
}
|
||||
|
||||
emulator_object(emulator& emu, const void* address)
|
||||
: emulator_object(emu, reinterpret_cast<uint64_t>(address))
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t value() const
|
||||
{
|
||||
return this->address_;
|
||||
}
|
||||
|
||||
uint64_t size() const
|
||||
{
|
||||
return sizeof(T);
|
||||
}
|
||||
|
||||
uint64_t end() const
|
||||
{
|
||||
return this->value() + this->size();
|
||||
}
|
||||
|
||||
T* ptr() const
|
||||
{
|
||||
return reinterpret_cast<T*>(this->address_);
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return this->address_ != 0;
|
||||
}
|
||||
|
||||
T read(const size_t index = 0) const
|
||||
{
|
||||
T obj{};
|
||||
this->emu_->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));
|
||||
}
|
||||
|
||||
template <typename F>
|
||||
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));
|
||||
|
||||
accessor(obj);
|
||||
|
||||
this->write(obj, index);
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->address_);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->address_);
|
||||
}
|
||||
|
||||
private:
|
||||
emulator* emu_{};
|
||||
uint64_t address_{};
|
||||
};
|
||||
|
||||
class emulator_allocator
|
||||
{
|
||||
public:
|
||||
emulator_allocator(emulator& emu)
|
||||
: emu_(&emu)
|
||||
{
|
||||
}
|
||||
|
||||
emulator_allocator(emulator& emu, const uint64_t address, const uint64_t size)
|
||||
: emu_(&emu)
|
||||
, address_(address)
|
||||
, size_(size)
|
||||
, active_address_(address)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t reserve(const uint64_t count, const uint64_t alignment = 1)
|
||||
{
|
||||
const auto potential_start = align_up(this->active_address_, alignment);
|
||||
const auto potential_end = potential_start + count;
|
||||
const auto total_end = this->address_ + this->size_;
|
||||
|
||||
if (potential_end > total_end)
|
||||
{
|
||||
throw std::runtime_error("Out of memory");
|
||||
}
|
||||
|
||||
this->active_address_ = potential_end;
|
||||
|
||||
return potential_start;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
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);
|
||||
}
|
||||
|
||||
void make_unicode_string(UNICODE_STRING& result, const std::wstring_view str)
|
||||
{
|
||||
constexpr auto element_size = sizeof(str[0]);
|
||||
constexpr auto required_alignment = alignof(decltype(str[0]));
|
||||
const auto total_length = str.size() * element_size;
|
||||
|
||||
const auto string_buffer = this->reserve(total_length + element_size, required_alignment);
|
||||
|
||||
this->emu_->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());
|
||||
|
||||
result.Buffer = reinterpret_cast<PWCH>(string_buffer);
|
||||
result.Length = static_cast<USHORT>(total_length);
|
||||
result.MaximumLength = result.Length;
|
||||
}
|
||||
|
||||
emulator_object<UNICODE_STRING> make_unicode_string(const std::wstring_view str)
|
||||
{
|
||||
const auto unicode_string = this->reserve<UNICODE_STRING>();
|
||||
|
||||
unicode_string.access([&](UNICODE_STRING& unicode_str)
|
||||
{
|
||||
this->make_unicode_string(unicode_str, str);
|
||||
});
|
||||
|
||||
return unicode_string;
|
||||
}
|
||||
|
||||
uint64_t get_base() const
|
||||
{
|
||||
return this->address_;
|
||||
}
|
||||
|
||||
uint64_t get_size() const
|
||||
{
|
||||
return this->size_;
|
||||
}
|
||||
|
||||
uint64_t get_next_address() const
|
||||
{
|
||||
return this->active_address_;
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->address_);
|
||||
buffer.write(this->size_);
|
||||
buffer.write(this->active_address_);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->address_);
|
||||
buffer.read(this->size_);
|
||||
buffer.read(this->active_address_);
|
||||
}
|
||||
|
||||
private:
|
||||
emulator* emu_{};
|
||||
uint64_t address_{};
|
||||
uint64_t size_{};
|
||||
uint64_t active_address_{0};
|
||||
};
|
||||
|
||||
inline std::wstring read_unicode_string(const emulator& emu, const UNICODE_STRING ucs)
|
||||
{
|
||||
static_assert(offsetof(UNICODE_STRING, Length) == 0);
|
||||
static_assert(offsetof(UNICODE_STRING, MaximumLength) == 2);
|
||||
static_assert(offsetof(UNICODE_STRING, Buffer) == 8);
|
||||
static_assert(sizeof(UNICODE_STRING) == 16);
|
||||
|
||||
std::wstring result{};
|
||||
result.resize(ucs.Length / 2);
|
||||
|
||||
emu.read_memory(reinterpret_cast<uint64_t>(ucs.Buffer), result.data(), ucs.Length);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
inline std::wstring read_unicode_string(const emulator& emu, const emulator_object<UNICODE_STRING> uc_string)
|
||||
{
|
||||
const auto ucs = uc_string.read();
|
||||
return read_unicode_string(emu, ucs);
|
||||
}
|
||||
|
||||
inline std::wstring read_unicode_string(emulator& emu, const UNICODE_STRING* uc_string)
|
||||
{
|
||||
return read_unicode_string(emu, emulator_object<UNICODE_STRING>{emu, uc_string});
|
||||
}
|
||||
235
src/windows-emulator/handles.hpp
Normal file
235
src/windows-emulator/handles.hpp
Normal file
@@ -0,0 +1,235 @@
|
||||
#pragma once
|
||||
|
||||
struct handle_types
|
||||
{
|
||||
enum type : uint16_t
|
||||
{
|
||||
file,
|
||||
event,
|
||||
section,
|
||||
symlink,
|
||||
directory,
|
||||
semaphore,
|
||||
port,
|
||||
};
|
||||
};
|
||||
|
||||
#pragma pack(push)
|
||||
#pragma pack(1)
|
||||
struct handle_value
|
||||
{
|
||||
uint64_t id : 32;
|
||||
uint64_t type : 16;
|
||||
uint64_t padding : 15;
|
||||
uint64_t is_pseudo : 1;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
static_assert(sizeof(handle_value) == 8);
|
||||
|
||||
union handle
|
||||
{
|
||||
handle_value value;
|
||||
uint64_t bits;
|
||||
HANDLE h;
|
||||
};
|
||||
|
||||
inline bool operator==(const handle& h1, const handle& h2)
|
||||
{
|
||||
return h1.bits == h2.bits;
|
||||
}
|
||||
|
||||
inline bool operator==(const handle& h1, const uint64_t& h2)
|
||||
{
|
||||
return h1.bits == h2;
|
||||
}
|
||||
|
||||
inline handle_value get_handle_value(const uint64_t h)
|
||||
{
|
||||
handle hh{};
|
||||
hh.bits = h;
|
||||
return hh.value;
|
||||
}
|
||||
|
||||
constexpr handle make_handle(const uint32_t id, const handle_types::type type, const bool is_pseudo)
|
||||
{
|
||||
handle_value value{};
|
||||
|
||||
value.padding = 0;
|
||||
value.id = id;
|
||||
value.type = type;
|
||||
value.is_pseudo = is_pseudo;
|
||||
|
||||
return {value};
|
||||
}
|
||||
|
||||
constexpr handle make_pseudo_handle(const uint32_t id, const handle_types::type type)
|
||||
{
|
||||
return make_handle(id, type, true);
|
||||
}
|
||||
|
||||
namespace handle_detail
|
||||
{
|
||||
template <typename, typename = void>
|
||||
struct has_deleter_function : std::false_type
|
||||
{
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct has_deleter_function<T, std::void_t<decltype(T::deleter(std::declval<T&>()))>>
|
||||
: std::is_same<decltype(T::deleter(std::declval<T&>())), bool>
|
||||
{
|
||||
};
|
||||
}
|
||||
|
||||
template <handle_types::type Type, typename T>
|
||||
requires(utils::Serializable<T>)
|
||||
class handle_store
|
||||
{
|
||||
public:
|
||||
using value_map = std::map<uint32_t, T>;
|
||||
|
||||
handle store(T value)
|
||||
{
|
||||
auto index = this->find_free_index();
|
||||
this->store_[index] = std::move(value);
|
||||
|
||||
return make_handle(index);
|
||||
}
|
||||
|
||||
handle make_handle(const uint32_t index)
|
||||
{
|
||||
handle h{};
|
||||
h.bits = 0;
|
||||
h.value.is_pseudo = false;
|
||||
h.value.type = Type;
|
||||
h.value.id = index;
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
T* get(const handle_value h)
|
||||
{
|
||||
const auto entry = this->get_iterator(h);
|
||||
if (entry == this->store_.end())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return &entry->second;
|
||||
}
|
||||
|
||||
T* get(const handle h)
|
||||
{
|
||||
return this->get(h.value);
|
||||
}
|
||||
|
||||
T* get(const uint64_t h)
|
||||
{
|
||||
handle hh{};
|
||||
hh.bits = h;
|
||||
|
||||
return this->get(hh);
|
||||
}
|
||||
|
||||
bool erase(const handle_value h)
|
||||
{
|
||||
const auto entry = this->get_iterator(h);
|
||||
if (entry == this->store_.end())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if constexpr (handle_detail::has_deleter_function<T>())
|
||||
{
|
||||
if (!T::deleter(entry->second))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
this->store_.erase(entry);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool erase(const handle h)
|
||||
{
|
||||
return this->erase(h.value);
|
||||
}
|
||||
|
||||
bool erase(const uint64_t h)
|
||||
{
|
||||
handle hh{};
|
||||
hh.bits = h;
|
||||
|
||||
return this->erase(hh);
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write_map(this->store_);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read_map(this->store_);
|
||||
}
|
||||
|
||||
value_map::iterator begin()
|
||||
{
|
||||
return this->store_.begin();
|
||||
}
|
||||
|
||||
value_map::const_iterator begin() const
|
||||
{
|
||||
return this->store_.begin();
|
||||
}
|
||||
|
||||
value_map::iterator end()
|
||||
{
|
||||
return this->store_.end();
|
||||
}
|
||||
|
||||
value_map::const_iterator end() const
|
||||
{
|
||||
return this->store_.end();
|
||||
}
|
||||
|
||||
private:
|
||||
typename value_map::iterator get_iterator(const handle_value h)
|
||||
{
|
||||
if (h.type != Type || h.is_pseudo)
|
||||
{
|
||||
return this->store_.end();
|
||||
}
|
||||
|
||||
return this->store_.find(h.id);
|
||||
}
|
||||
|
||||
uint32_t find_free_index()
|
||||
{
|
||||
uint32_t index = 1;
|
||||
for (; index > 0; ++index)
|
||||
{
|
||||
if (!this->store_.contains(index))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
|
||||
value_map store_{};
|
||||
};
|
||||
|
||||
constexpr auto KNOWN_DLLS_DIRECTORY = make_pseudo_handle(0x1337, handle_types::directory);
|
||||
constexpr auto KNOWN_DLLS_SYMLINK = make_pseudo_handle(0x1337, handle_types::symlink);
|
||||
constexpr auto SHARED_SECTION = make_pseudo_handle(0x1337, handle_types::section);
|
||||
constexpr auto CONSOLE_SERVER = make_pseudo_handle(0x1338, handle_types::section);
|
||||
constexpr auto CM_API = make_pseudo_handle(0x1338, handle_types::file);
|
||||
|
||||
constexpr auto CONSOLE_HANDLE = make_pseudo_handle(0x1, handle_types::file);
|
||||
constexpr auto STDOUT_HANDLE = make_pseudo_handle(0x2, handle_types::file);
|
||||
constexpr auto STDIN_HANDLE = make_pseudo_handle(0x3, handle_types::file);
|
||||
100
src/windows-emulator/logger.cpp
Normal file
100
src/windows-emulator/logger.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "std_include.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
#include <utils/finally.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
#ifdef _WIN32
|
||||
#define COLOR(win, posix) win
|
||||
using color_type = WORD;
|
||||
#else
|
||||
#define COLOR(win, posix) posix
|
||||
using color_type = const char*;
|
||||
#endif
|
||||
|
||||
color_type get_reset_color()
|
||||
{
|
||||
return COLOR(7, "\033[0m");
|
||||
}
|
||||
|
||||
color_type get_color_type(const color c)
|
||||
{
|
||||
using enum color;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case black: return COLOR(0x8, "\033[0;90m");
|
||||
case red: return COLOR(0xC, "\033[0;91m");
|
||||
case green: return COLOR(0xA, "\033[0;92m");
|
||||
case yellow: return COLOR(0xE, "\033[0;93m");
|
||||
case blue: return COLOR(0x9, "\033[0;94m");
|
||||
case cyan: return COLOR(0xB, "\033[0;96m");
|
||||
case pink: return COLOR(0xD, "\033[0;95m");
|
||||
case white: return COLOR(0xF, "\033[0;97m");
|
||||
case dark_gray: return COLOR(0x8, "\033[0;97m");
|
||||
case gray:
|
||||
default: return get_reset_color();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE get_console_handle()
|
||||
{
|
||||
return GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
#endif
|
||||
|
||||
void set_color(const color_type color)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
SetConsoleTextAttribute(get_console_handle(), color);
|
||||
#else
|
||||
printf("%s", color);
|
||||
#endif
|
||||
}
|
||||
|
||||
void reset_color()
|
||||
{
|
||||
(void)fflush(stdout);
|
||||
set_color(get_reset_color());
|
||||
(void)fflush(stdout);
|
||||
}
|
||||
|
||||
std::string_view format(va_list* ap, const char* message)
|
||||
{
|
||||
thread_local char buffer[0x1000];
|
||||
|
||||
#ifdef _WIN32
|
||||
const int count = _vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer), message, *ap);
|
||||
#else
|
||||
const int count = vsnprintf(buffer, sizeof(buffer), message, *ap);
|
||||
#endif
|
||||
|
||||
if (count < 0) return {};
|
||||
return {buffer, static_cast<size_t>(count)};
|
||||
}
|
||||
|
||||
void print_colored(const std::string_view& line, const color_type base_color)
|
||||
{
|
||||
const auto _ = utils::finally(&reset_color);
|
||||
set_color(base_color);
|
||||
(void)fwrite(line.data(), 1, line.size(), stdout);
|
||||
}
|
||||
}
|
||||
|
||||
void logger::print(const color c, const char* message, ...) const
|
||||
{
|
||||
if (this->disable_output_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, message);
|
||||
|
||||
const auto data = format(&ap, message);
|
||||
print_colored(data, get_color_type(c));
|
||||
|
||||
va_end(ap);
|
||||
}
|
||||
59
src/windows-emulator/logger.hpp
Normal file
59
src/windows-emulator/logger.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
enum class color
|
||||
{
|
||||
black,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
cyan,
|
||||
pink,
|
||||
white,
|
||||
gray,
|
||||
dark_gray,
|
||||
};
|
||||
|
||||
class logger
|
||||
{
|
||||
public:
|
||||
void print(color c, const char* message, ...) const;
|
||||
|
||||
template <typename... Args>
|
||||
void info(const char* message, Args... args)
|
||||
{
|
||||
this->print(color::cyan, message, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void warn(const char* message, Args... args)
|
||||
{
|
||||
this->print(color::yellow, message, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void error(const char* message, Args... args)
|
||||
{
|
||||
this->print(color::red, message, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void success(const char* message, Args... args)
|
||||
{
|
||||
this->print(color::green, message, args...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void log(const char* message, Args... args)
|
||||
{
|
||||
this->print(color::gray, message, args...);
|
||||
}
|
||||
|
||||
void disable_output(const bool value)
|
||||
{
|
||||
this->disable_output_ = value;
|
||||
}
|
||||
|
||||
private:
|
||||
bool disable_output_{false};
|
||||
};
|
||||
71
src/windows-emulator/memory_utils.hpp
Normal file
71
src/windows-emulator/memory_utils.hpp
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <emulator.hpp>
|
||||
|
||||
inline std::string get_permission_string(const memory_permission permission)
|
||||
{
|
||||
const bool has_exec = (permission & memory_permission::exec) != memory_permission::none;
|
||||
const bool has_read = (permission & memory_permission::read) != memory_permission::none;
|
||||
const bool has_write = (permission & memory_permission::write) != memory_permission::none;
|
||||
|
||||
std::string res = {};
|
||||
res.reserve(3);
|
||||
|
||||
res.push_back(has_read ? 'r' : '-');
|
||||
res.push_back(has_write ? 'w' : '-');
|
||||
res.push_back(has_exec ? 'x' : '-');
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
inline memory_permission map_nt_to_emulator_protection(const uint32_t nt_protection)
|
||||
{
|
||||
switch (nt_protection)
|
||||
{
|
||||
case PAGE_NOACCESS:
|
||||
return memory_permission::none;
|
||||
case PAGE_READONLY:
|
||||
return memory_permission::read;
|
||||
case PAGE_READWRITE:
|
||||
case PAGE_WRITECOPY:
|
||||
return memory_permission::read | memory_permission::write;
|
||||
case PAGE_EXECUTE:
|
||||
case PAGE_EXECUTE_READ:
|
||||
return memory_permission::read | memory_permission::exec;
|
||||
case PAGE_EXECUTE_READWRITE:
|
||||
case PAGE_EXECUTE_WRITECOPY:
|
||||
default:
|
||||
throw std::runtime_error("Failed to map protection");
|
||||
//return memory_permission::all;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t map_emulator_to_nt_protection(const memory_permission permission)
|
||||
{
|
||||
const bool has_exec = (permission & memory_permission::exec) != memory_permission::none;
|
||||
const bool has_read = (permission & memory_permission::read) != memory_permission::none;
|
||||
const bool has_write = (permission & memory_permission::write) != memory_permission::none;
|
||||
|
||||
if (!has_read)
|
||||
{
|
||||
return PAGE_NOACCESS;
|
||||
}
|
||||
|
||||
if (has_exec && has_write)
|
||||
{
|
||||
return PAGE_EXECUTE_READWRITE;
|
||||
}
|
||||
|
||||
if (has_exec)
|
||||
{
|
||||
return PAGE_EXECUTE_READ;
|
||||
}
|
||||
|
||||
if (has_write)
|
||||
{
|
||||
return PAGE_READWRITE;
|
||||
}
|
||||
|
||||
return PAGE_READONLY;
|
||||
}
|
||||
30
src/windows-emulator/module/mapped_module.hpp
Normal file
30
src/windows-emulator/module/mapped_module.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
struct exported_symbol
|
||||
{
|
||||
std::string name{};
|
||||
uint64_t ordinal{};
|
||||
uint64_t rva{};
|
||||
uint64_t address{};
|
||||
};
|
||||
|
||||
using exported_symbols = std::vector<exported_symbol>;
|
||||
using address_name_mapping = std::unordered_map<uint64_t, std::string>;
|
||||
|
||||
struct mapped_module
|
||||
{
|
||||
std::string name{};
|
||||
std::filesystem::path path{};
|
||||
|
||||
uint64_t image_base{};
|
||||
uint64_t size_of_image{};
|
||||
uint64_t entry_point{};
|
||||
|
||||
exported_symbols exports{};
|
||||
address_name_mapping address_names{};
|
||||
|
||||
bool is_within(const uint64_t address) const
|
||||
{
|
||||
return address >= this->image_base && address < (this->image_base + this->size_of_image);
|
||||
}
|
||||
};
|
||||
84
src/windows-emulator/module/module_manager.cpp
Normal file
84
src/windows-emulator/module/module_manager.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
#include "../std_include.hpp"
|
||||
#include "module_manager.hpp"
|
||||
#include "module_mapping.hpp"
|
||||
|
||||
static void serialize(utils::buffer_serializer& buffer, const exported_symbol& sym)
|
||||
{
|
||||
buffer.write(sym.name);
|
||||
buffer.write(sym.ordinal);
|
||||
buffer.write(sym.rva);
|
||||
buffer.write(sym.address);
|
||||
}
|
||||
|
||||
static void deserialize(utils::buffer_deserializer& buffer, exported_symbol& sym)
|
||||
{
|
||||
buffer.read(sym.name);
|
||||
buffer.read(sym.ordinal);
|
||||
buffer.read(sym.rva);
|
||||
buffer.read(sym.address);
|
||||
}
|
||||
|
||||
static void serialize(utils::buffer_serializer& buffer, const mapped_module& mod)
|
||||
{
|
||||
buffer.write_string(mod.name);
|
||||
buffer.write_string(mod.path.wstring());
|
||||
|
||||
buffer.write(mod.image_base);
|
||||
buffer.write(mod.size_of_image);
|
||||
buffer.write(mod.entry_point);
|
||||
|
||||
buffer.write_vector(mod.exports);
|
||||
buffer.write_map(mod.address_names);
|
||||
}
|
||||
|
||||
static void deserialize(utils::buffer_deserializer& buffer, mapped_module& mod)
|
||||
{
|
||||
mod.name = buffer.read_string();
|
||||
mod.path = buffer.read_string<wchar_t>();
|
||||
|
||||
buffer.read(mod.image_base);
|
||||
buffer.read(mod.size_of_image);
|
||||
buffer.read(mod.entry_point);
|
||||
|
||||
buffer.read_vector(mod.exports);
|
||||
buffer.read_map(mod.address_names);
|
||||
}
|
||||
|
||||
module_manager::module_manager(emulator& emu)
|
||||
: emu_(&emu)
|
||||
{
|
||||
}
|
||||
|
||||
mapped_module* module_manager::map_module(const std::filesystem::path& file)
|
||||
{
|
||||
for (auto& mod : this->modules_)
|
||||
{
|
||||
if (mod.second.path == file)
|
||||
{
|
||||
return &mod.second;
|
||||
}
|
||||
}
|
||||
|
||||
auto mod = map_module_from_file(*this->emu_, file);
|
||||
if (!mod)
|
||||
{
|
||||
printf("Failed to map %s\n", file.generic_string().c_str());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
printf("Mapped %s at 0x%llX\n", mod->path.generic_string().c_str(), mod->image_base);
|
||||
|
||||
const auto image_base = mod->image_base;
|
||||
const auto entry = this->modules_.try_emplace(image_base, std::move(*mod));
|
||||
return &entry.first->second;
|
||||
}
|
||||
|
||||
void module_manager::serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write_map(this->modules_);
|
||||
}
|
||||
|
||||
void module_manager::deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read_map(this->modules_);
|
||||
}
|
||||
59
src/windows-emulator/module/module_manager.hpp
Normal file
59
src/windows-emulator/module/module_manager.hpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
#include "mapped_module.hpp"
|
||||
#include <emulator.hpp>
|
||||
|
||||
class module_manager
|
||||
{
|
||||
public:
|
||||
module_manager(emulator& emu);
|
||||
|
||||
mapped_module* map_module(const std::filesystem::path& file);
|
||||
|
||||
mapped_module* find_by_address(const uint64_t address)
|
||||
{
|
||||
const auto entry = this->get_module(address);
|
||||
if (entry != this->modules_.end())
|
||||
{
|
||||
return &entry->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const char* find_name(const uint64_t address)
|
||||
{
|
||||
const auto* mod = this->find_by_address(address);
|
||||
if (!mod)
|
||||
{
|
||||
return "<N/A>";
|
||||
}
|
||||
|
||||
return mod->name.c_str();
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const;
|
||||
void deserialize(utils::buffer_deserializer& buffer);
|
||||
|
||||
private:
|
||||
emulator* emu_{};
|
||||
|
||||
using module_map = std::map<uint64_t, mapped_module>;
|
||||
module_map modules_{};
|
||||
|
||||
module_map::iterator get_module(const uint64_t address)
|
||||
{
|
||||
if (this->modules_.empty())
|
||||
{
|
||||
return this->modules_.end();
|
||||
}
|
||||
|
||||
auto upper_bound = this->modules_.upper_bound(address);
|
||||
if (upper_bound == this->modules_.begin())
|
||||
{
|
||||
return this->modules_.end();
|
||||
}
|
||||
|
||||
std::advance(upper_bound, -1);
|
||||
return upper_bound;
|
||||
}
|
||||
};
|
||||
257
src/windows-emulator/module/module_mapping.cpp
Normal file
257
src/windows-emulator/module/module_mapping.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
#include "../std_include.hpp"
|
||||
#include "module_mapping.hpp"
|
||||
#include <address_utils.hpp>
|
||||
|
||||
#include <utils/io.hpp>
|
||||
#include <utils/buffer_accessor.hpp>
|
||||
|
||||
namespace
|
||||
{
|
||||
uint64_t get_first_section_offset(const IMAGE_NT_HEADERS& nt_headers, const uint64_t nt_headers_offset)
|
||||
{
|
||||
const auto first_section_absolute = reinterpret_cast<uint64_t>(IMAGE_FIRST_SECTION(&nt_headers));
|
||||
const auto absolute_base = reinterpret_cast<uint64_t>(&nt_headers);
|
||||
return nt_headers_offset + (first_section_absolute - absolute_base);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> read_mapped_memory(emulator& emu, 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());
|
||||
|
||||
return memory;
|
||||
}
|
||||
|
||||
void collect_exports(mapped_module& binary, const utils::safe_buffer_accessor<const uint8_t> buffer,
|
||||
const IMAGE_OPTIONAL_HEADER& optional_header)
|
||||
{
|
||||
auto& export_directory_entry = optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
if (export_directory_entry.VirtualAddress == 0 || export_directory_entry.Size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto export_directory = buffer.as<IMAGE_EXPORT_DIRECTORY>(export_directory_entry.
|
||||
VirtualAddress).get();
|
||||
|
||||
//const auto function_count = export_directory->NumberOfFunctions;
|
||||
const auto names_count = export_directory.NumberOfNames;
|
||||
|
||||
const auto names = buffer.as<DWORD>(export_directory.AddressOfNames);
|
||||
const auto ordinals = buffer.as<WORD>(export_directory.AddressOfNameOrdinals);
|
||||
const auto functions = buffer.as<DWORD>(export_directory.AddressOfFunctions);
|
||||
|
||||
for (DWORD i = 0; i < names_count; i++)
|
||||
{
|
||||
exported_symbol symbol{};
|
||||
symbol.ordinal = ordinals.get(i);
|
||||
symbol.name = buffer.as_string(names.get(i));
|
||||
symbol.rva = functions.get(symbol.ordinal);
|
||||
symbol.address = binary.image_base + symbol.rva;
|
||||
|
||||
binary.exports.push_back(std::move(symbol));
|
||||
}
|
||||
|
||||
for (const auto& symbol : binary.exports)
|
||||
{
|
||||
binary.address_names.try_emplace(symbol.address, symbol.name);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires(std::is_integral_v<T>)
|
||||
void apply_relocation(const utils::safe_buffer_accessor<uint8_t> buffer, const uint64_t offset,
|
||||
const uint64_t delta)
|
||||
{
|
||||
const auto obj = buffer.as<T>(offset);
|
||||
const auto value = obj.get();
|
||||
const auto new_value = value + static_cast<T>(delta);
|
||||
obj.set(new_value);
|
||||
}
|
||||
|
||||
void apply_relocations(const mapped_module& binary, const utils::safe_buffer_accessor<uint8_t> buffer,
|
||||
const IMAGE_OPTIONAL_HEADER& optional_header)
|
||||
{
|
||||
const auto delta = binary.image_base - optional_header.ImageBase;
|
||||
if (delta == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto directory = &optional_header.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
if (directory->Size == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto relocation_offset = directory->VirtualAddress;
|
||||
const auto relocation_end = relocation_offset + directory->Size;
|
||||
|
||||
while (relocation_offset < relocation_end)
|
||||
{
|
||||
const auto relocation = buffer.as<IMAGE_BASE_RELOCATION>(relocation_offset).get();
|
||||
|
||||
if (relocation.VirtualAddress <= 0 || relocation.SizeOfBlock <= sizeof(IMAGE_BASE_RELOCATION))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
const auto data_size = relocation.SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION);
|
||||
const auto entry_count = data_size / sizeof(uint16_t);
|
||||
|
||||
const auto entries = buffer.as<uint16_t>(relocation_offset + sizeof(IMAGE_BASE_RELOCATION));
|
||||
|
||||
relocation_offset += relocation.SizeOfBlock;
|
||||
|
||||
for (size_t i = 0; i < entry_count; ++i)
|
||||
{
|
||||
const auto entry = entries.get(i);
|
||||
|
||||
const int type = entry >> 12;
|
||||
const int offset = entry & 0xfff;
|
||||
const auto total_offset = relocation.VirtualAddress + offset;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case IMAGE_REL_BASED_ABSOLUTE:
|
||||
break;
|
||||
|
||||
case IMAGE_REL_BASED_HIGHLOW:
|
||||
apply_relocation<DWORD>(buffer, total_offset, delta);
|
||||
break;
|
||||
|
||||
case IMAGE_REL_BASED_DIR64:
|
||||
apply_relocation<ULONGLONG>(buffer, total_offset, delta);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw std::runtime_error("Unknown relocation type: " + std::to_string(type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void map_sections(emulator& emu, const mapped_module& binary,
|
||||
const utils::safe_buffer_accessor<const uint8_t> buffer,
|
||||
const IMAGE_NT_HEADERS& 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>(first_section_offset);
|
||||
|
||||
for (size_t i = 0; i < nt_headers.FileHeader.NumberOfSections; ++i)
|
||||
{
|
||||
const auto section = sections.get(i);
|
||||
const auto target_ptr = binary.image_base + section.VirtualAddress;
|
||||
|
||||
if (section.SizeOfRawData > 0)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
auto permissions = memory_permission::none;
|
||||
|
||||
if (section.Characteristics & IMAGE_SCN_MEM_EXECUTE)
|
||||
{
|
||||
permissions |= memory_permission::exec;
|
||||
}
|
||||
|
||||
if (section.Characteristics & IMAGE_SCN_MEM_READ)
|
||||
{
|
||||
permissions |= memory_permission::read;
|
||||
}
|
||||
|
||||
if (section.Characteristics & IMAGE_SCN_MEM_WRITE)
|
||||
{
|
||||
permissions |= memory_permission::write;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<mapped_module> map_module(emulator& emu, const std::span<const uint8_t> data,
|
||||
std::filesystem::path file)
|
||||
{
|
||||
mapped_module binary{};
|
||||
binary.path = std::move(file);
|
||||
binary.name = binary.path.filename().string();
|
||||
|
||||
utils::safe_buffer_accessor buffer{data};
|
||||
|
||||
const auto dos_header = buffer.as<IMAGE_DOS_HEADER>(0).get();
|
||||
const auto nt_headers_offset = dos_header.e_lfanew;
|
||||
|
||||
const auto nt_headers = buffer.as<IMAGE_NT_HEADERS>(nt_headers_offset).get();
|
||||
auto& optional_header = nt_headers.OptionalHeader;
|
||||
|
||||
binary.image_base = optional_header.ImageBase;
|
||||
binary.size_of_image = optional_header.SizeOfImage; // TODO: Sanitize
|
||||
|
||||
if (!emu.allocate_memory(binary.image_base, binary.size_of_image, memory_permission::read))
|
||||
{
|
||||
binary.image_base = emu.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))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
map_sections(emu, binary, buffer, nt_headers, nt_headers_offset);
|
||||
|
||||
auto mapped_memory = read_mapped_memory(emu, 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());
|
||||
|
||||
return binary;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<mapped_module> map_module_from_data(emulator& emu, const std::span<const uint8_t> data,
|
||||
std::filesystem::path file)
|
||||
{
|
||||
try
|
||||
{
|
||||
return map_module(emu, data, std::move(file));
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<mapped_module> map_module_from_file(emulator& emu, std::filesystem::path file)
|
||||
{
|
||||
const auto data = utils::io::read_file(file);
|
||||
if (data.empty())
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
return map_module_from_data(emu, data, std::move(file));
|
||||
}
|
||||
|
||||
bool unmap_module(emulator& emu, const mapped_module& mod)
|
||||
{
|
||||
return emu.release_memory(mod.image_base, mod.size_of_image);
|
||||
}
|
||||
10
src/windows-emulator/module/module_mapping.hpp
Normal file
10
src/windows-emulator/module/module_mapping.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
#include "mapped_module.hpp"
|
||||
|
||||
std::optional<mapped_module> map_module_from_data(emulator& emu, std::span<const uint8_t> data,
|
||||
std::filesystem::path file);
|
||||
std::optional<mapped_module> map_module_from_file(emulator& emu, std::filesystem::path file);
|
||||
|
||||
bool unmap_module(emulator& emu, const mapped_module& mod);
|
||||
203
src/windows-emulator/process_context.hpp
Normal file
203
src/windows-emulator/process_context.hpp
Normal file
@@ -0,0 +1,203 @@
|
||||
#pragma once
|
||||
|
||||
#include "emulator_utils.hpp"
|
||||
#include "handles.hpp"
|
||||
|
||||
#include "module/module_manager.hpp"
|
||||
#include <utils/nt_handle.hpp>
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
|
||||
struct event
|
||||
{
|
||||
bool signaled{};
|
||||
EVENT_TYPE type{};
|
||||
std::wstring name{};
|
||||
uint32_t ref_count{0};
|
||||
|
||||
bool is_signaled()
|
||||
{
|
||||
const auto res = this->signaled;
|
||||
|
||||
if (this->type == SynchronizationEvent)
|
||||
{
|
||||
this->signaled = false;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->signaled);
|
||||
buffer.write(this->type);
|
||||
buffer.write(this->name);
|
||||
buffer.write(this->ref_count);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->signaled);
|
||||
buffer.read(this->type);
|
||||
buffer.read(this->name);
|
||||
buffer.read(this->ref_count);
|
||||
}
|
||||
|
||||
static bool deleter(event& e)
|
||||
{
|
||||
return --e.ref_count == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct file
|
||||
{
|
||||
utils::nt::handle<utils::nt::invalid_handle> handle{};
|
||||
std::wstring name{};
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->name);
|
||||
// TODO: Serialize handle
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->name);
|
||||
this->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
};
|
||||
|
||||
struct semaphore
|
||||
{
|
||||
std::wstring name{};
|
||||
volatile uint32_t current_count{};
|
||||
uint32_t max_count{};
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->name);
|
||||
buffer.write(this->current_count);
|
||||
buffer.write(this->max_count);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->name);
|
||||
buffer.read(this->current_count);
|
||||
buffer.read(this->max_count);
|
||||
}
|
||||
};
|
||||
|
||||
struct port
|
||||
{
|
||||
std::wstring name{};
|
||||
uint64_t view_base{};
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->name);
|
||||
buffer.write(this->view_base);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->name);
|
||||
buffer.read(this->view_base);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
struct process_context
|
||||
{
|
||||
process_context(x64_emulator& emu)
|
||||
: teb(emu)
|
||||
, peb(emu)
|
||||
, process_params(emu)
|
||||
, kusd(emu)
|
||||
, module_manager(emu)
|
||||
, gs_segment(emu)
|
||||
{
|
||||
}
|
||||
|
||||
uint64_t executed_instructions{0};
|
||||
uint64_t current_ip{0};
|
||||
uint64_t previous_ip{0};
|
||||
|
||||
emulator_object<TEB> teb;
|
||||
emulator_object<PEB> peb;
|
||||
emulator_object<RTL_USER_PROCESS_PARAMETERS> process_params;
|
||||
emulator_object<KUSER_SHARED_DATA> kusd;
|
||||
|
||||
module_manager module_manager;
|
||||
|
||||
mapped_module* executable{};
|
||||
mapped_module* ntdll{};
|
||||
mapped_module* win32u{};
|
||||
|
||||
uint64_t ki_user_exception_dispatcher{};
|
||||
|
||||
uint64_t shared_section_size{};
|
||||
|
||||
handle_store<handle_types::event, event> events{};
|
||||
handle_store<handle_types::file, file> files{};
|
||||
handle_store<handle_types::semaphore, semaphore> semaphores{};
|
||||
handle_store<handle_types::port, port> ports{};
|
||||
std::map<uint16_t, std::wstring> atoms{};
|
||||
emulator_allocator gs_segment;
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
buffer.write(this->executed_instructions);
|
||||
buffer.write(this->current_ip);
|
||||
buffer.write(this->previous_ip);
|
||||
buffer.write(this->teb);
|
||||
buffer.write(this->peb);
|
||||
buffer.write(this->process_params);
|
||||
buffer.write(this->kusd);
|
||||
buffer.write(this->module_manager);
|
||||
|
||||
buffer.write(this->executable->image_base);
|
||||
buffer.write(this->ntdll->image_base);
|
||||
buffer.write(this->win32u->image_base);
|
||||
|
||||
buffer.write(this->ki_user_exception_dispatcher);
|
||||
|
||||
buffer.write(this->shared_section_size);
|
||||
buffer.write(this->events);
|
||||
buffer.write(this->files);
|
||||
buffer.write(this->semaphores);
|
||||
buffer.write(this->ports);
|
||||
buffer.write_map(this->atoms);
|
||||
buffer.write(this->gs_segment);
|
||||
}
|
||||
|
||||
void deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
buffer.read(this->executed_instructions);
|
||||
buffer.read(this->current_ip);
|
||||
buffer.read(this->previous_ip);
|
||||
buffer.read(this->teb);
|
||||
buffer.read(this->peb);
|
||||
buffer.read(this->process_params);
|
||||
buffer.read(this->kusd);
|
||||
buffer.read(this->module_manager);
|
||||
|
||||
const auto executable_base = buffer.read<uint64_t>();
|
||||
const auto ntdll_base = buffer.read<uint64_t>();
|
||||
const auto win32u_base = buffer.read<uint64_t>();
|
||||
|
||||
this->executable = this->module_manager.find_by_address(executable_base);
|
||||
this->ntdll = this->module_manager.find_by_address(ntdll_base);
|
||||
this->win32u = this->module_manager.find_by_address(win32u_base);
|
||||
|
||||
buffer.read(this->ki_user_exception_dispatcher);
|
||||
|
||||
buffer.read(this->shared_section_size);
|
||||
buffer.read(this->events);
|
||||
buffer.read(this->files);
|
||||
buffer.read(this->semaphores);
|
||||
buffer.read(this->ports);
|
||||
buffer.read_map(this->atoms);
|
||||
buffer.read(this->gs_segment);
|
||||
}
|
||||
};
|
||||
79
src/windows-emulator/std_include.hpp
Normal file
79
src/windows-emulator/std_include.hpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable: 4005)
|
||||
#pragma warning(disable: 4127)
|
||||
#pragma warning(disable: 4201)
|
||||
#pragma warning(disable: 4244)
|
||||
#pragma warning(disable: 4245)
|
||||
#pragma warning(disable: 4324)
|
||||
#pragma warning(disable: 4458)
|
||||
#pragma warning(disable: 4471)
|
||||
#pragma warning(disable: 4505)
|
||||
#pragma warning(disable: 4702)
|
||||
#pragma warning(disable: 4996)
|
||||
#pragma warning(disable: 5054)
|
||||
#pragma warning(disable: 6011)
|
||||
#pragma warning(disable: 6297)
|
||||
#pragma warning(disable: 6385)
|
||||
#pragma warning(disable: 6386)
|
||||
#pragma warning(disable: 6387)
|
||||
#pragma warning(disable: 26110)
|
||||
#pragma warning(disable: 26451)
|
||||
#pragma warning(disable: 26444)
|
||||
#pragma warning(disable: 26451)
|
||||
#pragma warning(disable: 26489)
|
||||
#pragma warning(disable: 26495)
|
||||
#pragma warning(disable: 26498)
|
||||
#pragma warning(disable: 26812)
|
||||
#pragma warning(disable: 28020)
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define NOMINMAX
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <list>
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <ranges>
|
||||
#include <atomic>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <cassert>
|
||||
|
||||
#define NTDDI_WIN11_GE 0
|
||||
#define PHNT_VERSION PHNT_WIN11
|
||||
#include <phnt_windows.h>
|
||||
#include <phnt.h>
|
||||
#include <ntgdi.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using namespace std::literals;
|
||||
2015
src/windows-emulator/syscalls.cpp
Normal file
2015
src/windows-emulator/syscalls.cpp
Normal file
File diff suppressed because it is too large
Load Diff
38
src/windows-emulator/syscalls.hpp
Normal file
38
src/windows-emulator/syscalls.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "process_context.hpp"
|
||||
|
||||
struct syscall_context;
|
||||
using syscall_handler = void(*)(const syscall_context& c);
|
||||
|
||||
struct syscall_handler_entry
|
||||
{
|
||||
syscall_handler handler{};
|
||||
std::string name{};
|
||||
};
|
||||
|
||||
class windows_emulator;
|
||||
|
||||
class syscall_dispatcher
|
||||
{
|
||||
public:
|
||||
syscall_dispatcher() = default;
|
||||
syscall_dispatcher(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports);
|
||||
|
||||
void dispatch(windows_emulator& win_emu);
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const;
|
||||
void deserialize(utils::buffer_deserializer& buffer);
|
||||
|
||||
void setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports);
|
||||
|
||||
std::string get_syscall_name(const uint64_t id)
|
||||
{
|
||||
return this->handlers_.at(id).name;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unordered_map<uint64_t, syscall_handler_entry> handlers_{};
|
||||
|
||||
void add_handlers();
|
||||
};
|
||||
690
src/windows-emulator/windows_emulator.cpp
Normal file
690
src/windows-emulator/windows_emulator.cpp
Normal file
@@ -0,0 +1,690 @@
|
||||
#include "std_include.hpp"
|
||||
#include "windows_emulator.hpp"
|
||||
#include "context_frame.hpp"
|
||||
|
||||
#include <unicorn_x64_emulator.hpp>
|
||||
|
||||
#define GS_SEGMENT_ADDR 0x6000000ULL
|
||||
#define GS_SEGMENT_SIZE (20 << 20) // 20 MB
|
||||
|
||||
#define IA32_GS_BASE_MSR 0xC0000101
|
||||
|
||||
#define STACK_SIZE 0x40000
|
||||
#define STACK_ADDRESS (0x80000000000 - STACK_SIZE)
|
||||
#define KUSD_ADDRESS 0x7ffe0000
|
||||
|
||||
#define GDT_ADDR 0x30000
|
||||
#define GDT_LIMIT 0x1000
|
||||
#define GDT_ENTRY_SIZE 0x8
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename T>
|
||||
emulator_object<T> allocate_object_on_stack(x64_emulator& emu)
|
||||
{
|
||||
const auto old_sp = emu.reg(x64_register::rsp);
|
||||
const auto new_sp = align_down(old_sp - sizeof(CONTEXT),
|
||||
std::max(alignof(CONTEXT), alignof(x64_emulator::pointer_type)));
|
||||
emu.reg(x64_register::rsp, new_sp);
|
||||
|
||||
return {emu, new_sp};
|
||||
}
|
||||
|
||||
void unalign_stack(x64_emulator& emu)
|
||||
{
|
||||
auto sp = emu.reg(x64_register::rsp);
|
||||
sp = align_down(sp - 0x10, 0x10) + 8;
|
||||
emu.reg(x64_register::rsp, sp);
|
||||
}
|
||||
|
||||
void setup_stack(x64_emulator& emu, const uint64_t stack_base, const size_t stack_size)
|
||||
{
|
||||
emu.allocate_memory(stack_base, stack_size, memory_permission::read_write);
|
||||
|
||||
const uint64_t stack_end = stack_base + stack_size;
|
||||
emu.reg(x64_register::rsp, stack_end);
|
||||
}
|
||||
|
||||
emulator_allocator setup_gs_segment(x64_emulator& emu, const uint64_t segment_base, const uint64_t size)
|
||||
{
|
||||
struct msr_value
|
||||
{
|
||||
uint32_t id;
|
||||
uint64_t value;
|
||||
};
|
||||
|
||||
const msr_value value{
|
||||
IA32_GS_BASE_MSR,
|
||||
segment_base
|
||||
};
|
||||
|
||||
emu.write_register(x64_register::msr, &value, sizeof(value));
|
||||
emu.allocate_memory(segment_base, size, memory_permission::read_write);
|
||||
|
||||
return {emu, segment_base, size};
|
||||
}
|
||||
|
||||
emulator_object<KUSER_SHARED_DATA> setup_kusd(x64_emulator& emu)
|
||||
{
|
||||
emu.allocate_memory(KUSD_ADDRESS, page_align_up(sizeof(KUSER_SHARED_DATA)), memory_permission::read);
|
||||
|
||||
const emulator_object<KUSER_SHARED_DATA> kusd_object{emu, KUSD_ADDRESS};
|
||||
kusd_object.access([](KUSER_SHARED_DATA& kusd)
|
||||
{
|
||||
const auto& real_kusd = *reinterpret_cast<KUSER_SHARED_DATA*>(KUSD_ADDRESS);
|
||||
|
||||
memcpy(&kusd, &real_kusd, sizeof(kusd));
|
||||
|
||||
kusd.ImageNumberLow = IMAGE_FILE_MACHINE_I386;
|
||||
kusd.ImageNumberHigh = IMAGE_FILE_MACHINE_AMD64;
|
||||
|
||||
memset(&kusd.ProcessorFeatures, 0, sizeof(kusd.ProcessorFeatures));
|
||||
|
||||
// ...
|
||||
});
|
||||
|
||||
return kusd_object;
|
||||
}
|
||||
|
||||
uint64_t copy_string(x64_emulator& emu, emulator_allocator& allocator, const void* base_ptr, const uint64_t offset,
|
||||
const size_t length)
|
||||
{
|
||||
if (!length)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto length_to_allocate = length + 2;
|
||||
const auto str_obj = allocator.reserve(length_to_allocate);
|
||||
emu.write_memory(str_obj, static_cast<const uint8_t*>(base_ptr) + offset, length);
|
||||
|
||||
return str_obj;
|
||||
}
|
||||
|
||||
ULONG copy_string_as_relative(x64_emulator& emu, emulator_allocator& allocator, const uint64_t result_base,
|
||||
const void* base_ptr, const uint64_t offset,
|
||||
const size_t length)
|
||||
{
|
||||
const auto address = copy_string(emu, allocator, base_ptr, offset, length);
|
||||
if (!address)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(address > result_base);
|
||||
return static_cast<ULONG>(address - result_base);
|
||||
}
|
||||
|
||||
emulator_object<API_SET_NAMESPACE> clone_api_set_map(x64_emulator& emu, emulator_allocator& allocator,
|
||||
const API_SET_NAMESPACE& orig_api_set_map)
|
||||
{
|
||||
const auto api_set_map_obj = allocator.reserve<API_SET_NAMESPACE>();
|
||||
const auto ns_entries_obj = allocator.reserve<API_SET_NAMESPACE_ENTRY>(orig_api_set_map.Count);
|
||||
const auto hash_entries_obj = allocator.reserve<API_SET_HASH_ENTRY>(orig_api_set_map.Count);
|
||||
|
||||
api_set_map_obj.access([&](API_SET_NAMESPACE& api_set)
|
||||
{
|
||||
api_set = orig_api_set_map;
|
||||
api_set.EntryOffset = static_cast<ULONG>(ns_entries_obj.value() - api_set_map_obj.value());
|
||||
api_set.HashOffset = static_cast<ULONG>(hash_entries_obj.value() - api_set_map_obj.value());
|
||||
});
|
||||
|
||||
const auto orig_ns_entries = offset_pointer<API_SET_NAMESPACE_ENTRY>(&orig_api_set_map,
|
||||
orig_api_set_map.EntryOffset);
|
||||
const auto orig_hash_entries = offset_pointer<API_SET_HASH_ENTRY>(&orig_api_set_map,
|
||||
orig_api_set_map.HashOffset);
|
||||
|
||||
for (ULONG i = 0; i < orig_api_set_map.Count; ++i)
|
||||
{
|
||||
auto ns_entry = orig_ns_entries[i];
|
||||
const auto hash_entry = orig_hash_entries[i];
|
||||
|
||||
ns_entry.NameOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(), &orig_api_set_map,
|
||||
ns_entry.NameOffset, ns_entry.NameLength);
|
||||
|
||||
if (!ns_entry.ValueCount)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto values_obj = allocator.reserve<API_SET_VALUE_ENTRY>(ns_entry.ValueCount);
|
||||
const auto orig_values = offset_pointer<API_SET_VALUE_ENTRY>(&orig_api_set_map,
|
||||
ns_entry.ValueOffset);
|
||||
|
||||
ns_entry.ValueOffset = static_cast<ULONG>(values_obj.value() - api_set_map_obj.value());
|
||||
|
||||
for (ULONG j = 0; j < ns_entry.ValueCount; ++j)
|
||||
{
|
||||
auto value = orig_values[j];
|
||||
|
||||
value.ValueOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(), &orig_api_set_map,
|
||||
value.ValueOffset, value.ValueLength);
|
||||
|
||||
if (value.NameLength)
|
||||
{
|
||||
value.NameOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(),
|
||||
&orig_api_set_map,
|
||||
value.NameOffset, value.NameLength);
|
||||
}
|
||||
|
||||
values_obj.write(value, j);
|
||||
}
|
||||
|
||||
ns_entries_obj.write(ns_entry, i);
|
||||
hash_entries_obj.write(hash_entry, i);
|
||||
}
|
||||
|
||||
//watch_object(emu, api_set_map_obj);
|
||||
|
||||
return api_set_map_obj;
|
||||
}
|
||||
|
||||
emulator_object<API_SET_NAMESPACE> build_api_set_map(x64_emulator& emu, emulator_allocator& allocator)
|
||||
{
|
||||
const auto& orig_api_set_map = *NtCurrentTeb()->ProcessEnvironmentBlock->ApiSetMap;
|
||||
return clone_api_set_map(emu, allocator, orig_api_set_map);
|
||||
}
|
||||
|
||||
emulator_allocator create_allocator(emulator& emu, const size_t size)
|
||||
{
|
||||
const auto base = emu.find_free_allocation_base(size);
|
||||
emu.allocate_memory(base, size, memory_permission::read_write);
|
||||
|
||||
return emulator_allocator{emu, base, size};
|
||||
}
|
||||
|
||||
void setup_gdt(x64_emulator& emu)
|
||||
{
|
||||
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);
|
||||
|
||||
emu.write_memory<uint64_t>(GDT_ADDR + 6 * (sizeof(uint64_t)), 0xEFFE000000FFFF);
|
||||
emu.reg<uint16_t>(x64_register::cs, 0x33);
|
||||
|
||||
emu.write_memory<uint64_t>(GDT_ADDR + 5 * (sizeof(uint64_t)), 0xEFF6000000FFFF);
|
||||
emu.reg<uint16_t>(x64_register::ss, 0x2B);
|
||||
}
|
||||
|
||||
void setup_context(process_context& context, x64_emulator& emu, const std::filesystem::path& file,
|
||||
const std::vector<std::wstring>& arguments)
|
||||
{
|
||||
setup_stack(emu, STACK_ADDRESS, STACK_SIZE);
|
||||
setup_gdt(emu);
|
||||
|
||||
context.kusd = setup_kusd(emu);
|
||||
context.gs_segment = setup_gs_segment(emu, GS_SEGMENT_ADDR, GS_SEGMENT_SIZE);
|
||||
|
||||
auto allocator = create_allocator(emu, 1 << 20);
|
||||
|
||||
auto& gs = context.gs_segment;
|
||||
|
||||
context.teb = gs.reserve<TEB>();
|
||||
context.peb = gs.reserve<PEB>();
|
||||
|
||||
context.teb.access([&](TEB& teb)
|
||||
{
|
||||
teb.ClientId.UniqueProcess = reinterpret_cast<HANDLE>(1);
|
||||
teb.ClientId.UniqueThread = reinterpret_cast<HANDLE>(2);
|
||||
teb.NtTib.StackLimit = reinterpret_cast<void*>(STACK_ADDRESS);
|
||||
teb.NtTib.StackBase = reinterpret_cast<void*>((STACK_ADDRESS + STACK_SIZE));
|
||||
teb.NtTib.Self = &context.teb.ptr()->NtTib;
|
||||
teb.ProcessEnvironmentBlock = context.peb.ptr();
|
||||
});
|
||||
|
||||
/* Values of the following fields must be
|
||||
* allocated relative to the process_params themselves.
|
||||
* and included in the length:
|
||||
*
|
||||
* CurrentDirectory
|
||||
* DllPath
|
||||
* ImagePathName
|
||||
* CommandLine
|
||||
* WindowTitle
|
||||
* DesktopInfo
|
||||
* ShellInfo
|
||||
* RuntimeData
|
||||
* RedirectionDllName
|
||||
*/
|
||||
|
||||
context.process_params = gs.reserve<RTL_USER_PROCESS_PARAMETERS>();
|
||||
|
||||
context.process_params.access([&](RTL_USER_PROCESS_PARAMETERS& proc_params)
|
||||
{
|
||||
proc_params.Flags = 0x6001; //| 0x80000000; // Prevent CsrClientConnectToServer
|
||||
|
||||
proc_params.ConsoleHandle = CONSOLE_HANDLE.h;
|
||||
proc_params.StandardOutput = STDOUT_HANDLE.h;
|
||||
proc_params.StandardInput = STDIN_HANDLE.h;
|
||||
proc_params.StandardError = proc_params.StandardOutput;
|
||||
|
||||
|
||||
std::wstring command_line = L"\"" + file.wstring() + L"\"";
|
||||
|
||||
for (const auto& arg : arguments)
|
||||
{
|
||||
command_line.push_back(L' ');
|
||||
command_line.append(arg);
|
||||
}
|
||||
|
||||
gs.make_unicode_string(proc_params.CommandLine, command_line);
|
||||
//gs.make_unicode_string(proc_params.CurrentDirectory.DosPath, file.parent_path().wstring());
|
||||
gs.make_unicode_string(proc_params.ImagePathName, file.wstring());
|
||||
|
||||
const auto total_length = gs.get_next_address() - context.process_params.value();
|
||||
|
||||
proc_params.Length = static_cast<uint32_t>(std::max(sizeof(proc_params), total_length));
|
||||
proc_params.MaximumLength = proc_params.Length;
|
||||
});
|
||||
|
||||
context.peb.access([&](PEB& peb)
|
||||
{
|
||||
peb.ImageBaseAddress = nullptr;
|
||||
peb.ProcessParameters = context.process_params.ptr();
|
||||
peb.ApiSetMap = build_api_set_map(emu, allocator).ptr();
|
||||
|
||||
peb.ProcessHeap = nullptr;
|
||||
peb.ProcessHeaps = nullptr;
|
||||
peb.HeapSegmentReserve = 0x0000000000100000; // TODO: Read from executable
|
||||
peb.HeapSegmentCommit = 0x0000000000002000;
|
||||
peb.HeapDeCommitTotalFreeThreshold = 0x0000000000010000;
|
||||
peb.HeapDeCommitFreeBlockThreshold = 0x0000000000001000;
|
||||
peb.NumberOfHeaps = 0x00000000;
|
||||
peb.MaximumNumberOfHeaps = 0x00000010;
|
||||
});
|
||||
}
|
||||
|
||||
uint64_t find_exported_function(const std::vector<exported_symbol>& exports, const std::string_view name)
|
||||
{
|
||||
for (auto& symbol : exports)
|
||||
{
|
||||
if (symbol.name == name)
|
||||
{
|
||||
return symbol.address;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
using exception_record_map = std::unordered_map<const EXCEPTION_RECORD*, emulator_object<EXCEPTION_RECORD>>;
|
||||
|
||||
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<EXCEPTION_RECORD>();
|
||||
record_obj.write(record);
|
||||
|
||||
if (record.ExceptionRecord)
|
||||
{
|
||||
record_mapping[&record] = record_obj;
|
||||
|
||||
emulator_object<EXCEPTION_RECORD> nested_record_obj{};
|
||||
const auto nested_record = record_mapping.find(record.ExceptionRecord);
|
||||
|
||||
if (nested_record != record_mapping.end())
|
||||
{
|
||||
nested_record_obj = nested_record->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
nested_record_obj = save_exception_record(allocator, *record.ExceptionRecord,
|
||||
record_mapping);
|
||||
}
|
||||
|
||||
record_obj.access([&](EXCEPTION_RECORD& r)
|
||||
{
|
||||
r.ExceptionRecord = nested_record_obj.ptr();
|
||||
});
|
||||
}
|
||||
|
||||
return record_obj;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
uint32_t map_violation_operation_to_parameter(const memory_operation operation)
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
default:
|
||||
case memory_operation::read:
|
||||
return 0;
|
||||
case memory_operation::write:
|
||||
return 1;
|
||||
case memory_operation::exec:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t calculate_exception_record_size(const EXCEPTION_RECORD& record)
|
||||
{
|
||||
std::unordered_set<const EXCEPTION_RECORD*> records{};
|
||||
size_t total_size = 0;
|
||||
|
||||
const EXCEPTION_RECORD* current_record = &record;
|
||||
while (current_record)
|
||||
{
|
||||
if (!records.insert(current_record).second)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
total_size += sizeof(*current_record);
|
||||
current_record = record.ExceptionRecord;
|
||||
}
|
||||
|
||||
return total_size;
|
||||
}
|
||||
|
||||
struct machine_frame
|
||||
{
|
||||
uint64_t rip;
|
||||
uint64_t cs;
|
||||
uint64_t eflags;
|
||||
uint64_t rsp;
|
||||
uint64_t ss;
|
||||
};
|
||||
|
||||
void dispatch_exception_pointers(x64_emulator& emu, const uint64_t dispatcher, const EXCEPTION_POINTERS pointers)
|
||||
{
|
||||
constexpr auto mach_frame_size = 0x40;
|
||||
constexpr auto context_record_size = 0x4F0;
|
||||
const auto exception_record_size = calculate_exception_record_size(*pointers.ExceptionRecord);
|
||||
const auto combined_size = align_up(exception_record_size + context_record_size, 0x10);
|
||||
|
||||
assert(combined_size == 0x590);
|
||||
|
||||
const auto allocation_size = combined_size + mach_frame_size;
|
||||
|
||||
const auto initial_sp = emu.reg(x64_register::rsp);
|
||||
const auto new_sp = align_down(initial_sp - allocation_size, 0x100);
|
||||
|
||||
const auto total_size = initial_sp - new_sp;
|
||||
assert(total_size >= allocation_size);
|
||||
|
||||
std::vector<uint8_t> zero_memory{};
|
||||
zero_memory.resize(total_size, 0);
|
||||
|
||||
emu.write_memory(new_sp, zero_memory.data(), zero_memory.size());
|
||||
|
||||
emu.reg(x64_register::rsp, new_sp);
|
||||
emu.reg(x64_register::rip, dispatcher);
|
||||
|
||||
const emulator_object<CONTEXT> context_record_obj{emu, new_sp};
|
||||
context_record_obj.write(*pointers.ContextRecord);
|
||||
|
||||
emulator_allocator allocator{emu, new_sp + context_record_size, exception_record_size};
|
||||
const auto exception_record_obj = save_exception_record(allocator, *pointers.ExceptionRecord);
|
||||
|
||||
if (exception_record_obj.value() != allocator.get_base())
|
||||
{
|
||||
throw std::runtime_error("Bad exception record position on stack");
|
||||
}
|
||||
|
||||
const emulator_object<machine_frame> machine_frame_obj{emu, new_sp + combined_size};
|
||||
machine_frame_obj.access([&](machine_frame& frame)
|
||||
{
|
||||
frame.rip = pointers.ContextRecord->Rip;
|
||||
frame.rsp = pointers.ContextRecord->Rsp;
|
||||
frame.ss = pointers.ContextRecord->SegSs;
|
||||
frame.cs = pointers.ContextRecord->SegCs;
|
||||
frame.eflags = pointers.ContextRecord->EFlags;
|
||||
});
|
||||
}
|
||||
|
||||
void dispatch_access_violation(x64_emulator& emu, const uint64_t dispatcher, const uint64_t address,
|
||||
const memory_operation operation)
|
||||
{
|
||||
CONTEXT ctx{};
|
||||
ctx.ContextFlags = CONTEXT_ALL;
|
||||
context_frame::save(emu, ctx);
|
||||
|
||||
EXCEPTION_RECORD record{};
|
||||
memset(&record, 0, sizeof(record));
|
||||
record.ExceptionCode = static_cast<DWORD>(STATUS_ACCESS_VIOLATION);
|
||||
record.ExceptionFlags = 0;
|
||||
record.ExceptionRecord = nullptr;
|
||||
record.ExceptionAddress = reinterpret_cast<void*>(emu.read_instruction_pointer());
|
||||
record.NumberParameters = 2;
|
||||
record.ExceptionInformation[0] = map_violation_operation_to_parameter(operation);
|
||||
record.ExceptionInformation[1] = address;
|
||||
|
||||
EXCEPTION_POINTERS pointers{};
|
||||
pointers.ContextRecord = &ctx;
|
||||
pointers.ExceptionRecord = &record;
|
||||
|
||||
dispatch_exception_pointers(emu, dispatcher, pointers);
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<x64_emulator> create_default_x64_emulator()
|
||||
{
|
||||
return unicorn::create_x64_emulator();
|
||||
}
|
||||
|
||||
windows_emulator::windows_emulator(const std::filesystem::path& application, const std::vector<std::wstring>& arguments,
|
||||
std::unique_ptr<x64_emulator> emu)
|
||||
: windows_emulator(std::move(emu))
|
||||
{
|
||||
this->setup_process(application, arguments);
|
||||
}
|
||||
|
||||
windows_emulator::windows_emulator(std::unique_ptr<x64_emulator> emu)
|
||||
: emu_(std::move(emu))
|
||||
, process_(*emu_)
|
||||
{
|
||||
this->setup_hooks();
|
||||
}
|
||||
|
||||
void windows_emulator::setup_process(const std::filesystem::path& application,
|
||||
const std::vector<std::wstring>& arguments)
|
||||
{
|
||||
auto& emu = this->emu();
|
||||
|
||||
auto& context = this->process();
|
||||
context.module_manager = module_manager(emu); // TODO: Cleanup module manager
|
||||
|
||||
setup_context(context, emu, application, arguments);
|
||||
|
||||
context.executable = context.module_manager.map_module(application);
|
||||
|
||||
context.peb.access([&](PEB& peb)
|
||||
{
|
||||
peb.ImageBaseAddress = reinterpret_cast<void*>(context.executable->image_base);
|
||||
});
|
||||
|
||||
context.ntdll = context.module_manager.map_module(R"(C:\Windows\System32\ntdll.dll)");
|
||||
context.win32u = context.module_manager.map_module(R"(C:\Windows\System32\win32u.dll)");
|
||||
|
||||
this->dispatcher_.setup(context.ntdll->exports, context.win32u->exports);
|
||||
|
||||
const auto ldr_initialize_thunk = find_exported_function(context.ntdll->exports, "LdrInitializeThunk");
|
||||
const auto rtl_user_thread_start = find_exported_function(context.ntdll->exports, "RtlUserThreadStart");
|
||||
context.ki_user_exception_dispatcher = find_exported_function(context.ntdll->exports, "KiUserExceptionDispatcher");
|
||||
|
||||
CONTEXT ctx{};
|
||||
ctx.ContextFlags = CONTEXT_ALL;
|
||||
|
||||
unalign_stack(emu);
|
||||
context_frame::save(emu, ctx);
|
||||
|
||||
ctx.Rip = rtl_user_thread_start;
|
||||
ctx.Rcx = context.executable->entry_point;
|
||||
|
||||
const auto ctx_obj = allocate_object_on_stack<CONTEXT>(emu);
|
||||
ctx_obj.write(ctx);
|
||||
|
||||
unalign_stack(emu);
|
||||
|
||||
emu.reg(x64_register::rcx, ctx_obj.value());
|
||||
emu.reg(x64_register::rdx, context.ntdll->image_base);
|
||||
emu.reg(x64_register::rip, ldr_initialize_thunk);
|
||||
}
|
||||
|
||||
void windows_emulator::setup_hooks()
|
||||
{
|
||||
this->emu().hook_instruction(x64_hookable_instructions::syscall, [&]
|
||||
{
|
||||
for (const auto& hook : this->syscall_hooks_)
|
||||
{
|
||||
if (hook() == instruction_hook_continuation::skip_instruction)
|
||||
{
|
||||
return instruction_hook_continuation::skip_instruction;
|
||||
}
|
||||
}
|
||||
|
||||
this->dispatcher_.dispatch(*this);
|
||||
return instruction_hook_continuation::skip_instruction;
|
||||
});
|
||||
|
||||
this->emu().hook_instruction(x64_hookable_instructions::invalid, [&]
|
||||
{
|
||||
const auto ip = this->emu().read_instruction_pointer();
|
||||
printf("Invalid instruction at: 0x%llX\n", ip);
|
||||
return instruction_hook_continuation::skip_instruction;
|
||||
});
|
||||
|
||||
this->emu().hook_interrupt([&](const int interrupt)
|
||||
{
|
||||
printf("Interrupt: %i 0x%llX\n", interrupt, this->emu().read_instruction_pointer());
|
||||
});
|
||||
|
||||
this->emu().hook_memory_violation([&](const uint64_t address, const size_t size, const memory_operation operation,
|
||||
const memory_violation_type type)
|
||||
{
|
||||
const auto permission = get_permission_string(operation);
|
||||
const auto ip = this->emu().read_instruction_pointer();
|
||||
const char* name = this->process().module_manager.find_name(ip);
|
||||
|
||||
if (type == memory_violation_type::protection)
|
||||
{
|
||||
printf("Protection violation: 0x%llX (%zX) - %s at 0x%llX (%s)\n", address, size, permission.c_str(), ip,
|
||||
name);
|
||||
}
|
||||
else if (type == memory_violation_type::unmapped)
|
||||
{
|
||||
printf("Mapping violation: 0x%llX (%zX) - %s at 0x%llX (%s)\n", address, size, permission.c_str(), ip,
|
||||
name);
|
||||
}
|
||||
|
||||
dispatch_access_violation(this->emu(), this->process().ki_user_exception_dispatcher, address, operation);
|
||||
return memory_violation_continuation::resume;
|
||||
});
|
||||
|
||||
this->emu().hook_memory_execution(0, std::numeric_limits<size_t>::max(),
|
||||
[&](const uint64_t address, const size_t, const uint64_t)
|
||||
{
|
||||
auto& process = this->process();
|
||||
|
||||
++process.executed_instructions;
|
||||
|
||||
process.previous_ip = process.current_ip;
|
||||
process.current_ip = this->emu().read_instruction_pointer();
|
||||
|
||||
const auto is_interesting_call = process.executable->is_within(
|
||||
process.previous_ip) || process.executable->is_within(address);
|
||||
|
||||
/*if (address == 0x180038B65)
|
||||
{
|
||||
puts("!!! DLL init failed");
|
||||
}
|
||||
if (address == 0x180038A20)
|
||||
{
|
||||
const auto* name = this->process().module_manager.find_name(
|
||||
this->emu().reg(x64_register::rcx));
|
||||
printf("!!! DLL init: %s\n", name);
|
||||
}*/
|
||||
|
||||
if (!this->verbose && !this->verbose_calls && !is_interesting_call)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto* binary = this->process().module_manager.find_by_address(address);
|
||||
|
||||
if (binary)
|
||||
{
|
||||
const auto export_entry = binary->address_names.find(address);
|
||||
if (export_entry != binary->address_names.end())
|
||||
{
|
||||
logger.print(is_interesting_call ? color::yellow : color::dark_gray,
|
||||
"Executing function: %s - %s (0x%llX)\n",
|
||||
binary->name.c_str(),
|
||||
export_entry->second.c_str(), address);
|
||||
}
|
||||
else if (address == binary->entry_point)
|
||||
{
|
||||
logger.print(is_interesting_call ? color::yellow : color::gray,
|
||||
"Executing entry point: %s (0x%llX)\n",
|
||||
binary->name.c_str(),
|
||||
address);
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->verbose)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto& emu = this->emu();
|
||||
|
||||
printf(
|
||||
"Inst: %16llX - RAX: %16llX - RBX: %16llX - RCX: %16llX - RDX: %16llX - R8: %16llX - R9: %16llX - RDI: %16llX - RSI: %16llX - %s\n",
|
||||
address,
|
||||
emu.reg(x64_register::rax), emu.reg(x64_register::rbx),
|
||||
emu.reg(x64_register::rcx),
|
||||
emu.reg(x64_register::rdx), emu.reg(x64_register::r8),
|
||||
emu.reg(x64_register::r9),
|
||||
emu.reg(x64_register::rdi), emu.reg(x64_register::rsi),
|
||||
binary ? binary->name.c_str() : "<N/A>");
|
||||
});
|
||||
}
|
||||
|
||||
void windows_emulator::serialize(utils::buffer_serializer& buffer) const
|
||||
{
|
||||
this->emu().serialize(buffer);
|
||||
this->process_.serialize(buffer);
|
||||
this->dispatcher_.serialize(buffer);
|
||||
}
|
||||
|
||||
void windows_emulator::deserialize(utils::buffer_deserializer& buffer)
|
||||
{
|
||||
this->emu().deserialize(buffer);
|
||||
this->process_.deserialize(buffer);
|
||||
this->dispatcher_.deserialize(buffer);
|
||||
}
|
||||
|
||||
void windows_emulator::save_snapshot()
|
||||
{
|
||||
this->emu().save_snapshot();
|
||||
|
||||
utils::buffer_serializer serializer{};
|
||||
this->process_.serialize(serializer);
|
||||
|
||||
this->process_snapshot_ = serializer.move_buffer();
|
||||
|
||||
// TODO: Make process copyable
|
||||
//this->process_snapshot_ = this->process();
|
||||
}
|
||||
|
||||
void windows_emulator::restore_snapshot()
|
||||
{
|
||||
if (this->process_snapshot_.empty())
|
||||
{
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
this->emu().restore_snapshot();
|
||||
|
||||
utils::buffer_deserializer deserializer{this->process_snapshot_};
|
||||
this->process_.deserialize(deserializer);
|
||||
//this->process_ = *this->process_snapshot_;
|
||||
}
|
||||
83
src/windows-emulator/windows_emulator.hpp
Normal file
83
src/windows-emulator/windows_emulator.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
#include <x64_emulator.hpp>
|
||||
|
||||
#include "syscalls.hpp"
|
||||
#include "process_context.hpp"
|
||||
#include "logger.hpp"
|
||||
|
||||
std::unique_ptr<x64_emulator> create_default_x64_emulator();
|
||||
|
||||
class windows_emulator
|
||||
{
|
||||
public:
|
||||
windows_emulator(std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
|
||||
windows_emulator(const std::filesystem::path& application, const std::vector<std::wstring>& arguments = {},
|
||||
std::unique_ptr<x64_emulator> emu = create_default_x64_emulator());
|
||||
|
||||
windows_emulator(windows_emulator&&) = delete;
|
||||
windows_emulator(const windows_emulator&) = delete;
|
||||
windows_emulator& operator=(windows_emulator&&) = delete;
|
||||
windows_emulator& operator=(const windows_emulator&) = delete;
|
||||
|
||||
~windows_emulator() = default;
|
||||
|
||||
x64_emulator& emu()
|
||||
{
|
||||
return *this->emu_;
|
||||
}
|
||||
|
||||
const x64_emulator& emu() const
|
||||
{
|
||||
return *this->emu_;
|
||||
}
|
||||
|
||||
process_context& process()
|
||||
{
|
||||
return this->process_;
|
||||
}
|
||||
|
||||
const process_context& process() const
|
||||
{
|
||||
return this->process_;
|
||||
}
|
||||
|
||||
syscall_dispatcher& dispatcher()
|
||||
{
|
||||
return this->dispatcher_;
|
||||
}
|
||||
|
||||
const syscall_dispatcher& dispatcher() const
|
||||
{
|
||||
return this->dispatcher_;
|
||||
}
|
||||
|
||||
void serialize(utils::buffer_serializer& buffer) const;
|
||||
void deserialize(utils::buffer_deserializer& buffer);
|
||||
|
||||
void save_snapshot();
|
||||
void restore_snapshot();
|
||||
|
||||
void add_syscall_hook(instruction_hook_callback callback)
|
||||
{
|
||||
this->syscall_hooks_.push_back(std::move(callback));
|
||||
}
|
||||
|
||||
logger logger{};
|
||||
bool verbose{false};
|
||||
bool verbose_calls{false};
|
||||
bool buffer_stdout{false};
|
||||
|
||||
private:
|
||||
std::unique_ptr<x64_emulator> emu_{};
|
||||
|
||||
std::vector<instruction_hook_callback> syscall_hooks_{};
|
||||
|
||||
process_context process_;
|
||||
syscall_dispatcher dispatcher_;
|
||||
|
||||
std::vector<std::byte> process_snapshot_{};
|
||||
//std::optional<process_context> process_snapshot_{};
|
||||
|
||||
void setup_hooks();
|
||||
void setup_process(const std::filesystem::path& application, const std::vector<std::wstring>& arguments);
|
||||
};
|
||||
Reference in New Issue
Block a user