diff --git a/src/common/platform/process.hpp b/src/common/platform/process.hpp index ecc9ce2f..cd512b9d 100644 --- a/src/common/platform/process.hpp +++ b/src/common/platform/process.hpp @@ -670,6 +670,20 @@ typedef struct DECLSPEC_ALIGN(16) _CONTEXT64 DWORD64 LastExceptionFromRip; } CONTEXT64, *PCONTEXT64; +typedef struct _CONTEXT_CHUNK +{ + LONG Offset; // Offset may be negative. + ULONG Length; +} CONTEXT_CHUNK, *PCONTEXT_CHUNK; + +typedef struct _CONTEXT_EX +{ + CONTEXT_CHUNK All; + CONTEXT_CHUNK Legacy; + CONTEXT_CHUNK XState; + CONTEXT_CHUNK KernelCet; +} CONTEXT_EX, *PCONTEXT_EX; + template struct EMU_EXCEPTION_RECORD { diff --git a/src/common/platform/status.hpp b/src/common/platform/status.hpp index 79d97a3b..7c2fd78e 100644 --- a/src/common/platform/status.hpp +++ b/src/common/platform/status.hpp @@ -6,6 +6,7 @@ using NTSTATUS = std::uint32_t; #ifndef OS_WINDOWS #define STATUS_WAIT_0 ((NTSTATUS)0x00000000L) +#define STATUS_USER_APC ((NTSTATUS)0x000000C0L) #define STATUS_TIMEOUT ((NTSTATUS)0x00000102L) #define STATUS_PENDING ((NTSTATUS)0x00000103L) diff --git a/src/common/platform/threading.hpp b/src/common/platform/threading.hpp index 96e61816..de430220 100644 --- a/src/common/platform/threading.hpp +++ b/src/common/platform/threading.hpp @@ -1,6 +1,6 @@ #pragma once -// NOLINTBEGIN(modernize-use-using) +// NOLINTBEGIN(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) typedef enum _THREADINFOCLASS { @@ -90,4 +90,32 @@ typedef struct _THREAD_TEB_INFORMATION ULONG BytesToRead; // Number of bytes to read. } THREAD_TEB_INFORMATION, *PTHREAD_TEB_INFORMATION; -// NOLINTEND(modernize-use-using) +typedef enum _KCONTINUE_TYPE +{ + KCONTINUE_UNWIND, + KCONTINUE_RESUME, + KCONTINUE_LONGJUMP, + KCONTINUE_SET, + KCONTINUE_LAST, +} KCONTINUE_TYPE; + +typedef struct _KCONTINUE_ARGUMENT +{ + KCONTINUE_TYPE ContinueType; + ULONG ContinueFlags; + ULONGLONG Reserved[2]; +} KCONTINUE_ARGUMENT, *PKCONTINUE_ARGUMENT; + +#define KCONTINUE_FLAG_TEST_ALERT 0x00000001 +#define KCONTINUE_FLAG_DELIVER_APC 0x00000002 + +#ifndef OS_WINDOWS +typedef enum _QUEUE_USER_APC_FLAGS +{ + QUEUE_USER_APC_FLAGS_NONE, + QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC, + QUEUE_USER_APC_CALLBACK_DATA_CONTEXT +} QUEUE_USER_APC_FLAGS; +#endif + +// NOLINTEND(modernize-use-using,cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) diff --git a/src/samples/test-sample/test.cpp b/src/samples/test-sample/test.cpp index 62254120..a3b03a34 100644 --- a/src/samples/test-sample/test.cpp +++ b/src/samples/test-sample/test.cpp @@ -431,6 +431,28 @@ namespace const auto epoch_time = std::chrono::system_clock::now().time_since_epoch(); printf("Time: %lld\n", std::chrono::duration_cast(epoch_time).count()); } + + bool test_apc() + { + int executions = 0; + + auto* apc_func = +[](ULONG_PTR param) { + *reinterpret_cast(param) += 1; // + }; + + QueueUserAPC(apc_func, GetCurrentThread(), reinterpret_cast(&executions)); + QueueUserAPC(apc_func, GetCurrentThread(), reinterpret_cast(&executions)); + + Sleep(1); + + if (executions != 0) + { + return false; + } + + SleepEx(1, TRUE); + return executions == 2; + } } #define RUN_TEST(func, name) \ @@ -461,8 +483,7 @@ int main(const int argc, const char* argv[]) RUN_TEST(test_native_exceptions, "Native Exceptions") RUN_TEST(test_tls, "TLS") RUN_TEST(test_socket, "Socket") - - Sleep(1); + RUN_TEST(test_apc, "APC") return valid ? 0 : 1; } diff --git a/src/windows-emulator/emulator_thread.hpp b/src/windows-emulator/emulator_thread.hpp index b0cc364b..eab80c8c 100644 --- a/src/windows-emulator/emulator_thread.hpp +++ b/src/windows-emulator/emulator_thread.hpp @@ -8,6 +8,33 @@ struct process_context; +struct pending_apc +{ + uint32_t flags{}; + uint64_t apc_routine{}; + uint64_t apc_argument1{}; + uint64_t apc_argument2{}; + uint64_t apc_argument3{}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->flags); + buffer.write(this->apc_routine); + buffer.write(this->apc_argument1); + buffer.write(this->apc_argument2); + buffer.write(this->apc_argument3); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->flags); + buffer.read(this->apc_routine); + buffer.read(this->apc_argument1); + buffer.read(this->apc_argument2); + buffer.read(this->apc_argument3); + } +}; + class emulator_thread : public ref_counted_object { public: @@ -57,6 +84,9 @@ class emulator_thread : public ref_counted_object uint32_t suspended{0}; std::optional await_time{}; + bool apc_alertable{false}; + std::vector pending_apcs{}; + std::optional pending_status{}; std::optional gs_segment; @@ -125,8 +155,11 @@ class emulator_thread : public ref_counted_object buffer.write(this->alerted); buffer.write(this->suspended); - buffer.write_optional(this->await_time); + + buffer.write(this->apc_alertable); + buffer.write_vector(this->pending_apcs); + buffer.write_optional(this->pending_status); buffer.write_optional(this->gs_segment); buffer.write_optional(this->teb); @@ -160,8 +193,11 @@ class emulator_thread : public ref_counted_object buffer.read(this->alerted); buffer.read(this->suspended); - buffer.read_optional(this->await_time); + + buffer.read(this->apc_alertable); + buffer.read_vector(this->pending_apcs); + buffer.read_optional(this->pending_status); buffer.read_optional(this->gs_segment, [this] { return emulator_allocator(*this->memory_ptr); }); buffer.read_optional(this->teb, [this] { return emulator_object(*this->memory_ptr); }); diff --git a/src/windows-emulator/emulator_utils.hpp b/src/windows-emulator/emulator_utils.hpp index 42a36cdb..b0e71ece 100644 --- a/src/windows-emulator/emulator_utils.hpp +++ b/src/windows-emulator/emulator_utils.hpp @@ -161,6 +161,11 @@ class emulator_object this->address_ = address; } + emulator_object shift(const int64_t offset) const + { + return emulator_object(*this->memory_, this->address_ + offset); + } + private: memory_interface* memory_{}; uint64_t address_{}; diff --git a/src/windows-emulator/process_context.cpp b/src/windows-emulator/process_context.cpp index f604eacd..bb720ec4 100644 --- a/src/windows-emulator/process_context.cpp +++ b/src/windows-emulator/process_context.cpp @@ -119,6 +119,7 @@ void process_context::setup(x64_emulator& emu, memory_manager& memory, const app this->ntdll_image_base = ntdll.image_base; this->ldr_initialize_thunk = ntdll.find_export("LdrInitializeThunk"); this->rtl_user_thread_start = ntdll.find_export("RtlUserThreadStart"); + this->ki_user_apc_dispatcher = ntdll.find_export("KiUserApcDispatcher"); this->ki_user_exception_dispatcher = ntdll.find_export("KiUserExceptionDispatcher"); this->default_register_set = emu.save_registers(); @@ -139,6 +140,7 @@ void process_context::serialize(utils::buffer_serializer& buffer) const buffer.write(this->ntdll_image_base); buffer.write(this->ldr_initialize_thunk); buffer.write(this->rtl_user_thread_start); + buffer.write(this->ki_user_apc_dispatcher); buffer.write(this->ki_user_exception_dispatcher); buffer.write(this->events); @@ -173,6 +175,7 @@ void process_context::deserialize(utils::buffer_deserializer& buffer) buffer.read(this->ntdll_image_base); buffer.read(this->ldr_initialize_thunk); buffer.read(this->rtl_user_thread_start); + buffer.read(this->ki_user_apc_dispatcher); buffer.read(this->ki_user_exception_dispatcher); buffer.read(this->events); diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index f01647b4..1be428b3 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -75,6 +75,7 @@ struct process_context uint64_t ntdll_image_base{}; uint64_t ldr_initialize_thunk{}; uint64_t rtl_user_thread_start{}; + uint64_t ki_user_apc_dispatcher{}; uint64_t ki_user_exception_dispatcher{}; handle_store events{}; diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 5a40a921..622cd4b9 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -270,7 +270,9 @@ namespace syscalls NTSTATUS handle_NtResumeThread(const syscall_context& c, handle thread_handle, emulator_object previous_suspend_count); NTSTATUS handle_NtContinue(const syscall_context& c, emulator_object thread_context, - BOOLEAN /*raise_alert*/); + BOOLEAN raise_alert); + NTSTATUS handle_NtContinueEx(const syscall_context& c, emulator_object thread_context, + uint64_t continue_argument); NTSTATUS handle_NtGetNextThread(const syscall_context& c, handle process_handle, handle thread_handle, ACCESS_MASK /*desired_access*/, ULONG /*handle_attributes*/, ULONG flags, emulator_object new_thread_handle); @@ -289,6 +291,14 @@ namespace syscalls emulator_object>> attribute_list); NTSTATUS handle_NtGetCurrentProcessorNumberEx(const syscall_context&, emulator_object processor_number); + NTSTATUS handle_NtQueueApcThreadEx2(const syscall_context& c, handle thread_handle, handle reserve_handle, + uint32_t apc_flags, uint64_t apc_routine, uint64_t apc_argument1, + uint64_t apc_argument2, uint64_t apc_argument3); + NTSTATUS handle_NtQueueApcThreadEx(const syscall_context& c, handle thread_handle, handle reserve_handle, + uint64_t apc_routine, uint64_t apc_argument1, uint64_t apc_argument2, + uint64_t apc_argument3); + NTSTATUS handle_NtQueueApcThread(const syscall_context& c, handle thread_handle, uint64_t apc_routine, + uint64_t apc_argument1, uint64_t apc_argument2, uint64_t apc_argument3); // syscalls/timer.cpp: NTSTATUS handle_NtQueryTimerResolution(const syscall_context&, emulator_object maximum_time, @@ -413,10 +423,10 @@ namespace syscalls return STATUS_NOT_SUPPORTED; } - NTSTATUS handle_NtTestAlert() + NTSTATUS handle_NtTestAlert(const syscall_context& c) { - // puts("NtTestAlert not supported"); - return STATUS_NOT_SUPPORTED; + c.win_emu.yield_thread(true); + return STATUS_SUCCESS; } NTSTATUS handle_NtUserSystemParametersInfo() @@ -664,6 +674,7 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtQueryLicenseValue); add_handler(NtTestAlert); add_handler(NtContinue); + add_handler(NtContinueEx); add_handler(NtTerminateProcess); add_handler(NtWriteFile); add_handler(NtRaiseHardError); @@ -749,6 +760,9 @@ void syscall_dispatcher::add_handlers(std::map& ha add_handler(NtClearEvent); add_handler(NtTraceControl); add_handler(NtUserGetProcessUIContextInformation); + add_handler(NtQueueApcThreadEx2); + add_handler(NtQueueApcThreadEx); + add_handler(NtQueueApcThread); #undef add_handler } \ No newline at end of file diff --git a/src/windows-emulator/syscalls/object.cpp b/src/windows-emulator/syscalls/object.cpp index 76ca46e4..7420e43e 100644 --- a/src/windows-emulator/syscalls/object.cpp +++ b/src/windows-emulator/syscalls/object.cpp @@ -116,11 +116,6 @@ namespace syscalls const emulator_object handles, const WAIT_TYPE wait_type, const BOOLEAN alertable, const emulator_object timeout) { - if (alertable) - { - c.win_emu.log.print(color::gray, "Alertable NtWaitForMultipleObjects not supported yet!\n"); - } - if (wait_type != WaitAny && wait_type != WaitAll) { c.win_emu.log.error("Wait type not supported!\n"); @@ -151,18 +146,13 @@ namespace syscalls t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.clock(), timeout.read()); } - c.win_emu.yield_thread(); + c.win_emu.yield_thread(alertable); return STATUS_SUCCESS; } NTSTATUS handle_NtWaitForSingleObject(const syscall_context& c, const handle h, const BOOLEAN alertable, const emulator_object timeout) { - if (alertable) - { - c.win_emu.log.print(color::gray, "Alertable NtWaitForSingleObject not supported yet!\n"); - } - if (!is_awaitable_object_type(h)) { c.win_emu.log.print(color::gray, "Unsupported handle type for NtWaitForSingleObject: %d!\n", h.value.type); @@ -178,7 +168,7 @@ namespace syscalls t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.clock(), timeout.read()); } - c.win_emu.yield_thread(); + c.win_emu.yield_thread(alertable); return STATUS_SUCCESS; } diff --git a/src/windows-emulator/syscalls/thread.cpp b/src/windows-emulator/syscalls/thread.cpp index 8c65a5a1..5a6b1f4c 100644 --- a/src/windows-emulator/syscalls/thread.cpp +++ b/src/windows-emulator/syscalls/thread.cpp @@ -282,17 +282,9 @@ namespace syscalls NTSTATUS handle_NtDelayExecution(const syscall_context& c, const BOOLEAN alertable, const emulator_object delay_interval) { - if (alertable) - { - c.win_emu.log.error("Alertable NtDelayExecution not supported yet!\n"); - c.emu.stop(); - return STATUS_NOT_SUPPORTED; - } - auto& t = c.win_emu.current_thread(); t.await_time = utils::convert_delay_interval_to_time_point(c.win_emu.clock(), delay_interval.read()); - - c.win_emu.yield_thread(); + c.win_emu.yield_thread(alertable); return STATUS_SUCCESS; } @@ -369,17 +361,38 @@ namespace syscalls return STATUS_SUCCESS; } - NTSTATUS handle_NtContinue(const syscall_context& c, const emulator_object thread_context, - const BOOLEAN /*raise_alert*/) + NTSTATUS handle_NtContinueEx(const syscall_context& c, const emulator_object thread_context, + const uint64_t continue_argument) { c.write_status = false; + KCONTINUE_ARGUMENT argument{}; + if (continue_argument <= 0xFF) + { + argument.ContinueFlags = KCONTINUE_FLAG_TEST_ALERT; + } + else + { + argument = c.emu.read_memory(continue_argument); + } + const auto context = thread_context.read(); cpu_context::restore(c.emu, context); + if (argument.ContinueFlags & KCONTINUE_FLAG_TEST_ALERT) + { + c.win_emu.yield_thread(true); + } + return STATUS_SUCCESS; } + NTSTATUS handle_NtContinue(const syscall_context& c, const emulator_object thread_context, + const BOOLEAN raise_alert) + { + return handle_NtContinueEx(c, thread_context, raise_alert ? 1 : 0); + } + NTSTATUS handle_NtGetNextThread(const syscall_context& c, const handle process_handle, const handle thread_handle, const ACCESS_MASK /*desired_access*/, const ULONG /*handle_attributes*/, const ULONG flags, const emulator_object new_thread_handle) @@ -549,4 +562,60 @@ namespace syscalls processor_number.write(number); return STATUS_SUCCESS; } -} \ No newline at end of file + + NTSTATUS handle_NtQueueApcThreadEx2(const syscall_context& c, const handle thread_handle, + const handle /*reserve_handle*/, const uint32_t apc_flags, + const uint64_t apc_routine, const uint64_t apc_argument1, + const uint64_t apc_argument2, const uint64_t apc_argument3) + { + auto* thread = thread_handle == CURRENT_THREAD ? c.proc.active_thread : c.proc.threads.get(thread_handle); + + if (!thread) + { + return STATUS_INVALID_HANDLE; + } + + if (apc_flags) + { + c.win_emu.log.error("Unsupported APC flags: %X\n", apc_flags); + c.emu.stop(); + return STATUS_NOT_SUPPORTED; + } + + thread->pending_apcs.push_back({ + .flags = apc_flags, + .apc_routine = apc_routine, + .apc_argument1 = apc_argument1, + .apc_argument2 = apc_argument2, + .apc_argument3 = apc_argument3, + }); + + return STATUS_NOT_SUPPORTED; + } + + NTSTATUS handle_NtQueueApcThreadEx(const syscall_context& c, const handle thread_handle, + const handle reserve_handle, const uint64_t apc_routine, + const uint64_t apc_argument1, const uint64_t apc_argument2, + const uint64_t apc_argument3) + { + uint32_t flags{0}; + auto real_reserve_handle = reserve_handle; + if (reserve_handle.bits == QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC) + { + real_reserve_handle.bits = 0; + flags = QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC; + static_assert(QUEUE_USER_APC_FLAGS_SPECIAL_USER_APC == 1); + } + + return handle_NtQueueApcThreadEx2(c, thread_handle, real_reserve_handle, flags, apc_routine, apc_argument1, + apc_argument2, apc_argument3); + } + + NTSTATUS handle_NtQueueApcThread(const syscall_context& c, const handle thread_handle, const uint64_t apc_routine, + const uint64_t apc_argument1, const uint64_t apc_argument2, + const uint64_t apc_argument3) + { + return handle_NtQueueApcThreadEx(c, thread_handle, make_handle(0), apc_routine, apc_argument1, apc_argument2, + apc_argument3); + } +} diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 8bdb30bb..1178b449 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -79,6 +79,51 @@ namespace return nullptr; } + void dispatch_next_apc(windows_emulator& win_emu, emulator_thread& thread) + { + assert(&win_emu.current_thread() == &thread); + + auto& emu = win_emu.emu(); + auto& apcs = thread.pending_apcs; + if (apcs.empty()) + { + return; + } + + win_emu.log.print(color::dark_gray, "Dispatching APC...\n"); + + const auto next_apx = apcs.front(); + apcs.erase(apcs.begin()); + + struct + { + CONTEXT64 context{}; + CONTEXT_EX context_ex{}; + KCONTINUE_ARGUMENT continue_argument{}; + } stack_layout; + + static_assert(offsetof(decltype(stack_layout), continue_argument) == 0x4F0); + + stack_layout.context.P1Home = next_apx.apc_argument1; + stack_layout.context.P2Home = next_apx.apc_argument2; + stack_layout.context.P3Home = next_apx.apc_argument3; + stack_layout.context.P4Home = next_apx.apc_routine; + + stack_layout.continue_argument.ContinueFlags |= KCONTINUE_FLAG_TEST_ALERT; + + auto& ctx = stack_layout.context; + ctx.ContextFlags = CONTEXT64_ALL; + cpu_context::save(emu, ctx); + + const auto initial_sp = emu.reg(x64_register::rsp); + const auto new_sp = align_down(initial_sp - sizeof(stack_layout), 0x100); + + emu.write_memory(new_sp, stack_layout); + + emu.reg(x64_register::rsp, new_sp); + emu.reg(x64_register::rip, win_emu.process.ki_user_apc_dispatcher); + } + bool switch_to_thread(windows_emulator& win_emu, emulator_thread& thread, const bool force = false) { if (thread.is_terminated()) @@ -90,30 +135,38 @@ namespace auto& context = win_emu.process; const auto is_ready = thread.is_thread_ready(context, win_emu.clock()); + const auto can_dispatch_apcs = thread.apc_alertable && !thread.pending_apcs.empty(); - if (!is_ready && !force) + if (!is_ready && !force && !can_dispatch_apcs) { return false; } auto* active_thread = context.active_thread; - if (active_thread == &thread) + if (active_thread != &thread) { - thread.setup_if_necessary(emu, context); - return true; + if (active_thread) + { + win_emu.log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", active_thread->id, + thread.id); + active_thread->save(emu); + } + + context.active_thread = &thread; + + thread.restore(emu); } - if (active_thread) - { - win_emu.log.print(color::dark_gray, "Performing thread switch: %X -> %X\n", active_thread->id, thread.id); - active_thread->save(emu); - } - - context.active_thread = &thread; - - thread.restore(emu); thread.setup_if_necessary(emu, context); + + if (can_dispatch_apcs) + { + thread.mark_as_ready(STATUS_USER_APC); + dispatch_next_apc(win_emu, thread); + } + + thread.apc_alertable = false; return true; } @@ -298,9 +351,10 @@ void windows_emulator::setup_process(const application_settings& app_settings) switch_to_thread(*this, main_thread_id); } -void windows_emulator::yield_thread() +void windows_emulator::yield_thread(const bool alertable) { this->switch_thread_ = true; + this->current_thread().apc_alertable = alertable; this->emu().stop(); } diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index 986f0be7..5a68f614 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -183,7 +183,7 @@ class windows_emulator bool buffer_stdout{false}; bool fuzzing{false}; - void yield_thread(); + void yield_thread(bool alertable = false); void perform_thread_switch(); bool activate_thread(uint32_t id);