#include #include "std_include.hpp" #include "emulator_utils.hpp" #include "process_context.hpp" #include "syscalls.hpp" #include "reflect_extension.hpp" #include #include #include #include "gdb_stub.hpp" #include "module_mapper.hpp" #include "context_frame.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 bool use_gdb = false; struct breakpoint_key { size_t addr{}; size_t size{}; breakpoint_type type{}; bool operator==(const breakpoint_key& other) const { return this->addr == other.addr && this->size == other.size && this->type == other.type; } }; template <> struct std::hash { std::size_t operator()(const breakpoint_key& k) const noexcept { return ((std::hash()(k.addr) ^ (std::hash()(k.size) << 1)) >> 1) ^ (std::hash()(static_cast(k.type)) << 1); } }; namespace { template class type_info { public: type_info() { this->type_name_ = reflect::type_name(); reflect::for_each([this](auto I) { const auto member_name = reflect::member_name(); const auto member_offset = reflect::offset_of(); this->members_[member_offset] = member_name; }); } std::string get_member_name(const size_t offset) const { size_t last_offset{}; std::string_view last_member{}; for (const auto& member : this->members_) { if (offset == member.first) { return member.second; } if (offset < member.first) { const auto diff = offset - last_offset; return std::string(last_member) + "+" + std::to_string(diff); } last_offset = member.first; last_member = member.second; } return ""; } const std::string& get_type_name() const { return this->type_name_; } private: std::string type_name_{}; std::map members_{}; }; template void watch_object(x64_emulator& emu, emulator_object object) { const type_info info{}; emu.hook_memory_read(object.value(), object.size(), [i = std::move(info), object](const uint64_t address, size_t) { const auto offset = address - object.value(); printf("%s: %llX (%s)\n", i.get_type_name().c_str(), offset, i.get_member_name(offset).c_str()); }); } template emulator_object allocate_object_on_stack(x64_emulator& emu) { const auto old_sp = emu.reg(x64_register::rsp); const auto new_sp = align_down(old_sp - sizeof(CONTEXT), std::max(alignof(CONTEXT), alignof(x64_emulator::pointer_type))); emu.reg(x64_register::rsp, new_sp); return {emu, new_sp}; } 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) { struct msr_value { uint32_t id; uint64_t value; }; 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}; } emulator_object setup_kusd(x64_emulator& emu) { emu.allocate_memory(KUSD_ADDRESS, page_align_up(sizeof(KUSER_SHARED_DATA)), memory_permission::read); const emulator_object kusd_object{emu, KUSD_ADDRESS}; kusd_object.access([](KUSER_SHARED_DATA& kusd) { const auto& real_kusd = *reinterpret_cast(KUSD_ADDRESS); memcpy(&kusd, &real_kusd, sizeof(kusd)); kusd.ImageNumberLow = IMAGE_FILE_MACHINE_I386; kusd.ImageNumberHigh = IMAGE_FILE_MACHINE_AMD64; memset(&kusd.ProcessorFeatures, 0, sizeof(kusd.ProcessorFeatures)); // ... }); return kusd_object; } uint64_t copy_string(x64_emulator& emu, emulator_allocator& allocator, const void* base_ptr, const uint64_t offset, const size_t length) { if (!length) { return 0; } const auto length_to_allocate = length + 2; const auto str_obj = allocator.reserve(length_to_allocate); emu.write_memory(str_obj, static_cast(base_ptr) + offset, length); return str_obj; } ULONG copy_string_as_relative(x64_emulator& emu, emulator_allocator& allocator, const uint64_t result_base, const void* base_ptr, const uint64_t offset, const size_t length) { const auto address = copy_string(emu, allocator, base_ptr, offset, length); if (!address) { return 0; } assert(address > result_base); return static_cast(address - result_base); } emulator_object clone_api_set_map(x64_emulator& emu, emulator_allocator& allocator, const API_SET_NAMESPACE& orig_api_set_map) { const auto api_set_map_obj = allocator.reserve(); const auto ns_entries_obj = allocator.reserve(orig_api_set_map.Count); const auto hash_entries_obj = allocator.reserve(orig_api_set_map.Count); api_set_map_obj.access([&](API_SET_NAMESPACE& api_set) { api_set = orig_api_set_map; api_set.EntryOffset = static_cast(ns_entries_obj.value() - api_set_map_obj.value()); api_set.HashOffset = static_cast(hash_entries_obj.value() - api_set_map_obj.value()); }); const auto orig_ns_entries = offset_pointer(&orig_api_set_map, orig_api_set_map.EntryOffset); const auto orig_hash_entries = offset_pointer(&orig_api_set_map, orig_api_set_map.HashOffset); for (ULONG i = 0; i < orig_api_set_map.Count; ++i) { auto ns_entry = orig_ns_entries[i]; const auto hash_entry = orig_hash_entries[i]; ns_entry.NameOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(), &orig_api_set_map, ns_entry.NameOffset, ns_entry.NameLength); if (!ns_entry.ValueCount) { continue; } const auto values_obj = allocator.reserve(ns_entry.ValueCount); const auto orig_values = offset_pointer(&orig_api_set_map, ns_entry.ValueOffset); ns_entry.ValueOffset = static_cast(values_obj.value() - api_set_map_obj.value()); for (ULONG j = 0; j < ns_entry.ValueCount; ++j) { auto value = orig_values[j]; value.ValueOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(), &orig_api_set_map, value.ValueOffset, value.ValueLength); if (value.NameLength) { value.NameOffset = copy_string_as_relative(emu, allocator, api_set_map_obj.value(), &orig_api_set_map, value.NameOffset, value.NameLength); } values_obj.write(value, j); } ns_entries_obj.write(ns_entry, i); hash_entries_obj.write(hash_entry, i); } //watch_object(emu, api_set_map_obj); return api_set_map_obj; } emulator_object build_api_set_map(x64_emulator& emu, emulator_allocator& allocator) { const auto& orig_api_set_map = *NtCurrentTeb()->ProcessEnvironmentBlock->ApiSetMap; return clone_api_set_map(emu, allocator, orig_api_set_map); } emulator_allocator create_allocator(emulator& emu, const size_t size) { const auto base = emu.find_free_allocation_base(size); emu.allocate_memory(base, size, memory_permission::read_write); return emulator_allocator{emu, base, size}; } void setup_gdt(x64_emulator& emu) { constexpr uint64_t gdtr[4] = {0, GDT_ADDR, GDT_LIMIT, 0}; emu.write_register(x64_register::gdtr, &gdtr, sizeof(gdtr)); emu.allocate_memory(GDT_ADDR, GDT_LIMIT, memory_permission::read); emu.write_memory(GDT_ADDR + 6 * (sizeof(uint64_t)), 0xEFFE000000FFFF); emu.reg(x64_register::cs, 0x33); emu.write_memory(GDT_ADDR + 5 * (sizeof(uint64_t)), 0xEFF6000000FFFF); emu.reg(x64_register::ss, 0x2B); } process_context setup_context(x64_emulator& emu) { process_context context{}; 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); auto& gs = context.gs_segment; context.teb = gs.reserve(); context.peb = gs.reserve(); context.process_params = gs.reserve(); context.teb.access([&](TEB& teb) { teb.ClientId.UniqueProcess = reinterpret_cast(1); teb.ClientId.UniqueThread = reinterpret_cast(2); teb.NtTib.StackLimit = reinterpret_cast(STACK_ADDRESS); teb.NtTib.StackBase = reinterpret_cast((STACK_ADDRESS + STACK_SIZE)); teb.NtTib.Self = &context.teb.ptr()->NtTib; teb.ProcessEnvironmentBlock = context.peb.ptr(); }); context.process_params.access([&](RTL_USER_PROCESS_PARAMETERS& proc_params) { proc_params.Length = sizeof(proc_params); proc_params.Flags = 0x6001 | 0x80000000; // Prevent CsrClientConnectToServer proc_params.ConsoleHandle = reinterpret_cast(CONSOLE_HANDLE); proc_params.StandardOutput = reinterpret_cast(STDOUT_HANDLE); proc_params.StandardInput = reinterpret_cast(STDIN_HANDLE); proc_params.StandardError = proc_params.StandardOutput; gs.make_unicode_string(proc_params.CurrentDirectory.DosPath, L"C:\\Users\\mauri\\Desktop"); gs.make_unicode_string(proc_params.ImagePathName, L"C:\\Users\\mauri\\Desktop\\ConsoleApplication6.exe"); gs.make_unicode_string(proc_params.CommandLine, L"C:\\Users\\mauri\\Desktop\\ConsoleApplication6.exe"); }); context.peb.access([&](PEB& peb) { peb.ImageBaseAddress = nullptr; peb.ProcessHeap = nullptr; peb.ProcessHeaps = nullptr; peb.ProcessParameters = context.process_params.ptr(); peb.ApiSetMap = build_api_set_map(emu, allocator).ptr(); }); return context; } std::vector gdb_registers{ x64_register::rax, x64_register::rbx, x64_register::rcx, x64_register::rdx, x64_register::rsi, x64_register::rdi, x64_register::rbp, x64_register::rsp, x64_register::r8, x64_register::r9, x64_register::r10, x64_register::r11, x64_register::r12, x64_register::r13, x64_register::r14, x64_register::r15, x64_register::rip, x64_register::rflags, /*x64_register::cs, x64_register::ss, x64_register::ds, x64_register::es, x64_register::fs, x64_register::gs,*/ }; memory_operation map_breakpoint_type(const breakpoint_type type) { switch (type) { case breakpoint_type::software: case breakpoint_type::hardware_exec: return memory_operation::exec; case breakpoint_type::hardware_read: return memory_permission::read; case breakpoint_type::hardware_write: return memory_permission::write; case breakpoint_type::hardware_read_write: return memory_permission::read_write; default: throw std::runtime_error("Bad bp type"); } } class scoped_emulator_hook { public: scoped_emulator_hook() = default; scoped_emulator_hook(emulator& emu, emulator_hook* hook) : emu_(&emu) , hook_(hook) { } ~scoped_emulator_hook() { this->remove(); } scoped_emulator_hook(const scoped_emulator_hook&) = delete; scoped_emulator_hook& operator=(const scoped_emulator_hook&) = delete; scoped_emulator_hook(scoped_emulator_hook&& obj) noexcept { this->operator=(std::move(obj)); } scoped_emulator_hook& operator=(scoped_emulator_hook&& obj) noexcept { if (this != &obj) { this->remove(); this->emu_ = obj.emu_; this->hook_ = obj.hook_; obj.hook_ = {}; } return *this; } void remove() { if (this->hook_) { this->emu_->delete_hook(this->hook_); this->hook_ = {}; } } private: emulator* emu_{}; emulator_hook* hook_{}; }; class x64_gdb_stub_handler : public gdb_stub_handler { public: x64_gdb_stub_handler(x64_emulator& emu) : emu_(&emu) { } ~x64_gdb_stub_handler() override = default; gdb_action cont() override { try { this->emu_->start_from_ip(); } catch (const std::exception& e) { puts(e.what()); } return gdb_action::resume; } gdb_action stepi() override { try { this->emu_->start_from_ip({}, 1); } catch (const std::exception& e) { puts(e.what()); } return gdb_action::resume; } bool read_reg(const int regno, size_t* value) override { *value = 0; try { if (static_cast(regno) >= gdb_registers.size()) { return true; } this->emu_->read_register(gdb_registers[regno], value, sizeof(*value)); return true; } catch (...) { return true; } } bool write_reg(const int regno, const size_t value) override { try { if (static_cast(regno) >= gdb_registers.size()) { return true; } this->emu_->write_register(gdb_registers[regno], &value, sizeof(value)); return true; } catch (...) { return false; } } bool read_mem(const size_t addr, const size_t len, void* val) override { return this->emu_->try_read_memory(addr, val, len); } bool write_mem(const size_t addr, const size_t len, void* val) override { try { this->emu_->write_memory(addr, val, len); return true; } catch (...) { return false; } } bool set_bp(const breakpoint_type type, const size_t addr, const size_t size) override { try { this->hooks_[{addr, size, type}] = scoped_emulator_hook(*this->emu_, this->emu_->hook_memory_access( addr, size, map_breakpoint_type(type), [this](uint64_t, size_t, memory_operation) { this->on_interrupt(); })); return true; } catch (...) { return false; } } bool del_bp(const breakpoint_type type, const size_t addr, const size_t size) override { try { const auto entry = this->hooks_.find({addr, size, type}); if (entry == this->hooks_.end()) { return false; } this->hooks_.erase(entry); return true; } catch (...) { return false; } } void on_interrupt() override { this->emu_->stop(); } private: x64_emulator* emu_{}; std::unordered_map hooks_{}; }; uint64_t find_exported_function(const std::vector& exports, const std::string_view name) { for (auto& symbol : exports) { if (symbol.name == name) { return symbol.address; } } return 0; } emulator_object save_context_on_stack(x64_emulator& emu) { CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_ALL; context_frame::save(emu, ctx); const auto ctx_obj = allocate_object_on_stack(emu); ctx_obj.write(ctx); return ctx_obj; } using exception_record_map = std::unordered_map>; emulator_object save_exception_record_on_stack(x64_emulator& emu, const EXCEPTION_RECORD& record, exception_record_map& record_mapping) { const auto record_obj = allocate_object_on_stack(emu); record_obj.write(record); if (record.ExceptionRecord) { record_mapping[&record] = record_obj; emulator_object nested_record_obj{}; const auto nested_record = record_mapping.find(record.ExceptionRecord); if (nested_record != record_mapping.end()) { nested_record_obj = nested_record->second; } else { nested_record_obj = save_exception_record_on_stack(emu, *record.ExceptionRecord, record_mapping); } record_obj.access([&](EXCEPTION_RECORD& r) { r.ExceptionRecord = nested_record_obj.ptr(); }); } return record_obj; } emulator_object save_exception_record_on_stack(x64_emulator& emu, const EXCEPTION_RECORD& record) { exception_record_map record_mapping{}; return save_exception_record_on_stack(emu, record, record_mapping); } uint32_t map_violation_operation_to_parameter(const memory_operation operation) { switch (operation) { default: case memory_operation::read: return 0; case memory_operation::write: return 1; case memory_operation::exec: return 1; } } EXCEPTION_POINTERS create_access_violation_exception_pointers(x64_emulator& emu, const uint64_t address, const memory_operation operation) { EXCEPTION_RECORD record{}; memset(&record, 0, sizeof(record)); record.ExceptionCode = STATUS_ACCESS_VIOLATION; record.ExceptionFlags = 0; record.ExceptionRecord = nullptr; record.ExceptionAddress = reinterpret_cast(address); record.NumberParameters = 2; record.ExceptionInformation[0] = map_violation_operation_to_parameter(operation); record.ExceptionInformation[1] = address; EXCEPTION_POINTERS pointers{}; pointers.ContextRecord = save_context_on_stack(emu).ptr(); pointers.ExceptionRecord = save_exception_record_on_stack(emu, record).ptr(); return pointers; } void dispatch_exception_pointers(x64_emulator& emu, uint64_t dispatcher, const EXCEPTION_POINTERS pointers) { emu.reg(x64_register::rcx, reinterpret_cast(pointers.ExceptionRecord)); emu.reg(x64_register::rdx, reinterpret_cast(pointers.ContextRecord)); emu.reg(x64_register::rip, dispatcher); } void dispatch_access_violation(x64_emulator& emu, uint64_t dispatcher, const uint64_t address, const memory_operation operation) { const auto pointers = create_access_violation_exception_pointers(emu, address, operation); dispatch_exception_pointers(emu, dispatcher, pointers); } void run() { const auto emu = unicorn::create_x64_emulator(); auto context = setup_context(*emu); context.executable = *map_file(*emu, R"(C:\Users\mauri\Desktop\ConsoleApplication6.exe)"); context.peb.access([&](PEB& peb) { peb.ImageBaseAddress = reinterpret_cast(context.executable.image_base); }); context.ntdll = *map_file(*emu, R"(C:\Windows\System32\ntdll.dll)"); const auto ldr_initialize_thunk = find_exported_function(context.ntdll.exports, "LdrInitializeThunk"); const auto rtl_user_thread_start = find_exported_function(context.ntdll.exports, "RtlUserThreadStart"); const auto ki_user_exception_dispatcher = find_exported_function( context.ntdll.exports, "KiUserExceptionDispatcher"); syscall_dispatcher dispatcher{context.ntdll.exports}; emu->hook_instruction(x64_hookable_instructions::syscall, [&] { dispatcher.dispatch(*emu, context); return instruction_hook_continuation::skip_instruction; }); emu->hook_instruction(x64_hookable_instructions::rdtsc, [&] { emu->reg(x64_register::rax, 0x0011223344556677); return instruction_hook_continuation::skip_instruction; }); emu->hook_instruction(x64_hookable_instructions::invalid, [&] { const auto ip = emu->read_instruction_pointer(); printf("Invalid instruction at: %llX\n", ip); return instruction_hook_continuation::skip_instruction; }); emu->hook_interrupt([&](int interrupt) { printf("Interrupt: %i\n", interrupt); }); bool continue_execution = true; emu->hook_memory_violation([&](const uint64_t address, const size_t size, const memory_operation operation, const memory_violation_type type) { const auto permission = get_permission_string(operation); const auto ip = emu->read_instruction_pointer(); if (type == memory_violation_type::protection) { printf("Protection violation: %llX (%zX) - %s at %llX\n", address, size, permission.c_str(), ip); } else if (type == memory_violation_type::unmapped) { printf("Mapping violation: %llX (%zX) - %s at %llX\n", address, size, permission.c_str(), ip); } dispatch_access_violation(*emu, ki_user_exception_dispatcher, address, operation); continue_execution = true; return memory_violation_continuation::stop; }); /* watch_object(*emu, context.teb); watch_object(*emu, context.peb); watch_object(*emu, context.process_params); watch_object(*emu, context.kusd); */ /*emu->hook_memory_execution(0, std::numeric_limits::max(), [&](const uint64_t address, const size_t) { if (address == 0x1800D52F4) { //emu->stop(); } printf( "Inst: %16llX - RAX: %16llX - RBX: %16llX - RCX: %16llX - RDX: %16llX - R8: %16llX - R9: %16llX - RDI: %16llX - RSI: %16llX\n", address, emu->reg(x64_register::rax), emu->reg(x64_register::rbx), emu->reg(x64_register::rcx), emu->reg(x64_register::rdx), emu->reg(x64_register::r8), emu->reg(x64_register::r9), emu->reg(x64_register::rdi), emu->reg(x64_register::rsi)); });*/ CONTEXT ctx{}; ctx.ContextFlags = CONTEXT_ALL; 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(*emu); ctx_obj.write(ctx); 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); try { if (use_gdb) { puts("Launching gdb stub..."); x64_gdb_stub_handler handler{*emu}; run_gdb_stub(handler, "i386:x86-64", gdb_registers.size(), "0.0.0.0:28960"); } else { while (continue_execution) { continue_execution = false; try { emu->start_from_ip(); } catch (...) { if (!continue_execution) { throw; } } } } } catch (...) { printf("Emulation failed at: %llX\n", emu->reg(x64_register::rip)); throw; } printf("Emulation done.\n"); } } int main(int /*argc*/, char** /*argv*/) { try { do { run(); } while (use_gdb); return 0; } catch (std::exception& e) { puts(e.what()); #ifdef _WIN32 //MessageBoxA(nullptr, e.what(), "ERROR", MB_ICONERROR); #endif } return 1; } #ifdef _WIN32 int WINAPI WinMain(HINSTANCE, HINSTANCE, PSTR, int) { return main(__argc, __argv); } #endif