Fix FS and GS handling

This commit is contained in:
ssvine
2025-12-30 15:19:05 +03:00
parent 57ad277158
commit f25ee26c36
7 changed files with 93 additions and 53 deletions

View File

@@ -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<decltype(ptr)>(user);
const auto res = func(address, 1, static_cast<memory_operation>(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);

View File

@@ -21,6 +21,15 @@ namespace unicorn
static_assert(static_cast<uint32_t>(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<uint64_t>(value);
break;
case x86_register::gs:
case x86_register::gs_base:
msr_val.id = IA32_GS_BASE_MSR;
preserved_gs_base_ = static_cast<uint64_t>(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<uint64_t>(size), operation, violation) == memory_violation_continuation::resume;
const auto result = c(address, static_cast<uint64_t>(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<uint16_t>(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<void, uc_engine*, uint64_t, uint32_t> 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<std::byte> save_registers() const override
@@ -707,11 +712,6 @@ namespace unicorn
std::vector<std::unique_ptr<hook_object>> hooks_{};
std::unordered_map<uint64_t, mmio_callbacks> 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)

View File

@@ -35,6 +35,7 @@ struct x86_emulator : arch_emulator<Traits>
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;
};

View File

@@ -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

View File

@@ -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<uint64_t>((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<WOW64_CPURESERVED>{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());
}
}

View File

@@ -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()
{

View File

@@ -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<uint16_t>(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())
{