#include namespace vm::locate { std::vector< vm_handler_table_info_t > all_handler_tables( std::uintptr_t module_base ) { std::vector< vm_handler_table_info_t > result; auto module_info = reinterpret_cast< win::image_t<> * >( module_base ); auto sections = module_info->get_nt_headers()->get_sections(); auto num_sections = module_info->get_file_header()->num_sections; auto umtils = xtils::um_t::get_instance(); static const auto lea_r12_validate = []( std::uintptr_t addr ) -> bool { ZydisDecodedInstruction instr; ZydisDecoder decoder; ZydisRegister jmp_reg = ZYDIS_REGISTER_NONE; ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); unsigned instr_count = 0u; bool found_table_idx = false, found_valid_jmp = false; while ( ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( addr ), 0x1000, &instr ) ) ) { ++instr_count; if ( instr_count >= 0x1000 || instr.mnemonic == ZYDIS_MNEMONIC_INVALID ) // prevent run offs and misalignment... break; // determine if we are looking at a JMP RCX/JMP RDX... if ( vm::util::is_jmp( instr ) ) { if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && ( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX || instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) ) { if ( jmp_reg == ZYDIS_REGISTER_NONE || jmp_reg != instr.operands[ 0 ].reg.value ) break; // else we set it to true and break... found_valid_jmp = true; break; } // take JCC branch no matter what... ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], addr, &addr ); // dont execute anymore address advancement code... continue; } // else if the instruction is a MOV RDX/RCX, [R12+RAX*0x8]... we know this is an index into // the vm handler table and thus this lea r12, xxxx is probably legit... else if ( instr.mnemonic == ZYDIS_MNEMONIC_MOV && instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && ( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX || instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) && instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && instr.operands[ 1 ].mem.base == ZYDIS_REGISTER_R12 && instr.operands[ 1 ].mem.index == ZYDIS_REGISTER_RAX && instr.operands[ 1 ].mem.scale == 0x8 ) { found_table_idx = true; jmp_reg = instr.operands[ 0 ].reg.value; } else if ( instr.mnemonic == ZYDIS_MNEMONIC_RET || instr.mnemonic == ZYDIS_MNEMONIC_CALL ) break; // advance the instruction address "addr"... addr += instr.length; } return found_table_idx && found_valid_jmp; }; ZydisDecodedInstruction instr; ZydisDecoder decoder; ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); for ( auto idx = 0u; idx < num_sections; ++idx ) { // if the section is executable and not discardable... scan it for lea r12, xxxx and ensure its loading // a virtual machine handler table into r12... if ( sections[ idx ].characteristics.mem_execute && !sections[ idx ].characteristics.mem_discardable ) { void *scan_result = reinterpret_cast< void * >( sections[ idx ].virtual_address + module_base ); do { // compute how far away from the beginning of the section we are... auto section_size = reinterpret_cast< std::uintptr_t >( scan_result ) - ( sections[ idx ].virtual_address + module_base ); // scan from the last scans result to the end of the section for lea r12's... scan_result = umtils->sigscan( scan_result, sections[ idx ].virtual_size - section_size, LEA_R12_SIG, LEA_R12_MASK ); if ( scan_result ) { // check to see if we are looking at: // 1.) a legit "lea r12, xxxx" and not a misaligned instruction... // 2.) if the instruction stream is followed in zydis it ends with a jmp rcx/jmp rdx... if ( lea_r12_validate( reinterpret_cast< std::uintptr_t >( scan_result ) ) ) { if ( ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, scan_result, sections[ idx ].virtual_size - section_size, &instr ) ) ) { vm_handler_table_info_t vm_handler_table_info; vm_handler_table_info.rva = ( instr.operands[ 1 ].mem.disp.value + reinterpret_cast< std::uintptr_t >( scan_result ) + instr.length ) - module_base; vm_handler_table_info.lea_r12_rva = reinterpret_cast< std::uintptr_t >( scan_result ) - module_base; vm_handler_table_info.lea_r12_instr = instr; result.push_back( vm_handler_table_info ); } } scan_result = reinterpret_cast< void * >( reinterpret_cast< std::uintptr_t >( scan_result ) + sizeof LEA_R12_SIG ); } } while ( scan_result ); } } return result; } std::vector< std::pair< std::uint32_t, std::uint32_t > > all_vm_enters( std::uintptr_t module_base, std::vector< vm_handler_table_info_t > &vm_handler_tables ) { std::vector< std::pair< std::uint32_t, std::uint32_t > > result; auto module_info = reinterpret_cast< win::image_t<> * >( module_base ); auto sections = module_info->get_nt_headers()->get_sections(); auto num_sections = module_info->get_file_header()->num_sections; auto umtils = xtils::um_t::get_instance(); auto module_end = module_base + module_info->get_nt_headers()->optional_header.size_image; static const auto validate_vm_enter = [ & ]( std::uintptr_t addr ) -> bool { ZydisDecodedInstruction instr; ZydisDecoder decoder; ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); unsigned instr_count = 0u; bool found_valid_jmp = false; std::vector< ZydisDecodedInstruction > instr_stream; while ( addr >= module_base && addr < module_end && ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, reinterpret_cast< void * >( addr ), 0x1000, &instr ) ) ) { if ( !instr_count && instr.mnemonic != ZYDIS_MNEMONIC_PUSH ) break; ++instr_count; // handle run offs and misaligned instructions... if ( instr_count > 500 || instr.mnemonic == ZYDIS_MNEMONIC_INVALID || instr.mnemonic == ZYDIS_MNEMONIC_RET || instr.mnemonic == ZYDIS_MNEMONIC_CALL ) return false; // determine if we are looking at a JMP RCX/JMP RDX... if ( vm::util::is_jmp( instr ) ) { if ( instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_REGISTER && ( instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RDX || instr.operands[ 0 ].reg.value == ZYDIS_REGISTER_RCX ) ) { // else we set it to true and break... found_valid_jmp = true; instr_stream.push_back( instr ); break; } // take JCC branch no matter what... ZydisCalcAbsoluteAddress( &instr, &instr.operands[ 0 ], addr, &addr ); // dont execute anymore address advancement code... continue; } instr_stream.push_back( instr ); addr += instr.length; } if ( !found_valid_jmp ) return false; // second instruction in the flattened stream should be a push... // this is also an optimization so we dont have to hit that 0^2 std::find_if every time... if ( instr_stream[ 1 ].mnemonic != ZYDIS_MNEMONIC_PUSH ) return false; if ( std::find_if( instr_stream.begin() + 1, instr_stream.end(), [ & ]( const ZydisDecodedInstruction &instr ) { return instr.mnemonic == ZYDIS_MNEMONIC_PUSH && instr.operands[ 0 ].type == ZYDIS_OPERAND_TYPE_IMMEDIATE; } ) == instr_stream.end() ) return false; // scan over the instruction stream to see if it contains an lea r12, xxxx which is a known vm handler table // load into r12... this is O^2 and very slow... whatever... return std::find_if( instr_stream.begin(), instr_stream.end(), [ & ]( const ZydisDecodedInstruction &instr ) { return instr.mnemonic == ZYDIS_MNEMONIC_LEA && instr.operands[ 1 ].type == ZYDIS_OPERAND_TYPE_MEMORY && std::find_if( vm_handler_tables.begin(), vm_handler_tables.end(), [ & ]( const vm_handler_table_info_t &table_info ) { return table_info.lea_r12_instr.operands[ 1 ].mem.disp.value == instr.operands[ 1 ].mem.disp.value; } ) != vm_handler_tables.end(); } ) != instr_stream.end(); }; ZydisDecodedInstruction instr; ZydisDecoder decoder; ZydisDecoderInit( &decoder, ZYDIS_MACHINE_MODE_LONG_64, ZYDIS_ADDRESS_WIDTH_64 ); // for each section... for ( auto idx = 0u; idx < num_sections; ++idx ) { // we are only interested in executable (non-discardable) sections... if ( sections[ idx ].characteristics.mem_execute && !sections[ idx ].characteristics.mem_discardable ) { void *scan_result = reinterpret_cast< void * >( sections[ idx ].virtual_address + module_base ); do { // compute how far away from the beginning of the section we are... auto section_size = reinterpret_cast< std::uintptr_t >( scan_result ) - ( sections[ idx ].virtual_address + module_base ); if ( section_size > sections[ idx ].virtual_size ) break; scan_result = umtils->sigscan( scan_result, sections[ idx ].virtual_size - section_size, PUSH_4B_IMM, PUSH_4B_MASK ); if ( scan_result ) { if ( validate_vm_enter( reinterpret_cast< std::uintptr_t >( scan_result ) ) && ZYAN_SUCCESS( ZydisDecoderDecodeBuffer( &decoder, scan_result, 0x1000, &instr ) ) ) { if ( std::find_if( result.begin(), result.end(), [ & ]( const std::pair< std::uint32_t, std::uint32_t > &info ) { return info.second == instr.operands[ 0 ].imm.value.u; } ) == result.end() ) { result.push_back( { reinterpret_cast< std::uintptr_t >( scan_result ) - module_base, instr.operands[ 0 ].imm.value.u } ); } } scan_result = reinterpret_cast< void * >( reinterpret_cast< std::uintptr_t >( scan_result ) + sizeof PUSH_4B_IMM ); } } while ( scan_result ); } } return result; } } // namespace vm::locate