From ffd45ecb8a6a6a5d066a9b5e96c878ed9e09e243 Mon Sep 17 00:00:00 2001 From: _xeroxz Date: Wed, 2 Jun 2021 20:38:46 -0700 Subject: [PATCH] in the middle of something, adding branch detection cod --- include/vmprofiler.hpp | 60 +++++++++++++++++++++++++++------- src/vmctx.cpp | 10 ++---- src/vminstrs.cpp | 73 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 19 deletions(-) diff --git a/include/vmprofiler.hpp b/include/vmprofiler.hpp index 9cbc78e..bbc0519 100644 --- a/include/vmprofiler.hpp +++ b/include/vmprofiler.hpp @@ -172,11 +172,13 @@ namespace vm class ctx_t { public: - explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uint32_t vm_entry_rva ); - ctx_t( std::vector< vm::handler::handler_t > &vm_handlers, zydis_routine_t &vm_entry, zydis_routine_t &calc_jmp, - vmp2::exec_type_t exec_type ); + explicit ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size, + std::uintptr_t vm_entry_rva ); + + // never change... + const std::uintptr_t module_base, image_base, vm_entry_rva, image_size; + const vmp2::exec_type_t exec_type; - vmp2::exec_type_t exec_type; zydis_routine_t vm_entry, calc_jmp; std::vector< vm::handler::handler_t > vm_handlers; }; @@ -213,17 +215,18 @@ namespace vm absolute }; - struct code_block_t + struct jcc_data { - struct - { - bool has_jcc; - jcc_type type; - std::uint32_t block_rva[ 2 ]; - } jcc; + bool has_jcc; + jcc_type type; + std::uint32_t block_rva[ 2 ]; + }; - std::uint32_t code_block_rva; + struct code_block_t + { + std::uintptr_t vip_begin; std::vector< virt_instr_t > vinstrs; + jcc_data jcc; }; // decrypt transformations for encrypted virtual instruction rva... @@ -245,6 +248,39 @@ namespace vm /// virtual instruction pointer, linear virtual address... /// returns immediate value if imm_size is not 0... std::optional< std::uint64_t > get_imm( vm::ctx_t &ctx, std::uint8_t imm_size, std::uintptr_t vip ); + + /// + /// get jcc data out of a code block... this function will loop over the code block + /// and look for the last two NANDW in the virtual instructions, then it will look + /// for the last LCONSTW which is the xor key... + /// + /// it will then loop and look for all PUSHVSP's, checking each to see if the stack + /// contains two encrypted rva's to each branch.. if there is not two encrypted rva's + /// then the virtual jmp instruction only has one dest... + /// + /// vm context + /// code block that does not have its jcc_data yet + /// if last lconstdw is found, return filled in jcc_data structure... + std::optional< jcc_data > get_jcc_data( vm::ctx_t &ctx, code_block_t &code_block ); + + /// + /// the top of the stack will contain the lower 32bits of the RVA to the virtual instructions + /// that will be jumping too... the RVA is image based (not module based, but optional header image + /// based)... this means the value ontop of the stack could be "40007fd8" with image base being + /// 0x140000000... as you can see the 0x100000000 is missing... the below statement deals with this... + /// + /// vm context + /// current trace entry for virtual JMP instruction + /// returns linear virtual address of the next code block... + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ); + + /// + /// same routine as above except lower_32bits is passed directly and not extracted from the stack... + /// + /// vm context + /// lower 32bits of the relative virtual address... + /// returns full linear virtual address of code block... + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ); } // namespace instrs namespace calc_jmp diff --git a/src/vmctx.cpp b/src/vmctx.cpp index 0b7e1ef..145a1ec 100644 --- a/src/vmctx.cpp +++ b/src/vmctx.cpp @@ -2,7 +2,9 @@ namespace vm { - ctx_t::ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uint32_t vm_entry_rva ) + ctx_t::ctx_t( std::uintptr_t module_base, std::uintptr_t image_base, std::uintptr_t image_size, + std::uintptr_t vm_entry_rva ) + : module_base( module_base ), image_base( image_base ), image_size( image_size ), vm_entry_rva( vm_entry_rva ) { vm::util::flatten( vm_entry, vm_entry_rva + module_base ); vm::util::deobfuscate( vm_entry ); @@ -11,10 +13,4 @@ namespace vm auto vm_handler_table = vm::handler::table::get( vm_entry ); vm::handler::get_all( module_base, image_base, vm_entry, vm_handler_table, vm_handlers ); } - - ctx_t::ctx_t( std::vector< vm::handler::handler_t > &vm_handlers, zydis_routine_t &vm_entry, - zydis_routine_t &calc_jmp, vmp2::exec_type_t exec_type ) - : vm_handlers( vm_handlers ), vm_entry( vm_entry ), calc_jmp( calc_jmp ), exec_type( exec_type ) - { - } } // namespace vm \ No newline at end of file diff --git a/src/vminstrs.cpp b/src/vminstrs.cpp index 066e159..f2d5df2 100644 --- a/src/vminstrs.cpp +++ b/src/vminstrs.cpp @@ -196,5 +196,78 @@ namespace vm return result; } + + std::optional< jcc_data > get_jcc_data( vm::ctx_t &vmctx, code_block_t &code_block ) + { + // there is no branch for this as this is a vmexit... + if ( code_block.vinstrs.back().mnemonic_t == vm::handler::VMEXIT ) + return {}; + + // find the last LCONSTDW... the imm value is the JMP xor decrypt key... + // we loop backwards here (using rbegin and rend)... + auto result = std::find_if( code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), + []( const vm::instrs::virt_instr_t &vinstr ) -> bool { + auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); + return profile && profile->mnemonic == vm::handler::LCONSTDW; + } ); + + jcc_data jcc; + const auto xor_key = static_cast< std::uint32_t >( result->operand.imm.u ); + const auto &last_trace = code_block.vinstrs.back().trace_data; + + // since result is already a variable and is a reverse itr + // im going to be using rbegin and rend here again... + // + // look for PUSHVSP virtual instructions with two encrypted virtual + // instruction rva's ontop of the virtual stack... + result = std::find_if( code_block.vinstrs.rbegin(), code_block.vinstrs.rend(), + [ & ]( const vm::instrs::virt_instr_t &vinstr ) -> bool { + auto profile = vm::handler::get_profile( vinstr.mnemonic_t ); + if ( profile && profile->mnemonic == vm::handler::PUSHVSP ) + { + const auto possible_block_1 = + code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 0 ] ^ xor_key ); + + const auto possible_block_2 = + code_block_addr( vmctx, vinstr.trace_data.vsp.qword[ 1 ] ^ xor_key ); + + // if this returns too many false positives we might have to get + // our hands dirty and look into trying to emulate each branch + // to see if the first instruction is an SREGQ... + return possible_block_1 > vmctx.module_base && + possible_block_1 < vmctx.module_base + vmctx.image_size && + possible_block_2 > vmctx.module_base && + possible_block_2 < vmctx.module_base + vmctx.image_size; + } + + return false; + } ); + + // if there is not two branches... + if ( result == code_block.vinstrs.rend() ) + { + jcc.block_rva[ 0 ] = code_block_addr( vmctx, last_trace ); + + jcc.has_jcc = false; + jcc.type = jcc_type::absolute; + } + // else there are two branches... + else + { + jcc.block_rva[ 0 ] = + } + + return jcc; + } + + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const vmp2::v2::entry_t &entry ) + { + return ( ( entry.vsp.qword[ 0 ] + ctx.image_base & ~0xFFFFFFFFull ) - ctx.image_base ) + ctx.module_base; + } + + std::uintptr_t code_block_addr( const vm::ctx_t &ctx, const std::uint32_t lower_32bits ) + { + return ( ( lower_32bits + ctx.image_base & ~0xFFFFFFFFull ) - ctx.image_base ) + ctx.module_base; + } } // namespace instrs } // namespace vm \ No newline at end of file