diff --git a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp index d240dc4a..9f45beec 100644 --- a/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp +++ b/src/backends/icicle-emulator/icicle_x86_64_emulator.cpp @@ -178,6 +178,21 @@ namespace icicle } } + pointer_type get_segment_base(const x86_register base) override + { + switch (base) + { + case x86_register::fs: + case x86_register::fs_base: + return this->reg(x86_register::fs_base); + case x86_register::gs: + case x86_register::gs_base: + return this->reg(x86_register::gs_base); + default: + return 0; + } + } + size_t write_raw_register(const int reg, const void* value, const size_t size) override { return icicle_write_register(this->emu_, reg, value, size); @@ -343,7 +358,9 @@ namespace icicle const auto& func = *static_cast(user); const auto res = func(address, 1, static_cast(operation), violation_type); - return res == memory_violation_continuation::resume ? 1 : 0; + const auto restart = res == memory_violation_continuation::restart; + const auto resume = res == memory_violation_continuation::resume || restart; + return resume ? 1 : 0; }; const auto id = icicle_add_violation_hook(this->emu_, wrapper, ptr); diff --git a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp index 31928ae7..c4374c5a 100644 --- a/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp +++ b/src/backends/unicorn-emulator/unicorn_x86_64_emulator.cpp @@ -21,6 +21,15 @@ namespace unicorn static_assert(static_cast(x86_register::end) == UC_X86_REG_ENDING); + constexpr auto IA32_FS_BASE_MSR = 0xC0000100; + constexpr auto IA32_GS_BASE_MSR = 0xC0000101; + + struct msr_value + { + uint64_t id{}; + uint64_t value{}; + }; + uc_x86_insn map_hookable_instruction(const x86_hookable_instructions instruction) { switch (instruction) @@ -253,15 +262,6 @@ namespace unicorn void set_segment_base(const x86_register base, const pointer_type value) override { - constexpr auto IA32_FS_BASE_MSR = 0xC0000100; - constexpr auto IA32_GS_BASE_MSR = 0xC0000101; - - struct msr_value - { - uint64_t id{}; - uint64_t value{}; - }; - msr_value msr_val{ .id = 0, .value = value, @@ -272,12 +272,10 @@ namespace unicorn case x86_register::fs: case x86_register::fs_base: msr_val.id = IA32_FS_BASE_MSR; - preserved_fs_base_ = static_cast(value); break; case x86_register::gs: case x86_register::gs_base: msr_val.id = IA32_GS_BASE_MSR; - preserved_gs_base_ = static_cast(value); break; default: return; @@ -286,6 +284,30 @@ namespace unicorn this->write_register(x86_register::msr, &msr_val, sizeof(msr_val)); } + pointer_type get_segment_base(const x86_register base) override + { + msr_value msr_val{}; + + switch (base) + { + case x86_register::fs: + case x86_register::fs_base: + msr_val.id = IA32_FS_BASE_MSR; + break; + case x86_register::gs: + case x86_register::gs_base: + msr_val.id = IA32_GS_BASE_MSR; + break; + default: + return 0; + } + + size_t result_size = sizeof(msr_value); + uce(uc_reg_read2(*this, (int)x86_register::msr, &msr_val, &result_size)); + + return msr_val.value; + } + size_t write_raw_register(const int reg, const void* value, const size_t size) override { auto result_size = size; @@ -490,18 +512,19 @@ namespace unicorn const auto operation = map_memory_operation(type); const auto violation = map_memory_violation_type(type); - const auto resume = - c(address, static_cast(size), operation, violation) == memory_violation_continuation::resume; + const auto result = c(address, static_cast(size), operation, violation); + const auto restart = result == memory_violation_continuation::restart; + const auto resume = result == memory_violation_continuation::resume || restart; const auto new_ip = this->read_instruction_pointer(); - const auto has_ip_changed = ip != new_ip; + const auto set_ip = ip != new_ip || restart; if (!resume) { return false; } - if (resume && has_ip_changed) + if (resume && set_ip) { this->violation_ip_ = new_ip; } @@ -510,7 +533,7 @@ namespace unicorn this->violation_ip_ = std::nullopt; } - if (has_ip_changed) + if (set_ip) { return false; } @@ -533,17 +556,8 @@ namespace unicorn emulator_hook* hook_memory_execution(const uint64_t address, const uint64_t size, memory_execution_hook_callback callback) { - auto exec_wrapper = [c = std::move(callback), this](uc_engine*, const uint64_t address, const uint32_t /*size*/) { + auto exec_wrapper = [c = std::move(callback)](uc_engine*, const uint64_t address, const uint32_t /*size*/) { c(address); // - - // Fix unicorn bug? - const auto cs_current = this->reg(x86_register::cs); - if (this->current_reg_cs_ != cs_current) - { - this->set_segment_base(x86_register::gs, preserved_gs_base_); - this->set_segment_base(x86_register::fs, preserved_fs_base_); - this->current_reg_cs_ = cs_current; - } }; function_wrapper wrapper(std::move(exec_wrapper)); @@ -652,11 +666,6 @@ namespace unicorn const uc_context_serializer serializer(this->uc_, is_snapshot); serializer.serialize(buffer); - - // Serialize unicorn bug workaround state - buffer.write(this->preserved_gs_base_); - buffer.write(this->preserved_fs_base_); - buffer.write(this->current_reg_cs_); } void deserialize_state(utils::buffer_deserializer& buffer, const bool is_snapshot) override @@ -669,10 +678,6 @@ namespace unicorn const uc_context_serializer serializer(this->uc_, is_snapshot); serializer.deserialize(buffer); - // Deserialize unicorn bug workaround state - buffer.read(this->preserved_gs_base_); - buffer.read(this->preserved_fs_base_); - buffer.read(this->current_reg_cs_); } std::vector save_registers() const override @@ -707,11 +712,6 @@ namespace unicorn std::vector> hooks_{}; std::unordered_map mmio_{}; - // gs & fs base (Fix unicorn Bug?) - mutable uint64_t preserved_gs_base_{0}; - mutable uint64_t preserved_fs_base_{0}; - mutable uint16_t current_reg_cs_{0x33}; - static uint64_t calc_end_address(const uint64_t address, uint64_t size) { if (size == 0) diff --git a/src/emulator/arch_emulator.hpp b/src/emulator/arch_emulator.hpp index f20283a1..2177497d 100644 --- a/src/emulator/arch_emulator.hpp +++ b/src/emulator/arch_emulator.hpp @@ -35,6 +35,7 @@ struct x86_emulator : arch_emulator using pointer_type = typename Traits::pointer_type; virtual void set_segment_base(register_type base, pointer_type value) = 0; + virtual pointer_type get_segment_base(register_type base) = 0; virtual void load_gdt(pointer_type address, uint32_t limit) = 0; }; diff --git a/src/emulator/hook_interface.hpp b/src/emulator/hook_interface.hpp index 7c3c6f1b..3edbb08c 100644 --- a/src/emulator/hook_interface.hpp +++ b/src/emulator/hook_interface.hpp @@ -16,10 +16,11 @@ enum class instruction_hook_continuation : bool skip_instruction = true, }; -enum class memory_violation_continuation : bool +enum class memory_violation_continuation : uint8_t { - stop = false, - resume = true, + stop, + resume, + restart, }; enum class memory_violation_type : uint8_t diff --git a/src/windows-emulator/emulator_thread.cpp b/src/windows-emulator/emulator_thread.cpp index c11daa9e..844b2dd3 100644 --- a/src/windows-emulator/emulator_thread.cpp +++ b/src/windows-emulator/emulator_thread.cpp @@ -19,7 +19,7 @@ namespace descriptor |= ((base & 0xFF0000) << 16); // Base[23:16] descriptor |= (0xF3ULL << 40); // P=1, DPL=3, S=1, Type=3 (Data RW Accessed) descriptor |= (static_cast((limit & 0xF0000) >> 16) << 48); // Limit[19:16] - descriptor |= (0x40ULL << 52); // G=0 (byte), D=1 (32-bit), L=0, AVL=0 + descriptor |= (0x40ULL << 48); // G=0 (byte), D=1 (32-bit), L=0, AVL=0 descriptor |= ((base & 0xFF000000) << 32); // Base[31:24] // Write the updated descriptor to GDT index 10 (selector 0x53) @@ -283,14 +283,10 @@ emulator_thread::emulator_thread(memory_manager& memory, const process_context& // Note: CurrentLocale and other fields will be initialized by WOW64 runtime }); - // CRITICAL: Setup FS segment (0x53) to point to 32-bit TEB for accurate WOW64 emulation - // This mimics what Windows kernel does during NtCreateUserProcess for WOW64 processes - // Without this, FS:0 won't correctly access the 32-bit TEB - // - // NOTE: We cannot use set_segment_base() here because that sets the FS_BASE MSR - // which is for 64-bit flat addressing. 32-bit code uses actual GDT-based segmentation - // with selector 0x53, so we must modify the GDT entry directly. - setup_wow64_fs_segment(memory, teb32_addr); + this->teb64->access([&](TEB64& teb_obj) { + // teb64.ExceptionList initially points to teb32 + teb_obj.NtTib.ExceptionList = teb32_addr; + }); // Use the allocator to reserve memory for CONTEXT64 this->wow64_cpu_reserved = emulator_object{memory, wow64_cpureserved_base}; @@ -489,3 +485,14 @@ void emulator_thread::setup_registers(x86_64_emulator& emu, const process_contex emu.reg(x86_register::rdx, context.ntdll_image_base); emu.reg(x86_register::rip, context.ldr_initialize_thunk); } + +void emulator_thread::refresh_execution_context(x86_64_emulator& emu) const +{ + (void)emu; + + if (this->teb32.has_value()) + { + // Refresh GDT entry for FS selector on context switch + setup_wow64_fs_segment(*this->memory_ptr, this->teb32->value()); + } +} diff --git a/src/windows-emulator/emulator_thread.hpp b/src/windows-emulator/emulator_thread.hpp index 78d13ba3..31ab7ff4 100644 --- a/src/windows-emulator/emulator_thread.hpp +++ b/src/windows-emulator/emulator_thread.hpp @@ -122,6 +122,7 @@ class emulator_thread : public ref_counted_object void restore(x86_64_emulator& emu) const { emu.restore_registers(this->last_registers); + this->refresh_execution_context(emu); } void setup_if_necessary(x86_64_emulator& emu, const process_context& context) @@ -242,6 +243,7 @@ class emulator_thread : public ref_counted_object private: void setup_registers(x86_64_emulator& emu, const process_context& context) const; + void refresh_execution_context(x86_64_emulator& emu) const; void release() { diff --git a/src/windows-emulator/windows_emulator.cpp b/src/windows-emulator/windows_emulator.cpp index e31d245b..c4057fc4 100644 --- a/src/windows-emulator/windows_emulator.cpp +++ b/src/windows-emulator/windows_emulator.cpp @@ -517,6 +517,18 @@ void windows_emulator::setup_hooks() this->emu().hook_memory_violation( [&](const uint64_t address, const size_t size, const memory_operation operation, const memory_violation_type type) { + if (this->emu().reg(x86_register::cs) == 0x33) + { + // loading gs selector only works in 64-bit mode + const auto required_gs_base = this->current_thread().gs_segment->get_base(); + const auto actual_gs_base = this->emu().get_segment_base(x86_register::gs); + if (actual_gs_base != required_gs_base) + { + this->emu().set_segment_base(x86_register::gs, required_gs_base); + return memory_violation_continuation::restart; + } + } + auto region = this->memory.get_region_info(address); if (region.permissions.is_guarded()) {