-
-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
345 additions
and
334 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Oops, something went wrong.