Skip to content

Commit

Permalink
Cleanup syscall dispatcher
Browse files Browse the repository at this point in the history
  • Loading branch information
momo5502 committed Oct 21, 2024
1 parent 70eb6ba commit e58d5ab
Show file tree
Hide file tree
Showing 5 changed files with 345 additions and 334 deletions.
140 changes: 140 additions & 0 deletions src/windows-emulator/syscall_dispatcher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#include "syscall_dispatcher.hpp"
#include "syscall_utils.hpp"

static void serialize(utils::buffer_serializer& buffer, const syscall_handler_entry& obj)
{
buffer.write(obj.name);
}

static void deserialize(utils::buffer_deserializer& buffer, syscall_handler_entry& obj)
{
buffer.read(obj.name);
obj.handler = nullptr;
}

void syscall_dispatcher::serialize(utils::buffer_serializer& buffer) const
{
buffer.write_map(this->handlers_);
}

void syscall_dispatcher::deserialize(utils::buffer_deserializer& buffer)
{
buffer.read_map(this->handlers_);
this->add_handlers();
}


void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports)
{
this->handlers_ = {};

const auto ntdll_syscalls = find_syscalls(ntdll_exports);
const auto win32u_syscalls = find_syscalls(win32u_exports);

map_syscalls(this->handlers_, ntdll_syscalls, 0);
map_syscalls(this->handlers_, win32u_syscalls, 0x1000);

this->add_handlers();
}

void syscall_dispatcher::add_handlers()
{
std::unordered_map<std::string, syscall_handler> handler_mapping{};
handler_mapping.reserve(this->handlers_.size());

this->add_handlers(handler_mapping);

for (auto& entry : this->handlers_)
{
const auto handler = handler_mapping.find(entry.second.name);
if (handler == handler_mapping.end())
{
continue;
}

entry.second.handler = handler->second;

#ifndef NDEBUG
handler_mapping.erase(handler);
#endif
}

#ifndef NDEBUG
if (!handler_mapping.empty())
{
puts("Unmapped handlers:");
for (const auto& h : handler_mapping)
{
printf(" %s\n", h.first.c_str());
}
}
#endif
}

void syscall_dispatcher::dispatch(windows_emulator& win_emu)
{
auto& emu = win_emu.emu();
auto& context = win_emu.process();

const auto address = emu.read_instruction_pointer();
const auto syscall_id = emu.reg<uint32_t>(x64_register::eax);


const syscall_context c{win_emu, emu, context, true};

try
{
const auto entry = this->handlers_.find(syscall_id);
if (entry == this->handlers_.end())
{
printf("Unknown syscall: 0x%X\n", syscall_id);
c.emu.reg<uint64_t>(x64_register::rax, STATUS_NOT_SUPPORTED);
c.emu.stop();
return;
}

if (!entry->second.handler)
{
printf("Unimplemented syscall: %s - 0x%X\n", entry->second.name.c_str(), syscall_id);
c.emu.reg<uint64_t>(x64_register::rax, STATUS_NOT_SUPPORTED);
c.emu.stop();
return;
}

const auto* mod = context.module_manager.find_by_address(address);
if (mod != context.ntdll && mod != context.win32u)
{
win_emu.logger.print(color::blue, "Executing inline syscall: %s (0x%X) at 0x%llX (%s)\n",
entry->second.name.c_str(),
syscall_id,
address, mod ? mod->name.c_str() : "<N/A>");
}
else
{
win_emu.logger.print(color::dark_gray, "Executing syscall: %s (0x%X) at 0x%llX\n",
entry->second.name.c_str(),
syscall_id,
address);
}

entry->second.handler(c);
}
catch (std::exception& e)
{
printf("Syscall threw an exception: %X (0x%llX) - %s\n", syscall_id, address, e.what());
emu.reg<uint64_t>(x64_register::rax, STATUS_UNSUCCESSFUL);
emu.stop();
}
catch (...)
{
printf("Syscall threw an unknown exception: %X (0x%llX)\n", syscall_id, address);
emu.reg<uint64_t>(x64_register::rax, STATUS_UNSUCCESSFUL);
emu.stop();
}
}

syscall_dispatcher::syscall_dispatcher(const exported_symbols& ntdll_exports,
const exported_symbols& win32u_exports)
{
this->setup(ntdll_exports, win32u_exports);
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,6 @@ class syscall_dispatcher
private:
std::unordered_map<uint64_t, syscall_handler_entry> handlers_{};

void add_handlers(std::unordered_map<std::string, syscall_handler>& handler_mapping);
void add_handlers();
};
200 changes: 200 additions & 0 deletions src/windows-emulator/syscall_utils.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#pragma once

#include "windows_emulator.hpp"

struct syscall_context
{
windows_emulator& win_emu;
x64_emulator& emu;
process_context& proc;
mutable bool write_status{true};
mutable bool retrigger_syscall{false};
};

inline uint64_t get_syscall_argument(x64_emulator& emu, const size_t index)
{
switch (index)
{
case 0:
return emu.reg(x64_register::r10);
case 1:
return emu.reg(x64_register::rdx);
case 2:
return emu.reg(x64_register::r8);
case 3:
return emu.reg(x64_register::r9);
default:
return emu.read_stack(index + 1);
}
}

inline bool is_uppercase(const char character)
{
return toupper(character) == character;
}

