mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-11 16:46:16 +00:00
Fix thread support (#640)
This PR fixes several things related to threads emulation:
1. Support `SameTebFlags.InitialThread`
This flag is needed to support emulation of .net executables (not yet
fully supported) that don't have an entry point set in `PE` header. This
applies to both `PE32` and `PE64` executables. If `InitialThread` is set
the loader substitutes an entry point of the .net executable with
`mscoree.dll!_CorExeMain`.
2. Fix static thread local storage for `WOW64`
This fix resolves `shell32.dll` initialization on `WOW64`. This fix also
uses correct structure and field names that are obtained from the
corresponding `.pdb` files.
3. Fix dynamic thread local storage for `WOW64`
4. Fix setting argument of a `WOW64` thread start proc
5. Fix creating suspended thread and parse create_flags
Currently creating suspended thread doesn't work because
`NtCreateThreadEx` handler uses invalid flag `CREATE_SUSPENDED`. This PR
fixes that, and moreover it carefully parses create_flags of the
`NtCreateThreadEx` call.
6. Fix `FS` and `GS` handling
This PR fixes several problems with `GS` and `FS` segments:
* Wrong GDT descriptor for selector 0x53
* Update GDT descriptor for selector 0x53 for a `WOW64` process every
context switch like Windows does
* Set `GS` base when `GS` segment register is updated in 64-bit code
(code selector is `0x33`). When `GS` segment register is loaded with
correct selector (`0x2b`) `GS` base is set to 0. So, when the code
accesses something like `gs:[0]`, a page fault occurs. `KiPageFault`
handles this situation and sets correct `GS` base.
Also, take into account that `teb64.ExceptionList` initially contains
`teb32` address for `WOW64` process. This is used to setup `FS` base
when `wrfsbase` instruction is available. We can enable this instruction
using `kusd.ProcessorFeatures.arr[PF_RDWRFSGSBASE_AVAILABLE] = 1;` and
this work perfectly with `unicorn` backend. Unfortunately `icicle`
backend does not support `wrfsbase`, so I don't enable this instruction
by default.
This commit is contained in:
@@ -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);
|
||||
@@ -348,7 +363,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);
|
||||
|
||||
@@ -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;
|
||||
@@ -495,18 +517,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;
|
||||
}
|
||||
@@ -515,7 +538,7 @@ namespace unicorn
|
||||
this->violation_ip_ = std::nullopt;
|
||||
}
|
||||
|
||||
if (has_ip_changed)
|
||||
if (set_ip)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -538,17 +561,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));
|
||||
@@ -657,11 +671,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
|
||||
@@ -674,10 +683,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
|
||||
@@ -712,11 +717,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)
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define WIN32_CLIENT_INFO_LENGTH 62
|
||||
#define STATIC_UNICODE_BUFFER_LENGTH 261
|
||||
#define TLS_MINIMUM_AVAILABLE 64
|
||||
#define TLS_EXPANSION_SLOTS 1024
|
||||
|
||||
#ifndef OS_WINDOWS
|
||||
#define PF_FLOATING_POINT_PRECISION_ERRATA 0
|
||||
@@ -970,6 +971,13 @@ union TEB_CROSS_TEB_FLAGS_UNION
|
||||
USHORT SpareCrossTebBits : 16;
|
||||
};
|
||||
|
||||
constexpr auto THREAD_CREATE_FLAGS_CREATE_SUSPENDED = 0x1;
|
||||
constexpr auto THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH = 0x2;
|
||||
constexpr auto THREAD_CREATE_FLAGS_HIDE_FROM_DEBUGGER = 0x4;
|
||||
constexpr auto THREAD_CREATE_FLAGS_LOADER_WORKER = 0x10;
|
||||
constexpr auto THREAD_CREATE_FLAGS_SKIP_LOADER_INIT = 0x20;
|
||||
constexpr auto THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE = 0x40;
|
||||
|
||||
union TEB_SAME_TEB_FLAGS_UNION
|
||||
{
|
||||
USHORT SameTebFlags;
|
||||
@@ -1671,45 +1679,45 @@ typedef struct _KERNEL_USER_TIMES
|
||||
LARGE_INTEGER UserTime;
|
||||
} KERNEL_USER_TIMES, *PKERNEL_USER_TIMES;
|
||||
|
||||
struct THREAD_TLS_INFO
|
||||
struct THREAD_TLS_INFORMATION
|
||||
{
|
||||
ULONG Flags;
|
||||
uint32_t _Padding;
|
||||
|
||||
union
|
||||
{
|
||||
EmulatorTraits<Emu64>::PVOID TlsVector;
|
||||
EmulatorTraits<Emu64>::PVOID TlsModulePointer;
|
||||
EmulatorTraits<Emu64>::PVOID NewTlsData;
|
||||
EmulatorTraits<Emu64>::PVOID OldTlsData;
|
||||
};
|
||||
|
||||
EMULATOR_CAST(std::uint64_t, ULONG_PTR) ThreadId;
|
||||
uint64_t ThreadId;
|
||||
};
|
||||
|
||||
static_assert(sizeof(THREAD_TLS_INFO) == 0x18);
|
||||
static_assert(sizeof(THREAD_TLS_INFORMATION) == 0x18);
|
||||
|
||||
typedef enum _PROCESS_TLS_INFORMATION_TYPE
|
||||
enum PROCESS_TLS_INFORMATION_TYPE
|
||||
{
|
||||
ProcessTlsReplaceIndex,
|
||||
ProcessTlsReplaceVector,
|
||||
MaxProcessTlsOperation
|
||||
} PROCESS_TLS_INFORMATION_TYPE, *PPROCESS_TLS_INFORMATION_TYPE;
|
||||
};
|
||||
|
||||
struct PROCESS_TLS_INFO
|
||||
struct PROCESS_TLS_INFORMATION
|
||||
{
|
||||
ULONG Unknown;
|
||||
PROCESS_TLS_INFORMATION_TYPE TlsRequest;
|
||||
ULONG Flags;
|
||||
PROCESS_TLS_INFORMATION_TYPE OperationType;
|
||||
ULONG ThreadDataCount;
|
||||
|
||||
union
|
||||
{
|
||||
ULONG TlsIndex;
|
||||
ULONG TlsVectorLength;
|
||||
ULONG PreviousCount;
|
||||
};
|
||||
|
||||
THREAD_TLS_INFO ThreadData[1];
|
||||
THREAD_TLS_INFORMATION ThreadData[1];
|
||||
};
|
||||
|
||||
static_assert(sizeof(PROCESS_TLS_INFO) - sizeof(THREAD_TLS_INFO) == 0x10);
|
||||
static_assert(sizeof(PROCESS_TLS_INFORMATION) == 0x28);
|
||||
|
||||
struct EMU_GENERIC_MAPPING
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -116,15 +116,18 @@ namespace
|
||||
}
|
||||
|
||||
emulator_thread::emulator_thread(memory_manager& memory, const process_context& context, const uint64_t start_address,
|
||||
const uint64_t argument, const uint64_t stack_size, const bool suspended, const uint32_t id)
|
||||
const uint64_t argument, const uint64_t stack_size, const uint32_t create_flags, const uint32_t id,
|
||||
const bool initial_thread)
|
||||
: memory_ptr(&memory),
|
||||
// stack_size(page_align_up(std::max(stack_size, static_cast<uint64_t>(STACK_SIZE)))),
|
||||
start_address(start_address),
|
||||
argument(argument),
|
||||
id(id),
|
||||
suspended(suspended),
|
||||
create_flags(create_flags),
|
||||
last_registers(context.default_register_set)
|
||||
{
|
||||
this->suspended = create_flags & THREAD_CREATE_FLAGS_CREATE_SUSPENDED;
|
||||
|
||||
// native 64-bit
|
||||
if (!context.is_wow64_process)
|
||||
{
|
||||
@@ -152,6 +155,10 @@ emulator_thread::emulator_thread(memory_manager& memory, const process_context&
|
||||
teb_obj.NtTib.Self = this->teb64->value();
|
||||
teb_obj.CurrentLocale = 0x409;
|
||||
teb_obj.ProcessEnvironmentBlock = context.peb64.value();
|
||||
teb_obj.SameTebFlags.InitialThread = initial_thread;
|
||||
teb_obj.SameTebFlags.SkipThreadAttach = (create_flags & THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH) ? 1 : 0;
|
||||
teb_obj.SameTebFlags.LoaderWorker = (create_flags & THREAD_CREATE_FLAGS_LOADER_WORKER) ? 1 : 0;
|
||||
teb_obj.SameTebFlags.SkipLoaderInit = (create_flags & THREAD_CREATE_FLAGS_SKIP_LOADER_INIT) ? 1 : 0;
|
||||
});
|
||||
|
||||
return;
|
||||
@@ -209,6 +216,10 @@ emulator_thread::emulator_thread(memory_manager& memory, const process_context&
|
||||
teb_obj.CurrentLocale = 0x409;
|
||||
|
||||
teb_obj.ProcessEnvironmentBlock = context.peb64.value();
|
||||
teb_obj.SameTebFlags.InitialThread = initial_thread;
|
||||
teb_obj.SameTebFlags.SkipThreadAttach = (create_flags & THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH) ? 1 : 0;
|
||||
teb_obj.SameTebFlags.LoaderWorker = (create_flags & THREAD_CREATE_FLAGS_LOADER_WORKER) ? 1 : 0;
|
||||
teb_obj.SameTebFlags.SkipLoaderInit = (create_flags & THREAD_CREATE_FLAGS_SKIP_LOADER_INIT) ? 1 : 0;
|
||||
teb_obj.StaticUnicodeString.MaximumLength = sizeof(teb_obj.StaticUnicodeBuffer);
|
||||
teb_obj.StaticUnicodeString.Buffer = this->teb64->value() + offsetof(TEB64, StaticUnicodeBuffer);
|
||||
|
||||
@@ -264,18 +275,18 @@ emulator_thread::emulator_thread(memory_manager& memory, const process_context&
|
||||
}
|
||||
|
||||
teb32_obj.WowTebOffset = -0x2000;
|
||||
teb32_obj.InitialThread = initial_thread;
|
||||
teb32_obj.SkipThreadAttach = (create_flags & THREAD_CREATE_FLAGS_SKIP_THREAD_ATTACH) ? 1 : 0;
|
||||
teb32_obj.LoaderWorker = (create_flags & THREAD_CREATE_FLAGS_LOADER_WORKER) ? 1 : 0;
|
||||
teb32_obj.SkipLoaderInit = (create_flags & THREAD_CREATE_FLAGS_SKIP_LOADER_INIT) ? 1 : 0;
|
||||
|
||||
// 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};
|
||||
@@ -439,6 +450,7 @@ void emulator_thread::setup_registers(x86_64_emulator& emu, const process_contex
|
||||
if (context.rtl_user_thread_start32.has_value())
|
||||
{
|
||||
ctx.Context.Eip = static_cast<uint32_t>(context.rtl_user_thread_start32.value());
|
||||
ctx.Context.Ebx = static_cast<uint32_t>(this->argument);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -473,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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class emulator_thread : public ref_counted_object
|
||||
}
|
||||
|
||||
emulator_thread(memory_manager& memory, const process_context& context, uint64_t start_address, uint64_t argument, uint64_t stack_size,
|
||||
bool suspended, uint32_t id);
|
||||
uint32_t create_flags, uint32_t id, bool initial_thread);
|
||||
|
||||
emulator_thread(const emulator_thread&) = delete;
|
||||
emulator_thread& operator=(const emulator_thread&) = delete;
|
||||
@@ -86,6 +86,7 @@ class emulator_thread : public ref_counted_object
|
||||
bool await_any{false};
|
||||
bool waiting_for_alert{false};
|
||||
bool alerted{false};
|
||||
uint32_t create_flags{0};
|
||||
uint32_t suspended{0};
|
||||
std::optional<std::chrono::steady_clock::time_point> await_time{};
|
||||
|
||||
@@ -123,6 +124,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)
|
||||
@@ -166,6 +168,7 @@ class emulator_thread : public ref_counted_object
|
||||
buffer.write(this->waiting_for_alert);
|
||||
buffer.write(this->alerted);
|
||||
|
||||
buffer.write(this->create_flags);
|
||||
buffer.write(this->suspended);
|
||||
buffer.write_optional(this->await_time);
|
||||
|
||||
@@ -213,6 +216,7 @@ class emulator_thread : public ref_counted_object
|
||||
buffer.read(this->waiting_for_alert);
|
||||
buffer.read(this->alerted);
|
||||
|
||||
buffer.read(this->create_flags);
|
||||
buffer.read(this->suspended);
|
||||
buffer.read_optional(this->await_time);
|
||||
|
||||
@@ -245,6 +249,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()
|
||||
{
|
||||
|
||||
@@ -224,12 +224,6 @@ mapped_module* module_manager::map_module_core(const pe_detection_result& detect
|
||||
const auto image_base = mod.image_base;
|
||||
const auto entry = this->modules_.try_emplace(image_base, std::move(mod));
|
||||
this->last_module_cache_ = this->modules_.end();
|
||||
|
||||
// TODO: Patch shell32.dll entry point to prevent TLS storage issues
|
||||
// The shell32.dll module in SysWOW64 has TLS storage that fails, causing crashes
|
||||
// This is a temporary workaround until the root cause is investigated and fixed
|
||||
this->patch_shell32_entry_point_if_needed(entry.first->second);
|
||||
|
||||
this->callbacks_->on_module_load(entry.first->second);
|
||||
return &entry.first->second;
|
||||
}
|
||||
@@ -573,49 +567,3 @@ bool module_manager::unmap(const uint64_t address)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void module_manager::patch_shell32_entry_point_if_needed(mapped_module& mod)
|
||||
{
|
||||
// Only patch shell32.dll in SysWOW64 directory (32-bit)
|
||||
// Convert module name to lowercase for case-insensitive comparison
|
||||
std::string module_name_lower = mod.name;
|
||||
std::transform(module_name_lower.begin(), module_name_lower.end(), module_name_lower.begin(),
|
||||
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (module_name_lower != "shell32.dll")
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is the SysWOW64 version by examining if it's a 32-bit module
|
||||
// Convert path to lowercase for case-insensitive comparison
|
||||
std::string path_str = mod.path.string();
|
||||
std::transform(path_str.begin(), path_str.end(), path_str.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||
if (path_str.find("syswow64") == std::string::npos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (mod.entry_point == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the page containing the entry point
|
||||
const auto entry_page_start = mod.entry_point & ~0xFFFULL;
|
||||
const auto page_size = 0x1000;
|
||||
|
||||
// Temporarily change memory protection to writable
|
||||
nt_memory_permission mem_permisson(memory_permission::none);
|
||||
if (!this->memory_->protect_memory(entry_page_start, page_size, memory_permission::all, &mem_permisson))
|
||||
{
|
||||
return; // Failed to change protection
|
||||
}
|
||||
|
||||
// Write the ret 0Ch instruction at the entry point (0xB8, 0x01, 0x00, 0x00, 0x00, 0xC2, 0x0C, 0x00)
|
||||
// This makes DllMain return immediately without executing CRT startup
|
||||
constexpr std::array<uint8_t, 8> patch_bytes = {0xB8, 0x01, 0x00, 0x00, 0x00, 0xC2, 0x0C, 0x00}; // mov eax, 1 && ret 0Ch
|
||||
this->memory_->write_memory(mod.entry_point, patch_bytes.data(), patch_bytes.size());
|
||||
|
||||
// Restore the original memory protection
|
||||
this->memory_->protect_memory(entry_page_start, page_size, mem_permisson, nullptr);
|
||||
}
|
||||
|
||||
@@ -183,9 +183,6 @@ class module_manager
|
||||
mapped_module* map_module_core(const pe_detection_result& detection_result, const std::function<mapped_module()>& mapper,
|
||||
const logger& logger, bool is_static);
|
||||
|
||||
// Shell32.dll entry point patching to prevent TLS storage issues
|
||||
void patch_shell32_entry_point_if_needed(mapped_module& mod);
|
||||
|
||||
// Execution mode detection
|
||||
execution_mode detect_execution_mode(const windows_path& executable_path, const logger& logger);
|
||||
|
||||
|
||||
@@ -528,9 +528,9 @@ generic_handle_store* process_context::get_handle_store(const handle handle)
|
||||
}
|
||||
|
||||
handle process_context::create_thread(memory_manager& memory, const uint64_t start_address, const uint64_t argument,
|
||||
const uint64_t stack_size, const bool suspended)
|
||||
const uint64_t stack_size, const uint32_t create_flags, const bool initial_thread)
|
||||
{
|
||||
emulator_thread t{memory, *this, start_address, argument, stack_size, suspended, ++this->spawned_thread_count};
|
||||
emulator_thread t{memory, *this, start_address, argument, stack_size, create_flags, ++this->spawned_thread_count, initial_thread};
|
||||
auto [h, thr] = this->threads.store_and_get(std::move(t));
|
||||
this->callbacks_->on_thread_create(h, *thr);
|
||||
return h;
|
||||
|
||||
@@ -74,7 +74,8 @@ struct process_context
|
||||
const mapped_module& executable, const mapped_module& ntdll, const apiset::container& apiset_container,
|
||||
const mapped_module* ntdll32 = nullptr);
|
||||
|
||||
handle create_thread(memory_manager& memory, uint64_t start_address, uint64_t argument, uint64_t stack_size, bool suspended);
|
||||
handle create_thread(memory_manager& memory, uint64_t start_address, uint64_t argument, uint64_t stack_size, uint32_t create_flags,
|
||||
bool initial_thread = false);
|
||||
|
||||
std::optional<uint16_t> find_atom(std::u16string_view name);
|
||||
uint16_t add_or_find_atom(std::u16string name);
|
||||
|
||||
@@ -214,15 +214,17 @@ namespace syscalls
|
||||
|
||||
if (info_class == ProcessTlsInformation)
|
||||
{
|
||||
constexpr auto thread_data_offset = offsetof(PROCESS_TLS_INFO, ThreadData);
|
||||
if (process_information_length < thread_data_offset)
|
||||
if (process_information_length < sizeof(PROCESS_TLS_INFORMATION) ||
|
||||
(process_information_length - (sizeof(PROCESS_TLS_INFORMATION) - sizeof(THREAD_TLS_INFORMATION))) %
|
||||
sizeof(THREAD_TLS_INFORMATION))
|
||||
{
|
||||
return STATUS_BUFFER_OVERFLOW;
|
||||
return STATUS_INFO_LENGTH_MISMATCH;
|
||||
}
|
||||
|
||||
const emulator_object<THREAD_TLS_INFO> data{c.emu, process_information + thread_data_offset};
|
||||
constexpr auto thread_data_offset = offsetof(PROCESS_TLS_INFORMATION, ThreadData);
|
||||
const emulator_object<THREAD_TLS_INFORMATION> data{c.emu, process_information + thread_data_offset};
|
||||
|
||||
PROCESS_TLS_INFO tls_info{};
|
||||
PROCESS_TLS_INFORMATION tls_info{};
|
||||
c.emu.read_memory(process_information, &tls_info, thread_data_offset);
|
||||
|
||||
for (uint32_t i = 0; i < tls_info.ThreadDataCount; ++i)
|
||||
@@ -242,38 +244,78 @@ namespace syscalls
|
||||
|
||||
entry.Flags = 2;
|
||||
|
||||
thread_iterator->second.teb64->access([&](TEB64& teb) {
|
||||
const auto is_wow64 = c.win_emu.process.is_wow64_process;
|
||||
const auto& thread = thread_iterator->second;
|
||||
|
||||
thread.teb64->access([&](TEB64& teb) {
|
||||
entry.ThreadId = teb.ClientId.UniqueThread;
|
||||
|
||||
const auto tls_vector = teb.ThreadLocalStoragePointer;
|
||||
constexpr auto ptr_size = sizeof(EmulatorTraits<Emu64>::PVOID);
|
||||
uint64_t tls_vector = teb.ThreadLocalStoragePointer;
|
||||
const auto ptr_size = is_wow64 ? sizeof(EmulatorTraits<Emu32>::PVOID) : sizeof(EmulatorTraits<Emu64>::PVOID);
|
||||
|
||||
if (is_wow64)
|
||||
{
|
||||
if (!thread.teb32.has_value())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
thread.teb32->access([&tls_vector](const TEB32& teb32) { tls_vector = teb32.ThreadLocalStoragePointer; });
|
||||
}
|
||||
|
||||
if (!tls_vector)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (tls_info.TlsRequest == ProcessTlsReplaceIndex)
|
||||
if (tls_info.OperationType == ProcessTlsReplaceIndex)
|
||||
{
|
||||
const auto tls_entry_ptr = tls_vector + (tls_info.TlsIndex * ptr_size);
|
||||
uint64_t old_entry{};
|
||||
|
||||
const auto old_entry = c.emu.read_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr);
|
||||
c.emu.write_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr, entry.TlsModulePointer);
|
||||
|
||||
entry.TlsModulePointer = old_entry;
|
||||
}
|
||||
else if (tls_info.TlsRequest == ProcessTlsReplaceVector)
|
||||
{
|
||||
const auto new_tls_vector = entry.TlsVector;
|
||||
|
||||
for (uint32_t index = 0; index < tls_info.TlsVectorLength; ++index)
|
||||
if (is_wow64)
|
||||
{
|
||||
const auto old_entry = c.emu.read_memory<uint64_t>(tls_vector + index * ptr_size);
|
||||
c.emu.write_memory(new_tls_vector + index * ptr_size, old_entry);
|
||||
old_entry = c.emu.read_memory<EmulatorTraits<Emu32>::PVOID>(tls_entry_ptr);
|
||||
c.emu.write_memory<EmulatorTraits<Emu32>::PVOID>(tls_entry_ptr, static_cast<uint32_t>(entry.NewTlsData));
|
||||
}
|
||||
else
|
||||
{
|
||||
old_entry = c.emu.read_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr);
|
||||
c.emu.write_memory<EmulatorTraits<Emu64>::PVOID>(tls_entry_ptr, entry.NewTlsData);
|
||||
}
|
||||
|
||||
teb.ThreadLocalStoragePointer = new_tls_vector;
|
||||
entry.TlsVector = tls_vector;
|
||||
entry.OldTlsData = old_entry;
|
||||
}
|
||||
else if (tls_info.OperationType == ProcessTlsReplaceVector)
|
||||
{
|
||||
const auto new_tls_vector = entry.NewTlsData;
|
||||
|
||||
for (uint32_t index = 0; index < tls_info.PreviousCount; ++index)
|
||||
{
|
||||
if (is_wow64)
|
||||
{
|
||||
const auto old_entry = c.emu.read_memory<uint32_t>(tls_vector + (index * ptr_size));
|
||||
c.emu.write_memory(new_tls_vector + (index * ptr_size), old_entry);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto old_entry = c.emu.read_memory<uint64_t>(tls_vector + (index * ptr_size));
|
||||
c.emu.write_memory(new_tls_vector + (index * ptr_size), old_entry);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_wow64)
|
||||
{
|
||||
thread.teb32->access([&new_tls_vector](TEB32& teb32) {
|
||||
teb32.ThreadLocalStoragePointer = static_cast<uint32_t>(new_tls_vector);
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
teb.ThreadLocalStoragePointer = new_tls_vector;
|
||||
}
|
||||
|
||||
entry.OldTlsData = tls_vector;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,17 +103,44 @@ namespace syscalls
|
||||
|
||||
for (const auto& t : c.proc.threads | std::views::values)
|
||||
{
|
||||
t.teb64->access([&](TEB64& teb) {
|
||||
if (tls_cell < TLS_MINIMUM_AVAILABLE)
|
||||
if (tls_cell < TLS_MINIMUM_AVAILABLE)
|
||||
{
|
||||
if (c.proc.is_wow64_process)
|
||||
{
|
||||
teb.TlsSlots.arr[tls_cell] = 0;
|
||||
if (t.teb32.has_value())
|
||||
{
|
||||
t.teb32->access([&](TEB32& teb32) { teb32.TlsSlots.arr[tls_cell] = 0; });
|
||||
}
|
||||
}
|
||||
else if (teb.TlsExpansionSlots)
|
||||
else
|
||||
{
|
||||
const emulator_object<emulator_pointer> expansion_slots(c.emu, teb.TlsExpansionSlots);
|
||||
expansion_slots.write(0, tls_cell - TLS_MINIMUM_AVAILABLE);
|
||||
t.teb64->access([&](TEB64& teb64) { teb64.TlsSlots.arr[tls_cell] = 0; });
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (tls_cell < TLS_MINIMUM_AVAILABLE + TLS_EXPANSION_SLOTS)
|
||||
{
|
||||
if (c.proc.is_wow64_process)
|
||||
{
|
||||
if (t.teb32.has_value())
|
||||
{
|
||||
t.teb32->access([&](TEB32& teb32) {
|
||||
if (teb32.TlsExpansionSlots)
|
||||
{
|
||||
c.emu.write_memory<uint32_t>(teb32.TlsExpansionSlots + (4 * tls_cell) - TLS_MINIMUM_AVAILABLE, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
t.teb64->access([&](TEB64& teb64) {
|
||||
if (teb64.TlsExpansionSlots)
|
||||
{
|
||||
c.emu.write_memory<uint64_t>(teb64.TlsExpansionSlots + (8 * tls_cell) - TLS_MINIMUM_AVAILABLE, 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
@@ -601,7 +628,7 @@ namespace syscalls
|
||||
return STATUS_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
const auto h = c.proc.create_thread(c.win_emu.memory, start_routine, argument, stack_size, create_flags & CREATE_SUSPENDED);
|
||||
const auto h = c.proc.create_thread(c.win_emu.memory, start_routine, argument, stack_size, create_flags);
|
||||
thread_handle.write(h);
|
||||
|
||||
if (!attribute_list)
|
||||
|
||||
@@ -371,7 +371,7 @@ void windows_emulator::setup_process(const application_settings& app_settings)
|
||||
this->dispatcher.setup(ntdll->exports, ntdll_data, win32u->exports, win32u_data);
|
||||
|
||||
const auto main_thread_id = context.create_thread(this->memory, this->mod_manager.executable->entry_point, 0,
|
||||
this->mod_manager.executable->size_of_stack_commit, false);
|
||||
this->mod_manager.executable->size_of_stack_commit, 0, true);
|
||||
|
||||
switch_to_thread(*this, main_thread_id);
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user