diff --git a/include/vmemu_t.hpp b/include/vmemu_t.hpp index 943ab1a..ebbd940 100644 --- a/include/vmemu_t.hpp +++ b/include/vmemu_t.hpp @@ -42,6 +42,8 @@ namespace vm std::vector< std::uintptr_t > vip_begins; std::vector< code_block_data_t > code_blocks; + std::map< std::uintptr_t, std::shared_ptr< vm::ctx_t > > vm_ctxs; + 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, diff --git a/src/main.cpp b/src/main.cpp index ef45150..79d1fad 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -11,8 +11,11 @@ int __cdecl main( int argc, const char *argv[] ) argparse::argument_parser_t parser( "VMEmu", "VMProtect 2 VM Handler Emulator" ); parser.add_argument().name( "--vmentry" ).description( "relative virtual address to a vm entry..." ); parser.add_argument().name( "--bin" ).description( "path to unpacked virtualized binary..." ); - parser.add_argument().required( true ).name( "--out" ).description( "output file name..." ); + parser.add_argument().name( "--out" ).description( "output file name..." ); parser.add_argument().name( "--unpack" ).description( "unpack a vmp2 binary..." ); + parser.add_argument() + .name( "--locateconst" ) + .description( "scan all vm enters for a specific constant value...\n" ); parser.enable_help(); auto result = parser.parse( argc, argv ); @@ -31,7 +34,7 @@ int __cdecl main( int argc, const char *argv[] ) auto umtils = xtils::um_t::get_instance(); - if ( !parser.exists( "unpack" ) && parser.exists( "vmentry" ) && parser.exists( "bin" ) ) + if ( !parser.exists( "unpack" ) && parser.exists( "vmentry" ) && parser.exists( "bin" ) && parser.exists( "out" ) ) { const auto module_base = reinterpret_cast< std::uintptr_t >( LoadLibraryExA( parser.get< std::string >( "bin" ).c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES ) ); @@ -127,7 +130,7 @@ int __cdecl main( int argc, const char *argv[] ) output.close(); } - else + else if ( parser.exists( "unpack" ) && parser.exists( "bin" ) && parser.exists( "out" ) ) { std::vector< std::uint8_t > packed_bin, unpacked_bin; if ( !umtils->open_binary_file( parser.get< std::string >( "unpack" ), packed_bin ) ) @@ -155,4 +158,73 @@ int __cdecl main( int argc, const char *argv[] ) output.write( reinterpret_cast< char * >( unpacked_bin.data() ), unpacked_bin.size() ); output.close(); } + else if ( parser.exists( "bin" ) && parser.exists( "locateconst" ) ) + { + std::vector< vm::instrs::code_block_t > code_blocks; + const auto module_base = reinterpret_cast< std::uintptr_t >( + LoadLibraryExA( parser.get< std::string >( "bin" ).c_str(), NULL, DONT_RESOLVE_DLL_REFERENCES ) ); + + const auto const_val = std::strtoull( parser.get< std::string >( "locateconst" ).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; + + auto vm_handler_tables = vm::locate::all_handler_tables( module_base ); + auto vm_enters = vm::locate::all_vm_enters( module_base, vm_handler_tables ); + + std::printf( "> number of vm enters = %d\n", vm_enters.size() ); + if ( std::find_if( vm_enters.begin() + 1, vm_enters.end(), + [ & ]( const std::pair< std::uint32_t, std::uint32_t > &vm_enter_data ) -> bool { + return vm_enter_data.second == vm_enters[ 0 ].second; + } ) != vm_enters.end() ) + { + std::printf( "> optimizations can be done.\n" ); + std::getchar(); + } + + for ( const auto &[ vm_enter_offset, encrypted_rva ] : vm_enters ) + { + std::printf( "> emulating vm enter at rva = 0x%x\n", vm_enter_offset ); + vm::ctx_t vm_ctx( module_base, image_base, image_size, vm_enter_offset ); + + if ( !vm_ctx.init() ) + { + std::printf( "[!] failed to init vmctx... this can be for many reasons..." + " try validating your vm entry rva... make sure the binary is unpacked and is" + "protected with VMProtect 2...\n" ); + return -1; + } + + vm::emu_t emu( &vm_ctx ); + + if ( !emu.init() ) + { + std::printf( "[!] failed to init emulator...\n" ); + return -1; + } + + std::vector< vm::instrs::code_block_t > new_code_blocks; + + if ( !emu.get_trace( new_code_blocks ) ) + { + std::printf( "[!] something failed during tracing, review the console for more information...\n" ); + return -1; + } + + std::printf( "> number of blocks = %d\n", new_code_blocks.size() ); + + for ( auto &code_block : new_code_blocks ) + { + for ( const auto &vinstr : code_block.vinstrs ) + { + if ( vinstr.operand.has_imm && vinstr.operand.imm.u == const_val ) + { + std::printf( "> found constant in vm enter at = 0x%x\n", vm_enter_offset ); + std::getchar(); + } + } + } + + code_blocks.insert( code_blocks.end(), new_code_blocks.begin(), new_code_blocks.end() ); + } + } } \ No newline at end of file diff --git a/src/vmemu_t.cpp b/src/vmemu_t.cpp index b19a4d9..9ed484d 100644 --- a/src/vmemu_t.cpp +++ b/src/vmemu_t.cpp @@ -92,35 +92,35 @@ namespace vm // 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 ) + const auto _code_block = code_blocks[ idx ]; + if ( !_code_block.code_block.jcc.has_jcc ) continue; - switch ( code_block.code_block.jcc.type ) + 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 ] ) != + 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 ]; + 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 }; + 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(); + g_vm_ctx = _code_block.g_vm_ctx.get(); // restore register values... - if ( ( err = uc_context_restore( uc_ctx, code_block.cpu_ctx->context ) ) ) + 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 ) ) ) + 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; @@ -140,8 +140,8 @@ namespace vm 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( "> 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; @@ -155,27 +155,27 @@ namespace vm } case vm::instrs::jcc_type::absolute: { - if ( std::find( vip_begins.begin(), vip_begins.end(), code_block.code_block.jcc.block_addr[ 0 ] ) != + 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 ]; + 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 }; + 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(); + g_vm_ctx = _code_block.g_vm_ctx.get(); // restore register values... - if ( ( err = uc_context_restore( uc_ctx, code_block.cpu_ctx->context ) ) ) + 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 ) ) ) + 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; @@ -195,8 +195,8 @@ namespace vm 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( "> 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; @@ -258,6 +258,9 @@ namespace vm std::uint8_t vm_handler_table_idx = 0u; std::uintptr_t vm_handler_addr; + static std::shared_ptr< vm::ctx_t > _jmp_ctx; + static zydis_routine_t _jmp_stream; + static ZydisDecoder decoder; static ZydisFormatter formatter; static ZydisDecodedInstruction instr; @@ -402,21 +405,28 @@ namespace vm // 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() ) + + // optimize so that we dont need to create a new vm::ctx_t every single virtual JMP... + if ( obj->vm_ctxs.find( vm_handler_addr ) == obj->vm_ctxs.end() ) { - 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 ); + obj->vm_ctxs[ vm_handler_addr ] = 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 ( ( err = uc_emu_stop( uc ) ) ) + if ( !obj->vm_ctxs[ vm_handler_addr ]->init() ) { - std::printf( "> failed to stop emulation, exiting... reason = %d\n", err ); - exit( 0 ); + 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; } - return false; } + _jmp_ctx = obj->vm_ctxs[ vm_handler_addr ]; if ( ( err = uc_context_alloc( uc, &new_cpu_ctx->context ) ) ) { std::printf( "> failed to allocate a unicorn context... reason = %d\n", err ); @@ -460,7 +470,7 @@ namespace vm } obj->cc_block->cpu_ctx = new_cpu_ctx; - obj->cc_block->g_vm_ctx = new_vm_ctx; + obj->cc_block->g_vm_ctx = _jmp_ctx; break; } default: