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:
Maurice Heumann
2025-12-30 17:56:30 +01:00
committed by GitHub
14 changed files with 241 additions and 159 deletions

View File

@@ -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;
}
});
}