From e9945bde6cc95c32e7ffb0d4db01009c282efbd6 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Tue, 3 Aug 2021 16:39:51 -0700 Subject: [PATCH] working on adding multi-vm branching support... --- include/vmemu_t.hpp | 34 +++- src/main.cpp | 6 + src/vmemu_t.cpp | 481 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 516 insertions(+), 5 deletions(-) diff --git a/include/vmemu_t.hpp b/include/vmemu_t.hpp index 7b4ddd4..943ab1a 100644 --- a/include/vmemu_t.hpp +++ b/include/vmemu_t.hpp @@ -1,20 +1,50 @@ #pragma once +#include #include #include +#define PAGE_4KB 0x1000 +#define STACK_SIZE PAGE_4KB * 512 +#define STACK_BASE 0xFFFF000000000000 + namespace vm { class emu_t { + struct cpu_ctx_t + { + std::uintptr_t rip; + uc_context *context; + std::uint8_t stack[ STACK_SIZE ]; + }; + + struct code_block_data_t + { + vm::instrs::code_block_t code_block; + std::shared_ptr< cpu_ctx_t > cpu_ctx; + std::shared_ptr< vm::ctx_t > g_vm_ctx; + }; + public: - explicit emu_t( vm::ctx_t *vm_ctx ); + explicit emu_t( vm::ctx_t *g_vm_ctx ); ~emu_t(); bool init(); bool get_trace( std::vector< vm::instrs::code_block_t > &code_blocks ); private: + std::uintptr_t img_base, img_size; + uc_hook code_exec_hook, invalid_mem_hook; + uc_engine *uc_ctx; - vm::ctx_t *vm_ctx; + vm::ctx_t *g_vm_ctx; + code_block_data_t *cc_block; + + std::vector< std::uintptr_t > vip_begins; + std::vector< code_block_data_t > code_blocks; + uc_err create_entry( vmp2::v2::entry_t *entry ); + static bool code_exec_callback( uc_engine *uc, uint64_t address, uint32_t size, emu_t *obj ); + static void invalid_mem( uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, + emu_t *obj ); }; } // namespace vm \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 53e2079..ef45150 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,6 +36,12 @@ int __cdecl main( int argc, const char *argv[] ) const auto module_base = reinterpret_cast< std::uintptr_t >( LoadLibraryExA( parser.get< std::string >( "bin" ).c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES ) ); + if ( !module_base ) + { + std::printf( "[!] failed to open binary file...\n" ); + return -1; + } + const auto vm_entry_rva = std::strtoull( parser.get< std::string >( "vmentry" ).c_str(), nullptr, 16 ); const auto image_base = umtils->image_base( parser.get< std::string >( "bin" ).c_str() ); const auto image_size = NT_HEADER( module_base )->OptionalHeader.SizeOfImage; diff --git a/src/vmemu_t.cpp b/src/vmemu_t.cpp index 59399d1..b19a4d9 100644 --- a/src/vmemu_t.cpp +++ b/src/vmemu_t.cpp @@ -2,7 +2,8 @@ namespace vm { - emu_t::emu_t( vm::ctx_t *vm_ctx ) : vm_ctx( vm_ctx ), uc_ctx( nullptr ) + emu_t::emu_t( vm::ctx_t *g_vm_ctx ) + : g_vm_ctx( g_vm_ctx ), uc_ctx( nullptr ), img_base( g_vm_ctx->image_base ), img_size( g_vm_ctx->image_size ) { } @@ -14,11 +15,485 @@ namespace vm bool emu_t::init() { - return {}; + uc_err err; + if ( ( err = uc_open( UC_ARCH_X86, UC_MODE_64, &uc_ctx ) ) ) + { + std::printf( "> uc_open err = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_map( uc_ctx, STACK_BASE, STACK_SIZE, UC_PROT_ALL ) ) ) + { + std::printf( "> uc_mem_map stack err, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_map( uc_ctx, img_base, img_size, UC_PROT_ALL ) ) ) + { + std::printf( "> map memory failed, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_mem_write( uc_ctx, img_base, reinterpret_cast< void * >( g_vm_ctx->module_base ), img_size ) ) ) + { + std::printf( "> failed to write memory... reason = %d\n", err ); + return false; + } + + if ( ( err = uc_hook_add( uc_ctx, &code_exec_hook, UC_HOOK_CODE, &vm::emu_t::code_exec_callback, this, img_base, + img_base + img_size ) ) ) + { + std::printf( "> uc_hook_add error, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_hook_add( uc_ctx, &invalid_mem_hook, + UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_FETCH_UNMAPPED | + UC_HOOK_INSN_INVALID, + &vm::emu_t::invalid_mem, this, true, false ) ) ) + { + std::printf( "> uc_hook_add error, reason = %d\n", err ); + return false; + } + return true; } bool emu_t::get_trace( std::vector< vm::instrs::code_block_t > &entries ) { - return {}; + uc_err err; + std::uintptr_t rip = g_vm_ctx->vm_entry_rva + img_base, rsp = STACK_BASE + STACK_SIZE - PAGE_4KB; + + if ( ( err = uc_reg_write( uc_ctx, UC_X86_REG_RSP, &rsp ) ) ) + { + std::printf( "> uc_reg_write error, reason = %d\n", err ); + return false; + } + + if ( ( err = uc_reg_write( uc_ctx, UC_X86_REG_RIP, &rip ) ) ) + { + std::printf( "> uc_reg_write error, reason = %d\n", err ); + return false; + } + + // trace the first block given the vm enter... + code_block_data_t code_block{ { rip }, nullptr, nullptr }; + cc_block = &code_block; + + std::printf( "> beginning execution at = 0x%p\n", rip ); + if ( ( err = uc_emu_start( uc_ctx, rip, 0ull, 0ull, 0ull ) ) ) + { + std::printf( "> error starting emu... reason = %d\n", err ); + return false; + } + + code_blocks.push_back( code_block ); + + // code_blocks.size() will continue to grow as all branches are traced... + // when idx is > code_blocks.size() then we have traced all branches... + for ( auto idx = 0u; idx < code_blocks.size(); ++idx ) + { + const auto &code_block = code_blocks[ idx ]; + if ( !code_block.code_block.jcc.has_jcc ) + continue; + + switch ( code_block.code_block.jcc.type ) + { + case vm::instrs::jcc_type::branching: + { + if ( std::find( vip_begins.begin(), vip_begins.end(), code_block.code_block.jcc.block_addr[ 1 ] ) != + vip_begins.end() ) + continue; + + std::uintptr_t rbp = 0ull; + std::uint32_t branch_rva = code_block.code_block.jcc.block_addr[ 1 ]; + + // setup object globals so that the tracing will work... + code_block_data_t branch_block{ { code_block.cpu_ctx->rip }, nullptr, nullptr }; + cc_block = &branch_block; + g_vm_ctx = code_block.g_vm_ctx.get(); + + // restore register values... + if ( ( err = uc_context_restore( uc_ctx, code_block.cpu_ctx->context ) ) ) + { + std::printf( "> failed to restore emu context... reason = %d\n", err ); + return false; + } + + // restore stack values... + if ( ( err = uc_mem_write( uc_ctx, STACK_BASE, code_block.cpu_ctx->stack, STACK_SIZE ) ) ) + { + std::printf( "> failed to restore stack... reason = %d\n", err ); + return false; + } + + // get the address in rbp (top of vsp)... then patch the branch rva... + if ( ( err = uc_reg_read( uc_ctx, UC_X86_REG_RBP, &rbp ) ) ) + { + std::printf( "> failed to read rbp... reason = %d\n", err ); + return false; + } + + // patch the branch rva... + if ( ( err = uc_mem_write( uc_ctx, rbp, &branch_rva, sizeof branch_rva ) ) ) + { + std::printf( "> failed to patch branch rva... reason = %d\n", err ); + return false; + } + + std::printf( "> beginning execution at = 0x%p\n", code_block.cpu_ctx->rip ); + if ( ( err = uc_emu_start( uc_ctx, code_block.cpu_ctx->rip, 0ull, 0ull, 0ull ) ) ) + { + std::printf( "> error starting emu... reason = %d\n", err ); + return false; + } + + // push back new block that has been traced... + code_blocks.push_back( branch_block ); + + // drop down and execute the absolute case as well since that + // will trace the first branch... + } + case vm::instrs::jcc_type::absolute: + { + if ( std::find( vip_begins.begin(), vip_begins.end(), code_block.code_block.jcc.block_addr[ 0 ] ) != + vip_begins.end() ) + continue; + + std::uintptr_t rbp = 0ull; + std::uint32_t branch_rva = code_block.code_block.jcc.block_addr[ 0 ]; + + // setup object globals so that the tracing will work... + code_block_data_t branch_block{ { code_block.cpu_ctx->rip }, nullptr, nullptr }; + cc_block = &branch_block; + g_vm_ctx = code_block.g_vm_ctx.get(); + + // restore register values... + if ( ( err = uc_context_restore( uc_ctx, code_block.cpu_ctx->context ) ) ) + { + std::printf( "> failed to restore emu context... reason = %d\n", err ); + return false; + } + + // restore stack values... + if ( ( err = uc_mem_write( uc_ctx, STACK_BASE, code_block.cpu_ctx->stack, STACK_SIZE ) ) ) + { + std::printf( "> failed to restore stack... reason = %d\n", err ); + return false; + } + + // get the address in rbp (top of vsp)... then patch the branch rva... + if ( ( err = uc_reg_read( uc_ctx, UC_X86_REG_RBP, &rbp ) ) ) + { + std::printf( "> failed to read rbp... reason = %d\n", err ); + return false; + } + + // patch the branch rva... + if ( ( err = uc_mem_write( uc_ctx, rbp, &branch_rva, sizeof branch_rva ) ) ) + { + std::printf( "> failed to patch branch rva... reason = %d\n", err ); + return false; + } + + std::printf( "> beginning execution at = 0x%p\n", code_block.cpu_ctx->rip ); + if ( ( err = uc_emu_start( uc_ctx, code_block.cpu_ctx->rip, 0ull, 0ull, 0ull ) ) ) + { + std::printf( "> error starting emu... reason = %d\n", err ); + return false; + } + + // push back new block that has been traced... + code_blocks.push_back( branch_block ); + break; + } + } + } + + for ( const auto &[ code_block, cpu_ctx, vm_ctx ] : code_blocks ) + entries.push_back( code_block ); + + return true; + } + + uc_err emu_t::create_entry( vmp2::v2::entry_t *entry ) + { + uc_reg_read( uc_ctx, UC_X86_REG_R15, &entry->regs.r15 ); + uc_reg_read( uc_ctx, UC_X86_REG_R14, &entry->regs.r14 ); + uc_reg_read( uc_ctx, UC_X86_REG_R13, &entry->regs.r13 ); + uc_reg_read( uc_ctx, UC_X86_REG_R12, &entry->regs.r12 ); + uc_reg_read( uc_ctx, UC_X86_REG_R11, &entry->regs.r11 ); + uc_reg_read( uc_ctx, UC_X86_REG_R10, &entry->regs.r10 ); + uc_reg_read( uc_ctx, UC_X86_REG_R9, &entry->regs.r9 ); + uc_reg_read( uc_ctx, UC_X86_REG_R8, &entry->regs.r8 ); + uc_reg_read( uc_ctx, UC_X86_REG_RBP, &entry->regs.rbp ); + uc_reg_read( uc_ctx, UC_X86_REG_RDI, &entry->regs.rdi ); + uc_reg_read( uc_ctx, UC_X86_REG_RSI, &entry->regs.rsi ); + uc_reg_read( uc_ctx, UC_X86_REG_RDX, &entry->regs.rdx ); + uc_reg_read( uc_ctx, UC_X86_REG_RCX, &entry->regs.rcx ); + uc_reg_read( uc_ctx, UC_X86_REG_RBX, &entry->regs.rbx ); + uc_reg_read( uc_ctx, UC_X86_REG_RAX, &entry->regs.rax ); + uc_reg_read( uc_ctx, 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_ctx, 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_ctx, entry->regs.rbp + ( idx * 8 ), &entry->vsp.qword[ idx ], + sizeof entry->vsp.qword[ idx ] ) ) ) + return err; + + return UC_ERR_OK; + } + + bool emu_t::code_exec_callback( uc_engine *uc, uint64_t address, uint32_t size, emu_t *obj ) + { + uc_err err; + vmp2::v2::entry_t vinstr_entry; + std::uint8_t vm_handler_table_idx = 0u; + std::uintptr_t vm_handler_addr; + + static ZydisDecoder decoder; + static ZydisFormatter formatter; + static ZydisDecodedInstruction instr; + + if ( static std::atomic< bool > once{ false }; !once.exchange( true ) ) + { + ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); + ZydisFormatterInit( &formatter, ZYDIS_FORMATTER_STYLE_INTEL ); + } + + auto instr_ptr = reinterpret_cast< void * >( obj->g_vm_ctx->module_base + ( address - obj->img_base ) ); + if ( !ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, instr_ptr, PAGE_4KB, &instr ) ) ) + { + std::printf( "> failed to decode instruction at = 0x%p\n", address ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + // if the native instruction is a jmp rcx/rdx... then AL will contain the vm handler + // table index of the vm handler that the emulator is about to jmp too... + if ( !( instr.mnemonic == ZYDIS_MNEMONIC_JMP && instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && + ( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX || + instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX ) ) ) + return true; + + // extract address of vm handler table... + switch ( instr.operands[ 0 ].reg.value ) + { + case ZYDIS_REGISTER_RCX: + if ( ( err = uc_reg_read( uc, UC_X86_REG_RCX, &vm_handler_addr ) ) ) + { + std::printf( "> failed to read rcx... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + break; + case ZYDIS_REGISTER_RDX: + if ( ( err = uc_reg_read( uc, UC_X86_REG_RDX, &vm_handler_addr ) ) ) + { + std::printf( "> failed to read rdx... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + break; + } + + if ( ( err = uc_reg_read( obj->uc_ctx, UC_X86_REG_AL, &vm_handler_table_idx ) ) ) + { + std::printf( "> failed to read register... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + const auto &vm_handler = obj->g_vm_ctx->vm_handlers[ vm_handler_table_idx ]; + + // quick check to ensure sanity... things can get crazy so this is good to check... + if ( vm_handler.address != vm_handler_addr ) + { + std::printf( "> vm handler index (%d) does not match vm handler address (%p)...\n", vm_handler_table_idx, + vm_handler_addr ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + if ( ( err = obj->create_entry( &vinstr_entry ) ) ) + { + std::printf( "> failed to create vinstr entry... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + auto vinstr = vm::instrs::get( *obj->g_vm_ctx, vinstr_entry ); + + if ( !vinstr.has_value() ) + { + std::printf( "> failed to decode virtual instruction...\n" ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + // log this virtual blocks vip_begin... + if ( obj->cc_block->code_block.vinstrs.empty() ) + { + obj->cc_block->code_block.vip_begin = + obj->g_vm_ctx->exec_type == vmp2::exec_type_t::forward ? vinstr_entry.vip - 1 : vinstr_entry.vip + 1; + + obj->vip_begins.push_back( obj->cc_block->code_block.vip_begin ); + } + + obj->cc_block->code_block.vinstrs.push_back( vinstr.value() ); + std::printf( "> %s %p\n", vm_handler.profile ? vm_handler.profile->name : "UNK", vm_handler_addr ); + + if ( vm_handler.profile ) + { + switch ( vm_handler.profile->mnemonic ) + { + case vm::handler::VMEXIT: + { + obj->cc_block->code_block.jcc.has_jcc = false; + obj->cc_block->code_block.jcc.type = vm::instrs::jcc_type::none; + + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + break; + } + case vm::handler::JMP: + { + // get jcc data about the virtual instruction code block that was just emulated... + auto jcc_data = vm::instrs::get_jcc_data( *obj->g_vm_ctx, obj->cc_block->code_block ); + obj->cc_block->code_block.jcc = jcc_data.value(); + + // allocate space for the cpu context and stack... + auto new_cpu_ctx = std::make_shared< vm::emu_t::cpu_ctx_t >(); + auto new_vm_ctx = std::make_shared< vm::ctx_t >( obj->g_vm_ctx->module_base, obj->img_base, + obj->img_size, vm_handler_addr - obj->img_base ); + if ( !new_vm_ctx->init() ) + { + std::printf( "> failed to init vm::ctx_t for virtual jmp... vip = 0x%p, jmp handler = 0x%p\n", + vinstr_entry.vip, vm_handler_addr ); + + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + if ( ( err = uc_context_alloc( uc, &new_cpu_ctx->context ) ) ) + { + std::printf( "> failed to allocate a unicorn context... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + // save the cpu's registers... + new_cpu_ctx->rip = vm_handler_addr; + if ( ( err = uc_context_save( uc, new_cpu_ctx->context ) ) ) + { + std::printf( "> failed to save emulator context... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + // save the entire stack... + if ( ( err = uc_mem_read( uc, STACK_BASE, new_cpu_ctx->stack, STACK_SIZE ) ) ) + { + std::printf( "> failed to read stack... reason = %d\n", err ); + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + return false; + } + + if ( ( err = uc_emu_stop( uc ) ) ) + { + std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); + exit( 0 ); + } + + obj->cc_block->cpu_ctx = new_cpu_ctx; + obj->cc_block->g_vm_ctx = new_vm_ctx; + break; + } + default: + break; + } + } + return true; + } + + void emu_t::invalid_mem( uc_engine *uc, uc_mem_type type, uint64_t address, int size, int64_t value, emu_t *obj ) + { + switch ( type ) + { + case UC_MEM_READ_UNMAPPED: + { + uc_mem_map( uc, address & ~0xFFFull, PAGE_4KB, UC_PROT_ALL ); + std::printf( ">>> reading invalid memory at address = 0x%p, size = 0x%x\n", address, size ); + break; + } + case UC_MEM_WRITE_UNMAPPED: + { + uc_mem_map( uc, address & ~0xFFFull, PAGE_4KB, UC_PROT_ALL ); + std::printf( ">>> writing invalid memory at address = 0x%p, size = 0x%x, val = 0x%x\n", address, size, + value ); + break; + } + case UC_MEM_FETCH_UNMAPPED: + { + std::printf( ">>> fetching invalid instructions at address = 0x%p\n", address ); + break; + } + default: + break; + } } } // namespace vm \ No newline at end of file