Prepare thread support

This commit is contained in:
momo5502
2024-10-16 19:19:36 +02:00
parent b756bf354f
commit 7cb1abb529
7 changed files with 277 additions and 101 deletions

View File

@@ -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);
@@ -86,43 +86,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);
}
}

View File

@@ -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_{};

View File

@@ -11,6 +11,7 @@ struct handle_types
directory,
semaphore,
port,
thread,
};
};

View File

@@ -106,15 +106,141 @@ 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
{
public:
emulator_thread() = default;
emulator_thread(x64_emulator& emu, std::vector<std::byte> default_register_set, const uint64_t start_address,
const uint64_t argument,
const uint64_t stack_size)
: emu_ptr(&emu)
, stack_size(page_align_up(stack_size))
, start_address(start_address)
, argument(argument)
, last_registers(std::move(default_register_set))
{
}
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{};
uint32_t id{};
uint64_t stack_base{};
uint64_t stack_size{};
uint64_t start_address{};
uint64_t argument{};
uint64_t executed_instructions{0};
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)
{
if (!this->teb.has_value())
{
this->setup(emu, context);
}
}
void serialize(utils::buffer_serializer&) const
{
// TODO
}
void deserialize(utils::buffer_deserializer&)
{
// TODO
}
private:
void setup(x64_emulator& emu, const process_context& context);
};
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 +250,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 +262,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 +273,11 @@ 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{};
handle_store<handle_types::thread, emulator_thread> threads{};
emulator_thread* active_thread{nullptr};
void serialize(utils::buffer_serializer& buffer) const
{
@@ -152,7 +285,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 +302,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 +312,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 +333,16 @@ 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, default_register_set, start_address, argument, stack_size};
const handle h = this->threads.store(std::move(t));
this->threads.get(h)->id = h.value.id;
return h;
}
};

View File

@@ -494,7 +494,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);
});
@@ -1136,8 +1136,8 @@ 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;
@@ -1556,7 +1556,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 +1881,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 +1902,23 @@ 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)
{
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);
return STATUS_SUCCESS;
}
}
void syscall_dispatcher::setup(const exported_symbols& ntdll_exports, const exported_symbols& win32u_exports)
@@ -2000,6 +2017,7 @@ void syscall_dispatcher::add_handlers()
add_handler(NtQueryInformationJobObject);
add_handler(NtSetSystemInformation);
add_handler(NtQueryInformationFile);
add_handler(NtCreateThreadEx);
#undef add_handler

View File

@@ -4,13 +4,12 @@
#include <unicorn_x64_emulator.hpp>
#define GS_SEGMENT_ADDR 0x6000000ULL
#define GS_SEGMENT_SIZE (20 << 20) // 20 MB
#define PEB_SEGMENT_SIZE (1 << 20) // 1 MB
#define GS_SEGMENT_SIZE (1 << 20) // 1 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
@@ -37,15 +36,16 @@ namespace
emu.reg(x64_register::rsp, sp);
}
void setup_stack(x64_emulator& emu, const uint64_t stack_base, const size_t stack_size)
uint64_t setup_stack(x64_emulator& emu, const size_t stack_size)
{
emu.allocate_memory(stack_base, stack_size, memory_permission::read_write);
const auto stack_base = emu.allocate_memory(stack_size, memory_permission::read_write);
const uint64_t stack_end = stack_base + stack_size;
emu.reg(x64_register::rsp, stack_end);
return stack_base;
}
emulator_allocator setup_gs_segment(x64_emulator& emu, const uint64_t segment_base, const uint64_t size)
emulator_allocator setup_gs_segment(x64_emulator& emu, const uint64_t size)
{
struct msr_value
{
@@ -53,13 +53,14 @@ namespace
uint64_t value;
};
const auto segment_base = emu.allocate_memory(size, memory_permission::read_write);
const msr_value value{
IA32_GS_BASE_MSR,
segment_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};
}
@@ -174,8 +175,6 @@ namespace
hash_entries_obj.write(hash_entry, i);
}
//watch_object(emu, api_set_map_obj);
return api_set_map_obj;
}
@@ -209,28 +208,14 @@ 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.
@@ -247,7 +232,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 +252,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;
@@ -449,6 +434,66 @@ 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 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 emulator_thread::setup(x64_emulator& emu, const process_context& context)
{
this->stack_base = setup_stack(emu, this->stack_size);
this->gs_segment = setup_gs_segment(emu, 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();
});
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;
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 +537,14 @@ 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);
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);
const auto main_thread_id = context.create_thread(emu, context.executable->entry_point, 0, 0x4000);
switch_to_thread(emu, context, main_thread_id);
}
void windows_emulator::setup_hooks()

View File

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