diff --git a/src/emulator/main.cpp b/src/emulator/main.cpp index 7dabae44..b766500f 100644 --- a/src/emulator/main.cpp +++ b/src/emulator/main.cpp @@ -1,5 +1,11 @@ #include "std_include.hpp" +#include "unicorn.hpp" +#include "memory_utils.hpp" +#include "unicorn_utils.hpp" +#include "process_context.hpp" +#include "syscalls.hpp" + #define GS_SEGMENT_ADDR 0x6000000ULL #define GS_SEGMENT_SIZE (20 << 20) // 20 MB @@ -10,269 +16,8 @@ #define KUSD_ADDRESS 0x7ffe0000 -#include "unicorn.hpp" -#include - namespace { - bool is_within_start_and_end(const uint64_t value, const uint64_t start, const uint64_t end) - { - return value >= start && value < end; - } - - bool is_within_start_and_length(const uint64_t value, const uint64_t start, const uint64_t length) - { - return is_within_start_and_end(value, start, start + length); - } - - uint64_t align_down(const uint64_t value, const uint64_t alignment) - { - return value & ~(alignment - 1); - } - - uint64_t align_up(const uint64_t value, const uint64_t alignment) - { - return align_down(value + (alignment - 1), alignment); - } - - uint64_t page_align_down(const uint64_t value) - { - return align_down(value, 0x1000); - } - - uint64_t page_align_up(const uint64_t value) - { - return align_up(value, 0x1000); - } - - template - class unicorn_object - { - public: - unicorn_object() = default; - - unicorn_object(const unicorn& uc, uint64_t address) - : uc_(&uc) - , address_(address) - { - } - - uint64_t value() const - { - return this->address_; - } - - uint64_t size() const - { - return sizeof(T); - } - - uint64_t end() const - { - return this->value() + this->size(); - } - - T* ptr() const - { - return reinterpret_cast(this->address_); - } - - operator bool() const - { - return this->address_ != 0; - } - - T read() const - { - T obj{}; - - e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj))); - - return obj; - } - - void write(const T& value) const - { - e(uc_mem_write(*this->uc_, this->address_, &value, sizeof(value))); - } - - template - void access(const F& accessor) const - { - T obj{}; - e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj))); - - accessor(obj); - - this->write(obj); - } - - private: - const unicorn* uc_{}; - uint64_t address_{}; - }; - - class unicorn_allocator - { - public: - unicorn_allocator() = default; - - unicorn_allocator(const unicorn& uc, const uint64_t address, const uint64_t size) - : uc_(&uc) - , address_(address) - , size_(size) - , active_address_(address) - { - } - - uint64_t reserve(const uint64_t count, const uint64_t alignment = 1) - { - const auto potential_start = align_up(this->active_address_, alignment); - const auto potential_end = potential_start + count; - const auto total_end = this->address_ + this->size_; - - if (potential_end > total_end) - { - throw std::runtime_error("Out of memory"); - } - - this->active_address_ = potential_end; - - return potential_start; - } - - template - unicorn_object reserve() - { - const auto potential_start = this->reserve(sizeof(T), alignof(T)); - return unicorn_object(*this->uc_, potential_start); - } - - void make_unicode_string(UNICODE_STRING& result, const std::wstring_view str) - { - constexpr auto element_size = sizeof(str[0]); - constexpr auto required_alignment = alignof(decltype(str[0])); - const auto total_length = str.size() * element_size; - - const auto string_buffer = this->reserve(total_length, required_alignment); - - e(uc_mem_write(*this->uc_, string_buffer, str.data(), total_length)); - - result.Buffer = reinterpret_cast(string_buffer); - result.Length = static_cast(total_length); - result.MaximumLength = result.Length; - } - - unicorn_object make_unicode_string(const std::wstring_view str) - { - const auto unicode_string = this->reserve(); - - unicode_string.access([&](UNICODE_STRING& unicode_str) - { - this->make_unicode_string(unicode_str, str); - }); - - return unicode_string; - } - - private: - const unicorn* uc_{}; - uint64_t address_{}; - uint64_t size_{}; - uint64_t active_address_{0}; - }; - - class unicorn_hook - { - public: - using function = std::function; - - template - unicorn_hook(const unicorn& uc, const int type, const uint64_t begin, const uint64_t end, function callback, - Args... args) - : uc_(&uc) - { - this->function_ = std::make_unique( - [c = std::move(callback), &uc](const uint64_t address, const uint32_t size) - { - c(uc, address, size); - }); - - void* handler = +[](uc_engine*, const uint64_t address, const uint32_t size, - void* user_data) - { - (*static_cast(user_data))(address, size); - }; - - if (type == UC_HOOK_INSN) - { - handler = +[](uc_engine* uc, void* user_data) - { - uint64_t rip{}; - uc_reg_read(uc, UC_X86_REG_RIP, &rip); - (*static_cast(user_data))(rip, 0); - }; - } - - if (type == UC_HOOK_MEM_READ) - { - handler = +[](uc_engine*, const uc_mem_type /*type*/, const uint64_t address, const int size, - const int64_t /*value*/, void* user_data) - { - (*static_cast(user_data))(address, size); - }; - } - e(uc_hook_add(*this->uc_, &this->hook_, type, handler, this->function_.get(), begin, end, args...)); - } - - unicorn_hook(const unicorn_hook&) = delete; - unicorn_hook& operator=(const unicorn_hook&) = delete; - - unicorn_hook(unicorn_hook&& obj) noexcept - { - this->operator=(std::move(obj)); - } - - unicorn_hook& operator=(unicorn_hook&& obj) noexcept - { - if (this != &obj) - { - this->remove(); - - this->uc_ = obj.uc_; - this->hook_ = obj.hook_; - this->function_ = std::move(obj.function_); - - obj.hook_ = {}; - } - - return *this; - } - - ~unicorn_hook() - { - this->remove(); - } - - void remove() - { - if (this->hook_) - { - uc_hook_del(*this->uc_, this->hook_); - this->hook_ = {}; - } - - this->function_ = {}; - } - - private: - using internal_function = std::function; - - const unicorn* uc_{}; - uc_hook hook_{}; - std::unique_ptr function_{}; - }; - void setup_stack(const unicorn& uc, uint64_t stack_base, size_t stack_size) { e(uc_mem_map(uc, stack_base, stack_size, UC_PROT_READ | UC_PROT_WRITE)); @@ -314,13 +59,6 @@ namespace }); } - struct mapped_binary - { - uint64_t image_base{}; - uint64_t size_of_image{}; - std::unordered_map exports{}; - }; - mapped_binary map_module(const unicorn& uc, const std::vector& module_data, const std::string& name) { @@ -419,37 +157,6 @@ namespace return binary; } - struct event - { - bool signaled{}; - EVENT_TYPE type{}; - - bool is_signaled() - { - const auto res = this->signaled; - - if (this->type == SynchronizationEvent) - { - this->signaled = false; - } - - return res; - } - }; - - struct process_context - { - unicorn_object teb{}; - unicorn_object peb{}; - unicorn_object process_params{}; - - mapped_binary executable{}; - mapped_binary ntdll{}; - - std::vector events{}; - unicorn_allocator gs_segment{}; - }; - process_context setup_teb_and_peb(const unicorn& uc) { setup_stack(uc, STACK_ADDRESS, STACK_SIZE); @@ -515,318 +222,6 @@ namespace return map_module(uc, data, file.generic_string()); } - void handle_NtQueryPerformanceCounter(const unicorn& uc) - { - const unicorn_object performance_counter{uc, uc.reg(UC_X86_REG_R10)}; - const unicorn_object performance_frequency{uc, uc.reg(UC_X86_REG_RDX)}; - - try - { - if (performance_counter) - { - performance_counter.access([](LARGE_INTEGER& value) - { - QueryPerformanceCounter(&value); - }); - } - - if (performance_frequency) - { - performance_frequency.access([](LARGE_INTEGER& value) - { - QueryPerformanceFrequency(&value); - }); - } - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - catch (...) - { - uc.reg(UC_X86_REG_RAX, STATUS_ACCESS_VIOLATION); - } - } - - uint32_t get_memory_protection(const unicorn& uc, uint64_t address) - { - uint32_t count{}; - uc_mem_region* regions{}; - - e(uc_mem_regions(uc, ®ions, &count)); - const auto _ = utils::finally([&] - { - uc_free(regions); - }); - - for (const auto& region : std::span(regions, count)) - { - if (is_within_start_and_end(address, region.begin, region.end)) - { - return region.perms; - } - } - - return UC_PROT_NONE; - } - - uint32_t map_nt_to_unicorn_protection(const uint32_t nt_protection) - { - switch (nt_protection) - { - case PAGE_NOACCESS: - return UC_PROT_NONE; - case PAGE_READONLY: - return UC_PROT_READ; - case PAGE_READWRITE: - case PAGE_WRITECOPY: - return UC_PROT_READ | UC_PROT_WRITE; - case PAGE_EXECUTE: - case PAGE_EXECUTE_READ: - return UC_PROT_READ | UC_PROT_EXEC; - case PAGE_EXECUTE_READWRITE: - case PAGE_EXECUTE_WRITECOPY: - default: - return UC_PROT_ALL; - } - } - - uint32_t map_unicorn_to_nt_protection(const uint32_t unicorn_protection) - { - const bool has_exec = unicorn_protection & UC_PROT_EXEC; - const bool has_read = unicorn_protection & UC_PROT_READ; - const bool has_write = unicorn_protection & UC_PROT_WRITE; - - if (!has_read) - { - return PAGE_NOACCESS; - } - - if (has_exec && has_write) - { - return PAGE_EXECUTE_READWRITE; - } - - if (has_exec) - { - return PAGE_EXECUTE_READ; - } - - if (has_write) - { - return PAGE_READWRITE; - } - - return PAGE_READONLY; - } - - void handle_NtManageHotPatch(const unicorn& uc) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); - } - - void handle_NtOpenKey(const unicorn& uc) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); - } - - void handle_NtCreateEvent(const unicorn& uc, process_context& context) - { - const unicorn_object event_handle{uc, uc.reg(UC_X86_REG_R10)}; - const auto object_attributes = uc.reg(UC_X86_REG_R8); - const auto event_type = uc.reg(UC_X86_REG_R9D); - const auto initial_state = static_cast(uc.read_stack(5)); - - if (object_attributes) - { - puts("Unsupported object attributes"); - uc.stop(); - return; - } - - const uint64_t index = context.events.size(); - event_handle.write(index); - - context.events.emplace_back(initial_state != FALSE, event_type); - - static_assert(sizeof(EVENT_TYPE) == sizeof(uint32_t)); - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - - void handle_NtQueryVirtualMemory(const unicorn& uc, const process_context& context) - { - const auto process_handle = uc.reg(UC_X86_REG_R10); - const auto base_address = uc.reg(UC_X86_REG_RDX); - const auto info_class = uc.reg(UC_X86_REG_R8D); - const auto memory_information = uc.reg(UC_X86_REG_R9); - const auto memory_information_length = static_cast(uc.read_stack(5)); - const unicorn_object return_length{uc, uc.read_stack(6)}; - - if (process_handle != ~0ULL) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); - return; - } - - if (info_class == MemoryWorkingSetExInformation) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); - return; - } - - if (info_class != MemoryImageInformation) - { - printf("Unsupported memory info class: %X\n", info_class); - uc.stop(); - return; - } - - if (return_length) - { - return_length.write(sizeof(MEMORY_IMAGE_INFORMATION)); - } - - if (memory_information_length != sizeof(MEMORY_IMAGE_INFORMATION)) - { - uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); - return; - } - - if (!is_within_start_and_length(base_address, context.ntdll.image_base, context.ntdll.size_of_image)) - { - puts("Bad image request"); - uc.stop(); - return; - } - - const unicorn_object info{uc, memory_information}; - - info.access([&](MEMORY_IMAGE_INFORMATION& image_info) - { - image_info.ImageBase = reinterpret_cast(context.ntdll.image_base); - image_info.SizeOfImage = context.ntdll.size_of_image; - }); - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - - void handle_NtQuerySystemInformation(const unicorn& uc, const process_context& context) - { - const auto info_class = uc.reg(UC_X86_REG_R10D); - const auto system_information = uc.reg(UC_X86_REG_RDX); - const auto system_information_length = uc.reg(UC_X86_REG_R8D); - const unicorn_object return_length{uc, uc.reg(UC_X86_REG_R9)}; - - if (info_class == SystemFlushInformation) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); - return; - } - - if (info_class != SystemBasicInformation && info_class != SystemEmulationBasicInformation) - { - printf("Unsupported system info class: %X\n", info_class); - uc.stop(); - return; - } - - if (return_length) - { - return_length.write(sizeof(SYSTEM_BASIC_INFORMATION)); - } - - if (system_information_length != sizeof(SYSTEM_BASIC_INFORMATION)) - { - uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); - return; - } - - const unicorn_object info{uc, system_information}; - - info.access([&](SYSTEM_BASIC_INFORMATION& basic_info) - { - basic_info.Reserved = 0; - basic_info.TimerResolution = 0x0002625a; - basic_info.PageSize = 0x1000; - basic_info.LowestPhysicalPageNumber = 0x00000001; - basic_info.HighestPhysicalPageNumber = 0x00c9c7ff; - basic_info.AllocationGranularity = 0x10000; - basic_info.MinimumUserModeAddress = 0x0000000000010000; - basic_info.MaximumUserModeAddress = 0x00007ffffffeffff; - basic_info.ActiveProcessorsAffinityMask = 0x0000000000000fff; - basic_info.NumberOfProcessors = 1; - }); - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - - void handle_NtQueryProcessInformation(const unicorn& uc, const process_context& context) - { - const auto process_handle = uc.reg(UC_X86_REG_R10); - const auto info_class = uc.reg(UC_X86_REG_EDX); - const auto process_information = uc.reg(UC_X86_REG_R8); - const auto process_information_length = uc.reg(UC_X86_REG_R9D); - const unicorn_object return_length{uc, uc.read_stack(5)}; - - if (process_handle != ~0ULL) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); - return; - } - - if (info_class != ProcessCookie) - { - printf("Unsupported process info class: %X\n", info_class); - uc.stop(); - return; - } - - if (return_length) - { - return_length.write(sizeof(uint32_t)); - } - - if (process_information_length != sizeof(uint32_t)) - { - uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); - return; - } - - const unicorn_object info{uc, process_information}; - info.write(0x01234567); - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - - void handle_NtProtectVirtualMemory(const unicorn& uc) - { - const auto process_handle = uc.reg(UC_X86_REG_R10); - const unicorn_object base_address{uc, uc.reg(UC_X86_REG_RDX)}; - const unicorn_object bytes_to_protect{uc, uc.reg(UC_X86_REG_R8)}; - const auto protection = uc.reg(UC_X86_REG_R9D); - const unicorn_object old_protection{uc, uc.read_stack(5)}; - - if (process_handle != ~0ULL) - { - uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); - return; - } - - const auto address = page_align_down(base_address.read()); - base_address.write(address); - - const auto size = page_align_up(bytes_to_protect.read()); - bytes_to_protect.write(static_cast(size)); - - const auto current_uc_protection = get_memory_protection(uc, address); - const auto current_protection = map_unicorn_to_nt_protection(current_uc_protection); - old_protection.write(current_protection); - - const auto requested_protection = map_nt_to_unicorn_protection(protection); - e(uc_mem_protect(uc, address, size, requested_protection)); - - uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); - } - void run() { const unicorn uc{UC_ARCH_X86, UC_MODE_64}; @@ -876,51 +271,9 @@ namespace } unicorn_hook hook(uc, UC_HOOK_INSN, 0, std::numeric_limits::max(), - [&](const unicorn&, const uint64_t address, const uint32_t /*size*/) + [&](const unicorn&, const uint64_t, const uint32_t) { - const auto syscall_id = uc.reg(UC_X86_REG_EAX); - - printf("Handling syscall: %X (%llX)\n", syscall_id, address); - - try - { - switch (syscall_id) - { - case 0x12: - handle_NtOpenKey(uc); - break; - case 0x19: - handle_NtQueryProcessInformation(uc, context); - break; - case 0x23: - handle_NtQueryVirtualMemory(uc, context); - break; - case 0x31: - handle_NtQueryPerformanceCounter(uc); - break; - case 0x36: - handle_NtQuerySystemInformation(uc, context); - break; - case 0x48: - handle_NtCreateEvent(uc, context); - break; - case 0x50: - handle_NtProtectVirtualMemory(uc); - break; - case 0x11A: - handle_NtManageHotPatch(uc); - break; - default: - printf("Unhandled syscall: %X\n", syscall_id); - uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); - uc.stop(); - break; - } - } - catch (...) - { - uc.reg(UC_X86_REG_RAX, STATUS_UNSUCCESSFUL); - } + handle_syscall(uc, context); }, UC_X86_INS_SYSCALL); unicorn_hook hook3(uc, UC_HOOK_MEM_READ, context.peb.value(), context.peb.end(), diff --git a/src/emulator/memory_utils.hpp b/src/emulator/memory_utils.hpp new file mode 100644 index 00000000..1c4970a8 --- /dev/null +++ b/src/emulator/memory_utils.hpp @@ -0,0 +1,105 @@ +#pragma once +#include +#include + +inline bool is_within_start_and_end(const uint64_t value, const uint64_t start, const uint64_t end) +{ + return value >= start && value < end; +} + +inline bool is_within_start_and_length(const uint64_t value, const uint64_t start, const uint64_t length) +{ + return is_within_start_and_end(value, start, start + length); +} + +inline uint64_t align_down(const uint64_t value, const uint64_t alignment) +{ + return value & ~(alignment - 1); +} + +inline uint64_t align_up(const uint64_t value, const uint64_t alignment) +{ + return align_down(value + (alignment - 1), alignment); +} + +inline uint64_t page_align_down(const uint64_t value) +{ + return align_down(value, 0x1000); +} + +inline uint64_t page_align_up(const uint64_t value) +{ + return align_up(value, 0x1000); +} + +inline uint32_t get_memory_protection(const unicorn& uc, uint64_t address) +{ + uint32_t count{}; + uc_mem_region* regions{}; + + e(uc_mem_regions(uc, ®ions, &count)); + const auto _ = utils::finally([&] + { + uc_free(regions); + }); + + for (const auto& region : std::span(regions, count)) + { + if (is_within_start_and_end(address, region.begin, region.end)) + { + return region.perms; + } + } + + return UC_PROT_NONE; +} + +inline uint32_t map_nt_to_unicorn_protection(const uint32_t nt_protection) +{ + switch (nt_protection) + { + case PAGE_NOACCESS: + return UC_PROT_NONE; + case PAGE_READONLY: + return UC_PROT_READ; + case PAGE_READWRITE: + case PAGE_WRITECOPY: + return UC_PROT_READ | UC_PROT_WRITE; + case PAGE_EXECUTE: + case PAGE_EXECUTE_READ: + return UC_PROT_READ | UC_PROT_EXEC; + case PAGE_EXECUTE_READWRITE: + case PAGE_EXECUTE_WRITECOPY: + default: + return UC_PROT_ALL; + } +} + +inline uint32_t map_unicorn_to_nt_protection(const uint32_t unicorn_protection) +{ + const bool has_exec = unicorn_protection & UC_PROT_EXEC; + const bool has_read = unicorn_protection & UC_PROT_READ; + const bool has_write = unicorn_protection & UC_PROT_WRITE; + + if (!has_read) + { + return PAGE_NOACCESS; + } + + if (has_exec && has_write) + { + return PAGE_EXECUTE_READWRITE; + } + + if (has_exec) + { + return PAGE_EXECUTE_READ; + } + + if (has_write) + { + return PAGE_READWRITE; + } + + return PAGE_READONLY; +} \ No newline at end of file diff --git a/src/emulator/process_context.hpp b/src/emulator/process_context.hpp new file mode 100644 index 00000000..8643ef99 --- /dev/null +++ b/src/emulator/process_context.hpp @@ -0,0 +1,40 @@ +#pragma once +#include "unicorn_utils.hpp" + +struct mapped_binary +{ + uint64_t image_base{}; + uint64_t size_of_image{}; + std::unordered_map exports{}; +}; + +struct event +{ + bool signaled{}; + EVENT_TYPE type{}; + + bool is_signaled() + { + const auto res = this->signaled; + + if (this->type == SynchronizationEvent) + { + this->signaled = false; + } + + return res; + } +}; + +struct process_context +{ + unicorn_object teb{}; + unicorn_object peb{}; + unicorn_object process_params{}; + + mapped_binary executable{}; + mapped_binary ntdll{}; + + std::vector events{}; + unicorn_allocator gs_segment{}; +}; diff --git a/src/emulator/syscalls.cpp b/src/emulator/syscalls.cpp new file mode 100644 index 00000000..317490ad --- /dev/null +++ b/src/emulator/syscalls.cpp @@ -0,0 +1,293 @@ +#include "std_include.hpp" +#include "syscalls.hpp" + +namespace +{ + void handle_NtQueryPerformanceCounter(const unicorn& uc) + { + const unicorn_object performance_counter{uc, uc.reg(UC_X86_REG_R10)}; + const unicorn_object performance_frequency{uc, uc.reg(UC_X86_REG_RDX)}; + + try + { + if (performance_counter) + { + performance_counter.access([](LARGE_INTEGER& value) + { + QueryPerformanceCounter(&value); + }); + } + + if (performance_frequency) + { + performance_frequency.access([](LARGE_INTEGER& value) + { + QueryPerformanceFrequency(&value); + }); + } + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } + catch (...) + { + uc.reg(UC_X86_REG_RAX, STATUS_ACCESS_VIOLATION); + } + } + + void handle_NtManageHotPatch(const unicorn& uc) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); + } + + void handle_NtOpenKey(const unicorn& uc) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); + } + + void handle_NtCreateEvent(const unicorn& uc, process_context& context) + { + const unicorn_object event_handle{uc, uc.reg(UC_X86_REG_R10)}; + const auto object_attributes = uc.reg(UC_X86_REG_R8); + const auto event_type = uc.reg(UC_X86_REG_R9D); + const auto initial_state = static_cast(uc.read_stack(5)); + + if (object_attributes) + { + puts("Unsupported object attributes"); + uc.stop(); + return; + } + + const uint64_t index = context.events.size(); + event_handle.write(index); + + context.events.emplace_back(initial_state != FALSE, event_type); + + static_assert(sizeof(EVENT_TYPE) == sizeof(uint32_t)); + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } + + void handle_NtQueryVirtualMemory(const unicorn& uc, const process_context& context) + { + const auto process_handle = uc.reg(UC_X86_REG_R10); + const auto base_address = uc.reg(UC_X86_REG_RDX); + const auto info_class = uc.reg(UC_X86_REG_R8D); + const auto memory_information = uc.reg(UC_X86_REG_R9); + const auto memory_information_length = static_cast(uc.read_stack(5)); + const unicorn_object return_length{uc, uc.read_stack(6)}; + + if (process_handle != ~0ULL) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); + return; + } + + if (info_class == MemoryWorkingSetExInformation) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); + return; + } + + if (info_class != MemoryImageInformation) + { + printf("Unsupported memory info class: %X\n", info_class); + uc.stop(); + return; + } + + if (return_length) + { + return_length.write(sizeof(MEMORY_IMAGE_INFORMATION)); + } + + if (memory_information_length != sizeof(MEMORY_IMAGE_INFORMATION)) + { + uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); + return; + } + + if (!is_within_start_and_length(base_address, context.ntdll.image_base, context.ntdll.size_of_image)) + { + puts("Bad image request"); + uc.stop(); + return; + } + + const unicorn_object info{uc, memory_information}; + + info.access([&](MEMORY_IMAGE_INFORMATION& image_info) + { + image_info.ImageBase = reinterpret_cast(context.ntdll.image_base); + image_info.SizeOfImage = context.ntdll.size_of_image; + }); + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } + + void handle_NtQuerySystemInformation(const unicorn& uc, const process_context& context) + { + const auto info_class = uc.reg(UC_X86_REG_R10D); + const auto system_information = uc.reg(UC_X86_REG_RDX); + const auto system_information_length = uc.reg(UC_X86_REG_R8D); + const unicorn_object return_length{uc, uc.reg(UC_X86_REG_R9)}; + + if (info_class == SystemFlushInformation) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_SUPPORTED); + return; + } + + if (info_class != SystemBasicInformation && info_class != SystemEmulationBasicInformation) + { + printf("Unsupported system info class: %X\n", info_class); + uc.stop(); + return; + } + + if (return_length) + { + return_length.write(sizeof(SYSTEM_BASIC_INFORMATION)); + } + + if (system_information_length != sizeof(SYSTEM_BASIC_INFORMATION)) + { + uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); + return; + } + + const unicorn_object info{uc, system_information}; + + info.access([&](SYSTEM_BASIC_INFORMATION& basic_info) + { + basic_info.Reserved = 0; + basic_info.TimerResolution = 0x0002625a; + basic_info.PageSize = 0x1000; + basic_info.LowestPhysicalPageNumber = 0x00000001; + basic_info.HighestPhysicalPageNumber = 0x00c9c7ff; + basic_info.AllocationGranularity = 0x10000; + basic_info.MinimumUserModeAddress = 0x0000000000010000; + basic_info.MaximumUserModeAddress = 0x00007ffffffeffff; + basic_info.ActiveProcessorsAffinityMask = 0x0000000000000fff; + basic_info.NumberOfProcessors = 1; + }); + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } + + void handle_NtQueryProcessInformation(const unicorn& uc, const process_context& context) + { + const auto process_handle = uc.reg(UC_X86_REG_R10); + const auto info_class = uc.reg(UC_X86_REG_EDX); + const auto process_information = uc.reg(UC_X86_REG_R8); + const auto process_information_length = uc.reg(UC_X86_REG_R9D); + const unicorn_object return_length{uc, uc.read_stack(5)}; + + if (process_handle != ~0ULL) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); + return; + } + + if (info_class != ProcessCookie) + { + printf("Unsupported process info class: %X\n", info_class); + uc.stop(); + return; + } + + if (return_length) + { + return_length.write(sizeof(uint32_t)); + } + + if (process_information_length != sizeof(uint32_t)) + { + uc.reg(UC_X86_REG_RAX, STATUS_BUFFER_OVERFLOW); + return; + } + + const unicorn_object info{uc, process_information}; + info.write(0x01234567); + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } + + void handle_NtProtectVirtualMemory(const unicorn& uc) + { + const auto process_handle = uc.reg(UC_X86_REG_R10); + const unicorn_object base_address{uc, uc.reg(UC_X86_REG_RDX)}; + const unicorn_object bytes_to_protect{uc, uc.reg(UC_X86_REG_R8)}; + const auto protection = uc.reg(UC_X86_REG_R9D); + const unicorn_object old_protection{uc, uc.read_stack(5)}; + + if (process_handle != ~0ULL) + { + uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); + return; + } + + const auto address = page_align_down(base_address.read()); + base_address.write(address); + + const auto size = page_align_up(bytes_to_protect.read()); + bytes_to_protect.write(static_cast(size)); + + const auto current_uc_protection = get_memory_protection(uc, address); + const auto current_protection = map_unicorn_to_nt_protection(current_uc_protection); + old_protection.write(current_protection); + + const auto requested_protection = map_nt_to_unicorn_protection(protection); + e(uc_mem_protect(uc, address, size, requested_protection)); + + uc.reg(UC_X86_REG_RAX, STATUS_SUCCESS); + } +} + +void handle_syscall(const unicorn& uc, process_context& context) +{ + const auto address = uc.reg(UC_X86_REG_RIP); + const auto syscall_id = uc.reg(UC_X86_REG_EAX); + + printf("Handling syscall: %X (%llX)\n", syscall_id, address); + + try + { + switch (syscall_id) + { + case 0x12: + handle_NtOpenKey(uc); + break; + case 0x19: + handle_NtQueryProcessInformation(uc, context); + break; + case 0x23: + handle_NtQueryVirtualMemory(uc, context); + break; + case 0x31: + handle_NtQueryPerformanceCounter(uc); + break; + case 0x36: + handle_NtQuerySystemInformation(uc, context); + break; + case 0x48: + handle_NtCreateEvent(uc, context); + break; + case 0x50: + handle_NtProtectVirtualMemory(uc); + break; + case 0x11A: + handle_NtManageHotPatch(uc); + break; + default: + printf("Unhandled syscall: %X\n", syscall_id); + uc.reg(UC_X86_REG_RAX, STATUS_NOT_IMPLEMENTED); + uc.stop(); + break; + } + } + catch (...) + { + uc.reg(UC_X86_REG_RAX, STATUS_UNSUCCESSFUL); + } +} diff --git a/src/emulator/syscalls.hpp b/src/emulator/syscalls.hpp new file mode 100644 index 00000000..cefaccdd --- /dev/null +++ b/src/emulator/syscalls.hpp @@ -0,0 +1,6 @@ +#pragma once + +#include "unicorn.hpp" +#include "process_context.hpp" + +void handle_syscall(const unicorn& uc, process_context& context); diff --git a/src/emulator/unicorn_utils.hpp b/src/emulator/unicorn_utils.hpp new file mode 100644 index 00000000..6411faad --- /dev/null +++ b/src/emulator/unicorn_utils.hpp @@ -0,0 +1,231 @@ +#pragma once +#include "unicorn.hpp" +#include "memory_utils.hpp" + +template +class unicorn_object +{ +public: + unicorn_object() = default; + + unicorn_object(const unicorn& uc, uint64_t address) + : uc_(&uc) + , address_(address) + { + } + + uint64_t value() const + { + return this->address_; + } + + uint64_t size() const + { + return sizeof(T); + } + + uint64_t end() const + { + return this->value() + this->size(); + } + + T* ptr() const + { + return reinterpret_cast(this->address_); + } + + operator bool() const + { + return this->address_ != 0; + } + + T read() const + { + T obj{}; + + e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj))); + + return obj; + } + + void write(const T& value) const + { + e(uc_mem_write(*this->uc_, this->address_, &value, sizeof(value))); + } + + template + void access(const F& accessor) const + { + T obj{}; + e(uc_mem_read(*this->uc_, this->address_, &obj, sizeof(obj))); + + accessor(obj); + + this->write(obj); + } + +private: + const unicorn* uc_{}; + uint64_t address_{}; +}; + +class unicorn_allocator +{ +public: + unicorn_allocator() = default; + + unicorn_allocator(const unicorn& uc, const uint64_t address, const uint64_t size) + : uc_(&uc) + , address_(address) + , size_(size) + , active_address_(address) + { + } + + uint64_t reserve(const uint64_t count, const uint64_t alignment = 1) + { + const auto potential_start = align_up(this->active_address_, alignment); + const auto potential_end = potential_start + count; + const auto total_end = this->address_ + this->size_; + + if (potential_end > total_end) + { + throw std::runtime_error("Out of memory"); + } + + this->active_address_ = potential_end; + + return potential_start; + } + + template + unicorn_object reserve() + { + const auto potential_start = this->reserve(sizeof(T), alignof(T)); + return unicorn_object(*this->uc_, potential_start); + } + + void make_unicode_string(UNICODE_STRING& result, const std::wstring_view str) + { + constexpr auto element_size = sizeof(str[0]); + constexpr auto required_alignment = alignof(decltype(str[0])); + const auto total_length = str.size() * element_size; + + const auto string_buffer = this->reserve(total_length, required_alignment); + + e(uc_mem_write(*this->uc_, string_buffer, str.data(), total_length)); + + result.Buffer = reinterpret_cast(string_buffer); + result.Length = static_cast(total_length); + result.MaximumLength = result.Length; + } + + unicorn_object make_unicode_string(const std::wstring_view str) + { + const auto unicode_string = this->reserve(); + + unicode_string.access([&](UNICODE_STRING& unicode_str) + { + this->make_unicode_string(unicode_str, str); + }); + + return unicode_string; + } + +private: + const unicorn* uc_{}; + uint64_t address_{}; + uint64_t size_{}; + uint64_t active_address_{ 0 }; +}; + +class unicorn_hook +{ +public: + using function = std::function; + + template + unicorn_hook(const unicorn& uc, const int type, const uint64_t begin, const uint64_t end, function callback, + Args... args) + : uc_(&uc) + { + this->function_ = std::make_unique( + [c = std::move(callback), &uc](const uint64_t address, const uint32_t size) + { + c(uc, address, size); + }); + + void* handler = +[](uc_engine*, const uint64_t address, const uint32_t size, + void* user_data) + { + (*static_cast(user_data))(address, size); + }; + + if (type == UC_HOOK_INSN) + { + handler = +[](uc_engine* uc, void* user_data) + { + uint64_t rip{}; + uc_reg_read(uc, UC_X86_REG_RIP, &rip); + (*static_cast(user_data))(rip, 0); + }; + } + + if (type == UC_HOOK_MEM_READ) + { + handler = +[](uc_engine*, const uc_mem_type /*type*/, const uint64_t address, const int size, + const int64_t /*value*/, void* user_data) + { + (*static_cast(user_data))(address, size); + }; + } + e(uc_hook_add(*this->uc_, &this->hook_, type, handler, this->function_.get(), begin, end, args...)); + } + + unicorn_hook(const unicorn_hook&) = delete; + unicorn_hook& operator=(const unicorn_hook&) = delete; + + unicorn_hook(unicorn_hook&& obj) noexcept + { + this->operator=(std::move(obj)); + } + + unicorn_hook& operator=(unicorn_hook&& obj) noexcept + { + if (this != &obj) + { + this->remove(); + + this->uc_ = obj.uc_; + this->hook_ = obj.hook_; + this->function_ = std::move(obj.function_); + + obj.hook_ = {}; + } + + return *this; + } + + ~unicorn_hook() + { + this->remove(); + } + + void remove() + { + if (this->hook_) + { + uc_hook_del(*this->uc_, this->hook_); + this->hook_ = {}; + } + + this->function_ = {}; + } + +private: + using internal_function = std::function; + + const unicorn* uc_{}; + uc_hook hook_{}; + std::unique_ptr function_{}; +};