diff --git a/src/analyzer/main.cpp b/src/analyzer/main.cpp index bbb56e02..554838c4 100644 --- a/src/analyzer/main.cpp +++ b/src/analyzer/main.cpp @@ -11,7 +11,7 @@ namespace { void watch_system_objects(windows_emulator& win_emu) { - watch_object(win_emu, win_emu.process().teb); + //watch_object(win_emu, *win_emu.current_thread().teb); watch_object(win_emu, win_emu.process().peb); watch_object(win_emu, win_emu.process().kusd); auto* params_hook = watch_object(win_emu, win_emu.process().process_params); @@ -48,7 +48,17 @@ namespace } else { - win_emu.emu().start_from_ip(); + while (true) + { + win_emu.emu().start_from_ip(); + if (win_emu.switch_thread) + { + win_emu.perform_thread_switch(); + continue; + } + + break; + } } } catch (...) @@ -68,7 +78,7 @@ namespace (void)&watch_system_objects; //watch_system_objects(win_emu); - win_emu.buffer_stdout = true; + win_emu.buffer_stdout = false; //win_emu.verbose_calls = true; const auto& exe = *win_emu.process().executable; @@ -86,43 +96,6 @@ namespace } }); - win_emu.add_syscall_hook([&] - { - // Read syscall id and name - - const auto syscall_id = win_emu.emu().reg(x64_register::eax); - const auto syscall_name = win_emu.dispatcher().get_syscall_name(syscall_id); - - - // Check if desired syscall - - if (syscall_name != "NtQueryInformationProcess") - { - return instruction_hook_continuation::run_instruction; - } - - // Check if image file name is read - - const auto info_class = win_emu.emu().reg(x64_register::rdx); - if (info_class != ProcessImageFileNameWin32) - { - return instruction_hook_continuation::run_instruction; - } - - // Patch result and feed expected filename - - win_emu.logger.print(color::pink, "Patching NtQueryInformationProcess...\n"); - - const auto data = win_emu.emu().reg(x64_register::r8); - - emulator_allocator data_allocator{win_emu.emu(), data, 0x100}; - data_allocator.make_unicode_string(L"C:\\Users\\Maurice\\Desktop\\protected.exe"); - - win_emu.emu().reg(x64_register::rax, STATUS_SUCCESS); - - return instruction_hook_continuation::skip_instruction; - }); - run_emulation(win_emu); } } diff --git a/src/windows-emulator/emulator_utils.hpp b/src/windows-emulator/emulator_utils.hpp index c5dd9ea1..a05c5881 100644 --- a/src/windows-emulator/emulator_utils.hpp +++ b/src/windows-emulator/emulator_utils.hpp @@ -181,6 +181,16 @@ public: buffer.read(this->active_address_); } + void release() + { + if (this->emu_ && this->address_ && this->size_) + { + this->emu_->release_memory(this->address_, this->size_); + this->address_ = 0; + this->size_ = 0; + } + } + private: emulator* emu_{}; uint64_t address_{}; diff --git a/src/windows-emulator/handles.hpp b/src/windows-emulator/handles.hpp index 97d16683..b0f7dd22 100644 --- a/src/windows-emulator/handles.hpp +++ b/src/windows-emulator/handles.hpp @@ -11,6 +11,7 @@ struct handle_types directory, semaphore, port, + thread, }; }; @@ -132,9 +133,13 @@ public: return this->get(hh); } - bool erase(const handle_value h) + size_t size() const + { + return this->store_.size(); + } + + bool erase(const typename value_map::iterator& entry) { - const auto entry = this->get_iterator(h); if (entry == this->store_.end()) { return false; @@ -152,6 +157,12 @@ public: return true; } + bool erase(const handle_value h) + { + const auto entry = this->get_iterator(h); + return this->erase(entry); + } + bool erase(const handle h) { return this->erase(h.value); @@ -165,6 +176,12 @@ public: return this->erase(hh); } + bool erase(const T& value) + { + const auto entry = this->find(value); + return this->erase(entry); + } + void serialize(utils::buffer_serializer& buffer) const { buffer.write_map(this->store_); @@ -175,22 +192,50 @@ public: buffer.read_map(this->store_); } - value_map::iterator begin() + typename value_map::iterator find(const T& value) + { + auto i = this->store_.begin(); + for (; i != this->store_.end(); ++i) + { + if (&i->second == &value) + { + break; + } + } + + return i; + } + + typename value_map::const_iterator find(const T& value) const + { + auto i = this->store_.begin(); + for (; i != this->store_.end(); ++i) + { + if (&i->second == &value) + { + break; + } + } + + return i; + } + + typename value_map::iterator begin() { return this->store_.begin(); } - value_map::const_iterator begin() const + typename value_map::const_iterator begin() const { return this->store_.begin(); } - value_map::iterator end() + typename value_map::iterator end() { return this->store_.end(); } - value_map::const_iterator end() const + typename value_map::const_iterator end() const { return this->store_.end(); } diff --git a/src/windows-emulator/process_context.hpp b/src/windows-emulator/process_context.hpp index 3e6d686b..0bd0e6e9 100644 --- a/src/windows-emulator/process_context.hpp +++ b/src/windows-emulator/process_context.hpp @@ -8,12 +8,44 @@ #include -struct event +#define PEB_SEGMENT_SIZE (1 << 20) // 1 MB +#define GS_SEGMENT_SIZE (1 << 20) // 1 MB + +#define IA32_GS_BASE_MSR 0xC0000101 + +#define KUSD_ADDRESS 0x7ffe0000 + +#define STACK_SIZE 0x40000ULL + +#define GDT_ADDR 0x30000 +#define GDT_LIMIT 0x1000 +#define GDT_ENTRY_SIZE 0x8 + +struct ref_counted_object +{ + uint32_t ref_count{1}; + + void serialize(utils::buffer_serializer& buffer) const + { + buffer.write(this->ref_count); + } + + void deserialize(utils::buffer_deserializer& buffer) + { + buffer.read(this->ref_count); + } + + static bool deleter(ref_counted_object& e) + { + return --e.ref_count == 0; + } +}; + +struct event : ref_counted_object { bool signaled{}; EVENT_TYPE type{}; std::wstring name{}; - uint32_t ref_count{0}; bool is_signaled() { @@ -32,7 +64,8 @@ struct event buffer.write(this->signaled); buffer.write(this->type); buffer.write(this->name); - buffer.write(this->ref_count); + + ref_counted_object::serialize(buffer); } void deserialize(utils::buffer_deserializer& buffer) @@ -40,12 +73,8 @@ struct event 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; + ref_counted_object::deserialize(buffer); } }; @@ -106,15 +135,136 @@ struct port } }; +struct process_context; + +class moved_marker +{ +public: + moved_marker() = default; + + moved_marker(const moved_marker& copy) = default; + moved_marker& operator=(const moved_marker&) = default; + + moved_marker(moved_marker&& obj) noexcept + : moved_marker() + { + this->operator=(std::move(obj)); + } + + moved_marker& operator=(moved_marker&& obj) noexcept + { + if (this != &obj) + { + this->was_moved_ = obj.was_moved_; + obj.was_moved_ = true; + } + + return *this; + } + + ~moved_marker() = default; + + bool was_moved() const + { + return this->was_moved_; + } + +private: + bool was_moved_{false}; +}; + +class emulator_thread : ref_counted_object +{ +public: + emulator_thread() = default; + + emulator_thread(x64_emulator& emu, const process_context& context, uint64_t start_address, uint64_t argument, + uint64_t stack_size, uint32_t id); + + emulator_thread(const emulator_thread&) = delete; + emulator_thread& operator=(const emulator_thread&) = delete; + + emulator_thread(emulator_thread&& obj) noexcept = default; + emulator_thread& operator=(emulator_thread&& obj) noexcept = default; + + ~emulator_thread() + { + if (marker.was_moved()) + { + return; + } + + if (this->stack_base) + { + this->emu_ptr->release_memory(this->stack_base, this->stack_size); + } + + if (this->gs_segment) + { + this->gs_segment->release(); + } + } + + moved_marker marker{}; + + x64_emulator* emu_ptr{}; + + uint64_t stack_base{}; + uint64_t stack_size{}; + uint64_t start_address{}; + uint64_t argument{}; + uint64_t executed_instructions{0}; + + uint32_t id{}; + + std::optional exit_status{}; + std::optional await_object{}; + + std::optional gs_segment; + std::optional> teb; + + std::vector last_registers{}; + + void save(x64_emulator& emu) + { + this->last_registers = emu.save_registers(); + } + + void restore(x64_emulator& emu) const + { + emu.restore_registers(this->last_registers); + } + + void setup_if_necessary(x64_emulator& emu, const process_context& context) const + { + if (!this->executed_instructions) + { + this->setup_registers(emu, context); + } + } + + void serialize(utils::buffer_serializer&) const + { + // TODO + } + + void deserialize(utils::buffer_deserializer&) + { + // TODO + } + +private: + void setup_registers(x64_emulator& emu, const process_context& context) const; +}; + struct process_context { process_context(x64_emulator& emu) - : teb(emu) + : base_allocator(emu) , peb(emu) , process_params(emu) , kusd(emu) , module_manager(emu) - , gs_segment(emu) { } @@ -124,7 +274,8 @@ struct process_context std::optional exception_rip{}; - emulator_object teb; + emulator_allocator base_allocator; + emulator_object peb; emulator_object process_params; emulator_object kusd; @@ -135,6 +286,8 @@ struct process_context mapped_module* ntdll{}; mapped_module* win32u{}; + uint64_t ldr_initialize_thunk{}; + uint64_t rtl_user_thread_start{}; uint64_t ki_user_exception_dispatcher{}; uint64_t shared_section_size{}; @@ -144,7 +297,12 @@ struct process_context handle_store semaphores{}; handle_store ports{}; std::map atoms{}; - emulator_allocator gs_segment; + + std::vector default_register_set{}; + + uint32_t current_thread_id{0}; + handle_store threads{}; + emulator_thread* active_thread{nullptr}; void serialize(utils::buffer_serializer& buffer) const { @@ -152,7 +310,6 @@ struct process_context buffer.write(this->current_ip); buffer.write(this->previous_ip); buffer.write_optional(this->exception_rip); - buffer.write(this->teb); buffer.write(this->peb); buffer.write(this->process_params); buffer.write(this->kusd); @@ -170,7 +327,8 @@ struct process_context buffer.write(this->semaphores); buffer.write(this->ports); buffer.write_map(this->atoms); - buffer.write(this->gs_segment); + + // TODO: Serialize/deserialize threads } void deserialize(utils::buffer_deserializer& buffer) @@ -179,7 +337,6 @@ struct process_context buffer.read(this->current_ip); buffer.read(this->previous_ip); buffer.read_optional(this->exception_rip); - buffer.read(this->teb); buffer.read(this->peb); buffer.read(this->process_params); buffer.read(this->kusd); @@ -201,6 +358,12 @@ struct process_context buffer.read(this->semaphores); buffer.read(this->ports); buffer.read_map(this->atoms); - buffer.read(this->gs_segment); + } + + handle create_thread(x64_emulator& emu, const uint64_t start_address, const uint64_t argument, + const uint64_t stack_size) + { + emulator_thread t{emu, *this, start_address, argument, stack_size, ++this->current_thread_id}; + return this->threads.store(std::move(t)); } }; diff --git a/src/windows-emulator/syscalls.cpp b/src/windows-emulator/syscalls.cpp index 0cc71f3b..0b76d5b5 100644 --- a/src/windows-emulator/syscalls.cpp +++ b/src/windows-emulator/syscalls.cpp @@ -161,6 +161,20 @@ namespace }; } + template + void write_attribute(emulator& emu, const PS_ATTRIBUTE& attribute, const T& value) + { + if (attribute.ReturnLength) + { + emulator_object{emu, attribute.ReturnLength}.write(sizeof(T)); + } + + if (attribute.Size >= sizeof(T)) + { + emulator_object{emu, attribute.Value}.write(value); + } + } + NTSTATUS handle_NtQueryPerformanceCounter(const syscall_context&, const emulator_object performance_counter, const emulator_object performance_frequency) @@ -259,6 +273,11 @@ namespace return STATUS_SUCCESS; } + if (value.type == handle_types::thread && c.proc.threads.erase(handle)) + { + return STATUS_SUCCESS; + } + if (value.type == handle_types::event && c.proc.events.erase(handle)) { return STATUS_SUCCESS; @@ -305,7 +324,6 @@ namespace event e{}; e.type = event_type; e.signaled = initial_state != FALSE; - e.ref_count = 1; e.name = std::move(name); const auto handle = c.proc.events.store(std::move(e)); @@ -494,7 +512,7 @@ namespace const emulator_object sysdir_obj{c.emu, obj_address + windir_obj.size()}; sysdir_obj.access([&](UNICODE_STRING& ucs) { - c.proc.gs_segment.make_unicode_string(ucs, L"C:\\WINDOWS\\System32"); + c.proc.base_allocator.make_unicode_string(ucs, L"C:\\WINDOWS\\System32"); ucs.Buffer = reinterpret_cast(reinterpret_cast(ucs.Buffer) - obj_address); }); @@ -866,6 +884,7 @@ namespace { if (info_class == SystemFlushInformation || info_class == SystemFeatureConfigurationInformation + || info_class == SystemSupportedProcessorArchitectures2 || info_class == SystemFeatureConfigurationSectionInformation) { //printf("Unsupported, but allowed system info class: %X\n", info_class); @@ -1136,13 +1155,31 @@ namespace const emulator_object info{c.emu, thread_information}; info.access([&](THREAD_BASIC_INFORMATION& i) { - i.TebBaseAddress = c.proc.teb.ptr(); - i.ClientId = c.proc.teb.read().ClientId; + i.TebBaseAddress = c.win_emu.current_thread().teb->ptr(); + i.ClientId = c.win_emu.current_thread().teb->read().ClientId; }); return STATUS_SUCCESS; } + if (info_class == ThreadAmILastThread) + { + if (return_length) + { + return_length.write(sizeof(ULONG)); + } + + if (thread_information_length != sizeof(ULONG)) + { + return STATUS_BUFFER_OVERFLOW; + } + + const emulator_object info{c.emu, thread_information}; + info.write(c.proc.threads.size() <= 1); + + return STATUS_SUCCESS; + } + printf("Unsupported thread info class: %X\n", info_class); c.emu.stop(); @@ -1195,6 +1232,7 @@ namespace || info_class == ProcessTlsInformation || info_class == ProcessConsoleHostProcess || info_class == ProcessFaultInformation + || info_class == ProcessDefaultHardErrorMode || info_class == ProcessRaiseUMExceptionOnInvalidHandleClose) { return STATUS_SUCCESS; @@ -1556,7 +1594,7 @@ namespace { if (!peb.GdiSharedHandleTable) { - peb.GdiSharedHandleTable = c.proc.gs_segment.reserve().ptr(); + peb.GdiSharedHandleTable = c.proc.base_allocator.reserve().ptr(); } }); @@ -1881,8 +1919,8 @@ namespace return STATUS_SUCCESS; } - NTSTATUS handle_NtUnmapViewOfSection(const syscall_context& c, uint64_t process_handle, uint64_t base_address - ) + NTSTATUS handle_NtUnmapViewOfSection(const syscall_context& c, const uint64_t process_handle, + const uint64_t base_address) { if (process_handle != ~0ULL) { @@ -1902,6 +1940,123 @@ namespace c.emu.stop(); return STATUS_NOT_SUPPORTED; } + + NTSTATUS handle_NtCreateThreadEx(const syscall_context& c, const emulator_object thread_handle, + const ACCESS_MASK /*desired_access*/, + const emulator_object /*object_attributes*/, + const uint64_t process_handle, const uint64_t start_routine, + const uint64_t argument, const ULONG /*create_flags*/, const SIZE_T /*zero_bits*/, + const SIZE_T stack_size, const SIZE_T /*maximum_stack_size*/, + const emulator_object attribute_list) + { + if (process_handle != ~0ULL) + { + return STATUS_NOT_SUPPORTED; + } + + const auto h = c.proc.create_thread(c.emu, start_routine, argument, stack_size); + thread_handle.write(h.bits); + + if (!attribute_list) + { + return STATUS_SUCCESS; + } + + const auto* thread = c.proc.threads.get(h); + + const emulator_object attributes{ + c.emu, attribute_list.value() + offsetof(PS_ATTRIBUTE_LIST, Attributes) + }; + + const auto total_length = attribute_list.read().TotalLength; + + constexpr auto entry_size = sizeof(PS_ATTRIBUTE); + constexpr auto header_size = sizeof(PS_ATTRIBUTE_LIST) - entry_size; + const auto attribute_count = (total_length - header_size) / entry_size; + + for (size_t i = 0; i < attribute_count; ++i) + { + attributes.access([&](const PS_ATTRIBUTE& attribute) + { + const auto type = attribute.Attribute & ~PS_ATTRIBUTE_THREAD; + + if (type == PsAttributeClientId) + { + const auto client_id = thread->teb->read().ClientId; + write_attribute(c.emu, attribute, client_id); + } + else if (type == PsAttributeTebAddress) + { + write_attribute(c.emu, attribute, thread->teb->ptr()); + } + else + { + printf("Unsupported thread attribute type: %llX\n", type); + } + }, i); + } + + return STATUS_SUCCESS; + } + + NTSTATUS handle_NtQueryDebugFilterState() + { + return FALSE; + } + + NTSTATUS handle_NtWaitForSingleObject(const syscall_context& c, const uint64_t handle_value, + const BOOLEAN alertable, + const emulator_object timeout) + { + if (timeout.value()) + { + puts("NtWaitForSingleObject timeout not supported yet!"); + return STATUS_NOT_SUPPORTED; + } + + if (alertable) + { + puts("Alertable NtWaitForSingleObject not supported yet!"); + return STATUS_NOT_SUPPORTED; + } + + handle h{}; + h.bits = handle_value; + + if (h.value.type != handle_types::thread) + { + puts("NtWaitForSingleObject only supported with thread handles yet!"); + return STATUS_NOT_SUPPORTED; + } + + c.win_emu.current_thread().await_object = h; + c.win_emu.switch_thread = true; + c.emu.stop(); + + return STATUS_WAIT_0; + } + + NTSTATUS handle_NtTerminateThread(const syscall_context& c, uint64_t thread_handle, + const NTSTATUS exit_status) + { + auto* thread = !thread_handle + ? c.proc.active_thread + : c.proc.threads.get(thread_handle); + + if (!thread) + { + return STATUS_INVALID_HANDLE; + } + + thread->exit_status = exit_status; + if (thread == c.proc.active_thread) + { + c.win_emu.switch_thread = true; + c.emu.stop(); + } + + return STATUS_SUCCESS; + } } void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports) @@ -2000,6 +2155,10 @@ void syscall_dispatcher::add_handlers() add_handler(NtQueryInformationJobObject); add_handler(NtSetSystemInformation); add_handler(NtQueryInformationFile); + add_handler(NtCreateThreadEx); + add_handler(NtQueryDebugFilterState); + add_handler(NtWaitForSingleObject); + add_handler(NtTerminateThread); #undef add_handler diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index 9b44eaef..0b9ddd68 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -4,18 +4,7 @@ #include -#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 +constexpr auto MAX_INSTRUCTIONS_PER_TIME_SLICE = 100; namespace { @@ -39,13 +28,11 @@ namespace 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) + void setup_gs_segment(x64_emulator& emu, const emulator_allocator& allocator) { struct msr_value { @@ -55,13 +42,10 @@ namespace const msr_value value{ IA32_GS_BASE_MSR, - segment_base + allocator.get_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 setup_kusd(x64_emulator& emu) @@ -174,8 +158,6 @@ namespace hash_entries_obj.write(hash_entry, i); } - //watch_object(emu, api_set_map_obj); - return api_set_map_obj; } @@ -209,31 +191,17 @@ namespace void setup_context(process_context& context, x64_emulator& emu, const std::filesystem::path& file, const std::vector& 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); + context.base_allocator = create_allocator(emu, PEB_SEGMENT_SIZE); + auto& allocator = context.base_allocator; - auto& gs = context.gs_segment; - - context.teb = gs.reserve(); - context.peb = gs.reserve(); - - context.teb.access([&](TEB& teb) - { - teb.ClientId.UniqueProcess = reinterpret_cast(1); - teb.ClientId.UniqueThread = reinterpret_cast(2); - teb.NtTib.StackLimit = reinterpret_cast(STACK_ADDRESS); - teb.NtTib.StackBase = reinterpret_cast((STACK_ADDRESS + STACK_SIZE)); - teb.NtTib.Self = &context.teb.ptr()->NtTib; - teb.ProcessEnvironmentBlock = context.peb.ptr(); - }); + context.peb = allocator.reserve(); /* Values of the following fields must be - * allocated relative to the process_params themselves. + * allocated relative to the process_params themselves * and included in the length: * * CurrentDirectory @@ -247,7 +215,7 @@ namespace * RedirectionDllName */ - context.process_params = gs.reserve(); + context.process_params = allocator.reserve(); context.process_params.access([&](RTL_USER_PROCESS_PARAMETERS& proc_params) { @@ -267,11 +235,11 @@ namespace command_line.append(arg); } - gs.make_unicode_string(proc_params.CommandLine, command_line); + allocator.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()); + allocator.make_unicode_string(proc_params.ImagePathName, file.wstring()); - const auto total_length = gs.get_next_address() - context.process_params.value(); + const auto total_length = allocator.get_next_address() - context.process_params.value(); proc_params.Length = static_cast(std::max(sizeof(proc_params), total_length)); proc_params.MaximumLength = proc_params.Length; @@ -344,7 +312,6 @@ namespace case memory_operation::read: return 0; case memory_operation::write: - return 1; case memory_operation::exec: return 1; } @@ -449,6 +416,142 @@ namespace dispatch_exception_pointers(emu, dispatcher, pointers); } + + void switch_to_thread(x64_emulator& emu, process_context& context, emulator_thread& thread) + { + auto* active_thread = context.active_thread; + if (active_thread) + { + active_thread->save(emu); + } + + context.active_thread = &thread; + thread.restore(emu); + thread.setup_if_necessary(emu, context); + } + + void cleanup_threads(process_context& context) + { + while (true) + { + bool has_changed = false; + for (auto i = context.threads.begin(); i != context.threads.end(); ++i) + { + if (i->second.exit_status.has_value()) + { + if (&i->second == context.active_thread) + { + context.active_thread = nullptr; + } + + context.threads.erase(i); + has_changed = true; + break; + } + } + + if (!has_changed) + { + break; + } + } + } + + void switch_to_thread(x64_emulator& emu, process_context& context, const handle thread_handle) + { + auto* thread = context.threads.get(thread_handle); + if (!thread) + { + throw std::runtime_error("Bad thread handle"); + } + + switch_to_thread(emu, context, *thread); + } + + void switch_to_next_thread(x64_emulator& emu, process_context& context) + { + //cleanup_threads(context); + + bool next_thread = false; + + for (auto& thread : context.threads) + { + if (next_thread) + { + if (thread.second.exit_status.has_value()) + { + continue; + } + + switch_to_thread(emu, context, thread.second); + return; + } + + if (&thread.second == context.active_thread) + { + next_thread = true; + } + } + + switch_to_thread(emu, context, context.threads.begin()->second); + } +} + +emulator_thread::emulator_thread(x64_emulator& emu, const process_context& context, + const uint64_t start_address, + const uint64_t argument, + const uint64_t stack_size, const uint32_t id) + : emu_ptr(&emu) + , stack_size(page_align_up(std::max(stack_size, STACK_SIZE))) + , start_address(start_address) + , argument(argument) + , id(id) + , last_registers(context.default_register_set) +{ + this->stack_base = emu.allocate_memory(this->stack_size, memory_permission::read_write); + + this->gs_segment = emulator_allocator{ + emu, + emu.allocate_memory(GS_SEGMENT_SIZE, memory_permission::read_write), + GS_SEGMENT_SIZE, + }; + + this->teb = this->gs_segment->reserve(); + + this->teb->access([&](TEB& teb_obj) + { + teb_obj.ClientId.UniqueProcess = reinterpret_cast(1); + teb_obj.ClientId.UniqueThread = reinterpret_cast(static_cast(this->id)); + teb_obj.NtTib.StackLimit = reinterpret_cast(this->stack_base); + teb_obj.NtTib.StackBase = reinterpret_cast(this->stack_base + this->stack_size); + teb_obj.NtTib.Self = &this->teb->ptr()->NtTib; + teb_obj.ProcessEnvironmentBlock = context.peb.ptr(); + }); +} + +void emulator_thread::setup_registers(x64_emulator& emu, const process_context& context) const +{ + setup_stack(emu, this->stack_base, this->stack_size); + setup_gs_segment(emu, *this->gs_segment); + + CONTEXT ctx{}; + ctx.ContextFlags = CONTEXT_ALL; + + unalign_stack(emu); + context_frame::save(emu, ctx); + + ctx.Rip = context.rtl_user_thread_start; + ctx.Rcx = this->start_address; + ctx.Rdx = this->argument; + + const auto ctx_obj = allocate_object_on_stack(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, context.ldr_initialize_thunk); } std::unique_ptr create_default_x64_emulator() @@ -492,27 +595,21 @@ void windows_emulator::setup_process(const std::filesystem::path& application, this->dispatcher_.setup(context.ntdll->exports, context.win32u->exports); - const auto ldr_initialize_thunk = context.ntdll->find_export("LdrInitializeThunk"); - const auto rtl_user_thread_start = context.ntdll->find_export("RtlUserThreadStart"); + context.ldr_initialize_thunk = context.ntdll->find_export("LdrInitializeThunk"); + context.rtl_user_thread_start = context.ntdll->find_export("RtlUserThreadStart"); context.ki_user_exception_dispatcher = context.ntdll->find_export("KiUserExceptionDispatcher"); - CONTEXT ctx{}; - ctx.ContextFlags = CONTEXT_ALL; + context.default_register_set = emu.save_registers(); - unalign_stack(emu); - context_frame::save(emu, ctx); + const auto main_thread_id = context.create_thread(emu, context.executable->entry_point, 0, 0); + switch_to_thread(emu, context, main_thread_id); +} - ctx.Rip = rtl_user_thread_start; - ctx.Rcx = context.executable->entry_point; - - const auto ctx_obj = allocate_object_on_stack(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::perform_thread_switch() +{ + this->logger.print(color::green, "Performing thread switch...\n"); + switch_to_next_thread(this->emu(), this->process()); + this->switch_thread = false; } void windows_emulator::setup_hooks() @@ -586,6 +683,16 @@ void windows_emulator::setup_hooks() ++process.executed_instructions; + auto& thread = this->current_thread(); + if (thread.executed_instructions == MAX_INSTRUCTIONS_PER_TIME_SLICE) + { + this->switch_thread = true; + this->emu().stop(); + } + + ++thread.executed_instructions; + thread.executed_instructions %= MAX_INSTRUCTIONS_PER_TIME_SLICE; + process.previous_ip = process.current_ip; process.current_ip = this->emu().read_instruction_pointer(); diff --git a/src/windows-emulator/windows_emulator.hpp b/src/windows-emulator/windows_emulator.hpp index e02ccb5e..b221b2f4 100644 --- a/src/windows-emulator/windows_emulator.hpp +++ b/src/windows-emulator/windows_emulator.hpp @@ -51,6 +51,16 @@ public: return this->dispatcher_; } + emulator_thread& current_thread() const + { + if (!this->process_.active_thread) + { + throw std::runtime_error("No active thread!"); + } + + return *this->process_.active_thread; + } + void serialize(utils::buffer_serializer& buffer) const; void deserialize(utils::buffer_deserializer& buffer); @@ -67,6 +77,9 @@ public: bool verbose_calls{false}; bool buffer_stdout{false}; bool fuzzing{false}; + bool switch_thread{false}; + + void perform_thread_switch(); private: std::unique_ptr emu_{};