mirror of
https://github.com/momo5502/emulator.git
synced 2026-01-19 03:33:56 +00:00
Merge pull request #12 from momo5502/task/thread-support
Add basic threading support
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_{};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -8,12 +8,44 @@
|
||||
|
||||
#include <x64_emulator.hpp>
|
||||
|
||||
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<uint32_t> exit_status{};
|
||||
std::optional<handle> await_object{};
|
||||
|
||||
std::optional<emulator_allocator> gs_segment;
|
||||
std::optional<emulator_object<TEB>> teb;
|
||||
|
||||
std::vector<std::byte> 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<uint64_t> exception_rip{};
|
||||
|
||||
emulator_object<TEB> teb;
|
||||
emulator_allocator base_allocator;
|
||||
|
||||
emulator_object<PEB> peb;
|
||||
emulator_object<RTL_USER_PROCESS_PARAMETERS> process_params;
|
||||
emulator_object<KUSER_SHARED_DATA> 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<handle_types::semaphore, semaphore> semaphores{};
|
||||
handle_store<handle_types::port, port> ports{};
|
||||
std::map<uint16_t, std::wstring> atoms{};
|
||||
emulator_allocator gs_segment;
|
||||
|
||||
std::vector<std::byte> default_register_set{};
|
||||
|
||||
uint32_t current_thread_id{0};
|
||||
handle_store<handle_types::thread, emulator_thread> 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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -161,6 +161,20 @@ namespace
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void write_attribute(emulator& emu, const PS_ATTRIBUTE& attribute, const T& value)
|
||||
{
|
||||
if (attribute.ReturnLength)
|
||||
{
|
||||
emulator_object<SIZE_T>{emu, attribute.ReturnLength}.write(sizeof(T));
|
||||
}
|
||||
|
||||
if (attribute.Size >= sizeof(T))
|
||||
{
|
||||
emulator_object<T>{emu, attribute.Value}.write(value);
|
||||
}
|
||||
}
|
||||
|
||||
NTSTATUS handle_NtQueryPerformanceCounter(const syscall_context&,
|
||||
const emulator_object<LARGE_INTEGER> performance_counter,
|
||||
const emulator_object<LARGE_INTEGER> 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<UNICODE_STRING> 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<wchar_t*>(reinterpret_cast<uint64_t>(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<THREAD_BASIC_INFORMATION> 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<ULONG> 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<GDI_SHARED_MEMORY>().ptr();
|
||||
peb.GdiSharedHandleTable = c.proc.base_allocator.reserve<GDI_SHARED_MEMORY>().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<uint64_t> thread_handle,
|
||||
const ACCESS_MASK /*desired_access*/,
|
||||
const emulator_object<OBJECT_ATTRIBUTES> /*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<PS_ATTRIBUTE_LIST> 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<PS_ATTRIBUTE> 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<LARGE_INTEGER> 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
|
||||
|
||||
|
||||
@@ -4,18 +4,7 @@
|
||||
|
||||
#include <unicorn_x64_emulator.hpp>
|
||||
|
||||
#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<KUSER_SHARED_DATA> 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<std::wstring>& 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<TEB>();
|
||||
context.peb = gs.reserve<PEB>();
|
||||
|
||||
context.teb.access([&](TEB& teb)
|
||||
{
|
||||
teb.ClientId.UniqueProcess = reinterpret_cast<HANDLE>(1);
|
||||
teb.ClientId.UniqueThread = reinterpret_cast<HANDLE>(2);
|
||||
teb.NtTib.StackLimit = reinterpret_cast<void*>(STACK_ADDRESS);
|
||||
teb.NtTib.StackBase = reinterpret_cast<void*>((STACK_ADDRESS + STACK_SIZE));
|
||||
teb.NtTib.Self = &context.teb.ptr()->NtTib;
|
||||
teb.ProcessEnvironmentBlock = context.peb.ptr();
|
||||
});
|
||||
context.peb = allocator.reserve<PEB>();
|
||||
|
||||
/* 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<RTL_USER_PROCESS_PARAMETERS>();
|
||||
context.process_params = allocator.reserve<RTL_USER_PROCESS_PARAMETERS>();
|
||||
|
||||
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<uint32_t>(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<TEB>();
|
||||
|
||||
this->teb->access([&](TEB& teb_obj)
|
||||
{
|
||||
teb_obj.ClientId.UniqueProcess = reinterpret_cast<HANDLE>(1);
|
||||
teb_obj.ClientId.UniqueThread = reinterpret_cast<HANDLE>(static_cast<uint64_t>(this->id));
|
||||
teb_obj.NtTib.StackLimit = reinterpret_cast<void*>(this->stack_base);
|
||||
teb_obj.NtTib.StackBase = reinterpret_cast<void*>(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<CONTEXT>(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<x64_emulator> 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<CONTEXT>(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();
|
||||
|
||||
|
||||
@@ -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<x64_emulator> emu_{};
|
||||
|
||||
Reference in New Issue
Block a user