diff --git a/src/vmemu.vcxproj b/src/vmemu.vcxproj index 7282200..d3e66c7 100644 --- a/src/vmemu.vcxproj +++ b/src/vmemu.vcxproj @@ -119,6 +119,7 @@ true + $(ProjectDir)..\dependencies\unicorn\include\;$(ProjectDir)..\dependencies\xtils\;$(ProjectDir)..\dependencies\vmprofiler\include\;$(ProjectDir)..\dependencies\cli-parser\;$(ProjectDir)..\dependencies\vmprofiler\dependencies\zydis\include;$(ProjectDir)..\dependencies\vmprofiler\dependencies\zydis\dependencies\zycore\include;$(ProjectDir)..\dependencies\vmprofiler\dependencies\zydis\msvc;$(IncludePath) false @@ -128,12 +129,14 @@ Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _DEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS true + stdcpp17 Console true + $(ProjectDir)..\dependencies\unicorn\msvc\x64\Debug\*.lib @@ -142,7 +145,7 @@ true true true - NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS true Disabled stdcpp17 @@ -152,7 +155,7 @@ true true true - $(ProjectDir)..\dependencies\unicorn\msvc\x64\Release\*.lib;%(AdditionalDependencies) + $(ProjectDir)..\dependencies\unicorn\msvc\x64\Debug\*.lib diff --git a/src/vmemu_t.cpp b/src/vmemu_t.cpp index 20dc5a1..9a2b663 100644 --- a/src/vmemu_t.cpp +++ b/src/vmemu_t.cpp @@ -9,7 +9,8 @@ namespace vm image_base(image_base), vm_entry_rva(vm_entry_rva), vm_handler_table(nullptr), - uc(nullptr) + uc(nullptr), + trace_entries(nullptr) {} bool emu_t::init() @@ -24,7 +25,7 @@ namespace vm return false; } - //vm::util::deobfuscate(vm_entry); + vm::util::deobfuscate(vm_entry); vm::util::print(vm_entry); if (!(vm_handler_table = vm::handler::table::get(vm_entry))) @@ -39,14 +40,20 @@ namespace vm std::printf("[!] failed to get all vm handlers...\n"); return false; } + std::printf("> got all vm handlers...\n"); // // unicorn init stuff... // - auto err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc); + const auto image_size = + NT_HEADER(module_base)->OptionalHeader.SizeOfImage; - if (err) + std::uintptr_t stack_base = 0x1000000; + std::uintptr_t stack_addr = stack_base + (0x1000 * 20); + + uc_err err; + if ((err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc))) { std::printf("failed on uc_mem_map() with error returned %u: %s\n", err, uc_strerror(err)); @@ -54,16 +61,7 @@ namespace vm return false; } - const auto image_size = - NT_HEADER(module_base)->OptionalHeader.SizeOfImage; - - const auto vm_entry = vm_entry_rva + module_base; - constexpr auto stack_addr = 0x1000000 + (0x1000 * 6); - - // allocate space for module... - err = uc_mem_map(uc, module_base, image_size, UC_PROT_ALL); - - if (err) + if ((err = uc_mem_map(uc, module_base, image_size, UC_PROT_ALL))) { std::printf("failed on uc_mem_map() with error returned %u: %s\n", err, uc_strerror(err)); @@ -71,10 +69,7 @@ namespace vm return false; } - // allocate 6 pages for stack... - err = uc_mem_map(uc, stack_addr, 0x1000 * 6, UC_PROT_ALL); - - if (err) + if ((err = uc_mem_map(uc, 0x1000000, 0x1000 * 20, UC_PROT_ALL))) { std::printf("failed on uc_mem_map() with error returned %u: %s\n", err, uc_strerror(err)); @@ -82,16 +77,15 @@ namespace vm return false; } - // write the module into memory... - err = uc_mem_write(uc, module_base, reinterpret_cast(module_base), image_size); - - if (err) std::printf("failed on uc_mem_write() with error returned %u: %s\n", - err, uc_strerror(err)); + if ((err = uc_mem_write(uc, module_base, reinterpret_cast(module_base), image_size))) + { + std::printf("failed on uc_mem_write() with error returned %u: %s\n", + err, uc_strerror(err)); - // set vm_entry into RIP... - err = uc_reg_write(uc, UC_X86_REG_RIP, &vm_entry); + return false; + } - if (err) + if ((err = uc_reg_write(uc, UC_X86_REG_RIP, &vm_entry))) { std::printf("failed on uc_reg_write() with error returned %u: %s\n", err, uc_strerror(err)); @@ -99,10 +93,7 @@ namespace vm return false; } - // set stack address up... - err = uc_reg_write(uc, UC_X86_REG_RSP, &stack_addr); - - if (err) + if ((err = uc_reg_write(uc, UC_X86_REG_RSP, &stack_addr))) { std::printf("failed on uc_reg_write() with error returned %u: %s\n", err, uc_strerror(err)); @@ -110,10 +101,8 @@ namespace vm return false; } - err = uc_hook_add(uc, &trace, UC_HOOK_CODE, &jmp_hook, - nullptr, module_base, module_base + image_size); - - if (err) + if ((err = uc_hook_add(uc, &trace, UC_HOOK_CODE, &vm::emu_t::hook_code, + this, module_base, module_base + image_size))) { std::printf("failed on uc_hook_add() with error returned %u: %s\n", err, uc_strerror(err)); @@ -121,16 +110,14 @@ namespace vm return false; } - // emulate machine code in infinite time - err = uc_emu_start(uc, vm_entry, NULL, NULL, NULL); - if (err) + if ((err = uc_hook_add(uc, &trace1, UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, + vm::emu_t::hook_mem_invalid, this, 1, 0))) { - std::printf("Failed on uc_emu_start() with error returned %u: %s\n", + std::printf("failed on uc_hook_add() with error returned %u: %s\n", err, uc_strerror(err)); return false; } - return true; } @@ -138,4 +125,152 @@ namespace vm { if (uc) uc_close(uc); } + + bool emu_t::get_trace(std::vector& entries) + { + // hook_code will fill this vector up with values... + trace_entries = &entries; + uc_err err; + + if ((err = uc_emu_start(uc, vm_entry_rva + module_base, NULL, NULL, NULL))) + { + std::printf("failed on uc_emu_start() with error returned %u: %s\n", + err, uc_strerror(err)); + + return false; + } + return true; + } + + uc_err emu_t::create_entry(vmp2::entry_t* entry) + { + uc_reg_read(uc, UC_X86_REG_R15, &entry->regs.r15); + uc_reg_read(uc, UC_X86_REG_R14, &entry->regs.r14); + uc_reg_read(uc, UC_X86_REG_R13, &entry->regs.r13); + uc_reg_read(uc, UC_X86_REG_R12, &entry->regs.r12); + uc_reg_read(uc, UC_X86_REG_R11, &entry->regs.r11); + uc_reg_read(uc, UC_X86_REG_R10, &entry->regs.r10); + uc_reg_read(uc, UC_X86_REG_R9, &entry->regs.r9); + uc_reg_read(uc, UC_X86_REG_R8, &entry->regs.r8); + uc_reg_read(uc, UC_X86_REG_RBP, &entry->regs.rbp); + uc_reg_read(uc, UC_X86_REG_RDI, &entry->regs.rdi); + uc_reg_read(uc, UC_X86_REG_RSI, &entry->regs.rsi); + uc_reg_read(uc, UC_X86_REG_RDX, &entry->regs.rdx); + uc_reg_read(uc, UC_X86_REG_RCX, &entry->regs.rcx); + uc_reg_read(uc, UC_X86_REG_RBX, &entry->regs.rbx); + uc_reg_read(uc, UC_X86_REG_RAX, &entry->regs.rax); + uc_reg_read(uc, UC_X86_REG_EFLAGS, &entry->regs.rflags); + + entry->vip = entry->regs.rsi; + entry->handler_idx = entry->regs.rax; + entry->decrypt_key = entry->regs.rbx; + + uc_err err; + if ((err = uc_mem_read(uc, entry->regs.rdi, + entry->vregs.raw, sizeof entry->vregs.raw))) + return err; + + // copy virtual stack values... + for (auto idx = 0u; idx < sizeof(entry->vsp) / 8; ++idx) + if ((err = uc_mem_read(uc, entry->regs.rbp + (idx * 8), + &entry->vsp.qword[idx], sizeof entry->vsp.qword[idx]))) + return err; + + return UC_ERR_OK; + } + + void emu_t::hook_code(uc_engine* uc, uint64_t address, uint32_t size, vm::emu_t* obj) + { + std::printf(">>> Tracing instruction at 0x%p, instruction size = 0x%x\n", address, size); + + // grab JMP RDX/RCX <-- this register... + static const auto jmp_reg = obj->vm_entry[obj->vm_entry.size()] + .instr.operands[0] + .reg + .value; + + static ZydisDecoder decoder; + static std::once_flag once; + static ZydisDecodedInstruction instr; + static std::uintptr_t reg_val = 0u; + + // init zydis decoder just a single time... + std::call_once(once, [&]() -> void { + ZydisDecoderInit(&decoder, + ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64); }); + + if (address == obj->vm_entry[obj->vm_entry.size()].addr) + { + std::printf("stopped at jmp... addr = 0x%p\n", address); + std::getchar(); + + vmp2::entry_t new_entry; + if (!obj->create_entry(&new_entry)) + { + std::printf("[!] failed to create new entry... exiting...\n"); + exit(0); + } + obj->trace_entries->push_back(new_entry); + } + // if we are getting a callback for a JMP RCX/RDX instruction... + else if (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer( + &decoder, reinterpret_cast(address), size, &instr)) && + instr.mnemonic == ZYDIS_MNEMONIC_JMP && + instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && + instr.operands[0].reg.value == jmp_reg) + { + switch (jmp_reg) + { + ZYDIS_REGISTER_RDX: + uc_reg_read(uc, UC_X86_REG_RDX, ®_val); + break; + ZYDIS_REGISTER_RCX: + uc_reg_read(uc, UC_X86_REG_RCX, ®_val); + break; + default: + std::printf("[!] invalid jump register...\n"); + exit(0); + } + + // checks to see if the address + // in JMP RDX/RCX is a vm handler address... + static const auto vm_handler_check = + [&](const vm::handler_t& vm_handler) -> bool + { return vm_handler.address == reg_val; }; + + if (std::find_if(obj->vm_handlers.begin(), obj->vm_handlers.end(), + vm_handler_check) == obj->vm_handlers.end()) + return; + + std::printf("stopped at jmp... addr = 0x%p\n", address); + std::getchar(); + + vmp2::entry_t new_entry; + if (!obj->create_entry(&new_entry)) + { + std::printf("[!] failed to create new entry... exiting...\n"); + exit(0); + } + obj->trace_entries->push_back(new_entry); + } + } + + bool emu_t::hook_mem_invalid(uc_engine* uc, uc_mem_type type, + uint64_t address, int size, int64_t value, vm::emu_t* obj) + { + switch (type) + { + default: + // return false to indicate we want to stop emulation + return false; + case UC_MEM_WRITE_UNMAPPED: + printf(">>> Missing memory is being WRITE at 0x%p, data size = %u, data value = 0x%p\n", + address, size, value); + return false; + case UC_MEM_READ_UNMAPPED: + printf(">>> Missing memory is being READ at 0x%p, data size = %u, data value = 0x%p\n", + address, size, value); + return false; + } + } } \ No newline at end of file diff --git a/src/vmemu_t.hpp b/src/vmemu_t.hpp index 5d26600..5a6de31 100644 --- a/src/vmemu_t.hpp +++ b/src/vmemu_t.hpp @@ -6,6 +6,7 @@ #include #include #include +#include namespace vm { @@ -15,12 +16,18 @@ namespace vm public: explicit emu_t(std::uint32_t vm_entry_rva, std::uintptr_t image_base, std::uintptr_t module_base); + ~emu_t(); bool init(); - ~emu_t(); + bool get_trace(std::vector& entries); private: + uc_err create_entry(vmp2::entry_t* entry); + static void hook_code(uc_engine* uc, uint64_t address, uint32_t size, vm::emu_t* obj); + static bool hook_mem_invalid(uc_engine* uc, uc_mem_type type, + uint64_t address, int size, int64_t value, vm::emu_t* obj); + uc_engine* uc; - uc_hook trace; + uc_hook trace, trace1; std::uintptr_t image_base, module_base; std::uint32_t vm_entry_rva; @@ -28,61 +35,6 @@ namespace vm zydis_routine_t vm_entry; std::uintptr_t* vm_handler_table; std::vector vm_handlers; - - // very janky work around to use classes & callbacks with unicorn... it is what it is... - callback_t jmp_hook = - [&, this](uc_engine* uc, uint64_t address, uint32_t size, void* user_data) -> void - { - // grab JMP RDX/RCX <-- this register... - static auto jmp_reg = vm_entry[ - vm_entry.size() - 1].instr.operands[0].reg.value; - - static ZydisDecoder decoder; - static std::once_flag once; - static ZydisDecodedInstruction instr; - static std::uintptr_t reg_val = 0u; - - // init zydis decoder just a single time... - std::call_once(once, []() -> void { - ZydisDecoderInit(&decoder, - ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64); }); - - if (address == vm_entry[vm_entry.size() - 1].addr) - { - std::printf("stopped at jmp... addr = 0x%p\n", address); - std::getchar(); - } - // if we are getting a callback for a JMP RCX/RDX instruction... - else if (ZYAN_SUCCESS(ZydisDecoderDecodeBuffer( - &decoder, reinterpret_cast(address), size, &instr)) && - instr.operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER && - instr.operands[0].reg.value == jmp_reg) - { - switch (jmp_reg) - { - ZYDIS_REGISTER_RDX: - uc_reg_read(uc, UC_X86_REG_RDX, ®_val); - break; - ZYDIS_REGISTER_RCX: - uc_reg_read(uc, UC_X86_REG_RCX, ®_val); - break; - default: - throw std::exception("invalid register to jump from...\n"); - } - - // checks to see if the address - // in JMP RDX/RCX is a vm handler address... - static const auto vm_handler_check = - [&](vm::handler_t& vm_handler) -> bool - { return vm_handler.address == reg_val; }; - - if (std::find_if(vm_handlers.begin(), vm_handlers.end(), - vm_handler_check) == vm_handlers.end()) - return; - - std::printf("stopped at jmp... addr = 0x%p\n", address); - std::getchar(); - } - }; + std::vector* trace_entries; }; } \ No newline at end of file diff --git a/src/vmptest.vmp.exe b/src/vmptest.vmp.exe new file mode 100644 index 0000000..60c276e Binary files /dev/null and b/src/vmptest.vmp.exe differ diff --git a/vmemu.sln b/vmemu.sln index 341a48f..b2791c0 100644 --- a/vmemu.sln +++ b/vmemu.sln @@ -11,6 +11,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Zydis", "dependencies\vmpro EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + DBG|x64 = DBG|x64 + DBG|x86 = DBG|x86 Debug Kernel|x64 = Debug Kernel|x64 Debug Kernel|x86 = Debug Kernel|x86 Debug MD DLL|x64 = Debug MD DLL|x64 @@ -37,6 +39,9 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F0D51879-E659-4BD3-B688-7864DB3C82AA}.DBG|x64.ActiveCfg = Debug|x64 + {F0D51879-E659-4BD3-B688-7864DB3C82AA}.DBG|x64.Build.0 = Debug|x64 + {F0D51879-E659-4BD3-B688-7864DB3C82AA}.DBG|x86.ActiveCfg = Debug|x64 {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Debug Kernel|x64.ActiveCfg = Debug|x64 {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Debug Kernel|x64.Build.0 = Debug|x64 {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Debug Kernel|x86.ActiveCfg = Release|x64 @@ -83,12 +88,15 @@ Global {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Release|x64.ActiveCfg = Release|x64 {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Release|x64.Build.0 = Release|x64 {F0D51879-E659-4BD3-B688-7864DB3C82AA}.Release|x86.ActiveCfg = Release|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.DBG|x64.ActiveCfg = DBG|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.DBG|x64.Build.0 = DBG|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.DBG|x86.ActiveCfg = DBG|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug Kernel|x64.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug Kernel|x64.Build.0 = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug Kernel|x86.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug Kernel|x86.Build.0 = Release|x64 - {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x64.ActiveCfg = Release|x64 - {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x64.Build.0 = Release|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x64.ActiveCfg = DBG|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x64.Build.0 = DBG|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x86.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD DLL|x86.Build.0 = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MD|x64.ActiveCfg = Release|x64 @@ -103,8 +111,8 @@ Global {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MT|x64.Build.0 = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MT|x86.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug MT|x86.Build.0 = Release|x64 - {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x64.ActiveCfg = Release|x64 - {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x64.Build.0 = Release|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x64.ActiveCfg = DBG|x64 + {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x64.Build.0 = DBG|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x86.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Debug|x86.Build.0 = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Release Kernel|x64.ActiveCfg = Release|x64 @@ -130,6 +138,10 @@ Global {D0B6092A-9944-4F24-9486-4B7DAE372619}.Release|x64.ActiveCfg = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Release|x64.Build.0 = Release|x64 {D0B6092A-9944-4F24-9486-4B7DAE372619}.Release|x86.ActiveCfg = Release|x64 + {88A23124-5640-35A0-B890-311D7A67A7D2}.DBG|x64.ActiveCfg = Debug MT|x64 + {88A23124-5640-35A0-B890-311D7A67A7D2}.DBG|x64.Build.0 = Debug MT|x64 + {88A23124-5640-35A0-B890-311D7A67A7D2}.DBG|x86.ActiveCfg = Debug MT|Win32 + {88A23124-5640-35A0-B890-311D7A67A7D2}.DBG|x86.Build.0 = Debug MT|Win32 {88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x64.ActiveCfg = Debug Kernel|x64 {88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x64.Build.0 = Debug Kernel|x64 {88A23124-5640-35A0-B890-311D7A67A7D2}.Debug Kernel|x64.Deploy.0 = Debug Kernel|x64