inline bool is_syscall(const std::string_view name)
{
return name.starts_with("Nt") && name.size() > 3 && is_uppercase(name[2]);
}

inline std::vector<std::string> find_syscalls(const exported_symbols& exports)
{
// Makes use of the fact that order of Nt* function addresses
// is equal to the order of syscall IDs.
// So first Nt* function is the first syscall with ID 0

std::map<uint64_t, size_t> reference_count{};
std::map<uint64_t, std::string> ordered_syscalls{};

for (const auto& symbol : exports)
{
if (is_syscall(symbol.name))
{
++reference_count[symbol.address];
ordered_syscalls[symbol.address] = symbol.name;
}
}

std::vector<std::string> syscalls{};
syscalls.reserve(ordered_syscalls.size());

for (auto& syscall : ordered_syscalls)
{
if (reference_count[syscall.first] == 1)
{
syscalls.push_back(std::move(syscall.second));
}
}

return syscalls;
}

inline void map_syscalls(std::unordered_map<uint64_t, syscall_handler_entry>& handlers,
const std::vector<std::string>& syscalls, const uint64_t base_index)
{
for (size_t i = 0; i < syscalls.size(); ++i)
{
const auto& syscall = syscalls[i];

auto& entry = handlers[base_index + i];
entry.name = syscall;
entry.handler = nullptr;
}
}

template <typename T>
requires(std::is_integral_v<T> || std::is_enum_v<T>)
T resolve_argument(x64_emulator& emu, const size_t index)
{
const auto arg = get_syscall_argument(emu, index);
return static_cast<T>(arg);
}

template <typename T>
requires(std::is_same_v<T, emulator_object<typename T::value_type>>)
T resolve_argument(x64_emulator& emu, const size_t index)
{
const auto arg = get_syscall_argument(emu, index);
return T(emu, arg);
}

template <typename T>
T resolve_indexed_argument(x64_emulator& emu, size_t& index)
{
return resolve_argument<T>(emu, index++);
}

inline void write_status(const syscall_context& c, const NTSTATUS status, const uint64_t initial_ip)
{
if (c.write_status && !c.retrigger_syscall)
{
c.emu.reg<uint64_t>(x64_register::rax, static_cast<uint64_t>(status));
}

const auto new_ip = c.emu.read_instruction_pointer();
if (initial_ip != new_ip || c.retrigger_syscall)
{
c.emu.reg(x64_register::rip, new_ip - 2);
}
}

inline void forward_syscall(const syscall_context& c, NTSTATUS (*handler)())
{
const auto ip = c.emu.read_instruction_pointer();

const auto ret = handler();
write_status(c, ret, ip);
}

template <typename... Args>
void forward_syscall(const syscall_context& c, NTSTATUS (*handler)(const syscall_context&, Args...))
{
const auto ip = c.emu.read_instruction_pointer();

size_t index = 0;
std::tuple<const syscall_context&, Args...> func_args
{
c,
resolve_indexed_argument<std::remove_cv_t<std::remove_reference_t<Args>>>(c.emu, index)...
};

const auto ret = std::apply(handler, std::move(func_args));
write_status(c, ret, ip);
}

template <auto Handler>
syscall_handler make_syscall_handler()
{
return +[](const syscall_context& c)
{
forward_syscall(c, Handler);
};
}

template <typename T>
void write_attribute(emulator& emu, const PS_ATTRIBUTE& attribute, const T& value)
{
if (attribute.ReturnLength)
{
emulator_object<SIZE_T>{emu, attribute.ReturnLength}.write(sizeof(T));
}

if (attribute.Size >= sizeof(T))
{
emulator_object<T>{emu, attribute.Value}.write(value);
}
}

inline std::chrono::steady_clock::time_point convert_delay_interval_to_time_point(const LARGE_INTEGER delay_interval)
{
constexpr auto HUNDRED_NANOSECONDS_IN_ONE_SECOND = 10000000LL;
constexpr auto EPOCH_DIFFERENCE_1601_TO_1970_SECONDS = 11644473600LL;

if (delay_interval.QuadPart < 0)
{
const auto relative_time = -delay_interval.QuadPart;
const auto relative_ticks_in_ms = relative_time / 10;
const auto relative_fraction_ns = (relative_time % 10) * 100;
const auto relative_duration = std::chrono::microseconds(relative_ticks_in_ms) +
std::chrono::nanoseconds(relative_fraction_ns);

return std::chrono::steady_clock::now() + relative_duration;
}

const auto delay_seconds_since_1601 = delay_interval.QuadPart / HUNDRED_NANOSECONDS_IN_ONE_SECOND;
const auto delay_fraction_ns = (delay_interval.QuadPart % HUNDRED_NANOSECONDS_IN_ONE_SECOND) * 100;

const auto delay_seconds_since_1970 = delay_seconds_since_1601 - EPOCH_DIFFERENCE_1601_TO_1970_SECONDS;

const auto target_time =
std::chrono::system_clock::from_time_t(delay_seconds_since_1970) +
std::chrono::nanoseconds(delay_fraction_ns);

const auto now_system = std::chrono::system_clock::now();

const auto duration_until_target = std::chrono::duration_cast<
std::chrono::microseconds>(target_time - now_system);

return std::chrono::steady_clock::now() + duration_until_target;
}
Loading

0 comments on commit e58d5ab

Please sign in to comment.